From 5456e53a0ae1e8e8959be07554e10c38088c221f Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Wed, 16 Apr 2025 05:09:38 +0800
Subject: [PATCH 1/7] fix: various hoistability check (#2740)
#2671 #2727
---
.../svelte2tsx/nodes/HoistableInterfaces.ts | 49 ++++++++++++++++---
.../expectedv2.ts | 19 +++++++
.../input.svelte | 10 ++++
.../expectedv2.ts | 17 +++++++
.../input.svelte | 9 ++++
.../expectedv2.ts | 20 ++++++++
.../input.svelte | 13 +++++
.../expectedv2.ts | 20 ++++++++
.../input.svelte | 13 +++++
.../expectedv2.ts | 20 ++++++++
.../input.svelte | 13 +++++
.../expectedv2.ts | 19 +++++++
.../input.svelte | 12 +++++
.../expectedv2.ts | 36 ++++++++++++++
.../input.svelte | 10 ++++
.../expectedv2.ts | 31 ++++++++++++
.../input.svelte | 5 ++
17 files changed, 309 insertions(+), 7 deletions(-)
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
index 7d95d1c73..dd2ebaceb 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts
@@ -86,6 +86,14 @@ export class HoistableInterfaces {
if (ts.isInterfaceDeclaration(node)) {
this.module_types.add(node.name.text);
}
+
+ if (ts.isEnumDeclaration(node)) {
+ this.module_types.add(node.name.text);
+ }
+
+ if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) {
+ this.module_types.add(node.name.text);
+ }
}
analyzeInstanceScriptNode(node: ts.Node) {
@@ -158,6 +166,24 @@ export class HoistableInterfaces {
}
});
+ node.heritageClauses?.forEach((clause) => {
+ clause.types.forEach((type) => {
+ if (ts.isIdentifier(type.expression)) {
+ const type_name = type.expression.text;
+ if (!generics.includes(type_name)) {
+ type_dependencies.add(type_name);
+ }
+ }
+
+ this.collectTypeDependencies(
+ type,
+ type_dependencies,
+ value_dependencies,
+ generics
+ );
+ });
+ });
+
if (this.module_types.has(interface_name)) {
// shadowed; we can't hoist
this.disallowed_types.add(interface_name);
@@ -229,6 +255,14 @@ export class HoistableInterfaces {
if (ts.isEnumDeclaration(node)) {
this.disallowed_values.add(node.name.text);
}
+
+ // namespace declaration should not be in the instance script.
+ // Only adding the top-level name to the disallowed list,
+ // so that at least there won't a confusing error message of "can't find namespace Foo"
+ if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) {
+ this.disallowed_types.add(node.name.text);
+ this.disallowed_values.add(node.name.text);
+ }
}
analyze$propsRune(
@@ -239,7 +273,7 @@ export class HoistableInterfaces {
if (node.initializer.typeArguments?.length > 0 || node.type) {
const generic_arg = node.initializer.typeArguments?.[0] || node.type;
if (ts.isTypeReferenceNode(generic_arg)) {
- const name = this.getEntityNameText(generic_arg.typeName);
+ const name = this.getEntityNameRoot(generic_arg.typeName);
const interface_node = this.interface_map.get(name);
if (interface_node) {
this.props_interface.name = name;
@@ -394,13 +428,13 @@ export class HoistableInterfaces {
) {
const walk = (node: ts.Node) => {
if (ts.isTypeReferenceNode(node)) {
- const type_name = this.getEntityNameText(node.typeName);
+ const type_name = this.getEntityNameRoot(node.typeName);
if (!generics.includes(type_name)) {
type_dependencies.add(type_name);
}
} else if (ts.isTypeQueryNode(node)) {
// Handle 'typeof' expressions: e.g., foo: typeof bar
- value_dependencies.add(this.getEntityNameText(node.exprName));
+ value_dependencies.add(this.getEntityNameRoot(node.exprName));
}
ts.forEachChild(node, walk);
@@ -410,15 +444,16 @@ export class HoistableInterfaces {
}
/**
- * Retrieves the full text of an EntityName (handles nested names).
+ * Retrieves the top-level variable/namespace of an EntityName (handles nested names).
+ * ex: `foo.bar.baz` -> `foo`
* @param entity_name The EntityName to extract text from.
- * @returns The full name as a string.
+ * @returns The top-level name as a string.
*/
- private getEntityNameText(entity_name: ts.EntityName): string {
+ private getEntityNameRoot(entity_name: ts.EntityName): string {
if (ts.isIdentifier(entity_name)) {
return entity_name.text;
} else {
- return this.getEntityNameText(entity_name.left) + '.' + entity_name.right.text;
+ return this.getEntityNameRoot(entity_name.left);
}
}
}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts
new file mode 100644
index 000000000..f063d70b8
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/expectedv2.ts
@@ -0,0 +1,19 @@
+///
+;;
+ interface A {
+ type: string;
+ };;
+
+ interface Props extends A {
+ a: string;
+ };function $$render() {
+
+
+
+ const { }: Props = $props();
+;
+async () => {};
+return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte
new file mode 100644
index 000000000..dec767819
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-6.v5/input.svelte
@@ -0,0 +1,10 @@
+
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts
new file mode 100644
index 000000000..22f05c3f8
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/expectedv2.ts
@@ -0,0 +1,17 @@
+///
+;;
+type Props = {
+ data: {cfg: string};
+};;function $$render() {
+
+
+let { data }: Props = $props();
+
+type A = typeof data.cfg;
+type B = (typeof data)['cfg'];
+;
+async () => {};
+return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte
new file mode 100644
index 000000000..42e0fbb2a
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-10.v5/input.svelte
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts
new file mode 100644
index 000000000..ed54ba6dc
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/expectedv2.ts
@@ -0,0 +1,20 @@
+///
+;function $$render() {
+
+ const a = 1;
+
+interface A {
+ Abc: typeof a
+}
+
+interface Abc {
+ foo: A.Abc
+}
+
+let {}: Abc = $props();
+;
+async () => {};
+return { props: {} as any as Abc, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte
new file mode 100644
index 000000000..c2532ebe6
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-11.v5/input.svelte
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts
new file mode 100644
index 000000000..1bcbfe8c5
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/expectedv2.ts
@@ -0,0 +1,20 @@
+///
+;function $$render() {
+
+const a = 1;
+
+namespace A {
+ export type Abc = typeof a
+}
+
+interface Abc {
+ foo: A.Abc
+}
+
+let {}: Abc = $props();
+;
+async () => {};
+return { props: {} as any as Abc, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte
new file mode 100644
index 000000000..897e08c65
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-12.v5/input.svelte
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts
new file mode 100644
index 000000000..1525d0cc2
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/expectedv2.ts
@@ -0,0 +1,20 @@
+///
+;
+ namespace A {
+ export type Abd = number
+ }
+;;function $$render() {
+
+interface A {
+ Abc: number
+}
+
+let {Abc}: A = $props()
+;
+async () => {
+
+};
+return { props: {} as any as A, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte
new file mode 100644
index 000000000..1617746f0
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-13.v5/input.svelte
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts
new file mode 100644
index 000000000..27b661d11
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/expectedv2.ts
@@ -0,0 +1,19 @@
+///
+;
+ enum A {
+ }
+;;function $$render() {
+
+interface A {
+ Abc: number
+}
+
+let {Abc}: A = $props()
+;
+async () => {
+
+};
+return { props: {} as any as A, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
+type Input__SvelteComponent_ = ReturnType;
+export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte
new file mode 100644
index 000000000..6d555bb69
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-14.v5/input.svelte
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts
new file mode 100644
index 000000000..60855f1fb
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/expectedv2.ts
@@ -0,0 +1,36 @@
+///
+;function $$render() {
+
+ interface WithItems {
+ items: T[];
+ }
+
+ interface Props extends WithItems {
+ prop: T;
+ };
+ let { prop }: Props = $props();
+;
+async () => {};
+return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+class __sveltets_Render {
+ props() {
+ return $$render().props;
+ }
+ events() {
+ return $$render().events;
+ }
+ slots() {
+ return $$render().slots;
+ }
+ bindings() { return __sveltets_$$bindings(''); }
+ exports() { return {}; }
+}
+
+interface $$IsomorphicComponent {
+ new (options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>;
+ (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>;
+ z_$$bindings?: ReturnType<__sveltets_Render['bindings']>;
+}
+const Input__SvelteComponent_: $$IsomorphicComponent = null as any;
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType>;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte
new file mode 100644
index 000000000..11461621f
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-8.v5/input.svelte
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts
new file mode 100644
index 000000000..39b62f5b3
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/expectedv2.ts
@@ -0,0 +1,31 @@
+///
+;function $$render() {
+
+ interface Props extends T {
+ };
+ let { a }: Props = $props();
+;
+async () => {};
+return { props: {} as any as Props, exports: {}, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+class __sveltets_Render {
+ props() {
+ return $$render().props;
+ }
+ events() {
+ return $$render().events;
+ }
+ slots() {
+ return $$render().slots;
+ }
+ bindings() { return __sveltets_$$bindings(''); }
+ exports() { return {}; }
+}
+
+interface $$IsomorphicComponent {
+ new (options: import('svelte').ComponentConstructorOptions['props']>>): import('svelte').SvelteComponent['props']>, ReturnType<__sveltets_Render['events']>, ReturnType<__sveltets_Render['slots']>> & { $$bindings?: ReturnType<__sveltets_Render['bindings']> } & ReturnType<__sveltets_Render['exports']>;
+ (internal: unknown, props: ReturnType<__sveltets_Render['props']> & {}): ReturnType<__sveltets_Render['exports']>;
+ z_$$bindings?: ReturnType<__sveltets_Render['bindings']>;
+}
+const Input__SvelteComponent_: $$IsomorphicComponent = null as any;
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType>;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte
new file mode 100644
index 000000000..fd8a6f6f1
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-runes-hoistable-props-false-9.v5/input.svelte
@@ -0,0 +1,5 @@
+
\ No newline at end of file
From ddc62b874be822111ce424a9d2ba64f752eaeab4 Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Fri, 25 Apr 2025 15:39:01 +0800
Subject: [PATCH 2/7] fix: ensure typed exports are marked as used (#2746)
#2717
---
packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts | 4 ++--
.../svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts | 2 +-
.../expectedv2.ts | 2 +-
.../ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
index 2703f6a80..c7fcb2879 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
@@ -791,11 +791,11 @@ export class ExportedNames {
private createReturnElements(
names: Array<[string, ExportedName]>,
dontAddTypeDef: boolean,
- omitTyped = false
+ onlyTyped = false
): string[] {
return names
.map(([key, value]) => {
- if (omitTyped && value.type) return;
+ if (onlyTyped && !value.type) return;
// Important to not use shorthand props for rename functionality
return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${
value.identifierText || key
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts
index d05d48969..23428b25c 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list-runes.v5/expectedv2.ts
@@ -20,7 +20,7 @@
;
async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});}
};
-return { props: {} as Record, exports: {Foo: Foo,bar: bar,RenamedFoo: RenameFoo,renamedbar: renamebar} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+return { props: {} as Record, exports: {name1: name1,name2: name2,name3: name3,name4: name4,renamed1: rename1,renamed2: rename2,baz: baz,renamedbaz: renamebaz} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
const Input__SvelteComponent_ = __sveltets_2_fn_component($$render());
type Input__SvelteComponent_ = ReturnType;
export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts
index c788dc05e..b2051ddd0 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune-unchanged.v5/expectedv2.ts
@@ -6,7 +6,7 @@
let { form, data }:/*Ωignore_startΩ*/$$ComponentProps/*Ωignore_endΩ*/ = $props();
;
async () => {};
-return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+return { props: {} as any as $$ComponentProps, exports: {snapshot: snapshot} as any as { snapshot: any }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
const Page__SvelteComponent_ = __sveltets_2_fn_component($$render());
type Page__SvelteComponent_ = ReturnType;
export default Page__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts
index 4a33a614f..ce2a45ef9 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-sveltekit-autotypes-$props-rune.v5/expectedv2.ts
@@ -5,7 +5,7 @@
let { form, data }: $$ComponentProps = $props();
;
async () => {};
-return { props: {} as any as $$ComponentProps, exports: {snapshot: snapshot} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
+return { props: {} as any as $$ComponentProps, exports: {} as any as { snapshot: typeof snapshot }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
const Page__SvelteComponent_ = __sveltets_2_fn_component($$render());
type Page__SvelteComponent_ = ReturnType;
export default Page__SvelteComponent_;
\ No newline at end of file
From 5366e7899cb44d73c930d32ffd7d9c29273507ca Mon Sep 17 00:00:00 2001
From: datstarkey <35140564+datstarkey@users.noreply.github.com>
Date: Fri, 2 May 2025 15:19:39 +0100
Subject: [PATCH 3/7] feat: support "add missing imports on save" (#2744)
Fixes #2616
Adds the source action to addMissingImports, reusing the same quick fix action as "import all missing"
---
.../features/CodeActionsProvider.ts | 60 +++++++++++--
packages/language-server/src/server.ts | 6 +-
.../features/CodeActionsProvider.test.ts | 87 ++++++++++++++++++-
...odeaction-custom-fix-all-component5.svelte | 7 ++
...odeaction-custom-fix-all-component6.svelte | 10 +++
5 files changed, 161 insertions(+), 9 deletions(-)
create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte
create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte
diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
index 581a46d8d..6f6bd8970 100644
--- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
@@ -1,3 +1,4 @@
+import { internalHelpers } from 'svelte2tsx';
import ts from 'typescript';
import {
CancellationToken,
@@ -24,6 +25,7 @@ import {
} from '../../../lib/documents';
import { LSConfigManager } from '../../../ls-config';
import {
+ createGetCanonicalFileName,
flatten,
getIndent,
isNotNullOrUndefined,
@@ -37,6 +39,7 @@ import {
import { CodeActionsProvider } from '../../interfaces';
import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
+import { LanguageServiceContainer } from '../service';
import {
changeSvelteComponentName,
convertRange,
@@ -44,6 +47,7 @@ import {
toGeneratedSvelteComponentName
} from '../utils';
import { CompletionsProviderImpl } from './CompletionProvider';
+import { DiagnosticCode } from './DiagnosticsProvider';
import {
findClosestContainingNode,
FormatCodeBasis,
@@ -53,15 +57,12 @@ import {
isTextSpanInGeneratedCode,
SnapshotMap
} from './utils';
-import { DiagnosticCode } from './DiagnosticsProvider';
-import { createGetCanonicalFileName } from '../../../utils';
-import { LanguageServiceContainer } from '../service';
-import { internalHelpers } from 'svelte2tsx';
/**
* TODO change this to protocol constant if it's part of the protocol
*/
export const SORT_IMPORT_CODE_ACTION_KIND = 'source.sortImports';
+export const ADD_MISSING_IMPORTS_CODE_ACTION_KIND = 'source.addMissingImports';
interface RefactorArgs {
type: 'refactor';
@@ -121,6 +122,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
);
}
+ if (context.only?.[0] === ADD_MISSING_IMPORTS_CODE_ACTION_KIND) {
+ return await this.addMissingImports(document, cancellationToken);
+ }
+
// for source action command (all source.xxx)
// vscode would show different source code action kinds to choose from
if (context.only?.[0] === CodeActionKind.Source) {
@@ -130,7 +135,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
document,
cancellationToken,
/**skipDestructiveCodeActions */ true
- ))
+ )),
+ ...(await this.addMissingImports(document, cancellationToken))
];
}
@@ -1553,4 +1559,48 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
private async getLSAndTSDoc(document: Document) {
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
}
+
+ private async addMissingImports(
+ document: Document,
+ cancellationToken?: CancellationToken
+ ): Promise {
+ // Re-introduce LS/TSDoc resolution and diagnostic check
+ const { lang, tsDoc } = await this.getLSAndTSDoc(document);
+ if (cancellationToken?.isCancellationRequested) {
+ return [];
+ }
+
+ // Check if there are any relevant "cannot find name" diagnostics
+ const diagnostics = lang.getSemanticDiagnostics(tsDoc.filePath);
+ const hasMissingImports = diagnostics.some(
+ (diag) =>
+ (diag.code === DiagnosticCode.CANNOT_FIND_NAME ||
+ diag.code === DiagnosticCode.CANNOT_FIND_NAME_X_DID_YOU_MEAN_Y) &&
+ // Ensure the diagnostic is not in generated code
+ !isTextSpanInGeneratedCode(tsDoc.getFullText(), {
+ start: diag.start ?? 0,
+ length: diag.length ?? 0
+ })
+ );
+
+ // Only return the action if there are potential imports to add
+ if (!hasMissingImports) {
+ return [];
+ }
+
+ // If imports might be needed, create the deferred action
+ const codeAction = CodeAction.create(
+ FIX_IMPORT_FIX_DESCRIPTION,
+ ADD_MISSING_IMPORTS_CODE_ACTION_KIND
+ );
+
+ const data: QuickFixAllResolveInfo = {
+ uri: document.uri,
+ fixName: FIX_IMPORT_FIX_NAME,
+ fixId: FIX_IMPORT_FIX_ID
+ };
+ codeAction.data = data;
+
+ return [codeAction];
+ }
}
diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts
index f1aa52ca0..af5f92a60 100644
--- a/packages/language-server/src/server.ts
+++ b/packages/language-server/src/server.ts
@@ -45,7 +45,10 @@ import { debounceThrottle, isNotNullOrUndefined, normalizeUri, urlToPath } from
import { FallbackWatcher } from './lib/FallbackWatcher';
import { configLoader } from './lib/documents/configLoader';
import { setIsTrusted } from './importPackage';
-import { SORT_IMPORT_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider';
+import {
+ SORT_IMPORT_CODE_ACTION_KIND,
+ ADD_MISSING_IMPORTS_CODE_ACTION_KIND
+} from './plugins/typescript/features/CodeActionsProvider';
import { createLanguageServices } from './plugins/css/service';
import { FileSystemProvider } from './plugins/css/FileSystemProvider';
@@ -270,6 +273,7 @@ export function startServer(options?: LSOptions) {
CodeActionKind.QuickFix,
CodeActionKind.SourceOrganizeImports,
SORT_IMPORT_CODE_ACTION_KIND,
+ ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
...(clientSupportApplyEditCommand ? [CodeActionKind.Refactor] : [])
].filter(
clientSupportedCodeActionKinds &&
diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
index e622ae1c2..a07288542 100644
--- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
@@ -1,5 +1,7 @@
import * as assert from 'assert';
import * as path from 'path';
+import { VERSION } from 'svelte/compiler';
+import { internalHelpers } from 'svelte2tsx';
import ts from 'typescript';
import {
CancellationTokenSource,
@@ -12,17 +14,16 @@ import {
import { Document, DocumentManager } from '../../../../src/lib/documents';
import { LSConfigManager } from '../../../../src/ls-config';
import {
+ ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
CodeActionsProviderImpl,
SORT_IMPORT_CODE_ACTION_KIND
} from '../../../../src/plugins/typescript/features/CodeActionsProvider';
import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider';
+import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider';
import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver';
import { __resetCache } from '../../../../src/plugins/typescript/service';
import { pathToUrl } from '../../../../src/utils';
import { recursiveServiceWarmup } from '../test-utils';
-import { DiagnosticCode } from '../../../../src/plugins/typescript/features/DiagnosticsProvider';
-import { VERSION } from 'svelte/compiler';
-import { internalHelpers } from 'svelte2tsx';
const testDir = path.join(__dirname, '..');
const indent = ' '.repeat(4);
@@ -2229,4 +2230,84 @@ describe('CodeActionsProvider', function () {
after(() => {
__resetCache();
});
+
+ it('provides source action for adding all missing imports', async () => {
+ const { provider, document } = setup('codeaction-custom-fix-all-component5.svelte');
+
+ const range = Range.create(Position.create(4, 1), Position.create(4, 15));
+
+ // Request the specific source action
+ const codeActions = await provider.getCodeActions(document, range, {
+ diagnostics: [], // Diagnostics might not be needed here if we only want the source action by kind
+ only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND]
+ });
+
+ assert.ok(codeActions.length > 0, 'No code actions found');
+
+ // Find the action by its kind
+ const addImportsAction = codeActions.find((action) => action.data);
+
+ // Ensure the action was found and has data (as it's now deferred)
+ assert.ok(addImportsAction, 'Add missing imports action should be found');
+ assert.ok(
+ addImportsAction.data,
+ 'Add missing imports action should have data for resolution'
+ );
+
+ // Resolve the action to get the edits
+ const resolvedAction = await provider.resolveCodeAction(document, addImportsAction);
+
+ // Assert the edits on the resolved action
+ assert.ok(resolvedAction.edit, 'Resolved action should have an edit');
+ (resolvedAction.edit?.documentChanges?.[0])?.edits.forEach(
+ (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+ );
+
+ assert.deepStrictEqual(resolvedAction.edit, {
+ documentChanges: [
+ {
+ edits: [
+ {
+ newText:
+ `\n${indent}import FixAllImported from \"./importing/FixAllImported.svelte\";\n` +
+ `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`,
+ range: {
+ start: {
+ character: 18,
+ line: 0
+ },
+ end: {
+ character: 18,
+ line: 0
+ }
+ }
+ }
+ ],
+ textDocument: {
+ uri: getUri('codeaction-custom-fix-all-component5.svelte'),
+ version: null
+ }
+ }
+ ]
+ });
+
+ // Optional: Verify the kind and title remain correct on the resolved action
+ assert.strictEqual(resolvedAction.kind, ADD_MISSING_IMPORTS_CODE_ACTION_KIND);
+ assert.strictEqual(resolvedAction.title, 'Add all missing imports');
+ });
+
+ it('provides source action for adding all missing imports only when imports are missing', async () => {
+ const { provider, document } = setup('codeaction-custom-fix-all-component6.svelte');
+
+ const codeActions = await provider.getCodeActions(
+ document,
+ Range.create(Position.create(1, 4), Position.create(1, 5)),
+ {
+ diagnostics: [], // No diagnostics = no missing imports
+ only: [ADD_MISSING_IMPORTS_CODE_ACTION_KIND]
+ }
+ );
+
+ assert.deepStrictEqual(codeActions, []);
+ });
});
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte
new file mode 100644
index 000000000..15f6d8fd7
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component5.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte
new file mode 100644
index 000000000..6c32ebc5e
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component6.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
From b887cbe2fc1882dbbbe8bd9bbd12141443bf2446 Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Fri, 2 May 2025 22:20:11 +0800
Subject: [PATCH 4/7] chore: bump vscode-html/css-language-service (#2752)
* chore: bump vscode-html/css-language-service
* update test
---
packages/language-server/package.json | 4 +-
.../test/plugins/css/CSSPlugin.test.ts | 39 +++++++++++++++++--
.../test/plugins/html/HTMLPlugin.test.ts | 5 ++-
pnpm-lock.yaml | 34 ++++++++--------
4 files changed, 59 insertions(+), 23 deletions(-)
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index 7933acf96..eafda89b6 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -64,8 +64,8 @@
"svelte2tsx": "workspace:~",
"typescript": "^5.8.2",
"typescript-auto-import-cache": "^0.3.5",
- "vscode-css-languageservice": "~6.3.2",
- "vscode-html-languageservice": "~5.3.2",
+ "vscode-css-languageservice": "~6.3.5",
+ "vscode-html-languageservice": "~5.4.0",
"vscode-languageserver": "9.0.1",
"vscode-languageserver-protocol": "3.17.5",
"vscode-languageserver-types": "3.17.5",
diff --git a/packages/language-server/test/plugins/css/CSSPlugin.test.ts b/packages/language-server/test/plugins/css/CSSPlugin.test.ts
index 5e9c4bf32..9a82c53ff 100644
--- a/packages/language-server/test/plugins/css/CSSPlugin.test.ts
+++ b/packages/language-server/test/plugins/css/CSSPlugin.test.ts
@@ -72,8 +72,8 @@ describe('CSS Plugin', () => {
kind: 'markdown',
value:
"Specifies the height of the content area, padding area or border area \\(depending on 'box\\-sizing'\\) of certain boxes\\.\n\n" +
- '(Edge 12, Firefox 1, Safari 1, Chrome 1, IE 4, Opera 7)\n\n' +
- 'Syntax: auto | <length> | <percentage> | min\\-content | max\\-content | fit\\-content | fit\\-content\\(<length\\-percentage>\\)\n\n' +
+ ' _Widely available across major browsers (Baseline since 2015)_\n\n' +
+ 'Syntax: auto | <length\\-percentage \\[0,∞\\]> | min\\-content | max\\-content | fit\\-content | fit\\-content\\(<length\\-percentage \\[0,∞\\]>\\) | <calc\\-size\\(\\)> | <anchor\\-size\\(\\)>\n\n' +
'[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/height)'
},
range: Range.create(0, 12, 0, 24)
@@ -105,7 +105,8 @@ describe('CSS Plugin', () => {
documentation: {
kind: 'markdown',
value:
- 'Defines character set of the document\\.\n\n(Edge 12, Firefox 1, Safari 4, Chrome 2, IE 5, Opera 9)\n\n' +
+ 'Defines character set of the document\\.\n\n' +
+ ' _Widely available across major browsers (Baseline since 2015)_\n\n' +
'[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/@charset)'
},
textEdit: TextEdit.insert(Position.create(0, 7), '@charset'),
@@ -344,6 +345,38 @@ describe('CSS Plugin', () => {
},
newText: 'hwb(240 0% -25400%)'
}
+ },
+ {
+ label: 'lab(3880.51% 6388.69 -8701.22)',
+ textEdit: {
+ newText: 'lab(3880.51% 6388.69 -8701.22)',
+ range: {
+ end: {
+ character: 21,
+ line: 0
+ },
+ start: {
+ character: 17,
+ line: 0
+ }
+ }
+ }
+ },
+ {
+ label: 'lch(3880.51% 10794.75 306.29)',
+ textEdit: {
+ newText: 'lch(3880.51% 10794.75 306.29)',
+ range: {
+ end: {
+ character: 21,
+ line: 0
+ },
+ start: {
+ character: 17,
+ line: 0
+ }
+ }
+ }
}
]);
});
diff --git a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts
index 4a45b6310..eff038a11 100644
--- a/packages/language-server/test/plugins/html/HTMLPlugin.test.ts
+++ b/packages/language-server/test/plugins/html/HTMLPlugin.test.ts
@@ -35,7 +35,10 @@ describe('HTML Plugin', () => {
assert.deepStrictEqual(plugin.doHover(document, Position.create(0, 2)), {
contents: {
kind: 'markdown',
- value: 'The h1 element represents a section heading.\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Element/Heading_Elements)'
+ value:
+ 'The h1 element represents a section heading.\n\n' +
+ ' _Widely available across major browsers (Baseline since 2015)_\n\n' +
+ '[MDN Reference](https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/Heading_Elements)'
},
range: Range.create(0, 1, 0, 3)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 905264a94..26e32643a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -64,11 +64,11 @@ importers:
specifier: ^0.3.5
version: 0.3.5
vscode-css-languageservice:
- specifier: ~6.3.2
- version: 6.3.2
+ specifier: ~6.3.5
+ version: 6.3.5
vscode-html-languageservice:
- specifier: ~5.3.2
- version: 5.3.2
+ specifier: ~5.4.0
+ version: 5.4.0
vscode-languageserver:
specifier: 9.0.1
version: 9.0.1
@@ -80,7 +80,7 @@ importers:
version: 3.17.5
vscode-uri:
specifier: ~3.0.0
- version: 3.0.8
+ version: 3.1.0
devDependencies:
'@types/estree':
specifier: ^0.0.42
@@ -181,7 +181,7 @@ importers:
version: 3.17.2
vscode-uri:
specifier: ~3.0.0
- version: 3.0.8
+ version: 3.1.0
packages/svelte-vscode:
dependencies:
@@ -1302,11 +1302,11 @@ packages:
vfile-message@3.1.4:
resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
- vscode-css-languageservice@6.3.2:
- resolution: {integrity: sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg==}
+ vscode-css-languageservice@6.3.5:
+ resolution: {integrity: sha512-ehEIMXYPYEz/5Svi2raL9OKLpBt5dSAdoCFoLpo0TVFKrVpDemyuQwS3c3D552z/qQCg3pMp8oOLMObY6M3ajQ==}
- vscode-html-languageservice@5.3.2:
- resolution: {integrity: sha512-3MgFQqVG+iQVNG7QI/slaoL7lJpne0nssX082kjUF1yn/YJa8BWCLeCJjM0YpTlp8A7JT1+J22mk4qSPx3NjSQ==}
+ vscode-html-languageservice@5.4.0:
+ resolution: {integrity: sha512-9/cbc90BSYCghmHI7/VbWettHZdC7WYpz2g5gBK6UDUI1MkZbM773Q12uAYJx9jzAiNHPpyo6KzcwmcnugncAQ==}
vscode-jsonrpc@8.0.2:
resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==}
@@ -1359,8 +1359,8 @@ packages:
vscode-uri@2.1.2:
resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==}
- vscode-uri@3.0.8:
- resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -2372,19 +2372,19 @@ snapshots:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.3
- vscode-css-languageservice@6.3.2:
+ vscode-css-languageservice@6.3.5:
dependencies:
'@vscode/l10n': 0.0.18
vscode-languageserver-textdocument: 1.0.12
vscode-languageserver-types: 3.17.5
- vscode-uri: 3.0.8
+ vscode-uri: 3.1.0
- vscode-html-languageservice@5.3.2:
+ vscode-html-languageservice@5.4.0:
dependencies:
'@vscode/l10n': 0.0.18
vscode-languageserver-textdocument: 1.0.12
vscode-languageserver-types: 3.17.5
- vscode-uri: 3.0.8
+ vscode-uri: 3.1.0
vscode-jsonrpc@8.0.2: {}
@@ -2437,7 +2437,7 @@ snapshots:
vscode-uri@2.1.2: {}
- vscode-uri@3.0.8: {}
+ vscode-uri@3.1.0: {}
which@2.0.2:
dependencies:
From 0c341bd264610f0becf53e29158444890903bd25 Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Fri, 2 May 2025 22:24:39 +0800
Subject: [PATCH 5/7] fix: Move hoisted snippet to right after import (#2753)
#2653
---
packages/svelte2tsx/src/svelte2tsx/index.ts | 23 +++++++++++++------
.../nodes/handleImportDeclaration.ts | 4 ++--
.../svelte2tsx/src/svelte2tsx/utils/tsAst.ts | 4 ++++
.../snippet-module-hoist-3.v5/expectedv2.ts | 7 +++---
.../snippet-module-hoist-5.v5/expectedv2.ts | 12 ++++++++++
.../snippet-module-hoist-5.v5/input.svelte | 5 ++++
.../snippet-module-hoist-6.v5/expectedv2.ts | 12 ++++++++++
.../snippet-module-hoist-6.v5/input.svelte | 5 ++++
8 files changed, 60 insertions(+), 12 deletions(-)
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts
create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index a3b45221f..4b2ad2913 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -10,6 +10,7 @@ import { processInstanceScriptContent } from './processInstanceScriptContent';
import { createModuleAst, ModuleAst, processModuleScriptTag } from './processModuleScriptTag';
import path from 'path';
import { parse, VERSION } from 'svelte/compiler';
+import { getTopLevelImports } from './utils/tsAst';
function processSvelteTemplate(
str: MagicString,
@@ -170,6 +171,20 @@ export function svelte2tsx(
}
if (moduleScriptTag || scriptTag) {
+ let snippetHoistTargetForModule = 0;
+ if (rootSnippets.length) {
+ if (scriptTag) {
+ snippetHoistTargetForModule = scriptTag.start + 1; // +1 because imports are also moved at that position, and we want to move interfaces after imports
+ } else {
+ const imports = getTopLevelImports(moduleAst.tsAst);
+ const lastImport = imports[imports.length - 1];
+ snippetHoistTargetForModule = lastImport
+ ? lastImport.end + moduleAst.astOffset
+ : moduleAst.astOffset;
+ str.appendLeft(snippetHoistTargetForModule, '\n');
+ }
+ }
+
for (const [start, end, globals] of rootSnippets) {
const hoist_to_module =
moduleScriptTag &&
@@ -179,13 +194,7 @@ export function svelte2tsx(
));
if (hoist_to_module) {
- str.move(
- start,
- end,
- scriptTag
- ? scriptTag.start + 1 // +1 because imports are also moved at that position, and we want to move interfaces after imports
- : instanceScriptTarget
- );
+ str.move(start, end, snippetHoistTargetForModule);
} else if (scriptTag) {
str.move(start, end, renderFunctionStart);
}
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
index 4f022260b..9daeb78e5 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
@@ -1,6 +1,6 @@
import MagicString from 'magic-string';
import ts from 'typescript';
-import { moveNode } from '../utils/tsAst';
+import { getTopLevelImports, moveNode } from '../utils/tsAst';
/**
* move imports to top of script so they appear outside our render function
@@ -25,7 +25,7 @@ export function handleFirstInstanceImport(
hasModuleScript: boolean,
str: MagicString
) {
- const imports = tsAst.statements.filter(ts.isImportDeclaration).sort((a, b) => a.end - b.end);
+ const imports = getTopLevelImports(tsAst);
const firstImport = imports[0];
if (!firstImport) {
return;
diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
index a189c75b5..d8c11ac64 100644
--- a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
@@ -273,3 +273,7 @@ function isNewGroup(sourceFile: ts.SourceFile, topLevelImportDecl: ts.Node, scan
return false;
}
+
+export function getTopLevelImports(sourceFile: ts.SourceFile): ts.ImportDeclaration[] {
+ return sourceFile.statements.filter(ts.isImportDeclaration).sort((a, b) => a.end - b.end);
+}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts
index 67a5d3f9d..c540fe409 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-3.v5/expectedv2.ts
@@ -1,11 +1,12 @@
///
;
- let foo = true;
-; const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
+ const hoistable1/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
{ svelteHTML.createElement("div", {}); }
};return __sveltets_2_any(0)}; const hoistable2/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
{ svelteHTML.createElement("div", {});foo; }
-};return __sveltets_2_any(0)};;function $$render() {
+};return __sveltets_2_any(0)};
+ let foo = true;
+;;function $$render() {
async () => {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts
new file mode 100644
index 000000000..1063eae01
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/expectedv2.ts
@@ -0,0 +1,12 @@
+///
+;
+ import {} from 'svelte'
+ const foo/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {};return __sveltets_2_any(0)};
+;;function $$render() {
+async () => {
+
+};
+return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event($$render())));
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte
new file mode 100644
index 000000000..6321ca3bf
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-5.v5/input.svelte
@@ -0,0 +1,5 @@
+
+
+{#snippet foo()}{/snippet}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts
new file mode 100644
index 000000000..39bb66302
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/expectedv2.ts
@@ -0,0 +1,12 @@
+///
+;
+ const _foo/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {};return __sveltets_2_any(0)};
+ export const foo = _foo;
+;;function $$render() {
+async () => {
+
+};
+return { props: /** @type {Record} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
+const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event($$render())));
+/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType;
+/*Ωignore_endΩ*/export default Input__SvelteComponent_;
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte
new file mode 100644
index 000000000..8a2e6f5ed
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-6.v5/input.svelte
@@ -0,0 +1,5 @@
+
+
+{#snippet _foo()}{/snippet}
\ No newline at end of file
From 0c4e103b7e4212afd10dfc36766a0f8adb28be35 Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Fri, 2 May 2025 22:29:54 +0800
Subject: [PATCH 6/7] fix: prevent error with unclosed tag followed by LF or
end of file (#2750)
#2742
The reason that it happens to LF but not CRLF is that CRLF has two characters, so it doesn't touch the range of the start tag name, and therefore won't trigger the magic string error. This should also improve auto-import performance a bit because TypeScript only needs to compute auto-imports for Button and not a global auto-import.
---
.../features/CodeActionsProvider.ts | 2 +-
.../typescript/features/CompletionProvider.ts | 13 ++++++++++--
.../src/plugins/typescript/features/utils.ts | 4 ++--
.../features/CompletionProvider.test.ts | 1 +
.../src/htmlxtojsx_v2/nodes/Element.ts | 19 +++++++++++++++---
.../htmlxtojsx_v2/nodes/InlineComponent.ts | 17 +++++++++++++---
.../expected.error.json | 20 +++++++++++++++++++
.../expectedv2.js | 5 +++++
.../input.svelte | 9 +++++++++
.../expected.error.json | 20 +++++++++++++++++++
.../expectedv2.js | 5 +++++
.../input.svelte | 9 +++++++++
12 files changed, 113 insertions(+), 11 deletions(-)
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/expected.error.json
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/expectedv2.js
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-component-no-attr.v5/input.svelte
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-element-no-attr.v5/expected.error.json
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-element-no-attr.v5/expectedv2.js
create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-unclosed-element-no-attr.v5/input.svelte
diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
index 6f6bd8970..244ae4e12 100644
--- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
@@ -383,7 +383,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
if (editForThisFile?.edits.length) {
const [first] = editForThisFile.edits;
first.newText =
- getNewScriptStartTag(this.configManager.getConfig()) +
+ getNewScriptStartTag(this.configManager.getConfig(), formatCodeBasis.newLine) +
formatCodeBasis.baseIndent +
first.newText.trimStart();
diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
index 48b6db53f..bc1764d87 100644
--- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
@@ -595,7 +595,8 @@ export class CompletionsProviderImpl implements CompletionsProvider ({
label: name,
kind: CompletionItemKind.Property,
- textEdit: TextEdit.replace(this.cloneRange(replacementRange), name)
+ textEdit: TextEdit.replace(this.cloneRange(replacementRange), name),
+ commitCharacters: []
}));
}
@@ -1131,9 +1132,17 @@ export class CompletionsProviderImpl implements CompletionsProvider${newLine}`
+ `${getNewScriptStartTag(config, newLine)}${newText}${newLine}`
);
}
diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts
index a8b0229e3..954ef03bc 100644
--- a/packages/language-server/src/plugins/typescript/features/utils.ts
+++ b/packages/language-server/src/plugins/typescript/features/utils.ts
@@ -429,10 +429,10 @@ export function findChildOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | u
}
}
-export function getNewScriptStartTag(lsConfig: Readonly) {
+export function getNewScriptStartTag(lsConfig: Readonly, newLine: string) {
const lang = lsConfig.svelte.defaultScriptLanguage;
const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
- return `