Skip to content

Commit 79a4fb7

Browse files
authored
fix: wrong scope in top level snippets (#486)
1 parent 1b13acb commit 79a4fb7

28 files changed

+15697
-2141
lines changed

.changeset/hot-impalas-do.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": patch
3+
---
4+
5+
fix: wrong scope in top level snippets

src/context/index.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class ScriptsSourceCode {
3333
separate: string;
3434
beforeSpaces: string;
3535
render: string;
36+
snippet: string;
3637
generics: string;
3738
} | null = null;
3839

@@ -57,23 +58,25 @@ export class ScriptsSourceCode {
5758
this._appendScriptLets.separate +
5859
this._appendScriptLets.beforeSpaces +
5960
this._appendScriptLets.render +
61+
this._appendScriptLets.snippet +
6062
this._appendScriptLets.generics
6163
);
6264
}
6365

6466
public getCurrentVirtualCodeInfo(): {
6567
script: string;
6668
render: string;
67-
generics: string;
69+
rootScope: string;
6870
} {
6971
if (this._appendScriptLets == null) {
70-
return { script: this.raw, render: "", generics: "" };
72+
return { script: this.raw, render: "", rootScope: "" };
7173
}
7274
return {
7375
script: this.trimmedRaw + this._appendScriptLets.separate,
7476
render:
7577
this._appendScriptLets.beforeSpaces + this._appendScriptLets.render,
76-
generics: this._appendScriptLets.generics,
78+
rootScope:
79+
this._appendScriptLets.snippet + this._appendScriptLets.generics,
7780
};
7881
}
7982

@@ -86,13 +89,14 @@ export class ScriptsSourceCode {
8689
this._appendScriptLets.separate.length +
8790
this._appendScriptLets.beforeSpaces.length +
8891
this._appendScriptLets.render.length +
92+
this._appendScriptLets.snippet.length +
8993
this._appendScriptLets.generics.length
9094
);
9195
}
9296

9397
public addLet(
9498
letCode: string,
95-
kind: "generics" | "render",
99+
kind: "generics" | "snippet" | "render",
96100
): { start: number; end: number } {
97101
if (this._appendScriptLets == null) {
98102
const currentLength = this.trimmedRaw.length;
@@ -102,6 +106,7 @@ export class ScriptsSourceCode {
102106
separate: "\n;",
103107
beforeSpaces: after,
104108
render: "",
109+
snippet: "",
105110
generics: "",
106111
};
107112
}

src/context/script-let.ts

+40-18
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ export class ScriptLetContext {
138138

139139
private readonly unique = new UniqueIdGenerator();
140140

141+
private currentScriptScopeKind: "render" | "snippet" = "render";
142+
141143
public constructor(ctx: Context) {
142144
this.script = ctx.sourceCode.scripts;
143145
this.ctx = ctx;
@@ -164,7 +166,7 @@ export class ScriptLetContext {
164166
this.appendScript(
165167
`(${part})${isTS ? `as (${typing})` : ""};`,
166168
range[0] - 1,
167-
"render",
169+
this.currentScriptScopeKind,
168170
(st, tokens, comments, result) => {
169171
const exprSt = st as ESTree.ExpressionStatement;
170172
const tsAs: TSAsExpression | null = isTS
@@ -220,7 +222,7 @@ export class ScriptLetContext {
220222
this.appendScript(
221223
`({${part}});`,
222224
range[0] - 2,
223-
"render",
225+
this.currentScriptScopeKind,
224226
(st, tokens, _comments, result) => {
225227
const exprSt = st as ESTree.ExpressionStatement;
226228
const objectExpression: ESTree.ObjectExpression =
@@ -259,7 +261,7 @@ export class ScriptLetContext {
259261
this.appendScript(
260262
`const ${part};`,
261263
range[0] - 6,
262-
"render",
264+
this.currentScriptScopeKind,
263265
(st, tokens, _comments, result) => {
264266
const decl = st as ESTree.VariableDeclaration;
265267
const node = decl.declarations[0];
@@ -393,7 +395,7 @@ export class ScriptLetContext {
393395
const restore = this.appendScript(
394396
`if(${part}){`,
395397
range[0] - 3,
396-
"render",
398+
this.currentScriptScopeKind,
397399
(st, tokens, _comments, result) => {
398400
const ifSt = st as ESTree.IfStatement;
399401
const node = ifSt.test;
@@ -417,7 +419,7 @@ export class ScriptLetContext {
417419
ifSt.consequent = null as never;
418420
},
419421
);
420-
this.pushScope(restore, "}");
422+
this.pushScope(restore, "}", this.currentScriptScopeKind);
421423
}
422424

423425
public nestEachBlock(
@@ -448,7 +450,7 @@ export class ScriptLetContext {
448450
const restore = this.appendScript(
449451
source,
450452
exprRange[0] - exprOffset,
451-
"render",
453+
this.currentScriptScopeKind,
452454
(st, tokens, comments, result) => {
453455
const expSt = st as ESTree.ExpressionStatement;
454456
const call = expSt.expression as ESTree.CallExpression;
@@ -525,21 +527,22 @@ export class ScriptLetContext {
525527
expSt.expression = null as never;
526528
},
527529
);
528-
this.pushScope(restore, "});");
530+
this.pushScope(restore, "});", this.currentScriptScopeKind);
529531
}
530532

531533
public nestSnippetBlock(
532534
id: ESTree.Identifier,
533535
closeParentIndex: number,
534536
snippetBlock: SvelteSnippetBlock,
537+
kind: "snippet" | "render",
535538
callback: (id: ESTree.Identifier, params: ESTree.Pattern[]) => void,
536539
): void {
537540
const idRange = getNodeRange(id);
538541
const part = this.ctx.code.slice(idRange[0], closeParentIndex + 1);
539542
const restore = this.appendScript(
540543
`function ${part}{`,
541544
idRange[0] - 9,
542-
"render",
545+
kind,
543546
(st, tokens, _comments, result) => {
544547
const fnDecl = st as ESTree.FunctionDeclaration;
545548
const idNode = fnDecl.id;
@@ -565,7 +568,7 @@ export class ScriptLetContext {
565568
fnDecl.params = [];
566569
},
567570
);
568-
this.pushScope(restore, "}");
571+
this.pushScope(restore, "}", kind);
569572
}
570573

571574
public nestBlock(
@@ -588,7 +591,7 @@ export class ScriptLetContext {
588591
for (const preparationScript of generatedTypes.preparationScript) {
589592
this.appendScriptWithoutOffset(
590593
preparationScript,
591-
"render",
594+
this.currentScriptScopeKind,
592595
(node, tokens, comments, result) => {
593596
tokens.length = 0;
594597
comments.length = 0;
@@ -608,7 +611,7 @@ export class ScriptLetContext {
608611
const restore = this.appendScript(
609612
`{`,
610613
block.range[0],
611-
"render",
614+
this.currentScriptScopeKind,
612615
(st, tokens, _comments, result) => {
613616
const blockSt = st as ESTree.BlockStatement;
614617

@@ -622,7 +625,7 @@ export class ScriptLetContext {
622625
blockSt.body = null as never;
623626
},
624627
);
625-
this.pushScope(restore, "}");
628+
this.pushScope(restore, "}", this.currentScriptScopeKind);
626629
} else {
627630
const sortedParams = [...resolvedParams]
628631
.map((d) => {
@@ -664,7 +667,7 @@ export class ScriptLetContext {
664667
const restore = this.appendScript(
665668
`(${source})=>{`,
666669
maps[0].range[0] - 1,
667-
"render",
670+
this.currentScriptScopeKind,
668671
(st, tokens, comments, result) => {
669672
const exprSt = st as ESTree.ExpressionStatement;
670673
const fn = exprSt.expression as ESTree.ArrowFunctionExpression;
@@ -723,7 +726,7 @@ export class ScriptLetContext {
723726
exprSt.expression = null as never;
724727
},
725728
);
726-
this.pushScope(restore, "};");
729+
this.pushScope(restore, "};", this.currentScriptScopeKind);
727730
}
728731
}
729732

@@ -738,7 +741,7 @@ export class ScriptLetContext {
738741
private appendScript(
739742
text: string,
740743
offset: number,
741-
kind: "generics" | "render",
744+
kind: "generics" | "snippet" | "render",
742745
callback: (
743746
node: ESTree.Node,
744747
tokens: Token[],
@@ -766,7 +769,7 @@ export class ScriptLetContext {
766769

767770
private appendScriptWithoutOffset(
768771
text: string,
769-
kind: "generics" | "render",
772+
kind: "generics" | "snippet" | "render",
770773
callback: (
771774
node: ESTree.Node,
772775
tokens: Token[],
@@ -788,9 +791,16 @@ export class ScriptLetContext {
788791
return restoreCallback;
789792
}
790793

791-
private pushScope(restoreCallback: RestoreCallback, closeToken: string) {
794+
private pushScope(
795+
restoreCallback: RestoreCallback,
796+
closeToken: string,
797+
kind: "snippet" | "render",
798+
) {
799+
const upper = this.currentScriptScopeKind;
800+
this.currentScriptScopeKind = kind;
792801
this.closeScopeCallbacks.push(() => {
793-
this.script.addLet(closeToken, "render");
802+
this.script.addLet(closeToken, kind);
803+
this.currentScriptScopeKind = upper;
794804
restoreCallback.end = this.script.getCurrentVirtualCodeLength();
795805
});
796806
}
@@ -825,7 +835,19 @@ export class ScriptLetContext {
825835
// If we replace the `scope.block` at this time,
826836
// the scope restore calculation will not work, so we will replace the `scope.block` later.
827837
postprocessList.push(() => {
838+
const beforeBlock = scope.block;
828839
scope.block = node;
840+
841+
for (const variable of [
842+
...scope.variables,
843+
...(scope.upper?.variables ?? []),
844+
]) {
845+
for (const def of variable.defs) {
846+
if (def.node === beforeBlock) {
847+
def.node = node;
848+
}
849+
}
850+
}
829851
});
830852

831853
const scopes = nodeToScope.get(node);

src/parser/analyze-scope.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,15 @@ export function analyzeSnippetsScope(
235235
(parent.kind === "special" && parent.name.name === "svelte:component"))
236236
) {
237237
const scope = getScopeFromNode(scopeManager, snippet.id);
238-
const variable = scope.upper
239-
? scope.upper.set.get(snippet.id.name)
240-
: null;
241-
if (variable) {
242-
// Add the virtual reference for reading.
243-
const reference = addVirtualReference(snippet.id, variable, scope, {
244-
read: true,
245-
});
246-
(reference as any).svelteSnippetReference = true;
247-
}
238+
const upperScope = scope.upper;
239+
if (!upperScope) continue;
240+
const variable = upperScope.set.get(snippet.id.name);
241+
if (!variable) continue;
242+
// Add the virtual reference for reading.
243+
const reference = addVirtualReference(snippet.id, variable, upperScope, {
244+
read: true,
245+
});
246+
(reference as any).svelteSnippetReference = true;
248247
}
249248
}
250249
}

src/parser/converts/block.ts

+3
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,13 @@ export function convertSnippetBlock(
655655
).end,
656656
);
657657

658+
const scopeKind = parent.type === "Program" ? "snippet" : "render";
659+
658660
ctx.scriptLet.nestSnippetBlock(
659661
node.expression,
660662
closeParenIndex,
661663
snippetBlock,
664+
scopeKind,
662665
(id, params) => {
663666
snippetBlock.id = id;
664667
snippetBlock.params = params;

src/parser/converts/root.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export function convertSvelteRoot(
4545
...ctx.getConvertLocation({ start: 0, end: ctx.code.length }),
4646
};
4747
const body = ast.body;
48+
const snippetChildren: Compiler.SnippetBlock[] = [];
4849
const fragment = getFragmentFromRoot(svelteAst);
4950
if (fragment) {
5051
let children = getChildren(fragment);
@@ -63,11 +64,21 @@ export function convertSvelteRoot(
6364
children.push(options);
6465
}
6566
}
66-
body.push(...convertChildren({ nodes: children }, ast, ctx));
67+
const nonSnippetChildren: typeof children = [];
68+
for (const child of children) {
69+
if (child.type === "SnippetBlock") {
70+
snippetChildren.push(child);
71+
} else {
72+
nonSnippetChildren.push(child);
73+
}
74+
}
75+
76+
body.push(...convertChildren({ nodes: nonSnippetChildren }, ast, ctx));
6777
}
78+
let script: SvelteScriptElement | null = null;
6879
const instance = getInstanceFromRoot(svelteAst);
6980
if (instance) {
70-
const script: SvelteScriptElement = {
81+
script = {
7182
type: "SvelteScriptElement",
7283
name: null as any,
7384
startTag: null as any,
@@ -77,15 +88,13 @@ export function convertSvelteRoot(
7788
...ctx.getConvertLocation(instance),
7889
};
7990
extractAttributes(script, ctx);
80-
if (ctx.parserOptions.svelteFeatures?.experimentalGenerics)
81-
convertGenericsAttribute(script, ctx);
8291
extractElementTags(script, ctx, {
8392
buildNameNode: (openTokenRange) => {
8493
ctx.addToken("HTMLIdentifier", openTokenRange);
8594
const name: SvelteName = {
8695
type: "SvelteName",
8796
name: "script",
88-
parent: script,
97+
parent: script!,
8998
...ctx.getConvertLocation(openTokenRange),
9099
};
91100
return name;
@@ -163,6 +172,9 @@ export function convertSvelteRoot(
163172

164173
body.push(style);
165174
}
175+
body.push(...convertChildren({ nodes: snippetChildren }, ast, ctx));
176+
if (script && ctx.parserOptions.svelteFeatures?.experimentalGenerics)
177+
convertGenericsAttribute(script, ctx);
166178

167179
// Set the scope of the Program node.
168180
ctx.scriptLet.addProgramRestore(
@@ -188,6 +200,8 @@ export function convertSvelteRoot(
188200
},
189201
);
190202

203+
sortNodes(body);
204+
191205
return ast;
192206
}
193207

src/parser/template.ts

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type * as Compiler from "svelte/compiler";
44
import type * as SvAST from "./svelte-ast-types";
55
import type { Context } from "../context";
66
import { convertSvelteRoot } from "./converts/index";
7-
import { sortNodes } from "./sort";
87
import type { SvelteProgram } from "../ast";
98
import { ParseError } from "..";
109
import type { NormalizedParserOptions } from "./parser-options";
@@ -27,7 +26,6 @@ export function parseTemplate(
2726
...(svelteVersion.gte(5) ? { modern: true } : {}),
2827
}) as never as Compiler.Root | SvAST.AstLegacy;
2928
const ast = convertSvelteRoot(svelteAst, ctx);
30-
sortNodes(ast.body);
3129

3230
return {
3331
ast,

0 commit comments

Comments
 (0)