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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy