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