diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
index a00e4852b..f0334143d 100644
--- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
@@ -1,17 +1,12 @@
import ts from 'typescript';
import { CancellationToken, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
-import {
- Document,
- mapObjWithRangeToOriginal,
- getTextInRange,
- isRangeInTag
-} from '../../../lib/documents';
+import { Document, getTextInRange, isRangeInTag, mapRangeToOriginal } from '../../../lib/documents';
import { DiagnosticsProvider } from '../../interfaces';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertRange, getDiagnosticTag, mapSeverity } from '../utils';
-import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
-import { isInGeneratedCode } from './utils';
-import { swapRangeStartEndIfNecessary } from '../../../utils';
+import { SvelteDocumentSnapshot, SvelteSnapshotFragment } from '../DocumentSnapshot';
+import { isInGeneratedCode, isAfterSvelte2TsxPropsReturn } from './utils';
+import { regexIndexOf, swapRangeStartEndIfNecessary } from '../../../utils';
export class DiagnosticsProviderImpl implements DiagnosticsProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
@@ -62,7 +57,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
code: diagnostic.code,
tags: getDiagnosticTag(diagnostic)
}))
- .map((diagnostic) => mapObjWithRangeToOriginal(fragment, diagnostic))
+ .map(mapRange(fragment, document))
.filter(hasNoNegativeLines)
.filter(isNoFalsePositive(document, tsDoc))
.map(enhanceIfNecessary)
@@ -74,6 +69,42 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
}
}
+function mapRange(
+ fragment: SvelteSnapshotFragment,
+ document: Document
+): (value: Diagnostic) => Diagnostic {
+ return (diagnostic) => {
+ let range = mapRangeToOriginal(fragment, diagnostic.range);
+
+ if (range.start.line < 0) {
+ const is$$PropsError =
+ isAfterSvelte2TsxPropsReturn(
+ fragment.text,
+ fragment.offsetAt(diagnostic.range.start)
+ ) && diagnostic.message.includes('$$Props');
+
+ if (is$$PropsError) {
+ const propsStart = regexIndexOf(
+ document.getText(),
+ /(interface|type)\s+\$\$Props[\s{=]/
+ );
+
+ if (propsStart) {
+ const start = document.positionAt(
+ propsStart + document.getText().substring(propsStart).indexOf('$$Props')
+ );
+ range = {
+ start,
+ end: { ...start, character: start.character + '$$Props'.length }
+ };
+ }
+ }
+ }
+
+ return { ...diagnostic, range };
+ };
+}
+
/**
* In some rare cases mapping of diagnostics does not work and produces negative lines.
* We filter out these diagnostics with negative lines because else the LSP
diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
index 7e1182d2b..0808183a4 100644
--- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
@@ -11,7 +11,12 @@ import { convertRange } from '../utils';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import ts from 'typescript';
import { uniqWith, isEqual } from 'lodash';
-import { isComponentAtPosition, isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';
+import {
+ isComponentAtPosition,
+ isAfterSvelte2TsxPropsReturn,
+ isNoTextSpanInGeneratedCode,
+ SnapshotFragmentMap
+} from './utils';
export class RenameProviderImpl implements RenameProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
@@ -273,11 +278,7 @@ export class RenameProviderImpl implements RenameProvider {
// --------> svelte2tsx?
private isInSvelte2TsxPropLine(fragment: SvelteSnapshotFragment, loc: ts.RenameLocation) {
- const textBeforeProp = fragment.text.substring(0, loc.textSpan.start);
- // This is how svelte2tsx writes out the props
- if (textBeforeProp.includes('\nreturn { props: {')) {
- return true;
- }
+ return isAfterSvelte2TsxPropsReturn(fragment.text, loc.textSpan.start);
}
/**
diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts
index b82bff13e..e26582fdf 100644
--- a/packages/language-server/src/plugins/typescript/features/utils.ts
+++ b/packages/language-server/src/plugins/typescript/features/utils.ts
@@ -130,3 +130,11 @@ export class SnapshotFragmentMap {
return (await this.retrieve(fileName)).fragment;
}
}
+
+export function isAfterSvelte2TsxPropsReturn(text: string, end: number) {
+ const textBeforeProp = text.substring(0, end);
+ // This is how svelte2tsx writes out the props
+ if (textBeforeProp.includes('\nreturn { props: {')) {
+ return true;
+ }
+}
diff --git a/packages/language-server/src/utils.ts b/packages/language-server/src/utils.ts
index 8f704ebd2..dbe8b65f0 100644
--- a/packages/language-server/src/utils.ts
+++ b/packages/language-server/src/utils.ts
@@ -164,6 +164,19 @@ export function regexLastIndexOf(text: string, regex: RegExp, endPos?: number) {
return lastIndexOf;
}
+/**
+ * Like str.indexOf, but for regular expressions.
+ */
+export function regexIndexOf(text: string, regex: RegExp, startPos?: number) {
+ if (startPos === undefined || startPos < 0) {
+ startPos = 0;
+ }
+
+ const stringToWorkWith = text.substring(startPos);
+ const result: RegExpExecArray | null = regex.exec(stringToWorkWith);
+ return result?.index ?? -1;
+}
+
/**
* Get all matches of a regexp.
*/
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 11c220a41..8dbe87a29 100644
--- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
@@ -1294,6 +1294,12 @@ describe('DiagnosticsProvider', () => {
]);
});
+ it('filters out unused $$Generic hint', async () => {
+ const { plugin, document } = setup('$$generic-unused.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, []);
+ });
+
it('checks $$Events usage', async () => {
const { plugin, document } = setup('$$events.svelte');
const diagnostics = await plugin.getDiagnostics(document);
@@ -1582,4 +1588,169 @@ describe('DiagnosticsProvider', () => {
}
]);
});
+
+ it('checks $$Props usage (valid)', async () => {
+ const { plugin, document } = setup('$$props-valid.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, []);
+ });
+
+ it('checks $$Props usage (invalid1)', async () => {
+ const { plugin, document } = setup('$$props-invalid1.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, [
+ {
+ code: 2345,
+ message:
+ // eslint-disable-next-line max-len
+ "Argument of type '$$Props' is not assignable to parameter of type '{ exported1: string; }'.\n Types of property 'exported1' are incompatible.\n Type 'string | undefined' is not assignable to type 'string'.\n Type 'undefined' is not assignable to type 'string'.",
+ range: {
+ end: {
+ character: 18,
+ line: 1
+ },
+ start: {
+ character: 11,
+ line: 1
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ }
+ ]);
+ });
+
+ it('checks $$Props usage (invalid2)', async () => {
+ const { plugin, document } = setup('$$props-invalid2.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, [
+ {
+ code: 2345,
+ message:
+ // eslint-disable-next-line max-len
+ "Argument of type '$$Props' is not assignable to parameter of type '{ exported1?: string | undefined; }'.\n Types of property 'exported1' are incompatible.\n Type 'boolean' is not assignable to type 'string | undefined'.",
+ range: {
+ end: {
+ character: 18,
+ line: 1
+ },
+ start: {
+ character: 11,
+ line: 1
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ }
+ ]);
+ });
+
+ it('checks $$Props usage (invalid3)', async () => {
+ const { plugin, document } = setup('$$props-invalid3.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, [
+ {
+ code: 2345,
+ message:
+ // eslint-disable-next-line max-len
+ "Argument of type '$$Props' is not assignable to parameter of type '{ wrong: boolean; }'.\n Property 'wrong' is missing in type '$$Props' but required in type '{ wrong: boolean; }'.",
+ range: {
+ end: {
+ character: 18,
+ line: 1
+ },
+ start: {
+ character: 11,
+ line: 1
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ },
+ {
+ code: 2345,
+ message:
+ // eslint-disable-next-line max-len
+ "Argument of type '{ wrong: boolean; }' is not assignable to parameter of type 'Partial<$$Props>'.\n Object literal may only specify known properties, and 'wrong' does not exist in type 'Partial<$$Props>'.",
+ range: {
+ end: {
+ character: 18,
+ line: 1
+ },
+ start: {
+ character: 11,
+ line: 1
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ }
+ ]);
+ });
+
+ it('checks $$Props component usage', async () => {
+ const { plugin, document } = setup('using-$$props.svelte');
+ const diagnostics = await plugin.getDiagnostics(document);
+ assert.deepStrictEqual(diagnostics, [
+ {
+ code: 2322,
+ message: "Type 'boolean' is not assignable to type 'string'.",
+ range: {
+ end: {
+ character: 16,
+ line: 9
+ },
+ start: {
+ character: 7,
+ line: 9
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ },
+ {
+ code: 2322,
+ message:
+ // eslint-disable-next-line max-len
+ "Type '{ exported1: string; exported2: string; invalidProp: boolean; }' is not assignable to type 'IntrinsicAttributes & { exported1: string; exported2?: string | undefined; }'.\n Property 'invalidProp' does not exist on type 'IntrinsicAttributes & { exported1: string; exported2?: string | undefined; }'.",
+ range: {
+ end: {
+ character: 54,
+ line: 10
+ },
+ start: {
+ character: 43,
+ line: 10
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ },
+ {
+ code: 2322,
+ message:
+ // eslint-disable-next-line max-len
+ "Type '{}' is not assignable to type 'IntrinsicAttributes & { exported1: string; exported2?: string | undefined; }'.\n Property 'exported1' is missing in type '{}' but required in type '{ exported1: string; exported2?: string | undefined; }'.",
+ range: {
+ end: {
+ character: 6,
+ line: 11
+ },
+ start: {
+ character: 1,
+ line: 11
+ }
+ },
+ severity: 1,
+ source: 'ts',
+ tags: []
+ }
+ ]);
+ });
});
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$generic-unused.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$generic-unused.svelte
new file mode 100644
index 000000000..c49d4f2d9
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$generic-unused.svelte
@@ -0,0 +1,5 @@
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid1.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid1.svelte
new file mode 100644
index 000000000..1e6461202
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid1.svelte
@@ -0,0 +1,7 @@
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid2.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid2.svelte
new file mode 100644
index 000000000..f612d827e
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid2.svelte
@@ -0,0 +1,7 @@
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid3.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid3.svelte
new file mode 100644
index 000000000..d37066d42
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-invalid3.svelte
@@ -0,0 +1,7 @@
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-valid.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-valid.svelte
new file mode 100644
index 000000000..141deecb7
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$props-valid.svelte
@@ -0,0 +1,9 @@
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$props.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$props.svelte
new file mode 100644
index 000000000..74340e9ed
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/using-$$props.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
index 005f2e7a3..cbec613f8 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
@@ -1,27 +1,68 @@
import ts from 'typescript';
-import { getLastLeadingDoc } from '../utils/tsAst';
+import { getLastLeadingDoc, isInterfaceOrTypeDeclaration } from '../utils/tsAst';
export interface IExportedNames {
has(name: string): boolean;
}
-export class ExportedNames
- extends Map<
- string,
- {
- type?: string;
- identifierText?: string;
- required?: boolean;
- doc?: string;
+export function is$$PropsDeclaration(
+ node: ts.Node
+): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration {
+ return isInterfaceOrTypeDeclaration(node) && node.name.text === '$$Props';
+}
+
+interface ExportedName {
+ isLet: boolean;
+ type?: string;
+ identifierText?: string;
+ required?: boolean;
+ doc?: string;
+}
+
+export class ExportedNames extends Map implements IExportedNames {
+ private uses$$Props = false;
+ private possibleExports = new Map();
+
+ setUses$$Props(): void {
+ this.uses$$Props = true;
+ }
+
+ /**
+ * Marks a top level declaration as a possible export
+ * which could be exported through `export { .. }` later.
+ */
+ addPossibleExport(
+ name: ts.BindingName,
+ isLet: boolean,
+ target: ts.BindingName = null,
+ type: ts.TypeNode = null,
+ required = false
+ ) {
+ if (!ts.isIdentifier(name)) {
+ return;
}
- >
- implements IExportedNames
-{
+
+ if (target && ts.isIdentifier(target)) {
+ this.possibleExports.set(name.text, {
+ isLet,
+ type: type?.getText(),
+ identifierText: (target as ts.Identifier).text,
+ required,
+ doc: this.getDoc(target)
+ });
+ } else {
+ this.possibleExports.set(name.text, {
+ isLet
+ });
+ }
+ }
+
/**
* Adds export to map
*/
addExport(
name: ts.BindingName,
+ isLet: boolean,
target: ts.BindingName = null,
type: ts.TypeNode = null,
required = false
@@ -33,15 +74,22 @@ export class ExportedNames
throw Error('export target kind not supported ' + target);
}
+ const existingDeclaration = this.possibleExports.get(name.text);
if (target) {
this.set(name.text, {
- type: type?.getText(),
+ isLet: isLet || existingDeclaration?.isLet,
+ type: type?.getText() || existingDeclaration?.type,
identifierText: (target as ts.Identifier).text,
- required,
- doc: this.getDoc(target)
+ required: required || existingDeclaration?.required,
+ doc: this.getDoc(target) || existingDeclaration?.doc
});
} else {
- this.set(name.text, {});
+ this.set(name.text, {
+ isLet: isLet || existingDeclaration?.isLet,
+ type: existingDeclaration?.type,
+ required: existingDeclaration?.required,
+ doc: existingDeclaration?.doc
+ });
}
}
@@ -62,27 +110,62 @@ export class ExportedNames
*
* @param isTsFile Whether this is a TypeScript file or not.
*/
- createPropsStr(isTsFile: boolean) {
+ createPropsStr(isTsFile: boolean): string {
const names = Array.from(this.entries());
+
+ if (this.uses$$Props) {
+ const lets = names.filter(([, { isLet }]) => isLet);
+ const others = names.filter(([, { isLet }]) => !isLet);
+ // We need to check both ways:
+ // - The check if exports are assignable to Parial<$$Props> is necessary to make sure
+ // no props are missing. Partial<$$Props> is needed because props with a default value
+ // count as optional, but semantically speaking it is still correctly implementing the interface
+ // - The check if $$Props is assignable to exports is necessary to make sure no extraneous props
+ // are defined and that no props are required that should be optional
+ // __sveltets_ensureRightProps needs to be declared in a way that doesn't affect the type result of props
+ return (
+ '{...__sveltets_ensureRightProps<{' +
+ this.createReturnElementsType(lets).join(',') +
+ '}>(__sveltets_any("") as $$Props), ' +
+ '...__sveltets_ensureRightProps>({' +
+ this.createReturnElements(lets, false).join(',') +
+ '}), ...{} as unknown as $$Props, ...{' +
+ this.createReturnElements(others, false).join(', ') +
+ '} as {' +
+ this.createReturnElementsType(others).join(',') +
+ '}}'
+ );
+ }
+
const dontAddTypeDef =
!isTsFile ||
names.length === 0 ||
names.every(([_, value]) => !value.type && value.required);
+ const returnElements = this.createReturnElements(names, dontAddTypeDef);
+ if (dontAddTypeDef) {
+ // No exports or only `typeof` exports -> omit the `as {...}` completely.
+ // If not TS, omit the types to not have a "cannot use types in jsx" error.
+ return `{${returnElements.join(' , ')}}`;
+ }
- const returnElements = names.map(([key, value]) => {
+ const returnElementsType = this.createReturnElementsType(names);
+ return `{${returnElements.join(' , ')}} as {${returnElementsType.join(', ')}}`;
+ }
+
+ private createReturnElements(
+ names: Array<[string, ExportedName]>,
+ dontAddTypeDef: boolean
+ ): string[] {
+ return names.map(([key, value]) => {
// Important to not use shorthand props for rename functionality
return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${
value.identifierText || key
}: ${key}`;
});
+ }
- if (dontAddTypeDef) {
- // No exports or only `typeof` exports -> omit the `as {...}` completely.
- // If not TS, omit the types to not have a "cannot use types in jsx" error.
- return `{${returnElements.join(' , ')}}`;
- }
-
- const returnElementsType = names.map(([key, value]) => {
+ private createReturnElementsType(names: Array<[string, ExportedName]>) {
+ return names.map(([key, value]) => {
const identifier = `${value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}${
value.required ? '' : '?'
}`;
@@ -92,11 +175,9 @@ export class ExportedNames
return `${identifier}: ${value.type}`;
});
-
- return `{${returnElements.join(' , ')}} as {${returnElementsType.join(', ')}}`;
}
- createOptionalPropsArray() {
+ createOptionalPropsArray(): string[] {
return Array.from(this.entries())
.filter(([_, entry]) => !entry.required)
.map(([name, entry]) => `'${entry.identifierText || name}'`);
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts
index 40ccc2f3e..d95941289 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/Generics.ts
@@ -47,7 +47,7 @@ export class Generics {
toDefinitionString(addIgnore = false) {
const surround = addIgnore ? surroundWithIgnoreComments : (str: string) => str;
- return this.definitions.length ? `<${surround(this.definitions.join(','))}>` : '';
+ return this.definitions.length ? surround(`<${this.definitions.join(',')}>`) : '';
}
toReferencesString() {
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 71dec7fe4..c2db71d7f 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -7,7 +7,7 @@ import {
isFirstInAnExpressionStatement,
isNotPropertyNameOfImport
} from './utils/tsAst';
-import { ExportedNames } from './nodes/ExportedNames';
+import { ExportedNames, is$$PropsDeclaration } from './nodes/ExportedNames';
import { ImplicitTopLevelNames } from './nodes/ImplicitTopLevelNames';
import { ComponentEvents, is$$EventsDeclaration } from './nodes/ComponentEvents';
import { Scope } from './utils/Scope';
@@ -143,7 +143,7 @@ export function processInstanceScriptContent(
// Can't export default here
if (node.name) {
- exportedNames.addExport(node.name);
+ exportedNames.addExport(node.name, false);
}
};
@@ -309,18 +309,22 @@ export function processInstanceScriptContent(
}
};
- const handleExportedVariableDeclarationList = (list: ts.VariableDeclarationList) => {
+ const handleExportedVariableDeclarationList = (
+ list: ts.VariableDeclarationList,
+ add: typeof exportedNames.addExport
+ ) => {
+ const isLet = list.flags === ts.NodeFlags.Let;
ts.forEachChild(list, (node) => {
if (ts.isVariableDeclaration(node)) {
if (ts.isIdentifier(node.name)) {
- exportedNames.addExport(node.name, node.name, node.type, !node.initializer);
+ add(node.name, isLet, node.name, node.type, !node.initializer);
} else if (
ts.isObjectBindingPattern(node.name) ||
ts.isArrayBindingPattern(node.name)
) {
ts.forEachChild(node.name, (element) => {
if (ts.isBindingElement(element)) {
- exportedNames.addExport(element.name);
+ add(element.name, isLet);
}
});
}
@@ -360,6 +364,9 @@ export function processInstanceScriptContent(
if (is$$SlotsDeclaration(node)) {
uses$$SlotsInterface = true;
}
+ if (is$$PropsDeclaration(node)) {
+ exportedNames.setUses$$Props();
+ }
if (ts.isVariableStatement(node)) {
const exportModifier = findExportKeyword(node);
@@ -367,7 +374,10 @@ export function processInstanceScriptContent(
const isLet = node.declarationList.flags === ts.NodeFlags.Let;
const isConst = node.declarationList.flags === ts.NodeFlags.Const;
- handleExportedVariableDeclarationList(node.declarationList);
+ handleExportedVariableDeclarationList(
+ node.declarationList,
+ exportedNames.addExport.bind(exportedNames)
+ );
if (isLet) {
propTypeAssertToUserDefined(node.declarationList);
} else if (isConst) {
@@ -379,6 +389,12 @@ export function processInstanceScriptContent(
}
removeExport(exportModifier.getStart(), exportModifier.end);
}
+ if (ts.isSourceFile(parent)) {
+ handleExportedVariableDeclarationList(
+ node.declarationList,
+ exportedNames.addPossibleExport.bind(exportedNames)
+ );
+ }
}
if (ts.isFunctionDeclaration(node)) {
@@ -407,9 +423,9 @@ export function processInstanceScriptContent(
if (ts.isNamedExports(exportClause)) {
for (const ne of exportClause.elements) {
if (ne.propertyName) {
- exportedNames.addExport(ne.propertyName, ne.name);
+ exportedNames.addExport(ne.propertyName, false, ne.name);
} else {
- exportedNames.addExport(ne.name);
+ exportedNames.addExport(ne.name, false);
}
}
//we can remove entire statement
diff --git a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
index 02d835951..b1728b912 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processModuleScriptTag.ts
@@ -7,6 +7,7 @@ import { Generics } from './nodes/Generics';
import { is$$EventsDeclaration } from './nodes/ComponentEvents';
import { throwError } from './utils/error';
import { is$$SlotsDeclaration } from './nodes/slot';
+import { is$$PropsDeclaration } from './nodes/ExportedNames';
export function processModuleScriptTag(
str: MagicString,
@@ -32,6 +33,7 @@ export function processModuleScriptTag(
generics.throwIfIsGeneric(node);
throwIfIs$$EventsDeclaration(node, str, astOffset);
throwIfIs$$SlotsDeclaration(node, str, astOffset);
+ throwIfIs$$PropsDeclaration(node, str, astOffset);
ts.forEachChild(node, (n) => walk(n));
};
@@ -84,6 +86,12 @@ function throwIfIs$$SlotsDeclaration(node: ts.Node, str: MagicString, astOffset:
}
}
+function throwIfIs$$PropsDeclaration(node: ts.Node, str: MagicString, astOffset: number) {
+ if (is$$PropsDeclaration(node)) {
+ throw$$Error(node, str, astOffset, '$$Props');
+ }
+}
+
function throw$$Error(node: ts.Node, str: MagicString, astOffset: number, type: string) {
throwError(
node.getStart() + astOffset,
diff --git a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
index 84b724fae..467006f93 100644
--- a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
@@ -112,6 +112,7 @@ declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionRet
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {};
declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2];
+declare function __sveltets_ensureRightProps(props: Props): {};
declare function __sveltets_cssProp(prop: Record): {};
declare function __sveltets_ctorOf(type: T): AConstructorTypeOf;
declare function __sveltets_instanceOf(type: AConstructorTypeOf): T;
diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts
index d6b84da38..dfe80d151 100644
--- a/packages/svelte2tsx/svelte-shims.d.ts
+++ b/packages/svelte2tsx/svelte-shims.d.ts
@@ -113,6 +113,7 @@ declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionRet
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {};
declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2];
+declare function __sveltets_ensureRightProps(props: Props): {};
declare function __sveltets_cssProp(prop: Record): {};
declare function __sveltets_ctorOf(type: T): AConstructorTypeOf;
declare function __sveltets_instanceOf(type: AConstructorTypeOf): T;
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
index deef3d6b0..52bd008c0 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx
@@ -110,6 +110,7 @@ declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionRet
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {};
declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2];
+declare function __sveltets_ensureRightProps(props: Props): {};
declare function __sveltets_cssProp(prop: Record): {};
declare function __sveltets_ctorOf(type: T): AConstructorTypeOf;
declare function __sveltets_instanceOf(type: AConstructorTypeOf): T;
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
index 9d0c2abc1..594846627 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
@@ -1,12 +1,24 @@
///
<>>;function render() {
- let name = "world"
- let name2 = "world"
+ let name1 = "world"
+ let name2
+
+ let rename1 = '';
+ let rename2;
+
+ class Foo {}
+ function bar() {}
+ const baz = '';
+
+ class RenameFoo {}
+ function renamebar() {}
+ const renamebaz = '';
+
;
() => (<>>);
-return { props: {name: name , name2: name2}, slots: {}, getters: {}, events: {} }}
+return { props: {name1: name1 , name2: name2 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz}, slots: {}, getters: {}, events: {} }}
-export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['name','name2'], __sveltets_with_any_event(render()))) {
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['name1','renamed1','Foo','bar','baz','RenamedFoo','renamedbar','renamedbaz'], __sveltets_with_any_event(render()))) {
}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/input.svelte
index 8d5929e12..036b2c529 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/input.svelte
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/input.svelte
@@ -1,5 +1,17 @@
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx
new file mode 100644
index 000000000..e4b7bb756
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx
@@ -0,0 +1,37 @@
+///
+<>>;function render() {
+
+
+ type $$Props = {
+ exported1: string;
+ exported2?: string;
+ name1?: string;
+ name2: string;
+ renamed1?: string;
+ renamed2: string;
+ }
+
+ let exported1: string;
+ let exported2: string = '';exported2 = __sveltets_any(exported2);;
+
+ let name1: string = "world"
+ let name2: string;
+
+ let rename1: string = '';
+ let rename2: string;
+
+ class Foo {}
+ function bar() {}
+ const baz: string = '';
+
+ class RenameFoo {}
+ function renamebar() {}
+ const renamebaz: string = '';
+
+
+;
+() => (<>>);
+return { props: {...__sveltets_ensureRightProps<{exported1: string,exported2?: string,name1?: string,name2: string,renamed1?: string,renamed2: string}>(__sveltets_any("") as $$Props), ...__sveltets_ensureRightProps>({exported1: exported1,exported2: exported2,name1: name1,name2: name2,renamed1: rename1,renamed2: rename2}), ...{} as unknown as $$Props, ...{Foo: Foo, bar: bar, baz: baz, RenamedFoo: RenameFoo, renamedbar: renamebar, renamedbaz: renamebaz} as {Foo?: typeof Foo,bar?: typeof bar,baz?: string,RenamedFoo?: typeof RenameFoo,renamedbar?: typeof renamebar,renamedbaz?: string}}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_with_any_event(render())) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/input.svelte
new file mode 100644
index 000000000..1024befec
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/input.svelte
@@ -0,0 +1,30 @@
+
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx
new file mode 100644
index 000000000..993a4e94b
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx
@@ -0,0 +1,37 @@
+///
+<>>;function render() {
+
+
+ interface $$Props {
+ exported1: string;
+ exported2?: string;
+ name1?: string;
+ name2: string;
+ renamed1?: string;
+ renamed2: string;
+ }
+
+ let exported1: string;
+ let exported2: string = '';exported2 = __sveltets_any(exported2);;
+
+ let name1: string = "world"
+ let name2: string;
+
+ let rename1: string = '';
+ let rename2: string;
+
+ class Foo {}
+ function bar() {}
+ const baz: string = '';
+
+ class RenameFoo {}
+ function renamebar() {}
+ const renamebaz: string = '';
+
+
+;
+() => (<>>);
+return { props: {...__sveltets_ensureRightProps<{exported1: string,exported2?: string,name1?: string,name2: string,renamed1?: string,renamed2: string}>(__sveltets_any("") as $$Props), ...__sveltets_ensureRightProps>({exported1: exported1,exported2: exported2,name1: name1,name2: name2,renamed1: rename1,renamed2: rename2}), ...{} as unknown as $$Props, ...{Foo: Foo, bar: bar, baz: baz, RenamedFoo: RenameFoo, renamedbar: renamebar, renamedbaz: renamebaz} as {Foo?: typeof Foo,bar?: typeof bar,baz?: string,RenamedFoo?: typeof RenameFoo,renamedbar?: typeof renamebar,renamedbaz?: string}}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_with_any_event(render())) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/input.svelte
new file mode 100644
index 000000000..422f7c218
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/input.svelte
@@ -0,0 +1,30 @@
+
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx
index 376f8b38a..0c7346aca 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx
@@ -110,6 +110,7 @@ declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionRet
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {};
declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2];
+declare function __sveltets_ensureRightProps(props: Props): {};
declare function __sveltets_cssProp(prop: Record): {};
declare function __sveltets_ctorOf(type: T): AConstructorTypeOf;
declare function __sveltets_instanceOf(type: AConstructorTypeOf): T;
@@ -206,7 +207,7 @@ declare function __sveltets_unwrapPromiseLike(promise: PromiseLike | T): T
import { createEventDispatcher } from 'svelte';
-function render*Ωignore_startΩ*/A,B extends keyof A,C extends boolean/*Ωignore_endΩ*/>() {
+function render/*Ωignore_startΩ*//*Ωignore_endΩ*/() {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx
index a5dd1d023..720e9cd04 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics/expected.tsx
@@ -1,7 +1,7 @@
///
<>>;
import { createEventDispatcher } from 'svelte';
-function render*Ωignore_startΩ*/A,B extends keyof A,C extends boolean/*Ωignore_endΩ*/>() {
+function render/*Ωignore_startΩ*//*Ωignore_endΩ*/() {
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 5c295ae54..683061994 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
@@ -110,6 +110,7 @@ declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionRet
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType(type: AConstructorTypeOf, el: T): {};
declare function __sveltets_createEnsureSlot>>(): (k1: K1, k2: K2, val: Slots[K1][K2]) => Slots[K1][K2];
+declare function __sveltets_ensureRightProps(props: Props): {};
declare function __sveltets_cssProp(prop: Record): {};
declare function __sveltets_ctorOf(type: T): AConstructorTypeOf;
declare function __sveltets_instanceOf(type: AConstructorTypeOf): T;
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx
new file mode 100644
index 000000000..7a5d2b2d4
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx
@@ -0,0 +1,24 @@
+///
+<>>;function render() {
+
+ let name1: string = "world"
+ let name2: string;
+
+ let rename1: string = '';
+ let rename2: string;
+
+ class Foo {}
+ function bar() {}
+ const baz: string = '';
+
+ class RenameFoo {}
+ function renamebar() {}
+ const renamebaz: string = '';
+
+
+;
+() => (<>>);
+return { props: {name1: name1 , name2: name2 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz} as {name1?: string, name2: string, renamed1?: string, renamed2: string, Foo?: typeof Foo, bar?: typeof bar, baz?: string, RenamedFoo?: typeof RenameFoo, renamedbar?: typeof renamebar, renamedbaz?: string}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_with_any_event(render())) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte
new file mode 100644
index 000000000..97aaf2197
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte
@@ -0,0 +1,17 @@
+
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-attr-present/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-attr-present/expected.tsx
index b2ce72bce..e21bdf959 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-attr-present/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-attr-present/expected.tsx
@@ -11,7 +11,7 @@
>);
return { props: {foo: foo , foo2: foo2 , class: clazz , bar: bar}, slots: {}, getters: {bar: bar}, events: {} }}
-export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['foo','foo2','class','bar'], __sveltets_with_any_event(render()))) {
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['foo','foo2','bar'], __sveltets_with_any_event(render()))) {
get bar() { return render().getters.bar }
get foo() { return render().props.foo }
/**accessor*/
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-mustachetag-true/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-mustachetag-true/expected.tsx
index b8d03c745..c63e2592d 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-mustachetag-true/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-accessors-mustachetag-true/expected.tsx
@@ -11,7 +11,7 @@
>);
return { props: {foo: foo , foo2: foo2 , class: clazz , bar: bar}, slots: {}, getters: {bar: bar}, events: {} }}
-export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['foo','foo2','class','bar'], __sveltets_with_any_event(render()))) {
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(['foo','foo2','bar'], __sveltets_with_any_event(render()))) {
get bar() { return render().getters.bar }
get foo() { return render().props.foo }
/**accessor*/