diff --git a/scripts/build/transform/transforms/transform-doc-module-imports.js b/scripts/build/transform/transforms/transform-doc-module-imports.js index 9a85fa5d2..ebf0ee34a 100644 --- a/scripts/build/transform/transforms/transform-doc-module-imports.js +++ b/scripts/build/transform/transforms/transform-doc-module-imports.js @@ -72,7 +72,7 @@ function getImportDeclarationReplacement(node, file) { * @returns {import("@babel/types").Program} */ function transformProgramWithoutCache(program, file) { - const replaced = []; + const replacements = []; const body = program.body .map((node) => { const replacement = getImportDeclarationReplacement(node, file); @@ -81,24 +81,38 @@ function transformProgramWithoutCache(program, file) { return node; } - replaced.push(...replacement.removed); + replacements.push(...replacement.removed); return replacement.node; }) .filter(Boolean); - if (replaced.length === 0) { + if (replacements.length === 0) { return; } const identityPrefix = "__doc_"; - const namespaces = [...new Set(replaced.map(({ namespace }) => namespace))]; + + const grouped = []; + for (const replacement of replacements) { + const group = grouped.find( + (searching) => searching.namespace === replacement.namespace, + ); + if (!group) { + grouped.push({ + namespace: replacement.namespace, + replacements: [replacement], + }); + } else { + group.replacements.push(replacement); + } + } body.unshift( { type: "ImportDeclaration", source: createStringLiteral(PUBLIC_DOC_MODULE_PATH), - specifiers: namespaces.map((namespace) => ({ + specifiers: grouped.map(({ namespace }) => ({ type: "ImportSpecifier", imported: createIdentifier(namespace), local: createIdentifier(`${identityPrefix}${namespace}`), @@ -107,10 +121,18 @@ function transformProgramWithoutCache(program, file) { { type: "VariableDeclaration", kind: "const", - declarations: replaced.map(({ variable, namespace, name }) => ({ + declarations: grouped.map(({ namespace, replacements }) => ({ type: "VariableDeclarator", - id: createIdentifier(variable), - init: createMemberExpression(`${identityPrefix}${namespace}.${name}`), + id: { + type: "ObjectPattern", + properties: replacements.map(({ variable, name }) => ({ + type: "ObjectProperty", + key: createIdentifier(name), + value: createIdentifier(variable), + shorthand: name === variable, + })), + }, + init: createMemberExpression(`${identityPrefix}${namespace}`), })), }, ); diff --git a/tests/unit/syntax-transform.js b/tests/unit/syntax-transform.js index 318d130bf..7ac716674 100644 --- a/tests/unit/syntax-transform.js +++ b/tests/unit/syntax-transform.js @@ -1,5 +1,6 @@ import path from "node:path"; import url from "node:url"; +import { outdent } from "outdent"; import transformCode from "../../scripts/build/transform/index.js"; const file = url.fileURLToPath( @@ -12,6 +13,7 @@ const publicDocPath = url.fileURLToPath( new URL("../../src/document/public.js", import.meta.url), ); const transform = (code) => + "\n" + transformCode(code, file, { reuseDocModule: true, }) @@ -22,128 +24,236 @@ const transform = (code) => .replaceAll( JSON.stringify(publicDocPath).slice(1, -1), "", - ); + ) + + "\n"; test("Object.hasOwn", () => { - expect(transform("Object.hasOwn(foo, bar)")).toMatchInlineSnapshot( - `"Object.prototype.hasOwnProperty.call(foo, bar);"`, - ); + expect(transform("Object.hasOwn(foo, bar)")).toMatchInlineSnapshot(` + " + Object.prototype.hasOwnProperty.call(foo, bar); + " + `); }); test(".at", () => { expect(transform("foo.at(-1)")).toMatchInlineSnapshot(` - "import __at from "/method-at.js"; + " + import __at from "/method-at.js"; - __at(/* OPTIONAL_OBJECT: false */0, foo, -1);" + __at(/* OPTIONAL_OBJECT: false */0, foo, -1); + " `); expect(transform("foo?.at(-1)")).toMatchInlineSnapshot(` - "import __at from "/method-at.js"; + " + import __at from "/method-at.js"; - __at(/* OPTIONAL_OBJECT: true */1, foo, -1);" + __at(/* OPTIONAL_OBJECT: true */1, foo, -1); + " `); expect(transform("foo?.bar.baz.at(-1)")).toMatchInlineSnapshot(` - "import __at from "/method-at.js"; + " + import __at from "/method-at.js"; - __at(/* OPTIONAL_OBJECT: true */1, foo?.bar.baz, -1);" + __at(/* OPTIONAL_OBJECT: true */1, foo?.bar.baz, -1); + " `); expect(transform("foo.at(-1)?.bar")).toMatchInlineSnapshot(` - "import __at from "/method-at.js"; + " + import __at from "/method-at.js"; - __at(/* OPTIONAL_OBJECT: false */0, foo, -1)?.bar;" + __at(/* OPTIONAL_OBJECT: false */0, foo, -1)?.bar; + " `); // Optional call not supported - expect(transform("foo.at?.(-1)")).toMatchInlineSnapshot(`"foo.at?.(-1)"`); + expect(transform("foo.at?.(-1)")).toMatchInlineSnapshot(` + " + foo.at?.(-1) + " + `); }); test("String#replaceAll", () => { expect(transform("foo.replaceAll('a', 'b')")).toMatchInlineSnapshot(` - "import __replaceAll from "/method-replace-all.js"; + " + import __replaceAll from "/method-replace-all.js"; - __replaceAll(/* OPTIONAL_OBJECT: false */0, foo, 'a', 'b');" + __replaceAll(/* OPTIONAL_OBJECT: false */0, foo, 'a', 'b'); + " `); }); test("Array#findLast", () => { expect(transform("foo.findLast(callback)")).toMatchInlineSnapshot(` - "import __findLast from "/method-find-last.js"; + " + import __findLast from "/method-find-last.js"; - __findLast(/* OPTIONAL_OBJECT: false */0, foo, callback);" + __findLast(/* OPTIONAL_OBJECT: false */0, foo, callback); + " `); expect(transform("foo?.findLast(callback)")).toMatchInlineSnapshot(` - "import __findLast from "/method-find-last.js"; + " + import __findLast from "/method-find-last.js"; - __findLast(/* OPTIONAL_OBJECT: true */1, foo, callback);" + __findLast(/* OPTIONAL_OBJECT: true */1, foo, callback); + " `); // Not supported - expect( - transform("foo.findLast(callback, thisArgument)"), - ).toMatchInlineSnapshot(`"foo.findLast(callback, thisArgument)"`); + expect(transform("foo.findLast(callback, thisArgument)")) + .toMatchInlineSnapshot(` + " + foo.findLast(callback, thisArgument) + " + `); }); test("Array#findLastIndex", () => { expect(transform("foo.findLastIndex(callback)")).toMatchInlineSnapshot(` - "import __findLastIndex from "/method-find-last-index.js"; + " + import __findLastIndex from "/method-find-last-index.js"; - __findLastIndex(/* OPTIONAL_OBJECT: false */0, foo, callback);" + __findLastIndex(/* OPTIONAL_OBJECT: false */0, foo, callback); + " `); expect(transform("foo?.findLastIndex(callback)")).toMatchInlineSnapshot(` - "import __findLastIndex from "/method-find-last-index.js"; + " + import __findLastIndex from "/method-find-last-index.js"; - __findLastIndex(/* OPTIONAL_OBJECT: true */1, foo, callback);" + __findLastIndex(/* OPTIONAL_OBJECT: true */1, foo, callback); + " `); // Not supported - expect( - transform("foo.findLastIndex(callback, thisArgument)"), - ).toMatchInlineSnapshot(`"foo.findLastIndex(callback, thisArgument)"`); + expect(transform("foo.findLastIndex(callback, thisArgument)")) + .toMatchInlineSnapshot(` + " + foo.findLastIndex(callback, thisArgument) + " + `); }); test("Array#toReversed", () => { expect(transform("foo.toReversed()")).toMatchInlineSnapshot(` - "import __toReversed from "/method-to-reversed.js"; + " + import __toReversed from "/method-to-reversed.js"; - __toReversed(/* OPTIONAL_OBJECT: false */0, foo);" + __toReversed(/* OPTIONAL_OBJECT: false */0, foo); + " `); expect(transform("foo?.toReversed()")).toMatchInlineSnapshot(` - "import __toReversed from "/method-to-reversed.js"; + " + import __toReversed from "/method-to-reversed.js"; - __toReversed(/* OPTIONAL_OBJECT: true */1, foo);" + __toReversed(/* OPTIONAL_OBJECT: true */1, foo); + " `); - expect(transform("foo.toReversed(extraArgument)")).toMatchInlineSnapshot( - `"foo.toReversed(extraArgument)"`, - ); + expect(transform("foo.toReversed(extraArgument)")).toMatchInlineSnapshot(` + " + foo.toReversed(extraArgument) + " + `); }); test("String.raw", () => { - expect(transform("String.raw`\\\\\\uINVALID`")).toMatchInlineSnapshot( - String.raw`""\\\\\\uINVALID";"`, - ); - expect(transform("String.raw`\\uINVALID${'world'}`")).toMatchInlineSnapshot( - `"\`\\\\uINVALID\${'world'}\`;"`, - ); + expect(transform("String.raw`\\\\\\uINVALID`")) + .toMatchInlineSnapshot(String.raw` +" +"\\\\\\uINVALID"; +" +`); + expect(transform("String.raw`\\uINVALID${'world'}`")).toMatchInlineSnapshot(` + " + \`\\\\uINVALID\${'world'}\`; + " + `); }); test("public doc functionality", () => { expect(transform('import {align, line} from "./document/index.js"')) .toMatchInlineSnapshot(` - "import { builders as __doc_builders } from ""; - const align = __doc_builders.align, - line = __doc_builders.line;" + " + import { builders as __doc_builders } from ""; + const { + align, + line + } = __doc_builders; + " `); + expect( + transform(outdent` + import {align as renamedAlign, line, notExists} from "./document/index.js" + import {line as renamedLine, notExists2} from "./document/index.js" + `), + ).toMatchInlineSnapshot(` + " + import { builders as __doc_builders } from ""; + const { + align: renamedAlign, + line, + line: renamedLine + } = __doc_builders; + import { notExists } from "./document/index.js"; + import { notExists2 } from "./document/index.js"; + " + `); expect( transform( - 'import {align as renamedAlign, line, notExists} from "./document/index.js"', + 'import {align as renamedAlign, line, printDocToString, findInDoc, canBreak} from "./document/index.js"', ), ).toMatchInlineSnapshot(` - "import { builders as __doc_builders } from ""; - const renamedAlign = __doc_builders.align, - line = __doc_builders.line; - import { notExists } from "./document/index.js";" + " + import { builders as __doc_builders, printer as __doc_printer, utils as __doc_utils } from ""; + const { + align: renamedAlign, + line + } = __doc_builders, + { + printDocToString + } = __doc_printer, + { + findInDoc, + canBreak + } = __doc_utils; + " + `); +}); + +test("All", () => { + expect( + transform(outdent` + import {align, line} from "./document/index.js" + + if (Object.hasOwn(foo, bar)) { + const a =foo?.at(-1)?.replaceAll(/bar/, ""), + b = bar?.findLast(() => true), + c = foo.findLastIndex(callback) + const d = [a, b, foo].toReversed().join(String.raw\`\${d}\`) + } + `), + ).toMatchInlineSnapshot(` + " + import __replaceAll from "/method-replace-all.js"; + import __at from "/method-at.js"; + import __findLast from "/method-find-last.js"; + import __findLastIndex from "/method-find-last-index.js"; + import __toReversed from "/method-to-reversed.js"; + + import { builders as __doc_builders } from ""; + const { + align, + line + } = __doc_builders; + if (Object.prototype.hasOwnProperty.call(foo, bar)) { + const a = __replaceAll(/* OPTIONAL_OBJECT: true */1, __at(/* OPTIONAL_OBJECT: true */1, foo, -1), /bar/, ""), + b = __findLast(/* OPTIONAL_OBJECT: true */1, bar, () => true), + c = __findLastIndex(/* OPTIONAL_OBJECT: false */0, foo, callback); + const d = __toReversed(/* OPTIONAL_OBJECT: false */0, [a, b, foo]).join(\`\${d}\`); + } + " `); });