diff --git a/apps/web/content/docs/2.components/01.props.md b/apps/web/content/docs/2.components/01.props.md index 82ec64529..30da7cdab 100644 --- a/apps/web/content/docs/2.components/01.props.md +++ b/apps/web/content/docs/2.components/01.props.md @@ -140,15 +140,17 @@ const { name, price, stock, available = true } = defineProps() ```svelte [ProductTile.svelte] - + + + + + + + + + + + + + + + + + + + + + + + + + +", "", "

", ""].join("\n"); + expect(await formatted(path, contents)).toBe( + [open, `${indent}let value: T | null = null;`, "", "", "

", ""].join("\n"), + ); + }); + it('formats a Vue ` + @@ -32,6 +34,21 @@ A component is that line, an instance script when it takes props or has a setup, (`$`-prefixed) is named `props`. Script lines are indented, but for lines inside a multi-line literal, which the target finds by parsing the code. ``, which the instance script reads, so + a consumer imports them from the component's file: + `import type { BadgeProps } from "./Badge.svelte"`. A generic component writes its type + parameters in the instance script's `generics`, ``); + return result( + ``, + ); } // --- Composition --------------------------------------------------------------------------- @@ -443,6 +607,18 @@ class ScriptWriter { if (!names.length) this.#names.importPassThrough(specifier); for (const name of names) this.#names.importPassThrough(specifier, name); } + // The types it reads, by name, under the source's names (ADR-0067): a child's from its + // output, `./Field.svelte`. Codegen's helper picks them; the script writes its own imports. + const types = new ImportSet(); + addTypeImports(component, this.#module, types, (name) => `${name}.svelte`); + for (const declaration of types.toDeclarations()) { + for (const specifier of declaration.specifiers) { + if (specifier.type !== "ImportSpecifier") continue; + const { imported, local } = specifier; + const name = imported.type === "Identifier" ? imported.name : imported.value; + this.#names.importType(declaration.source.value, name, local.name); + } + } } /** A key's value type as written, and the ref it holds: the module's own, or an imported one's. */ @@ -458,18 +634,26 @@ class ScriptWriter { } /** - * The ``; } diff --git a/packages/target-svelte/test/emit.test.ts b/packages/target-svelte/test/emit.test.ts index 2caea3428..de70bd0c5 100644 --- a/packages/target-svelte/test/emit.test.ts +++ b/packages/target-svelte/test/emit.test.ts @@ -18,6 +18,7 @@ import target from "../src/index.ts"; import { indent } from "../src/script.ts"; import { renderToString } from "../src/toolchain/server.ts"; import { + compileSources, corpus, emitFormatted, emitSource, @@ -57,6 +58,10 @@ const svelteFile = (...blocks: string[]) => `${[OPTIONS, ...blocks].join("\n\n") /** A ``; +/** A ``; + /** The lines that end the script of a component whose root takes its styled parents' scope. */ const SCOPE_HELPER = [ "", @@ -198,9 +203,8 @@ describe("svelte props", () => { // `count` and `hidden` are read by no expression: declared, they would be unused variables. expect((await emitSource(SHAPES.destructured)).contents).toBe( svelteFile( - script( - ' import type { ClassValue } from "svelte/elements";', - "", + // An output that exports a type declares its types in `"), - ); + const start = contents.indexOf('", start)); } // ADR-0045 to ADR-0049: the setup in source order, as runes; listeners, template refs and ids. The @@ -944,3 +950,126 @@ describe("svelte composition", () => { expect(field).not.toContain("className"); }); }); + +// ADR-0067, ADR-0068: types imported by name, a module's type exports in `", + ].join("\n"), + ); + expect(palette!.split('import type { Tone } from "./types";')).toHaveLength(2); + }); + + it("writes a generic component's type parameters in the instance script's generics", async () => { + const { "Picker.svelte": picker } = await caseOutputs("generics/scoped-slot"); + expect(picker).toContain(' + +", + "", + ``, + '', + ``, + '', + "", + ].join("\n"), + }); + const results = await toolchain.typecheck(Object.values(paths), context); + expect(results.get(paths["Pick.svelte"]!)).toEqual([]); + expect(results.get(paths["Use.svelte"]!)?.map(({ code, line }) => ({ code, line }))).toEqual([ + { code: "TS2322", line: 8 }, + ]); + }, + ); + it("leaves the Svelte compiler's warnings to L3", checker, async () => { const contents = '\n\n
a
\n\n'; diff --git a/tests/integration/cases/bindings/assigned-values/__output__/svelte/ReviewSummary.svelte b/tests/integration/cases/bindings/assigned-values/__output__/svelte/ReviewSummary.svelte index a2451fcef..2862e764f 100644 --- a/tests/integration/cases/bindings/assigned-values/__output__/svelte/ReviewSummary.svelte +++ b/tests/integration/cases/bindings/assigned-values/__output__/svelte/ReviewSummary.svelte @@ -1,14 +1,16 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration/cases/jsx/quoted-strings/__output__/svelte/DraftNotice.svelte b/tests/integration/cases/jsx/quoted-strings/__output__/svelte/DraftNotice.svelte index 36cf13d60..b0ddf5d9a 100644 --- a/tests/integration/cases/jsx/quoted-strings/__output__/svelte/DraftNotice.svelte +++ b/tests/integration/cases/jsx/quoted-strings/__output__/svelte/DraftNotice.svelte @@ -1,12 +1,14 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/toolchains/svelte/consumers/generics/constrained/MisuseProp.svelte b/tests/toolchains/svelte/consumers/generics/constrained/MisuseProp.svelte new file mode 100644 index 000000000..0ab11494f --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/constrained/MisuseProp.svelte @@ -0,0 +1,6 @@ + + + +
diff --git a/tests/toolchains/svelte/consumers/generics/list/Consumer.svelte b/tests/toolchains/svelte/consumers/generics/list/Consumer.svelte new file mode 100644 index 000000000..ba7698504 --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/list/Consumer.svelte @@ -0,0 +1,5 @@ + + + diff --git a/tests/toolchains/svelte/consumers/generics/list/MisuseProp.svelte b/tests/toolchains/svelte/consumers/generics/list/MisuseProp.svelte new file mode 100644 index 000000000..9626407a3 --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/list/MisuseProp.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/tests/toolchains/svelte/consumers/generics/scoped-slot/Consumer.svelte b/tests/toolchains/svelte/consumers/generics/scoped-slot/Consumer.svelte new file mode 100644 index 000000000..1ad6e96b0 --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/scoped-slot/Consumer.svelte @@ -0,0 +1,9 @@ + + + + {#snippet item({ item })}{item.name} × {item.count}{/snippet} + diff --git a/tests/toolchains/svelte/consumers/generics/scoped-slot/MisuseSlot.svelte b/tests/toolchains/svelte/consumers/generics/scoped-slot/MisuseSlot.svelte new file mode 100644 index 000000000..6b5de869e --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/scoped-slot/MisuseSlot.svelte @@ -0,0 +1,10 @@ + + + + + {#snippet item({ item })}{item.nmae}{/snippet} + diff --git a/tests/toolchains/svelte/consumers/generics/select/Consumer.svelte b/tests/toolchains/svelte/consumers/generics/select/Consumer.svelte new file mode 100644 index 000000000..d6a1016f0 --- /dev/null +++ b/tests/toolchains/svelte/consumers/generics/select/Consumer.svelte @@ -0,0 +1,7 @@ + + + diff --git a/tests/toolchains/svelte/consumers/types/child-type-import/Consumer.svelte b/tests/toolchains/svelte/consumers/types/child-type-import/Consumer.svelte new file mode 100644 index 000000000..ef831b298 --- /dev/null +++ b/tests/toolchains/svelte/consumers/types/child-type-import/Consumer.svelte @@ -0,0 +1,8 @@ + + +
diff --git a/tests/toolchains/svelte/consumers/types/child-type-import/MisuseProp.svelte b/tests/toolchains/svelte/consumers/types/child-type-import/MisuseProp.svelte new file mode 100644 index 000000000..f50d5af5e --- /dev/null +++ b/tests/toolchains/svelte/consumers/types/child-type-import/MisuseProp.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/tests/toolchains/svelte/consumers/types/child-type-import/MisuseType.svelte b/tests/toolchains/svelte/consumers/types/child-type-import/MisuseType.svelte new file mode 100644 index 000000000..f47eb7d7e --- /dev/null +++ b/tests/toolchains/svelte/consumers/types/child-type-import/MisuseType.svelte @@ -0,0 +1,8 @@ + + + diff --git a/tests/toolchains/svelte/consumers/types/exported-types/Consumer.svelte b/tests/toolchains/svelte/consumers/types/exported-types/Consumer.svelte new file mode 100644 index 000000000..3d4649c48 --- /dev/null +++ b/tests/toolchains/svelte/consumers/types/exported-types/Consumer.svelte @@ -0,0 +1,8 @@ + + +