From 9bdfffb05cc8773a25fdff33875f7fcd27909316 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Wed, 22 Apr 2020 15:10:00 +0200 Subject: [PATCH] =?UTF-8?q?Fix=C2=A0`addSource`=C2=A0to=C2=A0allow=20imple?= =?UTF-8?q?mentation=C2=A0path=20to=C2=A0point=C2=A0to=C2=A0a=C2=A0file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/transformer.js | 54 ++++++++---- test/__snapshots__/test.js.snap | 109 +++++++++++++++++++++++++ test/fixtures/custom-path/impl-file.js | 9 ++ test/fixtures/custom-path/some.webidl | 3 + test/test.js | 22 +++++ 5 files changed, 182 insertions(+), 15 deletions(-) create mode 100644 test/fixtures/custom-path/impl-file.js create mode 100644 test/fixtures/custom-path/some.webidl diff --git a/lib/transformer.js b/lib/transformer.js index 02211d7a..460edcdc 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -26,7 +26,7 @@ class Transformer { } }); - this.sources = []; // Absolute paths to the IDL and Impl directories. + this._sources = []; // Absolute paths to the IDL and Impl files or directories. this.utilPath = null; } @@ -34,31 +34,54 @@ class Transformer { if (typeof idl !== "string") { throw new TypeError("idl path has to be a string"); } + if (typeof impl !== "string") { throw new TypeError("impl path has to be a string"); } - this.sources.push({ idlPath: path.resolve(idl), impl: path.resolve(impl) }); + + this._sources.push({ + idlPath: path.resolve(idl), + implPath: path.resolve(impl) + }); + return this; } async _collectSources() { - const stats = await Promise.all(this.sources.map(src => fs.stat(src.idlPath))); + const { implSuffix } = this.ctx; + const stats = await Promise.all(this._sources.map(src => Promise.all([ + fs.stat(src.idlPath), + fs.stat(src.implPath) + ]))); + const files = []; for (let i = 0; i < stats.length; ++i) { - if (stats[i].isDirectory()) { - const folderContents = await fs.readdir(this.sources[i].idlPath); + const { idlPath, implPath } = this._sources[i]; + const [idlStat, implStat] = stats[i]; + + if (idlStat.isDirectory()) { + if (!implStat.isDirectory()) { + throw new Error("Internal error: When the idl path is a directory, the impl path must also be a directory."); + } + + const folderContents = await fs.readdir(this._sources[i].idlPath); for (const file of folderContents) { - if (file.endsWith(".webidl")) { + if (path.extname(file) === ".webidl") { files.push({ - idlPath: path.join(this.sources[i].idlPath, file), - impl: this.sources[i].impl + idlPath: path.join(idlPath, file), + implPath: path.join(implPath, `${path.basename(file, ".webidl")}${implSuffix}.js`) }); } } + } else if (implStat.isDirectory()) { + files.push({ + idlPath, + implPath: path.join(implPath, `${path.basename(idlPath, ".webidl")}${implSuffix}.js`) + }); } else { files.push({ - idlPath: this.sources[i].idlPath, - impl: this.sources[i].impl + idlPath, + implPath }); } } @@ -71,7 +94,7 @@ class Transformer { for (let i = 0; i < files.length; ++i) { zipped.push({ idlContent: fileContents[i], - impl: files[i].impl + implPath: files[i].implPath }); } return zipped; @@ -80,7 +103,7 @@ class Transformer { _parse(outputDir, contents) { const parsed = contents.map(content => ({ idl: webidl.parse(content.idlContent), - impl: content.impl + impl: content.implPath })); this.ctx.initialize(); @@ -97,7 +120,7 @@ class Transformer { } obj = new Interface(this.ctx, instruction, { - implDir: file.impl + implFile: file.impl }); interfaces.set(obj.name, obj); break; @@ -208,7 +231,7 @@ class Transformer { for (const obj of interfaces.values()) { let source = obj.toString(); - let implFile = path.relative(outputDir, path.resolve(obj.opts.implDir, obj.name + this.ctx.implSuffix)); + let implFile = path.relative(outputDir, obj.opts.implFile); implFile = implFile.replace(/\\/g, "/"); // fix windows file paths if (implFile[0] !== ".") { implFile = "./" + implFile; @@ -220,7 +243,7 @@ class Transformer { const conversions = require("webidl-conversions"); const utils = require("${relativeUtils}"); ${source} - const Impl = require("${implFile}.js"); + const Impl = require("${implFile}"); `; source = this._prettify(source); @@ -250,6 +273,7 @@ class Transformer { ${obj.toString()} `); + await fs.writeFile(path.join(outputDir, obj.name + ".js"), source); } } diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 46cbd7b4..67b311be 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -1,5 +1,114 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Transformer API addSource implementation file custom path 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +const implSymbol = utils.implSymbol; +const ctorRegistrySymbol = utils.ctorRegistrySymbol; + +const interfaceName = \\"Foo\\"; + +exports.is = value => { + return utils.isObject(value) && utils.hasOwn(value, implSymbol) && value[implSymbol] instanceof Impl.implementation; +}; +exports.isImpl = value => { + return utils.isObject(value) && value instanceof Impl.implementation; +}; +exports.convert = (value, { context = \\"The provided value\\" } = {}) => { + if (exports.is(value)) { + return utils.implForWrapper(value); + } + throw new TypeError(\`\${context} is not of type 'Foo'.\`); +}; + +function makeWrapper(globalObject) { + if (globalObject[ctorRegistrySymbol] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistrySymbol][\\"Foo\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor Foo is not installed on the passed global object\\"); + } + + return Object.create(ctor.prototype); +} + +exports.create = (globalObject, constructorArgs, privateData) => { + const wrapper = makeWrapper(globalObject); + return exports.setup(wrapper, globalObject, constructorArgs, privateData); +}; + +exports.createImpl = (globalObject, constructorArgs, privateData) => { + const wrapper = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(wrapper); +}; + +exports._internalSetup = (wrapper, globalObject) => {}; + +exports.setup = (wrapper, globalObject, constructorArgs = [], privateData = {}) => { + privateData.wrapper = wrapper; + + exports._internalSetup(wrapper, globalObject); + Object.defineProperty(wrapper, implSymbol, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + wrapper[implSymbol][utils.wrapperSymbol] = wrapper; + if (Impl.init) { + Impl.init(wrapper[implSymbol]); + } + return wrapper; +}; + +exports.new = globalObject => { + const wrapper = makeWrapper(globalObject); + + exports._internalSetup(wrapper, globalObject); + Object.defineProperty(wrapper, implSymbol, { + value: Object.create(Impl.implementation.prototype), + configurable: true + }); + + wrapper[implSymbol][utils.wrapperSymbol] = wrapper; + if (Impl.init) { + Impl.init(wrapper[implSymbol]); + } + return wrapper[implSymbol]; +}; + +const exposed = new Set([\\"Window\\"]); + +exports.install = (globalObject, globalNames) => { + if (!globalNames.some(globalName => exposed.has(globalName))) { + return; + } + class Foo { + constructor() { + throw new TypeError(\\"Illegal constructor\\"); + } + } + Object.defineProperties(Foo.prototype, { [Symbol.toStringTag]: { value: \\"Foo\\", configurable: true } }); + if (globalObject[ctorRegistrySymbol] === undefined) { + globalObject[ctorRegistrySymbol] = Object.create(null); + } + globalObject[ctorRegistrySymbol][interfaceName] = Foo; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: Foo + }); +}; + +const Impl = require(\\"../fixtures/custom-path/impl-file.js\\"); +" +`; + exports[`with processors AsyncCallbackInterface.webidl 1`] = ` "\\"use strict\\"; diff --git a/test/fixtures/custom-path/impl-file.js b/test/fixtures/custom-path/impl-file.js new file mode 100644 index 00000000..b5322825 --- /dev/null +++ b/test/fixtures/custom-path/impl-file.js @@ -0,0 +1,9 @@ +"use strict"; + +exports.implementation = class FooImpl { + constructor(globalObject, args, privateData) { + this._globalObject = globalObject; + this._args = args; + this._privateData = privateData; + } +}; diff --git a/test/fixtures/custom-path/some.webidl b/test/fixtures/custom-path/some.webidl new file mode 100644 index 00000000..75e3e6b3 --- /dev/null +++ b/test/fixtures/custom-path/some.webidl @@ -0,0 +1,3 @@ +[Exposed=Window] +interface Foo { +}; diff --git a/test/test.js b/test/test.js index 092338d0..51dec4c1 100644 --- a/test/test.js +++ b/test/test.js @@ -9,9 +9,31 @@ const rootDir = path.resolve(__dirname, ".."); const casesDir = path.resolve(__dirname, "cases"); const implsDir = path.resolve(__dirname, "implementations"); const outputDir = path.resolve(__dirname, "output"); +const fixturesDir = path.resolve(__dirname, "fixtures"); const idlFiles = fs.readdirSync(casesDir); +describe("Transformer API", () => { + describe("addSource", () => { + test("implementation file custom path", async () => { + const customPathDir = path.resolve(fixturesDir, "custom-path"); + + const transformer = new Transformer({ implSuffix: "-impl" }); + transformer.addSource( + path.join(customPathDir, "some.webidl"), + path.join(customPathDir, "impl-file.js") + ); + + await transformer.generate(outputDir); + + const outputFile = path.resolve(outputDir, "Foo.js"); + const output = fs.readFileSync(outputFile, { encoding: "utf-8" }); + + expect(output).toMatchSnapshot(); + }); + }); +}); + describe("without processors", () => { beforeAll(() => { const transformer = new Transformer();