diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte
new file mode 100644
index 000000000..0b9ff0c17
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/RunesGeneric.svelte
@@ -0,0 +1,7 @@
+
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json
index 72c8185db..b3e926780 100644
--- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/expected_svelte_5.json
@@ -66,5 +66,73 @@
"severity": 1,
"source": "ts",
"tags": []
+ },
+ {
+ "code": 2322,
+ "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { readonly = $bindable() } = $props()'",
+ "range": {
+ "end": {
+ "character": 27,
+ "line": 29
+ },
+ "start": {
+ "character": 14,
+ "line": 29
+ }
+ },
+ "severity": 1,
+ "source": "ts",
+ "tags": []
+ },
+ {
+ "code": 2353,
+ "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.",
+ "range": {
+ "end": {
+ "character": 28,
+ "line": 30
+ },
+ "start": {
+ "character": 19,
+ "line": 30
+ }
+ },
+ "severity": 1,
+ "source": "ts",
+ "tags": []
+ },
+ {
+ "code": 2322,
+ "message": "Cannot use 'bind:' with this property. It is declared as non-bindable inside the component.\nTo mark a property as bindable: 'let { only_bind = $bindable() } = $props()'",
+ "range": {
+ "end": {
+ "character": 28,
+ "line": 30
+ },
+ "start": {
+ "character": 14,
+ "line": 30
+ }
+ },
+ "severity": 1,
+ "source": "ts",
+ "tags": []
+ },
+ {
+ "code": 2353,
+ "message": "Object literal may only specify known properties, and 'only_bind' does not exist in type '$$ComponentProps'.",
+ "range": {
+ "end": {
+ "character": 24,
+ "line": 31
+ },
+ "start": {
+ "character": 15,
+ "line": 31
+ }
+ },
+ "severity": 1,
+ "source": "ts",
+ "tags": []
}
]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte
index 55de44f2d..73fdc9d0b 100644
--- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bindings/input.svelte
@@ -9,7 +9,7 @@
let can_bind = '';
let readonly = ''
- let instance: Runes;
+ let instance: ReturnType;
instance!.only_bind() === true;
@@ -26,3 +26,7 @@
+
+
+
+
diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
index 5e4403c35..fc9550b77 100644
--- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
@@ -109,28 +109,21 @@ ${
: `${returnType('props')} & {${eventsSlotsType.join(', ')}}`;
const bindingsType = `ReturnType<__sveltets_Render${generics.toReferencesAnyString()}['bindings']>`;
- if (exportedNames.usesRunes() && !events.hasEvents() && !usesSlots) {
- statement +=
- `\ntype $$$Component${genericsDef} = import('svelte').Component<${propsType}, ${returnType('exports')}, ${bindingsType}> \n` +
- `${doc}declare function ${className || '$$Component'}${genericsDef}(...args: Parameters<$$$Component${generics.toReferencesString()}>): ReturnType<$$$Component${generics.toReferencesString()}>;\n` +
- // The only way to preserve the generic type when transforming this function type to a class via __sveltets_2_ensureComponent later is a function,
- // and so we need to manually add the properties on the function itself this way.
- `${className || '$$Component'}.z_$$bindings = null as any as ${bindingsType};\n` +
- `${className || '$$Component'}.element = null as any;\n` +
- `export default ${className || '$$Component'};`;
- } else {
- statement +=
- `\ninterface $$IsomorphicComponent {\n` +
- ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
- ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
- ` z_$$bindings?: ${bindingsType};\n` +
- `}\n` +
- `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
- surroundWithIgnoreComments(
- `type ${className || '$$Component'}${genericsDef} = InstanceType;\n`
- ) +
- `export default ${className || '$$Component'};`;
- }
+ // Sadly, due to a combination of requirements and TypeScript limitations, we need to always create both a legacy class component and function component type.
+ // - Constraints: Need to support Svelte 4 class component types, therefore we need to use __sveltets_2_ensureComponent to transform function components to classes
+ // - Limitations: TypeScript is not able to preserve generics during said transformation (i.e. there's no way to express keeping the generic etc)
+ // TODO Svelte 6/7: Switch this around and not use new Component in svelte2tsx anymore, which means we can remove the legacy class component. We need something like _ensureFnComponent then.
+ statement +=
+ `\ninterface $$IsomorphicComponent {\n` +
+ ` new ${genericsDef}(options: import('svelte').ComponentConstructorOptions<${returnType('props') + (usesSlots ? '& {children?: any}' : '')}>): import('svelte').SvelteComponent<${returnType('props')}, ${returnType('events')}, ${returnType('slots')}> & { $$bindings?: ${returnType('bindings')} } & ${returnType('exports')};\n` +
+ ` ${genericsDef}(internal: unknown, props: ${propsType}): ${returnType('exports')};\n` +
+ ` z_$$bindings?: ${bindingsType};\n` +
+ `}\n` +
+ `${doc}const ${className || '$$Component'}: $$IsomorphicComponent = null as any;\n` +
+ surroundWithIgnoreComments(
+ `type ${className || '$$Component'}${genericsDef} = InstanceType;\n`
+ ) +
+ `export default ${className || '$$Component'};`;
} else if (mode === 'dts') {
statement +=
`export type ${PropsName}${genericsDef} = ${returnType('props')};\n` +
diff --git a/packages/svelte2tsx/svelte-shims-v4.d.ts b/packages/svelte2tsx/svelte-shims-v4.d.ts
index 292f99cdd..71bfbac6d 100644
--- a/packages/svelte2tsx/svelte-shims-v4.d.ts
+++ b/packages/svelte2tsx/svelte-shims-v4.d.ts
@@ -220,11 +220,36 @@ declare type ATypedSvelteComponent = {
* ```
*/
declare type ConstructorOfATypedSvelteComponent = new (args: {target: any, props?: any}) => ATypedSvelteComponent
+// Usage note: Cannot properly transform generic function components to class components due to TypeScript limitations
declare function __sveltets_2_ensureComponent<
- // @ts-ignore svelte.Component doesn't exist in Svelte 4
- T extends ConstructorOfATypedSvelteComponent | (typeof import('svelte') extends { mount: any } ? import('svelte').Component : never) | null | undefined
- // @ts-ignore svelte.Component doesn't exist in Svelte 4
->(type: T): NonNullable> ? typeof import('svelte').SvelteComponent : T : T>;
+ T extends
+ | ConstructorOfATypedSvelteComponent
+ | (typeof import('svelte') extends { mount: any }
+ ? // @ts-ignore svelte.Component doesn't exist in Svelte 4
+ import('svelte').Component
+ : never)
+ | null
+ | undefined
+>(
+ type: T
+): NonNullable<
+ T extends ConstructorOfATypedSvelteComponent
+ ? T
+ : typeof import('svelte') extends { mount: any }
+ ? // @ts-ignore svelte.Component doesn't exist in Svelte 4
+ T extends import('svelte').Component<
+ infer Props extends Record,
+ infer Exports extends Record,
+ infer Bindings extends string
+ >
+ ? new (
+ options: import('svelte').ComponentConstructorOptions
+ ) => import('svelte').SvelteComponent &
+ Exports & { $$bindings: Bindings }
+ : never
+ : never
+>;
+
declare function __sveltets_2_ensureArray | Iterable>(array: T): T extends ArrayLike ? U[] : T extends Iterable ? Iterable : any[];
type __sveltets_2_PropsWithChildren = Props &
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts
index 5d9fef43d..296b24ab4 100644
--- a/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript-runes-generics.v5/expected/TestRunes.svelte.d.ts
@@ -10,10 +10,13 @@ declare class __sveltets_Render, K extends keyof T
baz: () => T;
};
}
-type $$$Component, K extends keyof T> = import('svelte').Component['props']> & {}, ReturnType<__sveltets_Render['exports']>, ReturnType<__sveltets_Render['bindings']>>;
-declare function TestRunes, K extends keyof T>(...args: Parameters<$$$Component>): ReturnType<$$$Component>;
-declare namespace TestRunes {
- var z_$$bindings: "bar";
- var element: any;
+interface $$IsomorphicComponent {
+ new , K extends keyof T>(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']>;
+ , K extends keyof T>(internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>;
+ z_$$bindings?: ReturnType<__sveltets_Render['bindings']>;
}
+declare const TestRunes: $$IsomorphicComponent;
+type TestRunes, K extends keyof T> = InstanceType>;
export default TestRunes;
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts
index 2308c5e7e..d87b3a3ee 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-generics.v5/expectedv2.ts
@@ -21,8 +21,11 @@ class __sveltets_Render {
exports() { return {}; }
}
-type $$$Component = import('svelte').Component['props']> & {}, ReturnType<__sveltets_Render['exports']>, ReturnType<__sveltets_Render['bindings']>>
-declare function Input__SvelteComponent_(...args: Parameters<$$$Component>): ReturnType<$$$Component>;
-Input__SvelteComponent_.z_$$bindings = null as any as ReturnType<__sveltets_Render['bindings']>;
-Input__SvelteComponent_.element = null as any;
-export default Input__SvelteComponent_;
\ No newline at end of file
+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