From 0c57f7eeacdb460270b8a67d46e44b7a0cf4adf9 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Thu, 30 Jan 2020 10:00:00 +0100 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20Allow=C2=A0processors=20to=C2=A0add?= =?UTF-8?q?=C2=A0package=C2=A0imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constructs/attribute.js | 2 +- lib/constructs/dictionary.js | 2 +- lib/constructs/interface.js | 4 +- lib/parameters.js | 2 +- lib/types.js | 4 +- lib/utils.js | 25 ++- test/__snapshots__/test.js.snap | 318 ++++++++++++++++++++++++++++++++ test/test.js | 22 +++ 8 files changed, 369 insertions(+), 10 deletions(-) diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index 6da57367..dab3c7fc 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -79,7 +79,7 @@ class Attribute { let idlConversion; if (typeof this.idl.idlType.idlType === "string" && !this.idl.idlType.nullable && this.ctx.enumerations.has(this.idl.idlType.idlType)) { - requires.add(this.idl.idlType.idlType); + requires.addRelative(this.idl.idlType.idlType); idlConversion = ` V = \`\${V}\`; if (!${this.idl.idlType.idlType}.enumerationValues.has(V)) { diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index dcefdd58..97de5f74 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -95,7 +95,7 @@ class Dictionary { `; if (this.idl.inheritance) { - this.requires.add(this.idl.inheritance); + this.requires.addRelative(this.idl.inheritance); } this.str = ` ${this.requires.generate()} diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index deda7ff4..e0645467 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -503,11 +503,11 @@ class Interface { this.requires.addRaw("ctorRegistry", "utils.ctorRegistrySymbol"); if (this.idl.inheritance !== null) { - this.requires.add(this.idl.inheritance); + this.requires.addRelative(this.idl.inheritance); } for (const iface of this.consequentialInterfaces()) { - this.requires.add(iface); + this.requires.addRelative(iface); } this.str = ` diff --git a/lib/parameters.js b/lib/parameters.js index 932010ee..d2cb9494 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -131,7 +131,7 @@ module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, pare // Avoid requiring the interface itself if (iface !== parent.name) { fn = `is${iface}`; - requires.add(iface, "is"); + requires.addRelative(iface, "is"); } else { fn = "module.exports.is"; } diff --git a/lib/types.js b/lib/types.js index 3f534018..a63032fa 100644 --- a/lib/types.js +++ b/lib/types.js @@ -122,7 +122,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e // Avoid requiring the interface itself if (idlType.idlType !== parentName) { fn = `convert${idlType.idlType}`; - requires.add(idlType.idlType, "convert"); + requires.addRelative(idlType.idlType, "convert"); } else { fn = `module.exports.convert`; } @@ -174,7 +174,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e // Avoid requiring the interface itself if (iface !== parentName) { fn = `is${iface}`; - requires.add(iface, "is"); + requires.addRelative(iface, "is"); } else { fn = "module.exports.is"; } diff --git a/lib/utils.js b/lib/utils.js index 598d2f10..d54f3f22 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,5 @@ "use strict"; +const path = require("path"); function getDefault(dflt) { switch (dflt.type) { @@ -65,6 +66,10 @@ function stringifyPropertyName(prop) { return typeof prop === "symbol" ? symbolName(prop) : JSON.stringify(propertyName(prop)); } +function toKey(type, func = "") { + return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_"); +} + class RequiresMap extends Map { constructor(ctx) { super(); @@ -72,10 +77,24 @@ class RequiresMap extends Map { } add(type, func = "") { - const key = (func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_"); + const key = toKey(type, func); + + const importPath = (type.includes("/") || type.startsWith(".")) && !path.extname(type) ? type + ".js" : type; + let req = `require(${JSON.stringify(importPath)})`; + + if (func) { + req += `.${func}`; + } + + this.addRaw(key, req); + return key; + } + + addRelative(type, func = "") { + const key = toKey(type, func); - const path = type.startsWith(".") ? type : `./${type}`; - let req = `require("${path}.js")`; + const importPath = type.startsWith(".") ? type : `./${type}`; + let req = `require("${importPath}.js")`; if (func) { req += `.${func}`; diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 3f13d0a7..448a61ac 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -1,5 +1,323 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`package imports work CEReactions.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +const webidl_url = require(\\"webidl-url\\"); +const impl = utils.implSymbol; +const ctorRegistry = utils.ctorRegistrySymbol; + +/** + * When an interface-module that implements this interface as a mixin is loaded, it will append its own \`.is()\` + * method into this array. It allows objects that directly implements *those* interfaces to be recognized as + * implementing this mixin interface. + */ +exports._mixedIntoPredicates = []; +exports.is = function is(obj) { + if (obj) { + if (utils.hasOwn(obj, impl) && obj[impl] instanceof Impl.implementation) { + return true; + } + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(obj)) { + return true; + } + } + } + return false; +}; +exports.isImpl = function isImpl(obj) { + if (obj) { + if (obj instanceof Impl.implementation) { + return true; + } + + const wrapper = utils.wrapperForImpl(obj); + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(wrapper)) { + return true; + } + } + } + return false; +}; +exports.convert = function convert(obj, { context = \\"The provided value\\" } = {}) { + if (exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'CEReactions'.\`); +}; + +exports.create = function create(globalObject, constructorArgs, privateData) { + if (globalObject[ctorRegistry] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistry][\\"CEReactions\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor CEReactions is not installed on the passed global object\\"); + } + + let obj = Object.create(ctor.prototype); + obj = exports.setup(obj, globalObject, constructorArgs, privateData); + return obj; +}; +exports.createImpl = function createImpl(globalObject, constructorArgs, privateData) { + const obj = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(obj); +}; +exports._internalSetup = function _internalSetup(obj) {}; +exports.setup = function setup(obj, globalObject, constructorArgs = [], privateData = {}) { + privateData.wrapper = obj; + + exports._internalSetup(obj); + Object.defineProperty(obj, impl, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + obj = new Proxy(obj, proxyHandler); + + obj[impl][utils.wrapperSymbol] = obj; + if (Impl.init) { + Impl.init(obj[impl], privateData); + } + return obj; +}; + +exports.install = function install(globalObject) { + const interfaceName = \\"CEReactions\\"; + class CEReactions { + constructor() { + throw new TypeError(\\"Illegal constructor\\"); + } + + method() { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return this[impl].method(); + } + + get attr() { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return this[impl][\\"attr\\"]; + } + + set attr(V) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + V = conversions[\\"DOMString\\"](V, { + context: \\"Failed to set the 'attr' property on 'CEReactions': The provided value\\" + }); + + this[impl][\\"attr\\"] = V; + } + } + Object.defineProperties(CEReactions.prototype, { + method: { enumerable: true }, + attr: { enumerable: true }, + [Symbol.toStringTag]: { value: \\"CEReactions\\", configurable: true } + }); + if (globalObject[ctorRegistry] === undefined) { + globalObject[ctorRegistry] = Object.create(null); + } + globalObject[ctorRegistry][interfaceName] = CEReactions; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: CEReactions + }); +}; + +const proxyHandler = { + get(target, P, receiver) { + if (typeof P === \\"symbol\\") { + return Reflect.get(target, P, receiver); + } + const desc = this.getOwnPropertyDescriptor(target, P); + if (desc === undefined) { + const parent = Object.getPrototypeOf(target); + if (parent === null) { + return undefined; + } + return Reflect.get(target, P, receiver); + } + if (!desc.get && !desc.set) { + return desc.value; + } + const getter = desc.get; + if (getter === undefined) { + return undefined; + } + return Reflect.apply(getter, receiver, []); + }, + + has(target, P) { + if (typeof P === \\"symbol\\") { + return Reflect.has(target, P); + } + const desc = this.getOwnPropertyDescriptor(target, P); + if (desc !== undefined) { + return true; + } + const parent = Object.getPrototypeOf(target); + if (parent !== null) { + return Reflect.has(parent, P); + } + return false; + }, + + ownKeys(target) { + const keys = new Set(); + + for (const key of target[impl][utils.supportedPropertyNames]) { + if (!(key in target)) { + keys.add(\`\${key}\`); + } + } + + for (const key of Reflect.ownKeys(target)) { + keys.add(key); + } + return [...keys]; + }, + + getOwnPropertyDescriptor(target, P) { + if (typeof P === \\"symbol\\") { + return Reflect.getOwnPropertyDescriptor(target, P); + } + let ignoreNamedProps = false; + + if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + const namedValue = target[impl][utils.namedGet](P); + + return { + writable: true, + enumerable: true, + configurable: true, + value: utils.tryWrapperForImpl(namedValue) + }; + } + + return Reflect.getOwnPropertyDescriptor(target, P); + }, + + set(target, P, V, receiver) { + if (typeof P === \\"symbol\\") { + return Reflect.set(target, P, V, receiver); + } + if (target === receiver) { + if (typeof P === \\"string\\" && !utils.isArrayIndexPropName(P)) { + let namedValue = V; + + namedValue = conversions[\\"DOMString\\"](namedValue, { + context: \\"Failed to set the '\\" + P + \\"' property on 'CEReactions': The provided value\\" + }); + + const creating = !target[impl][utils.supportsPropertyName](P); + if (creating) { + target[impl][utils.namedSetNew](P, namedValue); + } else { + target[impl][utils.namedSetExisting](P, namedValue); + } + + return true; + } + } + let ownDesc; + + if (ownDesc === undefined) { + ownDesc = Reflect.getOwnPropertyDescriptor(target, P); + } + if (ownDesc === undefined) { + const parent = Reflect.getPrototypeOf(target); + if (parent !== null) { + return Reflect.set(parent, P, V, receiver); + } + ownDesc = { writable: true, enumerable: true, configurable: true, value: undefined }; + } + if (!ownDesc.writable) { + return false; + } + if (!utils.isObject(receiver)) { + return false; + } + const existingDesc = Reflect.getOwnPropertyDescriptor(receiver, P); + let valueDesc; + if (existingDesc !== undefined) { + if (existingDesc.get || existingDesc.set) { + return false; + } + if (!existingDesc.writable) { + return false; + } + valueDesc = { value: V }; + } else { + valueDesc = { writable: true, enumerable: true, configurable: true, value: V }; + } + return Reflect.defineProperty(receiver, P, valueDesc); + }, + + defineProperty(target, P, desc) { + if (typeof P === \\"symbol\\") { + return Reflect.defineProperty(target, P, desc); + } + if (!utils.hasOwn(target, P)) { + if (desc.get || desc.set) { + return false; + } + + let namedValue = desc.value; + + namedValue = conversions[\\"DOMString\\"](namedValue, { + context: \\"Failed to set the '\\" + P + \\"' property on 'CEReactions': The provided value\\" + }); + + const creating = !target[impl][utils.supportsPropertyName](P); + if (creating) { + target[impl][utils.namedSetNew](P, namedValue); + } else { + target[impl][utils.namedSetExisting](P, namedValue); + } + + return true; + } + return Reflect.defineProperty(target, P, desc); + }, + + deleteProperty(target, P) { + if (typeof P === \\"symbol\\") { + return Reflect.deleteProperty(target, P); + } + + if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + target[impl][utils.namedDelete](P); + return true; + } + + return Reflect.deleteProperty(target, P); + }, + + preventExtensions() { + return false; + } +}; + +const Impl = require(\\"../implementations/CEReactions.js\\"); +" +`; + exports[`with processors BufferSourceTypes.webidl 1`] = ` "\\"use strict\\"; diff --git a/test/test.js b/test/test.js index 28db1cd7..f0be2bd9 100644 --- a/test/test.js +++ b/test/test.js @@ -67,6 +67,28 @@ describe("with processors", () => { } }); +describe("package imports work", () => { + beforeAll(() => { + const transformer = new Transformer({ + processCEReactions(code) { + this.addImport("webidl-url"); + + return code; + } + }); + transformer.addSource(casesDir, implsDir); + + return transformer.generate(outputDir); + }); + + test("CEReactions.webidl", () => { + const outputFile = path.resolve(outputDir, "CEReactions.js"); + const output = fs.readFileSync(outputFile, { encoding: "utf-8" }); + + expect(output).toMatchSnapshot(); + }); +}); + test("utils.js", () => { const input = fs.readFileSync(path.resolve(rootDir, "lib/output/utils.js"), { encoding: "utf-8" }); const output = fs.readFileSync(path.resolve(outputDir, "utils.js"), { encoding: "utf-8" }); From da7f757e315ecd48a71948fffbebc3ae7d179c1d Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Sat, 1 Feb 2020 11:20:00 +0100 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Allow=C2=A0processors=20to=C2=A0add?= =?UTF-8?q?=C2=A0scoped=20package=C2=A0imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index d54f3f22..d83285bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,5 @@ "use strict"; -const path = require("path"); +const { extname } = require("path"); function getDefault(dflt) { switch (dflt.type) { @@ -70,16 +70,20 @@ function toKey(type, func = "") { return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_"); } +const PACKAGE_NAME_REGEX = /^(?:@([^/]+?)[/])?([^/]+?)$/u; + class RequiresMap extends Map { constructor(ctx) { super(); this.ctx = ctx; } - add(type, func = "") { - const key = toKey(type, func); + add(name, func = "") { + const key = toKey(name, func); - const importPath = (type.includes("/") || type.startsWith(".")) && !path.extname(type) ? type + ".js" : type; + // If `name` is a package name or has a file extension, then use it as-is, + // otherwise append the `.js` file extension: + const importPath = PACKAGE_NAME_REGEX.test(name) || extname(name) ? name : `${name}.js`; let req = `require(${JSON.stringify(importPath)})`; if (func) { @@ -93,8 +97,8 @@ class RequiresMap extends Map { addRelative(type, func = "") { const key = toKey(type, func); - const importPath = type.startsWith(".") ? type : `./${type}`; - let req = `require("${importPath}.js")`; + const path = type.startsWith(".") ? type : `./${type}`; + let req = `require("${path}.js")`; if (func) { req += `.${func}`;