Skip to content

Commit 646f2e6

Browse files
authored
feat: support generics attribute for JSDoc (#2624)
#2618
1 parent 3f2da5d commit 646f2e6

File tree

8 files changed

+74
-21
lines changed

8 files changed

+74
-21
lines changed

packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function addGenericsComponentExport({
4949
fileName,
5050
mode,
5151
usesAccessors,
52+
isTsFile,
5253
str,
5354
generics,
5455
usesSlots,
@@ -65,6 +66,8 @@ function addGenericsComponentExport({
6566
return `ReturnType<__sveltets_Render${genericsRef}['${forPart}']>`;
6667
}
6768

69+
// TODO once Svelte 4 compatibility is dropped, we can simplify this, because since TS 4.7 it is possible to use generics
70+
// like this: `typeof render<T>` - which wasn't possibly before, hence the class + methods workaround.
6871
let statement = `
6972
class __sveltets_Render${genericsDef} {
7073
props() {
@@ -76,14 +79,23 @@ class __sveltets_Render${genericsDef} {
7679
slots() {
7780
return render${genericsRef}().slots;
7881
}
79-
${
80-
isSvelte5
82+
`;
83+
84+
// For Svelte 5+ we assume TS > 4.7
85+
if (isSvelte5 && !isTsFile && exportedNames.usesRunes()) {
86+
statement = `
87+
class __sveltets_Render${genericsDef} {
88+
props(): ReturnType<typeof render${genericsRef}>['props'] { return null as any; }
89+
events(): ReturnType<typeof render${genericsRef}>['events'] { return null as any; }
90+
slots(): ReturnType<typeof render${genericsRef}>['slots'] { return null as any; }
91+
`;
92+
}
93+
94+
statement += isSvelte5
8195
? ` bindings() { return ${exportedNames.createBindingsStr()}; }
8296
exports() { return ${exportedNames.hasExports() ? `render${genericsRef}().exports` : '{}'}; }
83-
}`
84-
: '}'
85-
}
86-
`;
97+
}\n`
98+
: '}\n';
8799

88100
const svelteComponentClass = noSvelteComponentTyped
89101
? 'SvelteComponent'

packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import MagicString from 'magic-string';
22
import { Node } from 'estree-walker';
33
import { ComponentEvents } from './nodes/ComponentEvents';
44
import { InstanceScriptProcessResult } from './processInstanceScriptContent';
5-
import { surroundWithIgnoreComments } from '../utils/ignore';
5+
import {
6+
IGNORE_END_COMMENT,
7+
IGNORE_START_COMMENT,
8+
surroundWithIgnoreComments
9+
} from '../utils/ignore';
610

711
export interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
812
str: MagicString;
@@ -12,6 +16,7 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
1216
events: ComponentEvents;
1317
uses$$SlotsInterface: boolean;
1418
svelte5Plus: boolean;
19+
isTsFile: boolean;
1520
mode?: 'ts' | 'dts';
1621
}
1722

@@ -27,6 +32,7 @@ export function createRenderFunction({
2732
uses$$slots,
2833
uses$$SlotsInterface,
2934
generics,
35+
isTsFile,
3036
mode
3137
}: CreateRenderFunctionPara) {
3238
const htmlx = str.original;
@@ -70,8 +76,12 @@ export function createRenderFunction({
7076
end--;
7177
}
7278
str.overwrite(scriptTag.start + 1, start - 1, `function render`);
73-
str.overwrite(start - 1, start, `<`); // if the generics are unused, only this char is colored opaque
74-
str.overwrite(end, scriptTagEnd, `>() {${propsDecl}\n`);
79+
str.overwrite(start - 1, start, isTsFile ? '<' : `<${IGNORE_START_COMMENT}`); // if the generics are unused, only this char is colored opaque
80+
str.overwrite(
81+
end,
82+
scriptTagEnd,
83+
`>${isTsFile ? '' : IGNORE_END_COMMENT}() {${propsDecl}\n`
84+
);
7585
} else {
7686
str.overwrite(
7787
scriptTag.start + 1,

packages/svelte2tsx/src/svelte2tsx/index.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function svelte2tsx(
4848
const str = new MagicString(svelte);
4949
const basename = path.basename(options.filename || '');
5050
const svelte5Plus = Number(options.version![0]) > 4;
51+
const isTsFile = options?.isTsFile;
5152

5253
// process the htmlx as a svelte template
5354
let {
@@ -95,14 +96,7 @@ export function svelte2tsx(
9596
: instanceScriptTarget;
9697
const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart);
9798
//move the instance script and process the content
98-
let exportedNames = new ExportedNames(
99-
str,
100-
0,
101-
basename,
102-
options?.isTsFile,
103-
svelte5Plus,
104-
isRunes
105-
);
99+
let exportedNames = new ExportedNames(str, 0, basename, isTsFile, svelte5Plus, isRunes);
106100
let generics = new Generics(str, 0, { attributes: [] } as any);
107101
let uses$$SlotsInterface = false;
108102
if (scriptTag) {
@@ -117,7 +111,7 @@ export function svelte2tsx(
117111
implicitStoreValues,
118112
options.mode,
119113
moduleAst,
120-
options?.isTsFile,
114+
isTsFile,
121115
basename,
122116
svelte5Plus,
123117
isRunes
@@ -148,6 +142,7 @@ export function svelte2tsx(
148142
uses$$SlotsInterface,
149143
generics,
150144
svelte5Plus,
145+
isTsFile,
151146
mode: options.mode
152147
});
153148

@@ -195,7 +190,7 @@ export function svelte2tsx(
195190
str,
196191
canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps),
197192
events,
198-
isTsFile: options?.isTsFile,
193+
isTsFile,
199194
exportedNames,
200195
usesAccessors,
201196
usesSlots: slots.size > 0,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
///<reference types="svelte" />
2+
;function render</*Ωignore_startΩ*/A, B extends keyof A, C extends boolean>/*Ωignore_endΩ*/() {
3+
4+
/** @typedef {{ a: A; b: B; c: C }} $$ComponentProps *//** @type {$$ComponentProps} */
5+
const { a, b, c } = $props();
6+
7+
function getA() {
8+
return a;
9+
}
10+
;
11+
async () => {};
12+
return { props: /** @type {$$ComponentProps} */({}), exports: /** @type {{getA: typeof getA}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
13+
class __sveltets_Render<A,B extends keyof A,C extends boolean> {
14+
props(): ReturnType<typeof render<A,B,C>>['props'] { return null as any; }
15+
events(): ReturnType<typeof render<A,B,C>>['events'] { return null as any; }
16+
slots(): ReturnType<typeof render<A,B,C>>['slots'] { return null as any; }
17+
bindings() { return __sveltets_$$bindings(''); }
18+
exports() { return render<A,B,C>().exports; }
19+
}
20+
21+
interface $$IsomorphicComponent {
22+
new <A,B extends keyof A,C extends boolean>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<A,B,C>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<A,B,C>['props']>, ReturnType<__sveltets_Render<A,B,C>['events']>, ReturnType<__sveltets_Render<A,B,C>['slots']>> & { $$bindings?: ReturnType<__sveltets_Render<A,B,C>['bindings']> } & ReturnType<__sveltets_Render<A,B,C>['exports']>;
23+
<A,B extends keyof A,C extends boolean>(internal: unknown, props: ReturnType<__sveltets_Render<A,B,C>['props']> & {}): ReturnType<__sveltets_Render<A,B,C>['exports']>;
24+
z_$$bindings?: ReturnType<__sveltets_Render<any,any,any>['bindings']>;
25+
}
26+
const Input__SvelteComponent_: $$IsomorphicComponent = null as any;
27+
/*Ωignore_startΩ*/type Input__SvelteComponent_<A,B extends keyof A,C extends boolean> = InstanceType<typeof Input__SvelteComponent_<A,B,C>>;
28+
/*Ωignore_endΩ*/export default Input__SvelteComponent_;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script generics="A, B extends keyof A, C extends boolean">
2+
/** @type {{ a: A; b: B; c: C }} */
3+
const { a, b, c } = $props();
4+
5+
export function getA() {
6+
return a;
7+
}
8+
</script>

packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expected-svelte5.ts packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expected-svelte5.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
let items: T/*Ωignore_startΩ*/;items = __sveltets_2_any(items);/*Ωignore_endΩ*/;
55
;
66
async () => {};
7-
return { props: {items: items}, exports: {}, bindings: "", slots: {}, events: {} }}
7+
return { props: {items: items} as {items: T}, exports: {}, bindings: "", slots: {}, events: {} }}
88
class __sveltets_Render<const T extends readonly string[]> {
99
props() {
1010
return render<T>().props;

packages/svelte2tsx/test/svelte2tsx/samples/generic-attribute-const-modifier/expectedv2.ts packages/svelte2tsx/test/svelte2tsx/samples/ts-generic-attribute-const-modifier/expectedv2.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
let items: T/*Ωignore_startΩ*/;items = __sveltets_2_any(items);/*Ωignore_endΩ*/;
55
;
66
async () => {};
7-
return { props: {items: items}, slots: {}, events: {} }}
7+
return { props: {items: items} as {items: T}, slots: {}, events: {} }}
88
class __sveltets_Render<const T extends readonly string[]> {
99
props() {
1010
return render<T>().props;

0 commit comments

Comments
 (0)