Repository: sveltejs/language-tools
Pull request #1053 (2021-06-13 11:49:46)

(feat) add experimal support for generics
The content of a $$Generic type is moved to the render function: Its identifier is the generic name, a possibly type argument is the extends clause.

#442 
#273 
https://github.com/sveltejs/rfcs/pull/38

@jasonlyu123  during that, the seemingly unnecessary typing of props/slots/events as being optional on the render function is removed in various helper functions because it makes TS throw wrong errors in strict mode. Do you remember why we ever made these optional?

jasonlyu123: Don't remember either. Maybe just wanted to make it optional so that the generated code could save some character but never did that.
dummdidumm: Alright, this is what I thought, too 😄 Anything else about these changes or do they look good to you?
jasonlyu123: I notice there's a wrong semantic token on the script tag. 
![圖片](https://user-images.githubusercontent.com/36730922/121872470-decffc80-cd37-11eb-9a2a-99f9d7388897.png)

Also when There's an unused generic parameter it would also gray out the same character
```svelte
<script>
type C = $$Generic
</script>
```
dummdidumm: Oh yeah, the mapping is wrong there, for now I think it's best to somehow skip these.

Patch:
diff --git a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts
index 06c12e12..98f363d3 100644
--- a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts
@@ -7,9 +7,10 @@ import {
 } from 'vscode-languageserver';
 import { Document, mapRangeToOriginal } from '../../../lib/documents';
 import { SemanticTokensProvider } from '../../interfaces';
-import { SnapshotFragment } from '../DocumentSnapshot';
+import { SvelteSnapshotFragment } from '../DocumentSnapshot';
 import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
 import { convertToTextSpan } from '../utils';
+import { isInGeneratedCode } from './utils';
 
 const CONTENT_LENGTH_LIMIT = 50000;
 
@@ -104,10 +105,14 @@ export class SemanticTokensProviderImpl implements SemanticTokensProvider {
 
     private mapToOrigin(
         document: Document,
-        fragment: SnapshotFragment,
+        fragment: SvelteSnapshotFragment,
         generatedOffset: number,
         generatedLength: number
     ): [line: number, character: number, length: number] | undefined {
+        if (isInGeneratedCode(fragment.text, generatedOffset, generatedOffset + generatedLength)) {
+            return;
+        }
+
         const range = {
             start: fragment.positionAt(generatedOffset),
             end: fragment.positionAt(generatedOffset + generatedLength)
diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
new file mode 100644
index 00000000..7414d0ca
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
@@ -0,0 +1,206 @@
+import { pascalCase } from 'pascal-case';
+import path from 'path';
+import { createClassGetters } from './nodes/exportgetters';
+import { createClassAccessors } from './nodes/exportaccessors';
+import MagicString from 'magic-string';
+import { ExportedNames } from './nodes/ExportedNames';
+import { ComponentDocumentation } from './nodes/ComponentDocumentation';
+import { Generics } from './nodes/Generics';
+
+export interface AddComponentExportPara {
+    str: MagicString;
+    uses$$propsOr$$restProps: boolean;
+    /**
+     * If true, not fallback to `any`
+     * -> all unknown events will throw a type error
+     * */
+    strictEvents: boolean;
+    isTsFile: boolean;
+    getters: Set<string>;
+    usesAccessors: boolean;
+    exportedNames: ExportedNames;
+    fileName?: string;
+    componentDocumentation: ComponentDocumentation;
+    mode: 'dts' | 'tsx';
+    generics: Generics;
+}
+
+/**
+ * A component class name suffix is necessary to prevent class name clashes
+ * like reported in https://github.com/sveltejs/language-tools/issues/294
+ */
+export const COMPONENT_SUFFIX = '__SvelteComponent_';
+
+export function addComponentExport(params: AddComponentExportPara) {
+    if (params.generics.has()) {
+        addGenericsComponentExport(params);
+    } else {
+        addSimpleComponentExport(params);
+    }
+}
+
+function addGenericsComponentExport({
+    strictEvents,
+    isTsFile,
+    uses$$propsOr$$restProps,
+    exportedNames,
+    componentDocumentation,
+    fileName,
+    mode,
+    getters,
+    usesAccessors,
+    str,
+    generics
+}: AddComponentExportPara) {
+    const genericsDef = generics.toDefinitionString();
+    const genericsRef = generics.toReferencesString();
+
+    const doc = componentDocumentation.getFormatted();
+    const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
+
+    function returnType(forPart: string) {
+        return `ReturnType<__sveltets_Render${genericsRef}['${forPart}']>`;
+    }
+
+    let statement = `
+class __sveltets_Render${genericsDef} {
+    props() {
+        return ${props(
+            isTsFile,
+            uses$$propsOr$$restProps,
+            exportedNames,
+            `render${genericsRef}()`
+        )}.props;
+    }
+    events() {
+        return ${events(strictEvents, `render${genericsRef}()`)}.events;
+    }
+    slots() {
+        return render${genericsRef}().slots;
+    }
+}
+`;
+
+    if (mode === 'dts') {
+        statement +=
+            `export type ${className}Props${genericsDef} = ${returnType('props')};\n` +
+            `export type ${className}Events${genericsDef} = ${returnType('events')};\n` +
+            `export type ${className}Slots${genericsDef} = ${returnType('slots')};\n` +
+            `\n${doc}export default class${
+                className ? ` ${className}` : ''
+            }${genericsDef} extends SvelteComponentTyped<${className}Props${genericsRef}, ${className}Events${genericsRef}, ${className}Slots${genericsRef}> {` + // eslint-disable-line max-len
+            createClassGetters(getters) +
+            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
+            '\n}';
+    } else {
+        statement +=
+            `\n\n${doc}export default class${
+                className ? ` ${className}` : ''
+            }${genericsDef} extends Svelte2TsxComponent<${returnType('props')}, ${returnType(
+                'events'
+            )}, ${returnType('slots')}> {` +
+            createClassGetters(getters) +
+            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
+            '\n}';
+    }
+
+    str.append(statement);
+}
+
+function addSimpleComponentExport({
+    strictEvents,
+    isTsFile,
+    uses$$propsOr$$restProps,
+    exportedNames,
+    componentDocumentation,
+    fileName,
+    mode,
+    getters,
+    usesAccessors,
+    str
+}: AddComponentExportPara) {
+    const propDef = props(
+        isTsFile,
+        uses$$propsOr$$restProps,
+        exportedNames,
+        events(strictEvents, 'render()')
+    );
+
+    const doc = componentDocumentation.getFormatted();
+    const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
+
+    let statement: string;
+    if (mode === 'dts') {
+        statement =
+            `\nconst __propDef = ${propDef};\n` +
+            `export type ${className}Props = typeof __propDef.props;\n` +
+            `export type ${className}Events = typeof __propDef.events;\n` +
+            `export type ${className}Slots = typeof __propDef.slots;\n` +
+            `\n${doc}export default class${
+                className ? ` ${className}` : ''
+            } extends SvelteComponentTyped<${className}Props, ${className}Events, ${className}Slots> {` + // eslint-disable-line max-len
+            createClassGetters(getters) +
+            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
+            '\n}';
+    } else {
+        statement =
+            `\n\n${doc}export default class${
+                className ? ` ${className}` : ''
+            } extends createSvelte2TsxComponent(${propDef}) {` +
+            createClassGetters(getters) +
+            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
+            '\n}';
+    }
+
+    str.append(statement);
+}
+
+function events(strictEvents: boolean, renderStr: string) {
+    return strictEvents ? renderStr : `__sveltets_with_any_event(${renderStr})`;
+}
+
+function props(
+    isTsFile: boolean,
+    uses$$propsOr$$restProps: boolean,
+    exportedNames: ExportedNames,
+    renderStr: string
+) {
+    if (isTsFile) {
+        return uses$$propsOr$$restProps ? `__sveltets_with_any(${renderStr})` : renderStr;
+    } else {
+        const optionalProps = exportedNames.createOptionalPropsArray();
+        const partial = `__sveltets_partial${uses$$propsOr$$restProps ? '_with_any' : ''}`;
+        return optionalProps.length > 0
+            ? `${partial}([${optionalProps.join(',')}], ${renderStr})`
+            : `${partial}(${renderStr})`;
+    }
+}
+
+/**
+ * Returns a Svelte-compatible component name from a filename. Svelte
+ * components must use capitalized tags, so we try to transform the filename.
+ *
+ * https://svelte.dev/docs#Tags
+ */
+function classNameFromFilename(filename: string, appendSuffix: boolean): string | undefined {
+    try {
+        const withoutExtensions = path.parse(filename).name?.split('.')[0];
+        const withoutInvalidCharacters = withoutExtensions
+            .split('')
+            // Although "-" is invalid, we leave it in, pascal-case-handling will throw it out later
+            .filter((char) => /[A-Za-z$_\d-]/.test(char))
+            .join('');
+        const firstValidCharIdx = withoutInvalidCharacters
+            .split('')
+            // Although _ and $ are valid first characters for classes, they are invalid first characters
+            // for tag names. For a better import autocompletion experience, we therefore throw them out.
+            .findIndex((char) => /[A-Za-z]/.test(char));
+        const withoutLeadingInvalidCharacters = withoutInvalidCharacters.substr(firstValidCharIdx);
+        const inPascalCase = pascalCase(withoutLeadingInvalidCharacters);
+        const finalName = firstValidCharIdx === -1 ? `A${inPascalCase}` : inPascalCase;
+        return `${finalName}${appendSuffix ? COMPONENT_SUFFIX : ''}`;
+    } catch (error) {
+        console.warn(`Failed to create a name for the component class from filename ${filename}`);
+        return undefined;
+    }
+}
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index 2430e54b..d43eb628 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -1,15 +1,12 @@
 import { Node } from 'estree-walker';
 import MagicString from 'magic-string';
-import { pascalCase } from 'pascal-case';
-import path from 'path';
 import { convertHtmlxToJsx } from '../htmlxtojsx';
 import { parseHtmlx } from '../utils/htmlxparser';
 import { ComponentDocumentation } from './nodes/ComponentDocumentation';
 import { ComponentEvents } from './nodes/ComponentEvents';
 import { EventHandler } from './nodes/event-handler';
 import { ExportedNames } from './nodes/ExportedNames';
-import { createClassGetters, createRenderFunctionGetterStr } from './nodes/exportgetters';
-import { createClassAccessors } from './nodes/exportaccessors';
+import { createRenderFunctionGetterStr } from './nodes/exportgetters';
 import {
     handleScopeAndResolveForSlot,
     handleScopeAndResolveLetVarForSlot
@@ -26,6 +23,8 @@ import {
 import { processModuleScriptTag } from './processModuleScriptTag';
 import { ScopeStack } from './utils/Scope';
 import { svelteShims } from './svelteShims';
+import { Generics } from './nodes/Generics';
+import { addComponentExport } from './addComponentExport';
 
 interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
     str: MagicString;
@@ -36,23 +35,6 @@ interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
     isTsFile: boolean;
 }
 
-interface AddComponentExportPara {
-    str: MagicString;
-    uses$$propsOr$$restProps: boolean;
-    /**
-     * If true, not fallback to `any`
-     * -> all unknown events will throw a type error
-     * */
-    strictEvents: boolean;
-    isTsFile: boolean;
-    getters: Set<string>;
-    usesAccessors: boolean;
-    exportedNames: ExportedNames;
-    fileName?: string;
-    componentDocumentation: ComponentDocumentation;
-    mode: 'dts' | 'tsx';
-}
-
 type TemplateProcessResult = {
     uses$$props: boolean;
     uses$$restProps: boolean;
@@ -67,12 +49,6 @@ type TemplateProcessResult = {
     usesAccessors: boolean;
 };
 
-/**
- * A component class name suffix is necessary to prevent class name clashes
- * like reported in https://github.com/sveltejs/language-tools/issues/294
- */
-const COMPONENT_SUFFIX = '__SvelteComponent_';
-
 function processSvelteTemplate(
     str: MagicString,
     options?: { emitOnTemplateError?: boolean; namespace?: string }
@@ -313,89 +289,6 @@ function processSvelteTemplate(
     };
 }
 
-function addComponentExport({
-    str,
-    uses$$propsOr$$restProps,
-    strictEvents,
-    isTsFile,
-    getters,
-    usesAccessors,
-    exportedNames,
-    fileName,
-    componentDocumentation,
-    mode
-}: AddComponentExportPara) {
-    const eventsDef = strictEvents ? 'render()' : '__sveltets_with_any_event(render())';
-    let propDef = '';
-    if (isTsFile) {
-        propDef = uses$$propsOr$$restProps ? `__sveltets_with_any(${eventsDef})` : eventsDef;
-    } else {
-        const optionalProps = exportedNames.createOptionalPropsArray();
-        const partial = `__sveltets_partial${uses$$propsOr$$restProps ? '_with_any' : ''}`;
-        propDef =
-            optionalProps.length > 0
-                ? `${partial}([${optionalProps.join(',')}], ${eventsDef})`
-                : `${partial}(${eventsDef})`;
-    }
-
-    const doc = componentDocumentation.getFormatted();
-    const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
-
-    let statement: string;
-    if (mode === 'dts') {
-        statement =
-            `\nconst __propDef = ${propDef};\n` +
-            `export type ${className}Props = typeof __propDef.props;\n` +
-            `export type ${className}Events = typeof __propDef.events;\n` +
-            `export type ${className}Slots = typeof __propDef.slots;\n` +
-            `\n${doc}export default class${
-                className ? ` ${className}` : ''
-            } extends SvelteComponentTyped<${className}Props, ${className}Events, ${className}Slots> {` + // eslint-disable-line max-len
-            createClassGetters(getters) +
-            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
-            '\n}';
-    } else {
-        statement =
-            `\n\n${doc}export default class${
-                className ? ` ${className}` : ''
-            } extends createSvelte2TsxComponent(${propDef}) {` +
-            createClassGetters(getters) +
-            (usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
-            '\n}';
-    }
-
-    str.append(statement);
-}
-
-/**
- * Returns a Svelte-compatible component name from a filename. Svelte
- * components must use capitalized tags, so we try to transform the filename.
- *
- * https://svelte.dev/docs#Tags
- */
-function classNameFromFilename(filename: string, appendSuffix: boolean): string | undefined {
-    try {
-        const withoutExtensions = path.parse(filename).name?.split('.')[0];
-        const withoutInvalidCharacters = withoutExtensions
-            .split('')
-            // Although "-" is invalid, we leave it in, pascal-case-handling will throw it out later
-            .filter((char) => /[A-Za-z$_\d-]/.test(char))
-            .join('');
-        const firstValidCharIdx = withoutInvalidCharacters
-            .split('')
-            // Although _ and $ are valid first characters for classes, they are invalid first characters
-            // for tag names. For a better import autocompletion experience, we therefore throw them out.
-            .findIndex((char) => /[A-Za-z]/.test(char));
-        const withoutLeadingInvalidCharacters = withoutInvalidCharacters.substr(firstValidCharIdx);
-        const inPascalCase = pascalCase(withoutLeadingInvalidCharacters);
-        const finalName = firstValidCharIdx === -1 ? `A${inPascalCase}` : inPascalCase;
-        return `${finalName}${appendSuffix ? COMPONENT_SUFFIX : ''}`;
-    } catch (error) {
-        console.warn(`Failed to create a name for the component class from filename ${filename}`);
-        return undefined;
-    }
-}
-
 function createRenderFunction({
     str,
     scriptTag,
@@ -407,7 +300,8 @@ function createRenderFunction({
     isTsFile,
     uses$$props,
     uses$$restProps,
-    uses$$slots
+    uses$$slots,
+    generics
 }: CreateRenderFunctionPara) {
     const htmlx = str.original;
     let propsDecl = '';
@@ -432,7 +326,11 @@ function createRenderFunction({
         //I couldn't get magicstring to let me put the script before the <> we prepend during conversion of the template to jsx, so we just close it instead
         const scriptTagEnd = htmlx.lastIndexOf('>', scriptTag.content.start) + 1;
         str.overwrite(scriptTag.start, scriptTag.start + 1, '</>;');
-        str.overwrite(scriptTag.start + 1, scriptTagEnd, `function render() {${propsDecl}\n`);
+        str.overwrite(
+            scriptTag.start + 1,
+            scriptTagEnd,
+            `function render${generics.toDefinitionString(true)}() {${propsDecl}\n`
+        );
 
         const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
         // wrap template with callback
@@ -440,7 +338,10 @@ function createRenderFunction({
             contentOnly: true
         });
     } else {
-        str.prependRight(scriptDestination, `</>;function render() {${propsDecl}\n<>`);
+        str.prependRight(
+            scriptDestination,
+            `</>;function render${generics.toDefinitionString(true)}() {${propsDecl}\n<>`
+        );
     }
 
     const slotsAsDef =
@@ -518,6 +419,7 @@ export function svelte2tsx(
     //move the instance script and process the content
     let exportedNames = new ExportedNames();
     let getters = new Set<string>();
+    let generics = new Generics(str, 0);
     if (scriptTag) {
         //ensure it is between the module script and the rest of the template (the variables need to be declared before the jsx template)
         if (scriptTag.start != instanceScriptTarget) {
@@ -528,7 +430,7 @@ export function svelte2tsx(
         uses$$restProps = uses$$restProps || res.uses$$restProps;
         uses$$slots = uses$$slots || res.uses$$slots;
 
-        ({ exportedNames, events, getters } = res);
+        ({ exportedNames, events, getters, generics } = res);
     }
 
     //wrap the script tag and template content in a function returning the slot and exports
@@ -543,7 +445,8 @@ export function svelte2tsx(
         isTsFile: options?.isTsFile,
         uses$$props,
         uses$$restProps,
-        uses$$slots
+        uses$$slots,
+        generics
     });
 
     // we need to process the module script after the instance script has moved otherwise we get warnings about moving edited items
@@ -565,7 +468,8 @@ export function svelte2tsx(
         usesAccessors,
         fileName: options?.filename,
         componentDocumentation,
-        mode: options.mode
+        mode: options.mode,
+        generics
     });
 
     if (options.mode === 'dts') {
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts
new file mode 100644
index 00000000..40ccc2f3
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts
@@ -0,0 +1,60 @@
+import MagicString from 'magic-string';
+import ts from 'typescript';
+import { surroundWithIgnoreComments } from '../../utils/ignore';
+import { throwError } from '../utils/error';
+
+export class Generics {
+    private definitions: string[] = [];
+    private references: string[] = [];
+
+    constructor(private str: MagicString, private astOffset: number) {}
+
+    addIfIsGeneric(node: ts.Node) {
+        if (ts.isTypeAliasDeclaration(node) && this.is$$GenericType(node.type)) {
+            if (node.type.typeArguments?.length > 1) {
+                throw new Error('Invalid $$Generic declaration: Only one type argument allowed');
+            }
+            if (node.type.typeArguments?.length === 1) {
+                this.definitions.push(
+                    `${node.name.text} extends ${node.type.typeArguments[0].getText()}`
+                );
+            } else {
+                this.definitions.push(`${node.name.text}`);
+            }
+            this.references.push(node.name.text);
+            this.str.remove(this.astOffset + node.getStart(), this.astOffset + node.getEnd());
+        }
+    }
+
+    throwIfIsGeneric(node: ts.Node) {
+        if (ts.isTypeAliasDeclaration(node) && this.is$$GenericType(node.type)) {
+            throwError(
+                this.astOffset + node.getStart(),
+                this.astOffset + node.getEnd(),
+                '$$Generic declarations are only allowed in the instance script',
+                this.str.original
+            );
+        }
+    }
+
+    private is$$GenericType(node: ts.TypeNode): node is ts.TypeReferenceNode {
+        return (
+            ts.isTypeReferenceNode(node) &&
+            ts.isIdentifier(node.typeName) &&
+            node.typeName.text === '$$Generic'
+        );
+    }
+
+    toDefinitionString(addIgnore = false) {
+        const surround = addIgnore ? surroundWithIgnoreComments : (str: string) => str;
+        return this.definitions.length ? `<${surround(this.definitions.join(','))}>` : '';
+    }
+
+    toReferencesString() {
+        return this.references.length ? `<${this.references.join(',')}>` : '';
+    }
+
+    has() {
+        return this.definitions.length > 0;
+    }
+}
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index eb50c087..f0b0fa33 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -13,6 +13,7 @@ import { ComponentEvents } from './nodes/ComponentEvents';
 import { Scope } from './utils/Scope';
 import { handleTypeAssertion } from './nodes/handleTypeAssertion';
 import { ImplicitStoreValues } from './nodes/ImplicitStoreValues';
+import { Generics } from './nodes/Generics';
 
 export interface InstanceScriptProcessResult {
     exportedNames: ExportedNames;
@@ -21,6 +22,7 @@ export interface InstanceScriptProcessResult {
     uses$$restProps: boolean;
     uses$$slots: boolean;
     getters: Set<string>;
+    generics: Generics;
 }
 
 interface PendingStoreResolution {
@@ -47,6 +49,7 @@ export function processInstanceScriptContent(
     const astOffset = script.content.start;
     const exportedNames = new ExportedNames();
     const getters = new Set<string>();
+    const generics = new Generics(str, astOffset);
 
     const implicitTopLevelNames = new ImplicitTopLevelNames();
     let uses$$props = false;
@@ -292,7 +295,8 @@ export function processInstanceScriptContent(
                     (!ts.isPropertyAccessExpression(parent) || parent.expression == ident) &&
                     (!ts.isPropertyAssignment(parent) || parent.initializer == ident) &&
                     !ts.isPropertySignature(parent) &&
-                    !ts.isPropertyDeclaration(parent)
+                    !ts.isPropertyDeclaration(parent) &&
+                    !ts.isTypeReferenceNode(parent)
                 ) {
                     pendingStoreResolutions.push({ node: ident, parent, scope });
                 }
@@ -343,6 +347,8 @@ export function processInstanceScriptContent(
         type onLeaveCallback = () => void;
         const onLeaveCallbacks: onLeaveCallback[] = [];
 
+        generics.addIfIsGeneric(node);
+
         if (ts.isInterfaceDeclaration(node) && node.name.text === 'ComponentEvents') {
             events.setComponentEventsInterface(node);
         }
@@ -504,6 +510,7 @@ export function processInstanceScriptContent(
         uses$$props,
         uses$$restProps,
         uses$$slots,
-        getters
+        getters,
+        generics
     };
 }
diff --git a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
index ee3df17f..179b414c 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
@@ -3,6 +3,7 @@ import { Node } from 'estree-walker';
 import ts from 'typescript';
 import { ImplicitStoreValues } from './nodes/ImplicitStoreValues';
 import { handleTypeAssertion } from './nodes/handleTypeAssertion';
+import { Generics } from './nodes/Generics';
 
 export function processModuleScriptTag(
     str: MagicString,
@@ -10,21 +11,6 @@ export function processModuleScriptTag(
     implicitStoreValues: ImplicitStoreValues
 ) {
     const htmlx = str.original;
-
-    resolveImplicitStores(htmlx, script, implicitStoreValues, str);
-
-    const scriptStartTagEnd = htmlx.indexOf('>', script.start) + 1;
-    const scriptEndTagStart = htmlx.lastIndexOf('<', script.end - 1);
-
-    str.overwrite(script.start, scriptStartTagEnd, '</>;');
-    str.overwrite(scriptEndTagStart, script.end, ';<>');
-}
-function resolveImplicitStores(
-    htmlx: string,
-    script: Node,
-    implicitStoreValues: ImplicitStoreValues,
-    str: MagicString
-) {
     const scriptContent = htmlx.substring(script.content.start, script.content.end);
     const tsAst = ts.createSourceFile(
         'component.module.ts.svelte',
@@ -35,22 +21,12 @@ function resolveImplicitStores(
     );
     const astOffset = script.content.start;
 
-    const walk = (node: ts.Node) => {
-        if (ts.isVariableDeclaration(node)) {
-            implicitStoreValues.addVariableDeclaration(node);
-        }
-
-        if (ts.isImportClause(node)) {
-            implicitStoreValues.addImportStatement(node);
-        }
+    const generics = new Generics(str, astOffset);
 
-        if (ts.isImportSpecifier(node)) {
-            implicitStoreValues.addImportStatement(node);
-        }
+    const walk = (node: ts.Node) => {
+        resolveImplicitStoreValue(node, implicitStoreValues, str, astOffset);
 
-        if (ts.isTypeAssertionExpression?.(node)) {
-            handleTypeAssertion(str, node, astOffset);
-        }
+        generics.throwIfIsGeneric(node);
 
         ts.forEachChild(node, (n) => walk(n));
     };
@@ -60,4 +36,33 @@ function resolveImplicitStores(
 
     // declare store declarations we found in the script
     implicitStoreValues.modifyCode(astOffset, str);
+
+    const scriptStartTagEnd = htmlx.indexOf('>', script.start) + 1;
+    const scriptEndTagStart = htmlx.lastIndexOf('<', script.end - 1);
+
+    str.overwrite(script.start, scriptStartTagEnd, '</>;');
+    str.overwrite(scriptEndTagStart, script.end, ';<>');
+}
+
+function resolveImplicitStoreValue(
+    node: ts.Node,
+    implicitStoreValues: ImplicitStoreValues,
+    str: MagicString,
+    astOffset: any
+) {
+    if (ts.isVariableDeclaration(node)) {
+        implicitStoreValues.addVariableDeclaration(node);
+    }
+
+    if (ts.isImportClause(node)) {
+        implicitStoreValues.addImportStatement(node);
+    }
+
+    if (ts.isImportSpecifier(node)) {
+        implicitStoreValues.addImportStatement(node);
+    }
+
+    if (ts.isTypeAssertionExpression?.(node)) {
+        handleTypeAssertion(str, node, astOffset);
+    }
 }
diff --git a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
index 9c67eec3..9e069495 100644
--- a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
@@ -120,29 +120,29 @@ declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slo
 // optionalProps need to be first or its type cannot be infered correctly.
 
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props>, events: Events, slots: Slots }
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Slots }
 
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props> & SvelteAllProps, events: Events, slots: Slots }
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events: Events, slots: Slots }
 
 
 declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props & SvelteAllProps, events: Events, slots: Slots }
 
 declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots }
 
 declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
 declare function __sveltets_any(dummy: any): any;
@@ -196,7 +196,7 @@ declare function __sveltets_each<T>(
 ): any;
 
 declare function createSvelte2TsxComponent<Props, Events, Slots>(
-    render: {props?: Props, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
 ): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
 
 declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T
diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/error.ts b/packages/svelte2tsx/src/svelte2tsx/utils/error.ts
new file mode 100644
index 00000000..52f11c17
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/utils/error.ts
@@ -0,0 +1,65 @@
+/**
+ * Throw an error with start/end pos like the Svelte compiler does
+ */
+export function throwError(start: number, end: number, message: string, code: string) {
+    const error = new Error(message);
+    (error as any).start = positionAt(start, code);
+    (error as any).end = positionAt(end, code);
+    throw error;
+}
+
+/**
+ * Get the line (1-offset) and character (0-offset) based on the offset
+ * @param offset The index of the position
+ * @param text The text for which the position should be retrived
+ */
+function positionAt(offset: number, text: string): { line: number; column: number } {
+    offset = clamp(offset, 0, text.length);
+
+    const lineOffsets = getLineOffsets(text);
+    let low = 0;
+    let high = lineOffsets.length;
+    if (high === 0) {
+        return { line: 1, column: offset };
+    }
+
+    while (low < high) {
+        const mid = Math.floor((low + high) / 2);
+        if (lineOffsets[mid] > offset) {
+            high = mid;
+        } else {
+            low = mid + 1;
+        }
+    }
+
+    // low is the least x for which the line offset is larger than the current offset
+    // or array.length if no line offset is larger than the current offset
+    return { line: low, column: offset - lineOffsets[low - 1] };
+}
+
+export function clamp(num: number, min: number, max: number): number {
+    return Math.max(min, Math.min(max, num));
+}
+
+function getLineOffsets(text: string) {
+    const lineOffsets = [];
+    let isLineStart = true;
+
+    for (let i = 0; i < text.length; i++) {
+        if (isLineStart) {
+            lineOffsets.push(i);
+            isLineStart = false;
+        }
+        const ch = text.charAt(i);
+        isLineStart = ch === '\r' || ch === '\n';
+        if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
+            i++;
+        }
+    }
+
+    if (isLineStart && text.length > 0) {
+        lineOffsets.push(text.length);
+    }
+
+    return lineOffsets;
+}
diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts
index 6b0af822..00036582 100644
--- a/packages/svelte2tsx/svelte-shims.d.ts
+++ b/packages/svelte2tsx/svelte-shims.d.ts
@@ -121,29 +121,29 @@ declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slo
 // optionalProps need to be first or its type cannot be infered correctly.
 
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props>, events: Events, slots: Slots }
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Slots }
 
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props> & SvelteAllProps, events: Events, slots: Slots }
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events: Events, slots: Slots }
 
 
 declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props & SvelteAllProps, events: Events, slots: Slots }
 
 declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots }
 
 declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
 declare function __sveltets_any(dummy: any): any;
@@ -197,7 +197,7 @@ declare function __sveltets_each<T>(
 ): any;
 
 declare function createSvelte2TsxComponent<Props, Events, Slots>(
-    render: {props?: Props, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
 ): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
 
 declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
index ba8a23a1..06c52476 100644
--- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
@@ -1215,4 +1215,82 @@ describe('DiagnosticsProvider', () => {
         const diagnostics = await plugin.getDiagnostics(document);
         assertPropsDiagnostics(diagnostics, 'js');
     });
+
+    it('checks generics correctly', async () => {
+        const { plugin, document } = setup('diagnostics-generics.svelte');
+        const diagnostics = await plugin.getDiagnostics(document);
+        assert.deepStrictEqual(diagnostics, [
+            {
+                code: 2322,
+                message:
+                    'Type \'"asd"\' is not assignable to type \'number | unique symbol | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | ... 34 more ... | "replaceAll"\'.',
+                range: {
+                    start: {
+                        character: 25,
+                        line: 10
+                    },
+                    end: {
+                        character: 26,
+                        line: 10
+                    }
+                },
+                severity: 1,
+                source: 'ts',
+                tags: []
+            },
+            {
+                code: 2322,
+                message: "Type 'string' is not assignable to type 'boolean'.",
+                range: {
+                    start: {
+                        character: 35,
+                        line: 10
+                    },
+                    end: {
+                        character: 36,
+                        line: 10
+                    }
+                },
+                severity: 1,
+                source: 'ts',
+                tags: []
+            },
+            {
+                code: 2367,
+                message:
+                    "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.",
+                range: {
+                    start: {
+                        character: 3,
+                        line: 11
+                    },
+                    end: {
+                        character: 13,
+                        line: 11
+                    }
+                },
+                severity: 1,
+                source: 'ts',
+                tags: []
+            },
+            {
+                code: 2367,
+                message:
+                    "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.",
+                range: {
+                    end: {
+                        character: 72,
+                        line: 10
+                    },
+                    start: {
+                        character: 55,
+                        line: 10
+                    }
+                },
+                severity: 1,
+                source: 'ts',
+                tags: []
+            }
+        ]);
+    });
 });
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte
new file mode 100644
index 00000000..00b88e97
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+  import Generics from './generics.svelte';
+</script>
+
+<!-- valid -->
+<Generics a={['a', 'b']} b={'anchor'} c={false} on:b={(e) => e.detail === 'str'} let:a>
+  {a === 'str'}
+</Generics>
+
+<!-- invalid -->
+<Generics a={['a', 'b']} b={'asd'} c={''} on:b={(e) => e.detail === true} let:a>
+  {a === true}
+</Generics>
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte
new file mode 100644
index 00000000..03baef7c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+  import { createEventDispatcher } from 'svelte';
+
+  type A = $$Generic;
+  type B = $$Generic<keyof A>;
+  type C = $$Generic<boolean>;
+
+  export let a: A[];
+  export let b: B;
+  export let c: C;
+
+  const dispatch = createEventDispatcher<{ b: A }>();
+  dispatch('b', a[0]);
+</script>
+
+<slot a={a[0]} />
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
index 2816db6d..ab8c546b 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
@@ -118,29 +118,29 @@ declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slo
 // optionalProps need to be first or its type cannot be infered correctly.
 
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props>, events: Events, slots: Slots }
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Slots }
 
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props> & SvelteAllProps, events: Events, slots: Slots }
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events: Events, slots: Slots }
 
 
 declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props & SvelteAllProps, events: Events, slots: Slots }
 
 declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots }
 
 declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
 declare function __sveltets_any(dummy: any): any;
@@ -194,7 +194,7 @@ declare function __sveltets_each<T>(
 ): any;
 
 declare function createSvelte2TsxComponent<Props, Events, Slots>(
-    render: {props?: Props, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
 ): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
 
 declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx
new file mode 100644
index 00000000..de10fcd7
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx
@@ -0,0 +1,236 @@
+import { SvelteComponentTyped } from "svelte"
+declare class Svelte2TsxComponent<
+    Props extends {} = {},
+    Events extends {} = {},
+    Slots extends {} = {}
+> {
+    // svelte2tsx-specific
+    /**
+     * @internal This is for type checking capabilities only
+     * and does not exist at runtime. Don't use this property.
+     */
+    $$prop_def: Props;
+    /**
+     * @internal This is for type checking capabilities only
+     * and does not exist at runtime. Don't use this property.
+     */
+    $$events_def: Events;
+    /**
+     * @internal This is for type checking capabilities only
+     * and does not exist at runtime. Don't use this property.
+     */
+    $$slot_def: Slots;
+    // https://svelte.dev/docs#Client-side_component_API
+    constructor(options: Svelte2TsxComponentConstructorParameters<Props>);
+    /**
+     * Causes the callback function to be called whenever the component dispatches an event.
+     * A function is returned that will remove the event listener when called.
+     */
+    $on<K extends keyof Events & string>(event: K, handler: (e: Events[K]) => any): () => void;
+    /**
+     * Removes a component from the DOM and triggers any `onDestroy` handlers.
+     */
+    $destroy(): void;
+    /**
+     * Programmatically sets props on an instance.
+     * `component.$set({ x: 1 })` is equivalent to `x = 1` inside the component's `<script>` block.
+     * Calling this method schedules an update for the next microtask — the DOM is __not__ updated synchronously.
+     */
+    $set(props?: Partial<Props>): void;
+    // From SvelteComponent(Dev) definition
+    $$: any;
+    $capture_state(): void;
+    $inject_state(): void;
+}
+
+interface Svelte2TsxComponentConstructorParameters<Props extends {}> {
+    /**
+     * An HTMLElement to render to. This option is required.
+     */
+    target: Element;
+    /**
+     * A child of `target` to render the component immediately before.
+     */
+    anchor?: Element;
+    /**
+     * An object of properties to supply to the component.
+     */
+    props?: Props;
+    hydrate?: boolean;
+    intro?: boolean;
+    $$inline?: boolean;
+}
+
+type AConstructorTypeOf<T, U extends any[] = any[]> = new (...args: U) => T;
+type SvelteComponentConstructor<T, U extends Svelte2TsxComponentConstructorParameters<any>> = new (options: U) => T;
+
+type SvelteActionReturnType = {
+	update?: (args: any) => void,
+	destroy?: () => void
+} | void
+
+type SvelteTransitionConfig = {
+    delay?: number,
+    duration?: number,
+    easing?: (t: number) => number,
+    css?: (t: number, u: number) => string,
+    tick?: (t: number, u: number) => void
+}
+
+type SvelteTransitionReturnType = SvelteTransitionConfig | (() => SvelteTransitionConfig)
+
+type SvelteAnimationReturnType = {
+    delay?: number,
+    duration?: number,
+    easing?: (t: number) => number,
+    css?: (t: number, u: number) => string,
+    tick?: (t: number, u: number) => void
+}
+
+type SvelteWithOptionalProps<Props, Keys extends keyof Props> = Omit<Props, Keys> & Partial<Pick<Props, Keys>>;
+type SvelteAllProps = { [index: string]: any }
+type SveltePropsAnyFallback<Props> = {[K in keyof Props]: Props[K] extends undefined ? any : Props[K]}
+type SvelteRestProps = { [index: string]: any }
+type SvelteSlots = { [index: string]: any }
+type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }
+
+// Forces TypeScript to look into the type which results in a better representation of it
+// which helps for error messages
+type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
+
+declare var process: NodeJS.Process & { browser: boolean }
+declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect }
+
+declare function __sveltets_ensureAnimation(animationCall: SvelteAnimationReturnType): {};
+declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {};
+declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {};
+declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
+declare function __sveltets_ensureType<T>(type: AConstructorTypeOf<T>, el: T): {};
+declare function __sveltets_cssProp(prop: Record<string, any>): {};
+declare function __sveltets_ctorOf<T>(type: T): AConstructorTypeOf<T>;
+declare function __sveltets_instanceOf<T = any>(type: AConstructorTypeOf<T>): T;
+declare function __sveltets_allPropsType(): SvelteAllProps
+declare function __sveltets_restPropsType(): SvelteRestProps
+declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slots): Record<Key, boolean>;
+
+// Overload of the following two functions is necessary.
+// An empty array of optionalProps makes OptionalProps type any, which means we lose the prop typing.
+// optionalProps need to be first or its type cannot be infered correctly.
+
+declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props>, events: Events, slots: Slots }
+declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
+    optionalProps: OptionalProps[],
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Slots }
+
+declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props> & SvelteAllProps, events: Events, slots: Slots }
+declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
+    optionalProps: OptionalProps[],
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events: Events, slots: Slots }
+
+
+declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props & SvelteAllProps, events: Events, slots: Slots }
+
+declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots }
+
+declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
+declare function __sveltets_any(dummy: any): any;
+declare function __sveltets_empty(dummy: any): {};
+declare function __sveltets_componentType(): AConstructorTypeOf<Svelte2TsxComponent<any, any, any>>
+declare function __sveltets_invalidate<T>(getValue: () => T): T
+
+declare function __sveltets_mapWindowEvent<K extends keyof HTMLBodyElementEventMap>(
+    event: K
+): HTMLBodyElementEventMap[K];
+declare function __sveltets_mapBodyEvent<K extends keyof WindowEventMap>(
+    event: K
+): WindowEventMap[K];
+declare function __sveltets_mapElementEvent<K extends keyof HTMLElementEventMap>(
+    event: K
+): HTMLElementEventMap[K];
+declare function __sveltets_mapElementTag<K extends keyof ElementTagNameMap>(
+    tag: K
+): ElementTagNameMap[K];
+declare function __sveltets_mapElementTag<K extends keyof SVGElementTagNameMap>(
+    tag: K
+): SVGElementTagNameMap[K];
+declare function __sveltets_mapElementTag(
+    tag: any
+): HTMLElement;
+
+declare function __sveltets_bubbleEventDef<Events, K extends keyof Events>(
+    events: Events, eventKey: K
+): Events[K];
+declare function __sveltets_bubbleEventDef(
+    events: any, eventKey: string
+): any;
+
+declare const __sveltets_customEvent: CustomEvent<any>;
+declare function __sveltets_toEventTypings<Typings>(): {[Key in keyof Typings]: CustomEvent<Typings[Key]>};
+
+declare function __sveltets_unionType<T1, T2>(t1: T1, t2: T2): T1 | T2;
+declare function __sveltets_unionType<T1, T2, T3>(t1: T1, t2: T2, t3: T3): T1 | T2 | T3;
+declare function __sveltets_unionType<T1, T2, T3, T4>(t1: T1, t2: T2, t3: T3, t4: T4): T1 | T2 | T3 | T4;
+declare function __sveltets_unionType(...types: any[]): any;
+
+declare function __sveltets_awaitThen<T>(
+    promise: T,
+    onfulfilled: (value: T extends PromiseLike<infer U> ? U : T) => any,
+    onrejected?: (value: T extends PromiseLike<any> ? any : never) => any
+): any;
+
+declare function __sveltets_each<T>(
+    array: ArrayLike<T>,
+    callbackfn: (value: T, index: number) => any
+): any;
+
+declare function createSvelte2TsxComponent<Props, Events, Slots>(
+    render: {props: Props, events: Events, slots: Slots }
+): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
+
+declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T
+declare function __sveltets_unwrapPromiseLike<T>(promise: PromiseLike<T> | T): T
+
+
+import { createEventDispatcher } from 'svelte';
+function render</*Ωignore_startΩ*/A,B extends keyof A,C extends boolean/*Ωignore_endΩ*/>() {
+
+    
+
+    
+    
+    
+
+     let a: A;
+     let b: B;
+     let c: C;
+
+    const dispatch = createEventDispatcher<{a: A}>();
+;
+return { props: {a: a , b: b , c: c} as {a: A, b: B, c: C}, slots: {'default': {c:c}}, getters: {}, events: {...__sveltets_toEventTypings<{a: A}>()} }}
+class __sveltets_Render<A,B extends keyof A,C extends boolean> {
+    props() {
+        return render<A,B,C>().props;
+    }
+    events() {
+        return __sveltets_with_any_event(render<A,B,C>()).events;
+    }
+    slots() {
+        return render<A,B,C>().slots;
+    }
+}
+export type InputProps<A,B extends keyof A,C extends boolean> = ReturnType<__sveltets_Render<A,B,C>['props']>;
+export type InputEvents<A,B extends keyof A,C extends boolean> = ReturnType<__sveltets_Render<A,B,C>['events']>;
+export type InputSlots<A,B extends keyof A,C extends boolean> = ReturnType<__sveltets_Render<A,B,C>['slots']>;
+
+export default class Input<A,B extends keyof A,C extends boolean> extends SvelteComponentTyped<InputProps<A,B,C>, InputEvents<A,B,C>, InputSlots<A,B,C>> {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/input.svelte
new file mode 100644
index 00000000..869c66db
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/input.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+    import { createEventDispatcher } from 'svelte';
+
+    type A = $$Generic;
+    type B = $$Generic<keyof A>;
+    type C = $$Generic<boolean>;
+
+    export let a: A;
+    export let b: B;
+    export let c: C;
+
+    const dispatch = createEventDispatcher<{a: A}>();
+</script>
+
+<slot {c} />
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx
new file mode 100644
index 00000000..0b40768c
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx
@@ -0,0 +1,36 @@
+///<reference types="svelte" />
+<></>;
+import { createEventDispatcher } from 'svelte';
+function render</*Ωignore_startΩ*/A,B extends keyof A,C extends boolean/*Ωignore_endΩ*/>() {
+
+    
+
+    
+    
+    
+
+     let a: A;
+     let b: B;
+     let c: C;
+
+    const dispatch = createEventDispatcher<{a: A}>();
+;
+() => (<>
+
+<slot c={c} /></>);
+return { props: {a: a , b: b , c: c} as {a: A, b: B, c: C}, slots: {'default': {c:c}}, getters: {}, events: {...__sveltets_toEventTypings<{a: A}>()} }}
+class __sveltets_Render<A,B extends keyof A,C extends boolean> {
+    props() {
+        return render<A,B,C>().props;
+    }
+    events() {
+        return __sveltets_with_any_event(render<A,B,C>()).events;
+    }
+    slots() {
+        return render<A,B,C>().slots;
+    }
+}
+
+
+export default class Input__SvelteComponent_<A,B extends keyof A,C extends boolean> extends Svelte2TsxComponent<ReturnType<__sveltets_Render<A,B,C>['props']>, ReturnType<__sveltets_Render<A,B,C>['events']>, ReturnType<__sveltets_Render<A,B,C>['slots']>> {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/input.svelte
new file mode 100644
index 00000000..869c66db
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/input.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+    import { createEventDispatcher } from 'svelte';
+
+    type A = $$Generic;
+    type B = $$Generic<keyof A>;
+    type C = $$Generic<boolean>;
+
+    export let a: A;
+    export let b: B;
+    export let c: C;
+
+    const dispatch = createEventDispatcher<{a: A}>();
+</script>
+
+<slot {c} />
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
index 169fa215..b325b8cd 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
@@ -118,29 +118,29 @@ declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slo
 // optionalProps need to be first or its type cannot be infered correctly.
 
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props>, events: Events, slots: Slots }
 declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events: Events, slots: Slots }
 
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: SveltePropsAnyFallback<Props> & SvelteAllProps, events: Events, slots: Slots }
 declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
     optionalProps: OptionalProps[],
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events: Events, slots: Slots }
 
 
 declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props & SvelteAllProps, events: Events, slots: Slots }
 
 declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
-    render: {props?: Props, events?: Events, slots?: Slots }
-): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
+): {props: Props, events: Events & {[evt: string]: CustomEvent<any>;}, slots: Slots }
 
 declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
 declare function __sveltets_any(dummy: any): any;
@@ -194,7 +194,7 @@ declare function __sveltets_each<T>(
 ): any;
 
 declare function createSvelte2TsxComponent<Props, Events, Slots>(
-    render: {props?: Props, events?: Events, slots?: Slots }
+    render: {props: Props, events: Events, slots: Slots }
 ): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
 
 declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T