From f625198aea3743480b303b3ad69b547a34993270 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 11 Aug 2022 10:59:24 -0400 Subject: [PATCH 1/5] Name signals and stores --- packages/transform/README.md | 5 +- packages/transform/src/index.ts | 6 +- packages/transform/src/name.ts | 144 +++++++++++++++++++++ packages/transform/test/name.test.ts | 135 +++++++++++++++++++ packages/transform/test/utils.ts | 15 +++ packages/transform/test/wrapStores.test.ts | 28 +--- 6 files changed, 310 insertions(+), 23 deletions(-) create mode 100644 packages/transform/src/name.ts create mode 100644 packages/transform/test/name.test.ts create mode 100644 packages/transform/test/utils.ts diff --git a/packages/transform/README.md b/packages/transform/README.md index 10c94405..969f7f8b 100644 --- a/packages/transform/README.md +++ b/packages/transform/README.md @@ -6,7 +6,7 @@ # @solid-devtools/transform -Vite plugin for transforming SolidJS code in development to enchance solid-devtools usage. +Vite plugin for transforming SolidJS code in development to enhance solid-devtools usage. ## Getting Started @@ -44,12 +44,15 @@ interface DevtoolsPluginOptions { wrapStores?: boolean /** Inject location attributes to jsx templates */ jsxLocation?: boolean + /** Name signals and stores */ + name?: boolean } // in vite.config.ts plugins array: devtoolsPlugin({ wrapStores: true, jsxLocation: true, + name: true, }) ``` diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts index c94ec4d3..50b4867b 100644 --- a/packages/transform/src/index.ts +++ b/packages/transform/src/index.ts @@ -2,6 +2,7 @@ import { PluginItem, transformAsync } from "@babel/core" import { PluginOption } from "vite" import { getFileExtension } from "./utils" import jsxLocationPlugin from "./jsxLocation" +import namePlugin from "./name" import wrapStoresPlugin from "./wrapStores" export interface DevtoolsPluginOptions { @@ -9,11 +10,13 @@ export interface DevtoolsPluginOptions { wrapStores?: boolean /** Inject location attributes to jsx templates */ jsxLocation?: boolean + /** Name signals and stores */ + name?: boolean } // This export is used for configuration. const devtoolsPlugin = (options: DevtoolsPluginOptions = {}): PluginOption => { - const { wrapStores = false, jsxLocation = false } = options + const { wrapStores = false, jsxLocation = false, name = false } = options let enablePlugin = false let projectRoot = process.cwd() @@ -38,6 +41,7 @@ const devtoolsPlugin = (options: DevtoolsPluginOptions = {}): PluginOption => { // plugins that should only run on .tsx/.jsx files in development if (jsxLocation && isJSX) plugins.push(jsxLocationPlugin) if (wrapStores) plugins.push(wrapStoresPlugin) + if (name) plugins.push(namePlugin) if (plugins.length === 0) return diff --git a/packages/transform/src/name.ts b/packages/transform/src/name.ts new file mode 100644 index 00000000..ca0899a8 --- /dev/null +++ b/packages/transform/src/name.ts @@ -0,0 +1,144 @@ +import { PluginObj } from "@babel/core" +import * as t from "@babel/types" + +const nameId = t.identifier("name") + +type Comparable = t.Identifier | t.V8IntrinsicIdentifier | + t.PrivateName | t.Expression +function equal(a: Comparable, b: Comparable): boolean { + if (a.type !== b.type) return false + switch (a.type) { + case "Identifier": + case "V8IntrinsicIdentifier": + return a.name === (b as t.Identifier).name + case "PrivateName": + return a.id === (b as t.PrivateName).id + case "MemberExpression": + return equal(a.object, (b as t.MemberExpression).object) && + equal(a.property, (b as t.MemberExpression).property) + default: // other type of Expression + return false + } +} + +type Source = "createSignal" | "createStore" | "createMutable" + +let sources: Record + +const namePlugin: PluginObj = { + name: "@solid-devtools/name", + visitor: { + Program() { + sources = { + createSignal: [], + createStore: [], + createMutable: [], + } + }, + + // Track imported references to createSignal, createStore, createMutable + ImportDeclaration(path) { + const node = path.node + const source = node.source.value + let targets: Source[] + switch (source) { + case "solid-js": + targets = ["createSignal"]; + break; + case "solid-js/store": + targets = ["createStore", "createMutable"]; + break; + default: + return; + } + for (const s of node.specifiers) { + switch (s.type) { + // import * as local from "solid-js" + case "ImportNamespaceSpecifier": + for (let target of targets) { + sources[target].push( + t.memberExpression(s.local, t.identifier(target)) + ) + } + break + // import { createSignal } from "solid-js" + // import { createSignal as local } from "solid-js" + // import { "createSignal" as local } from "solid-js" + case "ImportSpecifier": + let target: Source + switch (s.imported.type) { + case "Identifier": + if (!targets.includes(s.imported.name)) continue + target = s.imported.name as Source + break + case "StringLiteral": + if (!targets.includes(s.imported.value)) continue + target = s.imported.value as Source + break + default: + continue + } + sources[target].push(s.local) + break + } + } + }, + + VariableDeclaration(path) { + const declarations = path.node.declarations + for (let declaration of declarations) { + // Check initializer is a call to createSignal/createStore/createMutable + const init = declaration.init + if (!init) continue + if (init.type !== "CallExpression") continue + if (!Object.entries(sources).some(([, sources]) => + (sources as Comparable[]).some(source => + equal(init.callee, source) + ) + )) continue + + // Check declaration is either identifier or [identifier, ...] + const id = declaration.id + let name + switch (id.type) { + case "Identifier": + name = id.name + break + case "ArrayPattern": + if (!id.elements.length) continue + const first = id.elements[0] + if (!first) continue + if (first.type !== "Identifier") continue + name = first.name + break + default: + continue + } + + // Modify call to include name in options + const nameProperty = t.objectProperty(nameId, t.stringLiteral(name)) + switch (init.arguments.length) { + case 0: + init.arguments.push(t.identifier("undefined")) + // now 1 argument, fall through to case 1: + case 1: + init.arguments.push(t.objectExpression([nameProperty])) + break + default: // 2 or more arguments + const second = init.arguments[1] + if (second.type !== "ObjectExpression") continue + // Check there isn't already a "name" property + if (second.properties.some((property) => + property.type === "ObjectProperty" && + property.key.type === "Identifier" && + property.key.name === nameId.name)) continue + if (second.type !== "ObjectExpression") continue + second.properties.unshift(nameProperty) + break + } + } + }, + }, +} + +export default namePlugin diff --git a/packages/transform/test/name.test.ts b/packages/transform/test/name.test.ts new file mode 100644 index 00000000..e45854eb --- /dev/null +++ b/packages/transform/test/name.test.ts @@ -0,0 +1,135 @@ +import plugin from "../src/name" +import { assertTransform } from "./utils" + +// Positive tests +for (let [create, module] of [ + ["createSignal", "solid-js"], + ["createStore", "solid-js/store"], + ["createMutable", "solid-js/store"], +]) { + describe(create, () => { + for (let [type, importStatement, creator] of [ + ["named import", `import { ${create} } from "${module}";`, create], + ["renamed import", `import { ${create} as foo } from "${module}";`, "foo"], + ["namespace import", `import * as foo from "${module}";`, `foo.${create}`], + ]) { + describe(type, () => { + test("no default value", () => { + const src = `${importStatement} + const signal = ${creator}();` + + const expectedOutput = `${importStatement} +const signal = ${creator}(undefined, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("default value", () => { + const src = `${importStatement} +const signal = ${creator}(5);` + + const expectedOutput = `${importStatement} +const signal = ${creator}(5, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("empty options", () => { + const src = `${importStatement} +const rest = {}; +const signal = ${creator}(5, {});` + + const expectedOutput = `${importStatement} +const rest = {}; +const signal = ${creator}(5, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("options excluding name", () => { + const src = `${importStatement} +const rest = {}; +const signal = ${creator}(5, { equals: false, ...rest });` + + const expectedOutput = `${importStatement} +const rest = {}; +const signal = ${creator}(5, { + name: "signal", + equals: false, + ...rest +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("options including name", () => { + const src = `${importStatement} +const rest = {}; +const signal = ${creator}(5, { equals: false, name: "foo", ...rest });` + + const expectedOutput = `${importStatement} +const rest = {}; +const signal = ${creator}(5, { + equals: false, + name: "foo", + ...rest +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("array of length 1", () => { + const src = `${importStatement} +const [signal] = ${creator}(5);` + + const expectedOutput = `${importStatement} +const [signal] = ${creator}(5, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin) + }) + + test("array of length 2", () => { + const src = `${importStatement} +const [signal, setSignal] = ${creator}(5);` + + const expectedOutput = `${importStatement} +const [signal, setSignal] = ${creator}(5, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin) + }) + }) + } + }) +} + +// Negative tests +for (let [create, module] of [ + ["createSignal", "solid-js/store"], + ["createStore", "solid-js"], + ["createMutable", "solid-js"], +]) { + describe(create, () => { + test(`no import`, () => { + const src = `const signal = ${create}();` + + assertTransform(src, src, plugin) + }) + + test(`incorrect import`, () => { + const src = `import { ${create} } from "${module}"; +const signal = ${create}();` + + assertTransform(src, src, plugin) + }) + }) +} diff --git a/packages/transform/test/utils.ts b/packages/transform/test/utils.ts new file mode 100644 index 00000000..28228200 --- /dev/null +++ b/packages/transform/test/utils.ts @@ -0,0 +1,15 @@ +import { PluginObj, traverse } from "@babel/core" +import { parse } from "@babel/parser" +import generate from "@babel/generator" + +export function assertTransform(src: string, expectedOutput: string, plugin: PluginObj) { + const ast = parse(src, { + sourceType: "module", + plugins: ["jsx"], + }) + + traverse(ast, plugin.visitor) + const res = generate(ast) + + expect(res.code).toBe(expectedOutput) +} diff --git a/packages/transform/test/wrapStores.test.ts b/packages/transform/test/wrapStores.test.ts index 6f7619ae..15e34881 100644 --- a/packages/transform/test/wrapStores.test.ts +++ b/packages/transform/test/wrapStores.test.ts @@ -1,20 +1,6 @@ -import { traverse } from "@babel/core" -import generate from "@babel/generator" -import { parse } from "@babel/parser" import { storeOverwriteName, storeOverwriteNamespace } from "../src/utils" import plugin from "../src/wrapStores" - -function assertTransform(src: string, expectedOutput: string) { - const ast = parse(src, { - sourceType: "module", - plugins: ["jsx"], - }) - - traverse(ast, plugin.visitor) - const res = generate(ast) - - expect(res.code).toBe(expectedOutput) -} +import { assertTransform } from "./utils" describe("createStore", () => { test("named import", () => { @@ -32,7 +18,7 @@ const createStore = (obj, options) => { return ${storeOverwriteName}0(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) test("renamed import", () => { @@ -50,7 +36,7 @@ const createSolidStore = (obj, options) => { return ${storeOverwriteName}0(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) }) @@ -70,7 +56,7 @@ const createMutable = (obj, options) => { return ${storeOverwriteName}0(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) test("renamed import", () => { @@ -88,7 +74,7 @@ const createSolidStore = (obj, options) => { return ${storeOverwriteName}0(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) }) @@ -121,7 +107,7 @@ Store.createMutable = (obj, options) => { return ${storeOverwriteName}1(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) test("both", () => { @@ -149,5 +135,5 @@ const createStore = (obj, options) => { return ${storeOverwriteName}1(wrappedObj, options); };` - assertTransform(src, expectedOutput) + assertTransform(src, expectedOutput, plugin) }) From d59e5945d1ce967e0f67ca8cad7a65ef550d3308 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 11 Aug 2022 11:10:29 -0400 Subject: [PATCH 2/5] Test and fix interaction with wrapStores plugin --- packages/transform/src/index.ts | 2 +- packages/transform/test/name.test.ts | 26 ++++++++++++++++++++++++++ packages/transform/test/utils.ts | 6 ++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts index 50b4867b..5699b622 100644 --- a/packages/transform/src/index.ts +++ b/packages/transform/src/index.ts @@ -40,8 +40,8 @@ const devtoolsPlugin = (options: DevtoolsPluginOptions = {}): PluginOption => { // plugins that should only run on .tsx/.jsx files in development if (jsxLocation && isJSX) plugins.push(jsxLocationPlugin) + if (name) plugins.push(namePlugin) // must come before wrapStores if (wrapStores) plugins.push(wrapStoresPlugin) - if (name) plugins.push(namePlugin) if (plugins.length === 0) return diff --git a/packages/transform/test/name.test.ts b/packages/transform/test/name.test.ts index e45854eb..bda2af1a 100644 --- a/packages/transform/test/name.test.ts +++ b/packages/transform/test/name.test.ts @@ -1,4 +1,5 @@ import plugin from "../src/name" +import wrapPlugin from "../src/wrapStores" import { assertTransform } from "./utils" // Positive tests @@ -133,3 +134,28 @@ const signal = ${create}();` }) }) } + +describe("wrapStores interaction", () => { + test("named import", () => { + const src = `import { createStore } from "solid-js/store"; +const signal = createStore();` + + const expectedOutput = `import { createStore as $sdt_createStore0 } from "solid-js/store"; + +const createStore = (obj, options) => { + let wrappedObj = obj; + + if (typeof window.$sdt_wrapStore === "function") { + wrappedObj = window.$sdt_wrapStore(obj); + } + + return $sdt_createStore0(wrappedObj, options); +}; + +const signal = createStore(undefined, { + name: "signal" +});` + + assertTransform(src, expectedOutput, plugin, wrapPlugin) + }) +}); diff --git a/packages/transform/test/utils.ts b/packages/transform/test/utils.ts index 28228200..1ed2d9de 100644 --- a/packages/transform/test/utils.ts +++ b/packages/transform/test/utils.ts @@ -2,13 +2,15 @@ import { PluginObj, traverse } from "@babel/core" import { parse } from "@babel/parser" import generate from "@babel/generator" -export function assertTransform(src: string, expectedOutput: string, plugin: PluginObj) { +export function assertTransform(src: string, expectedOutput: string, ...plugins: PluginObj[]) { const ast = parse(src, { sourceType: "module", plugins: ["jsx"], }) - traverse(ast, plugin.visitor) + for (const plugin of plugins) { + traverse(ast, plugin.visitor) + } const res = generate(ast) expect(res.code).toBe(expectedOutput) From d91d364686e86d76abc1a7c037c6bf6c4bf89398 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 11 Aug 2022 11:24:33 -0400 Subject: [PATCH 3/5] createMemo support --- packages/transform/src/name.ts | 5 +++-- packages/transform/test/name.test.ts | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/transform/src/name.ts b/packages/transform/src/name.ts index ca0899a8..e286aed0 100644 --- a/packages/transform/src/name.ts +++ b/packages/transform/src/name.ts @@ -21,7 +21,7 @@ function equal(a: Comparable, b: Comparable): boolean { } } -type Source = "createSignal" | "createStore" | "createMutable" +type Source = "createSignal" | "createMemo" | "createStore" | "createMutable" let sources: Record @@ -31,6 +31,7 @@ const namePlugin: PluginObj = { Program() { sources = { createSignal: [], + createMemo: [], createStore: [], createMutable: [], } @@ -43,7 +44,7 @@ const namePlugin: PluginObj = { let targets: Source[] switch (source) { case "solid-js": - targets = ["createSignal"]; + targets = ["createSignal", "createMemo"]; break; case "solid-js/store": targets = ["createStore", "createMutable"]; diff --git a/packages/transform/test/name.test.ts b/packages/transform/test/name.test.ts index bda2af1a..83867ccb 100644 --- a/packages/transform/test/name.test.ts +++ b/packages/transform/test/name.test.ts @@ -5,6 +5,7 @@ import { assertTransform } from "./utils" // Positive tests for (let [create, module] of [ ["createSignal", "solid-js"], + ["createMemo", "solid-js"], ["createStore", "solid-js/store"], ["createMutable", "solid-js/store"], ]) { @@ -116,6 +117,7 @@ const [signal, setSignal] = ${creator}(5, { // Negative tests for (let [create, module] of [ ["createSignal", "solid-js/store"], + ["createMemo", "solid-js/store"], ["createStore", "solid-js"], ["createMutable", "solid-js"], ]) { From 7ecef1d17882f4a659667050bde4533e105971c7 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 11 Aug 2022 11:26:27 -0400 Subject: [PATCH 4/5] Better description of name plugin --- packages/transform/README.md | 2 +- packages/transform/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/transform/README.md b/packages/transform/README.md index 969f7f8b..2e51319b 100644 --- a/packages/transform/README.md +++ b/packages/transform/README.md @@ -44,7 +44,7 @@ interface DevtoolsPluginOptions { wrapStores?: boolean /** Inject location attributes to jsx templates */ jsxLocation?: boolean - /** Name signals and stores */ + /** Add automatic name when creating signals, memos, stores, or mutables */ name?: boolean } diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts index 5699b622..f0f32456 100644 --- a/packages/transform/src/index.ts +++ b/packages/transform/src/index.ts @@ -10,7 +10,7 @@ export interface DevtoolsPluginOptions { wrapStores?: boolean /** Inject location attributes to jsx templates */ jsxLocation?: boolean - /** Name signals and stores */ + /** Add automatic name when creating signals, memos, stores, or mutables */ name?: boolean } From e8bf823e8d03a583c6af2f7f5b89bb7d8442a8e7 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 11 Aug 2022 11:41:55 -0400 Subject: [PATCH 5/5] Fix argument number for createMemo --- packages/transform/src/name.ts | 59 ++++++++++++++++------------ packages/transform/test/name.test.ts | 25 ++++++------ 2 files changed, 47 insertions(+), 37 deletions(-) diff --git a/packages/transform/src/name.ts b/packages/transform/src/name.ts index e286aed0..98933bf2 100644 --- a/packages/transform/src/name.ts +++ b/packages/transform/src/name.ts @@ -23,6 +23,13 @@ function equal(a: Comparable, b: Comparable): boolean { type Source = "createSignal" | "createMemo" | "createStore" | "createMutable" +const optionsArg: Record = { + createSignal: 1, + createMemo: 2, + createStore: 1, + createMutable: 1, +} + let sources: Record const namePlugin: PluginObj = { @@ -37,7 +44,7 @@ const namePlugin: PluginObj = { } }, - // Track imported references to createSignal, createStore, createMutable + // Track imported references to createSignal/createMemo/createStore/createMutable ImportDeclaration(path) { const node = path.node const source = node.source.value @@ -88,15 +95,18 @@ const namePlugin: PluginObj = { VariableDeclaration(path) { const declarations = path.node.declarations for (let declaration of declarations) { - // Check initializer is a call to createSignal/createStore/createMutable + // Check initializer is a call to createSignal/createMemo/createStore/createMutable const init = declaration.init if (!init) continue if (init.type !== "CallExpression") continue - if (!Object.entries(sources).some(([, sources]) => - (sources as Comparable[]).some(source => - equal(init.callee, source) - ) - )) continue + let target: Source | undefined + for (let [someTarget, someSources] of Object.entries(sources) as [Source, Comparable[]][]) { + if (someSources.some(source => equal(init.callee, source))) { + target = someTarget + break + } + } + if (!target) continue // Check declaration is either identifier or [identifier, ...] const id = declaration.id @@ -118,24 +128,23 @@ const namePlugin: PluginObj = { // Modify call to include name in options const nameProperty = t.objectProperty(nameId, t.stringLiteral(name)) - switch (init.arguments.length) { - case 0: - init.arguments.push(t.identifier("undefined")) - // now 1 argument, fall through to case 1: - case 1: - init.arguments.push(t.objectExpression([nameProperty])) - break - default: // 2 or more arguments - const second = init.arguments[1] - if (second.type !== "ObjectExpression") continue - // Check there isn't already a "name" property - if (second.properties.some((property) => - property.type === "ObjectProperty" && - property.key.type === "Identifier" && - property.key.name === nameId.name)) continue - if (second.type !== "ObjectExpression") continue - second.properties.unshift(nameProperty) - break + const argIndex = optionsArg[target] + while (init.arguments.length < argIndex) { + init.arguments.push(t.identifier("undefined")) + } + if (init.arguments.length === argIndex) { // no options argument + init.arguments.push(t.objectExpression([nameProperty])) + } else { // existing options argument + const options = init.arguments[argIndex] + if (options.type !== "ObjectExpression") continue + // Check there isn't already a "name" property + if (options.properties.some((property) => + property.type === "ObjectProperty" && + property.key.type === "Identifier" && + property.key.name === nameId.name)) continue + if (options.type !== "ObjectExpression") continue + options.properties.unshift(nameProperty) + break } } }, diff --git a/packages/transform/test/name.test.ts b/packages/transform/test/name.test.ts index 83867ccb..f499fa2c 100644 --- a/packages/transform/test/name.test.ts +++ b/packages/transform/test/name.test.ts @@ -3,12 +3,13 @@ import wrapPlugin from "../src/wrapStores" import { assertTransform } from "./utils" // Positive tests -for (let [create, module] of [ +for (let [create, module, extraArg] of [ ["createSignal", "solid-js"], - ["createMemo", "solid-js"], + ["createMemo", "solid-js", "1"], ["createStore", "solid-js/store"], ["createMutable", "solid-js/store"], ]) { + extraArg = extraArg ? "undefined, " : ""; describe(create, () => { for (let [type, importStatement, creator] of [ ["named import", `import { ${create} } from "${module}";`, create], @@ -21,7 +22,7 @@ for (let [create, module] of [ const signal = ${creator}();` const expectedOutput = `${importStatement} -const signal = ${creator}(undefined, { +const signal = ${creator}(undefined, ${extraArg}{ name: "signal" });` @@ -33,7 +34,7 @@ const signal = ${creator}(undefined, { const signal = ${creator}(5);` const expectedOutput = `${importStatement} -const signal = ${creator}(5, { +const signal = ${creator}(5, ${extraArg}{ name: "signal" });` @@ -43,11 +44,11 @@ const signal = ${creator}(5, { test("empty options", () => { const src = `${importStatement} const rest = {}; -const signal = ${creator}(5, {});` +const signal = ${creator}(5, ${extraArg}{});` const expectedOutput = `${importStatement} const rest = {}; -const signal = ${creator}(5, { +const signal = ${creator}(5, ${extraArg}{ name: "signal" });` @@ -57,11 +58,11 @@ const signal = ${creator}(5, { test("options excluding name", () => { const src = `${importStatement} const rest = {}; -const signal = ${creator}(5, { equals: false, ...rest });` +const signal = ${creator}(5, ${extraArg}{ equals: false, ...rest });` const expectedOutput = `${importStatement} const rest = {}; -const signal = ${creator}(5, { +const signal = ${creator}(5, ${extraArg}{ name: "signal", equals: false, ...rest @@ -73,11 +74,11 @@ const signal = ${creator}(5, { test("options including name", () => { const src = `${importStatement} const rest = {}; -const signal = ${creator}(5, { equals: false, name: "foo", ...rest });` +const signal = ${creator}(5, ${extraArg}{ equals: false, name: "foo", ...rest });` const expectedOutput = `${importStatement} const rest = {}; -const signal = ${creator}(5, { +const signal = ${creator}(5, ${extraArg}{ equals: false, name: "foo", ...rest @@ -91,7 +92,7 @@ const signal = ${creator}(5, { const [signal] = ${creator}(5);` const expectedOutput = `${importStatement} -const [signal] = ${creator}(5, { +const [signal] = ${creator}(5, ${extraArg}{ name: "signal" });` @@ -103,7 +104,7 @@ const [signal] = ${creator}(5, { const [signal, setSignal] = ${creator}(5);` const expectedOutput = `${importStatement} -const [signal, setSignal] = ${creator}(5, { +const [signal, setSignal] = ${creator}(5, ${extraArg}{ name: "signal" });`