From 883a282e31fdb528c89bb857e168ebdbbac61ad8 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 9 Dec 2024 21:39:38 +0100 Subject: [PATCH 1/3] feat: support `generics` attribute for JSDoc #2618 --- .../src/svelte2tsx/addComponentExport.ts | 24 ++++++++++++---- .../src/svelte2tsx/createRenderFunction.ts | 16 +++++++++-- packages/svelte2tsx/src/svelte2tsx/index.ts | 15 ++++------ .../generics-attribute.v5/expectedv2.ts | 28 +++++++++++++++++++ .../generics-attribute.v5/input.svelte | 8 ++++++ 5 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/expectedv2.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/input.svelte diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts index 5df9a5281..c4f016398 100644 --- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts +++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts @@ -49,6 +49,7 @@ function addGenericsComponentExport({ fileName, mode, usesAccessors, + isTsFile, str, generics, usesSlots, @@ -65,6 +66,8 @@ function addGenericsComponentExport({ return `ReturnType<__sveltets_Render${genericsRef}['${forPart}']>`; } + // TODO once Svelte 4 compatibility is dropped, we can simplify this, because since TS 4.7 it is possible to use generics + // like this: `typeof render` - which wasn't possibly before, hence the class + methods workaround. let statement = ` class __sveltets_Render${genericsDef} { props() { @@ -76,14 +79,23 @@ class __sveltets_Render${genericsDef} { slots() { return render${genericsRef}().slots; } -${ - isSvelte5 +`; + + // For Svelte 5+ we assume TS > 4.7 + if (isSvelte5 && !isTsFile && exportedNames.usesRunes()) { + statement = ` +class __sveltets_Render${genericsDef} { + props(): ReturnType['props'] { return null as any; } + events(): ReturnType['events'] { return null as any; } + slots(): ReturnType['slots'] { return null as any; } +`; + } + + statement += isSvelte5 ? ` bindings() { return ${exportedNames.createBindingsStr()}; } exports() { return ${exportedNames.hasExports() ? `render${genericsRef}().exports` : '{}'}; } -}` - : '}' -} -`; +}\n` + : '}\n'; const svelteComponentClass = noSvelteComponentTyped ? 'SvelteComponent' diff --git a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts index 6a929fe94..16f65d207 100644 --- a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts +++ b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts @@ -2,7 +2,11 @@ import MagicString from 'magic-string'; import { Node } from 'estree-walker'; import { ComponentEvents } from './nodes/ComponentEvents'; import { InstanceScriptProcessResult } from './processInstanceScriptContent'; -import { surroundWithIgnoreComments } from '../utils/ignore'; +import { + IGNORE_END_COMMENT, + IGNORE_START_COMMENT, + surroundWithIgnoreComments +} from '../utils/ignore'; export interface CreateRenderFunctionPara extends InstanceScriptProcessResult { str: MagicString; @@ -12,6 +16,7 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult { events: ComponentEvents; uses$$SlotsInterface: boolean; svelte5Plus: boolean; + isTsFile: boolean; mode?: 'ts' | 'dts'; } @@ -27,6 +32,7 @@ export function createRenderFunction({ uses$$slots, uses$$SlotsInterface, generics, + isTsFile, mode }: CreateRenderFunctionPara) { const htmlx = str.original; @@ -70,8 +76,12 @@ export function createRenderFunction({ end--; } str.overwrite(scriptTag.start + 1, start - 1, `function render`); - str.overwrite(start - 1, start, `<`); // if the generics are unused, only this char is colored opaque - str.overwrite(end, scriptTagEnd, `>() {${propsDecl}\n`); + str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque + str.overwrite( + end, + scriptTagEnd, + `>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n` + ); } else { str.overwrite( scriptTag.start + 1, diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index 0b48cc607..68a688bef 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -48,6 +48,7 @@ export function svelte2tsx( const str = new MagicString(svelte); const basename = path.basename(options.filename || ''); const svelte5Plus = Number(options.version![0]) > 4; + const isTsFile = options?.isTsFile; // process the htmlx as a svelte template let { @@ -95,14 +96,7 @@ export function svelte2tsx( : instanceScriptTarget; const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart); //move the instance script and process the content - let exportedNames = new ExportedNames( - str, - 0, - basename, - options?.isTsFile, - svelte5Plus, - isRunes - ); + let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes); let generics = new Generics(str, 0, { attributes: [] } as any); let uses$$SlotsInterface = false; if (scriptTag) { @@ -117,7 +111,7 @@ export function svelte2tsx( implicitStoreValues, options.mode, moduleAst, - options?.isTsFile, + isTsFile, basename, svelte5Plus, isRunes @@ -148,6 +142,7 @@ export function svelte2tsx( uses$$SlotsInterface, generics, svelte5Plus, + isTsFile, mode: options.mode }); @@ -195,7 +190,7 @@ export function svelte2tsx( str, canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps), events, - isTsFile: options?.isTsFile, + isTsFile, exportedNames, usesAccessors, usesSlots: slots.size > 0, diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/expectedv2.ts new file mode 100644 index 000000000..2333b42ea --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/expectedv2.ts @@ -0,0 +1,28 @@ +/// +;function render/*Ωignore_endΩ*/() { + + /** @typedef {{ a: A; b: B; c: C }} $$ComponentProps *//** @type {$$ComponentProps} */ + const { a, b, c } = $props(); + + function getA() { + return a; + } +; +async () => {}; +return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{getA: typeof getA}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }} +class __sveltets_Render { + props(): ReturnType>['props'] { return null as any; } + events(): ReturnType>['events'] { return null as any; } + slots(): ReturnType>['slots'] { return null as any; } + bindings() { return __sveltets_$$bindings(''); } + exports() { return render().exports; } +} + +interface $$IsomorphicComponent { + new (options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>; + (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>; + z_$$bindings?: ReturnType<__sveltets_Render['bindings']>; +} +const Input__SvelteComponent_: $$IsomorphicComponent = null as any; +/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType>; +/*Ωignore_endΩ*/export default Input__SvelteComponent_; \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/input.svelte new file mode 100644 index 000000000..304329dac --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/generics-attribute.v5/input.svelte @@ -0,0 +1,8 @@ + From 7be72730bcf4e2a5e7ccea03ef13354ab6cf6bbf Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 6 Jan 2025 11:27:15 +0100 Subject: [PATCH 2/3] make test a TS test --- .../expected-svelte5.ts | 0 .../expectedv2.ts | 0 .../input.svelte | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packages/svelte2tsx/test/svelte2tsx/samples/{generic-attribute-const-modifier => ts-generic-attribute-const-modifier}/expected-svelte5.ts (100%) rename packages/svelte2tsx/test/svelte2tsx/samples/{generic-attribute-const-modifier => ts-generic-attribute-const-modifier}/expectedv2.ts (100%) rename packages/svelte2tsx/test/svelte2tsx/samples/{generic-attribute-const-modifier => ts-generic-attribute-const-modifier}/input.svelte (100%) diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expected-svelte5.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts similarity index 100% rename from packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expected-svelte5.ts rename to packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts similarity index 100% rename from packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expectedv2.ts rename to packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/input.svelte similarity index 100% rename from packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/input.svelte rename to packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/input.svelte From cc2418da3d401e921509cec4233eb57b79baed6a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 6 Jan 2025 11:45:18 +0100 Subject: [PATCH 3/3] fix --- .../ts-generic-attribute-const-modifier/expected-svelte5.ts | 2 +- .../samples/ts-generic-attribute-const-modifier/expectedv2.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts index b3b5e5088..90ca76530 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts @@ -4,7 +4,7 @@ let items: T/*Ωignore_startΩ*/;items = __sveltets_2_any(items);/*Ωignore_endΩ*/; ; async () => {}; -return { props: {items: items}, exports: {}, bindings: "", slots: {}, events: {} }} +return { props: {items: items} as {items: T}, exports: {}, bindings: "", slots: {}, events: {} }} class __sveltets_Render { props() { return render().props; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts index 2d6b82d01..59aa438d6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts @@ -4,7 +4,7 @@ let items: T/*Ωignore_startΩ*/;items = __sveltets_2_any(items);/*Ωignore_endΩ*/; ; async () => {}; -return { props: {items: items}, slots: {}, events: {} }} +return { props: {items: items} as {items: T}, slots: {}, events: {} }} class __sveltets_Render { props() { return render().props;