Skip to content

Commit d5fbdfe

Browse files
authored
fix: better hoistability analysis (#2655)
Instead of collecting the types/values that are allowed, we collect the types/values that are _disallowed_ - this makes it possible to reference global values/types and still have them properly be declared as hoistable #2640
1 parent 931dd85 commit d5fbdfe

File tree

4 files changed

+74
-78
lines changed

4 files changed

+74
-78
lines changed

packages/svelte2tsx/src/svelte2tsx/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,13 @@ export function svelte2tsx(
166166
}
167167

168168
if (moduleScriptTag || scriptTag) {
169-
const allowed = exportedNames.hoistableInterfaces.getAllowedValues();
170169
for (const [start, end, globals] of rootSnippets) {
171170
const hoist_to_module =
172171
moduleScriptTag &&
173-
(globals.size === 0 || [...globals.keys()].every((id) => allowed.has(id)));
172+
(globals.size === 0 ||
173+
[...globals.keys()].every((id) =>
174+
exportedNames.hoistableInterfaces.isAllowedReference(id)
175+
));
174176

175177
if (hoist_to_module) {
176178
str.move(

packages/svelte2tsx/src/svelte2tsx/nodes/HoistableInterfaces.ts

Lines changed: 46 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import MagicString from 'magic-string';
55
* Collects all imports and module-level declarations to then find out which interfaces/types are hoistable.
66
*/
77
export class HoistableInterfaces {
8-
private import_value_set: Set<string> = new Set();
9-
private import_type_set: Set<string> = new Set();
8+
private module_types: Set<string> = new Set();
9+
private disallowed_types = new Set<string>();
10+
private disallowed_values = new Set<string>();
1011
private interface_map: Map<
1112
string,
1213
{ type_deps: Set<string>; value_deps: Set<string>; node: ts.Node }
@@ -18,6 +19,7 @@ export class HoistableInterfaces {
1819
value_deps: new Set<string>()
1920
};
2021

22+
/** should be called before analyzeInstanceScriptNode */
2123
analyzeModuleScriptNode(node: ts.Node) {
2224
// Handle Import Declarations
2325
if (ts.isImportDeclaration(node) && node.importClause) {
@@ -30,9 +32,7 @@ export class HoistableInterfaces {
3032
node.importClause.namedBindings.elements.forEach((element) => {
3133
const import_name = element.name.text;
3234
if (is_type_only || element.isTypeOnly) {
33-
this.import_type_set.add(import_name);
34-
} else {
35-
this.import_value_set.add(import_name);
35+
this.module_types.add(import_name);
3636
}
3737
});
3838
}
@@ -41,9 +41,7 @@ export class HoistableInterfaces {
4141
if (node.importClause.name) {
4242
const default_import = node.importClause.name.text;
4343
if (is_type_only) {
44-
this.import_type_set.add(default_import);
45-
} else {
46-
this.import_value_set.add(default_import);
44+
this.module_types.add(default_import);
4745
}
4846
}
4947

@@ -54,40 +52,17 @@ export class HoistableInterfaces {
5452
) {
5553
const namespace_import = node.importClause.namedBindings.name.text;
5654
if (is_type_only) {
57-
this.import_type_set.add(namespace_import);
58-
} else {
59-
this.import_value_set.add(namespace_import);
55+
this.module_types.add(namespace_import);
6056
}
6157
}
6258
}
6359

64-
// Handle top-level declarations
65-
if (ts.isVariableStatement(node)) {
66-
node.declarationList.declarations.forEach((declaration) => {
67-
if (ts.isIdentifier(declaration.name)) {
68-
this.import_value_set.add(declaration.name.text);
69-
}
70-
});
71-
}
72-
73-
if (ts.isFunctionDeclaration(node) && node.name) {
74-
this.import_value_set.add(node.name.text);
75-
}
76-
77-
if (ts.isClassDeclaration(node) && node.name) {
78-
this.import_value_set.add(node.name.text);
79-
}
80-
81-
if (ts.isEnumDeclaration(node)) {
82-
this.import_value_set.add(node.name.text);
83-
}
84-
8560
if (ts.isTypeAliasDeclaration(node)) {
86-
this.import_type_set.add(node.name.text);
61+
this.module_types.add(node.name.text);
8762
}
8863

8964
if (ts.isInterfaceDeclaration(node)) {
90-
this.import_type_set.add(node.name.text);
65+
this.module_types.add(node.name.text);
9166
}
9267
}
9368

@@ -103,9 +78,7 @@ export class HoistableInterfaces {
10378
node.importClause.namedBindings.elements.forEach((element) => {
10479
const import_name = element.name.text;
10580
if (is_type_only) {
106-
this.import_type_set.add(import_name);
107-
} else {
108-
this.import_value_set.add(import_name);
81+
this.module_types.add(import_name);
10982
}
11083
});
11184
}
@@ -114,9 +87,7 @@ export class HoistableInterfaces {
11487
if (node.importClause.name) {
11588
const default_import = node.importClause.name.text;
11689
if (is_type_only) {
117-
this.import_type_set.add(default_import);
118-
} else {
119-
this.import_value_set.add(default_import);
90+
this.module_types.add(default_import);
12091
}
12192
}
12293

@@ -127,9 +98,7 @@ export class HoistableInterfaces {
12798
) {
12899
const namespace_import = node.importClause.namedBindings.name.text;
129100
if (is_type_only) {
130-
this.import_type_set.add(namespace_import);
131-
} else {
132-
this.import_value_set.add(namespace_import);
101+
this.module_types.add(namespace_import);
133102
}
134103
}
135104
}
@@ -167,9 +136,9 @@ export class HoistableInterfaces {
167136
}
168137
});
169138

170-
if (this.import_type_set.has(interface_name)) {
171-
// shadowed; delete because we can't hoist
172-
this.import_type_set.delete(interface_name);
139+
if (this.module_types.has(interface_name)) {
140+
// shadowed; we can't hoist
141+
this.disallowed_types.add(interface_name);
173142
} else {
174143
this.interface_map.set(interface_name, {
175144
type_deps: type_dependencies,
@@ -193,9 +162,9 @@ export class HoistableInterfaces {
193162
generics
194163
);
195164

196-
if (this.import_type_set.has(alias_name)) {
197-
// shadowed; delete because we can't hoist
198-
this.import_type_set.delete(alias_name);
165+
if (this.module_types.has(alias_name)) {
166+
// shadowed; we can't hoist
167+
this.disallowed_types.add(alias_name);
199168
} else {
200169
this.interface_map.set(alias_name, {
201170
type_deps: type_dependencies,
@@ -209,29 +178,21 @@ export class HoistableInterfaces {
209178
if (ts.isVariableStatement(node)) {
210179
node.declarationList.declarations.forEach((declaration) => {
211180
if (ts.isIdentifier(declaration.name)) {
212-
this.import_value_set.delete(declaration.name.text);
181+
this.disallowed_values.add(declaration.name.text);
213182
}
214183
});
215184
}
216185

217186
if (ts.isFunctionDeclaration(node) && node.name) {
218-
this.import_value_set.delete(node.name.text);
187+
this.disallowed_values.add(node.name.text);
219188
}
220189

221190
if (ts.isClassDeclaration(node) && node.name) {
222-
this.import_value_set.delete(node.name.text);
191+
this.disallowed_values.add(node.name.text);
223192
}
224193

225194
if (ts.isEnumDeclaration(node)) {
226-
this.import_value_set.delete(node.name.text);
227-
}
228-
229-
if (ts.isTypeAliasDeclaration(node)) {
230-
this.import_type_set.delete(node.name.text);
231-
}
232-
233-
if (ts.isInterfaceDeclaration(node)) {
234-
this.import_type_set.delete(node.name.text);
195+
this.disallowed_values.add(node.name.text);
235196
}
236197
}
237198

@@ -281,13 +242,26 @@ export class HoistableInterfaces {
281242
continue;
282243
}
283244

284-
const can_hoist = [...deps.type_deps, ...deps.value_deps].every((dep) => {
285-
return (
286-
this.import_type_set.has(dep) ||
287-
this.import_value_set.has(dep) ||
288-
hoistable_interfaces.has(dep)
289-
);
290-
});
245+
let can_hoist = true;
246+
247+
for (const dep of deps.type_deps) {
248+
if (this.disallowed_types.has(dep)) {
249+
this.disallowed_types.add(interface_name);
250+
can_hoist = false;
251+
break;
252+
}
253+
if (this.interface_map.has(dep) && !hoistable_interfaces.has(dep)) {
254+
can_hoist = false;
255+
}
256+
}
257+
258+
for (const dep of deps.value_deps) {
259+
if (this.disallowed_values.has(dep)) {
260+
this.disallowed_types.add(interface_name);
261+
can_hoist = false;
262+
break;
263+
}
264+
}
291265

292266
if (can_hoist) {
293267
hoistable_interfaces.set(interface_name, deps.node);
@@ -301,11 +275,7 @@ export class HoistableInterfaces {
301275
...this.props_interface.type_deps,
302276
...this.props_interface.value_deps
303277
].every((dep) => {
304-
return (
305-
this.import_type_set.has(dep) ||
306-
this.import_value_set.has(dep) ||
307-
hoistable_interfaces.has(dep)
308-
);
278+
return !this.disallowed_types.has(dep) && !this.disallowed_values.has(dep);
309279
});
310280

311281
if (can_hoist) {
@@ -328,7 +298,7 @@ export class HoistableInterfaces {
328298
if (!this.props_interface.name) return;
329299

330300
for (const generic of generics) {
331-
this.import_type_set.delete(generic);
301+
this.disallowed_types.add(generic);
332302
}
333303

334304
const hoistable = this.determineHoistableInterfaces();
@@ -362,8 +332,8 @@ export class HoistableInterfaces {
362332
}
363333
}
364334

365-
getAllowedValues() {
366-
return this.import_value_set;
335+
isAllowedReference(reference: string) {
336+
return !this.disallowed_values.has(reference);
367337
}
368338

369339
/**

packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/expectedv2.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import { imported } from './x';
1818
{ svelteHTML.createElement("div", {});module; }
1919
};return __sveltets_2_any(0)}; const hoistable7/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType<import('svelte').Snippet>/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
2020
{ svelteHTML.createElement("div", {});imported; }
21+
};return __sveltets_2_any(0)}; const hoistable8/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType<import('svelte').Snippet>/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
22+
{ svelteHTML.createElement("div", {});global; }
23+
};return __sveltets_2_any(0)}; const hoistable9/*Ωignore_positionΩ*/ = (props: HTMLAttributes<HTMLDivElement>)/*Ωignore_startΩ*/: ReturnType<import('svelte').Snippet>/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => { };return __sveltets_2_any(0)}; const hoistable10/*Ωignore_positionΩ*/ = (foo)/*Ωignore_startΩ*/: ReturnType<import('svelte').Snippet>/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
24+
const bar = foo;
25+
bar;
2126
};return __sveltets_2_any(0)};function render() {
2227
const not_hoistable/*Ωignore_positionΩ*/ = ()/*Ωignore_startΩ*/: ReturnType<import('svelte').Snippet>/*Ωignore_endΩ*/ => { async ()/*Ωignore_positionΩ*/ => {
2328
{ svelteHTML.createElement("div", {});foo; }
@@ -40,6 +45,12 @@ async () => {
4045

4146

4247

48+
49+
50+
51+
52+
53+
4354

4455

4556

packages/svelte2tsx/test/svelte2tsx/samples/snippet-module-hoist-1.v5/input.svelte

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@
3535
<div>{imported}</div>
3636
{/snippet}
3737

38+
{#snippet hoistable8()}
39+
<div>{global}</div>
40+
{/snippet}
41+
42+
{#snippet hoistable9(props: HTMLAttributes<HTMLDivElement>)}
43+
Referencing global types
44+
{/snippet}
45+
46+
{#snippet hoistable10(foo)}
47+
{@const bar = foo}
48+
{bar}
49+
{/snippet}
50+
3851
{#snippet not_hoistable()}
3952
<div>{foo}</div>
4053
{/snippet}

0 commit comments

Comments
 (0)