Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: wrong scope in top level snippets #486

Merged
merged 4 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-impalas-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": patch
---

fix: wrong scope in top level snippets
13 changes: 9 additions & 4 deletions src/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class ScriptsSourceCode {
separate: string;
beforeSpaces: string;
render: string;
snippet: string;
generics: string;
} | null = null;

Expand All @@ -57,23 +58,25 @@ export class ScriptsSourceCode {
this._appendScriptLets.separate +
this._appendScriptLets.beforeSpaces +
this._appendScriptLets.render +
this._appendScriptLets.snippet +
this._appendScriptLets.generics
);
}

public getCurrentVirtualCodeInfo(): {
script: string;
render: string;
generics: string;
rootScope: string;
} {
if (this._appendScriptLets == null) {
return { script: this.raw, render: "", generics: "" };
return { script: this.raw, render: "", rootScope: "" };
}
return {
script: this.trimmedRaw + this._appendScriptLets.separate,
render:
this._appendScriptLets.beforeSpaces + this._appendScriptLets.render,
generics: this._appendScriptLets.generics,
rootScope:
this._appendScriptLets.snippet + this._appendScriptLets.generics,
};
}

Expand All @@ -86,13 +89,14 @@ export class ScriptsSourceCode {
this._appendScriptLets.separate.length +
this._appendScriptLets.beforeSpaces.length +
this._appendScriptLets.render.length +
this._appendScriptLets.snippet.length +
this._appendScriptLets.generics.length
);
}

public addLet(
letCode: string,
kind: "generics" | "render",
kind: "generics" | "snippet" | "render",
): { start: number; end: number } {
if (this._appendScriptLets == null) {
const currentLength = this.trimmedRaw.length;
Expand All @@ -102,6 +106,7 @@ export class ScriptsSourceCode {
separate: "\n;",
beforeSpaces: after,
render: "",
snippet: "",
generics: "",
};
}
Expand Down
58 changes: 40 additions & 18 deletions src/context/script-let.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ export class ScriptLetContext {

private readonly unique = new UniqueIdGenerator();

private currentScriptScopeKind: "render" | "snippet" = "render";

public constructor(ctx: Context) {
this.script = ctx.sourceCode.scripts;
this.ctx = ctx;
Expand All @@ -164,7 +166,7 @@ export class ScriptLetContext {
this.appendScript(
`(${part})${isTS ? `as (${typing})` : ""};`,
range[0] - 1,
"render",
this.currentScriptScopeKind,
(st, tokens, comments, result) => {
const exprSt = st as ESTree.ExpressionStatement;
const tsAs: TSAsExpression | null = isTS
Expand Down Expand Up @@ -220,7 +222,7 @@ export class ScriptLetContext {
this.appendScript(
`({${part}});`,
range[0] - 2,
"render",
this.currentScriptScopeKind,
(st, tokens, _comments, result) => {
const exprSt = st as ESTree.ExpressionStatement;
const objectExpression: ESTree.ObjectExpression =
Expand Down Expand Up @@ -259,7 +261,7 @@ export class ScriptLetContext {
this.appendScript(
`const ${part};`,
range[0] - 6,
"render",
this.currentScriptScopeKind,
(st, tokens, _comments, result) => {
const decl = st as ESTree.VariableDeclaration;
const node = decl.declarations[0];
Expand Down Expand Up @@ -393,7 +395,7 @@ export class ScriptLetContext {
const restore = this.appendScript(
`if(${part}){`,
range[0] - 3,
"render",
this.currentScriptScopeKind,
(st, tokens, _comments, result) => {
const ifSt = st as ESTree.IfStatement;
const node = ifSt.test;
Expand All @@ -417,7 +419,7 @@ export class ScriptLetContext {
ifSt.consequent = null as never;
},
);
this.pushScope(restore, "}");
this.pushScope(restore, "}", this.currentScriptScopeKind);
}

public nestEachBlock(
Expand Down Expand Up @@ -448,7 +450,7 @@ export class ScriptLetContext {
const restore = this.appendScript(
source,
exprRange[0] - exprOffset,
"render",
this.currentScriptScopeKind,
(st, tokens, comments, result) => {
const expSt = st as ESTree.ExpressionStatement;
const call = expSt.expression as ESTree.CallExpression;
Expand Down Expand Up @@ -525,21 +527,22 @@ export class ScriptLetContext {
expSt.expression = null as never;
},
);
this.pushScope(restore, "});");
this.pushScope(restore, "});", this.currentScriptScopeKind);
}

public nestSnippetBlock(
id: ESTree.Identifier,
closeParentIndex: number,
snippetBlock: SvelteSnippetBlock,
kind: "snippet" | "render",
callback: (id: ESTree.Identifier, params: ESTree.Pattern[]) => void,
): void {
const idRange = getNodeRange(id);
const part = this.ctx.code.slice(idRange[0], closeParentIndex + 1);
const restore = this.appendScript(
`function ${part}{`,
idRange[0] - 9,
"render",
kind,
(st, tokens, _comments, result) => {
const fnDecl = st as ESTree.FunctionDeclaration;
const idNode = fnDecl.id;
Expand All @@ -565,7 +568,7 @@ export class ScriptLetContext {
fnDecl.params = [];
},
);
this.pushScope(restore, "}");
this.pushScope(restore, "}", kind);
}

public nestBlock(
Expand All @@ -588,7 +591,7 @@ export class ScriptLetContext {
for (const preparationScript of generatedTypes.preparationScript) {
this.appendScriptWithoutOffset(
preparationScript,
"render",
this.currentScriptScopeKind,
(node, tokens, comments, result) => {
tokens.length = 0;
comments.length = 0;
Expand All @@ -608,7 +611,7 @@ export class ScriptLetContext {
const restore = this.appendScript(
`{`,
block.range[0],
"render",
this.currentScriptScopeKind,
(st, tokens, _comments, result) => {
const blockSt = st as ESTree.BlockStatement;

Expand All @@ -622,7 +625,7 @@ export class ScriptLetContext {
blockSt.body = null as never;
},
);
this.pushScope(restore, "}");
this.pushScope(restore, "}", this.currentScriptScopeKind);
} else {
const sortedParams = [...resolvedParams]
.map((d) => {
Expand Down Expand Up @@ -664,7 +667,7 @@ export class ScriptLetContext {
const restore = this.appendScript(
`(${source})=>{`,
maps[0].range[0] - 1,
"render",
this.currentScriptScopeKind,
(st, tokens, comments, result) => {
const exprSt = st as ESTree.ExpressionStatement;
const fn = exprSt.expression as ESTree.ArrowFunctionExpression;
Expand Down Expand Up @@ -723,7 +726,7 @@ export class ScriptLetContext {
exprSt.expression = null as never;
},
);
this.pushScope(restore, "};");
this.pushScope(restore, "};", this.currentScriptScopeKind);
}
}

Expand All @@ -738,7 +741,7 @@ export class ScriptLetContext {
private appendScript(
text: string,
offset: number,
kind: "generics" | "render",
kind: "generics" | "snippet" | "render",
callback: (
node: ESTree.Node,
tokens: Token[],
Expand Down Expand Up @@ -766,7 +769,7 @@ export class ScriptLetContext {

private appendScriptWithoutOffset(
text: string,
kind: "generics" | "render",
kind: "generics" | "snippet" | "render",
callback: (
node: ESTree.Node,
tokens: Token[],
Expand All @@ -788,9 +791,16 @@ export class ScriptLetContext {
return restoreCallback;
}

private pushScope(restoreCallback: RestoreCallback, closeToken: string) {
private pushScope(
restoreCallback: RestoreCallback,
closeToken: string,
kind: "snippet" | "render",
) {
const upper = this.currentScriptScopeKind;
this.currentScriptScopeKind = kind;
this.closeScopeCallbacks.push(() => {
this.script.addLet(closeToken, "render");
this.script.addLet(closeToken, kind);
this.currentScriptScopeKind = upper;
restoreCallback.end = this.script.getCurrentVirtualCodeLength();
});
}
Expand Down Expand Up @@ -825,7 +835,19 @@ export class ScriptLetContext {
// If we replace the `scope.block` at this time,
// the scope restore calculation will not work, so we will replace the `scope.block` later.
postprocessList.push(() => {
const beforeBlock = scope.block;
scope.block = node;

for (const variable of [
...scope.variables,
...(scope.upper?.variables ?? []),
]) {
for (const def of variable.defs) {
if (def.node === beforeBlock) {
def.node = node;
}
}
}
});

const scopes = nodeToScope.get(node);
Expand Down
19 changes: 9 additions & 10 deletions src/parser/analyze-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,16 +235,15 @@ export function analyzeSnippetsScope(
(parent.kind === "special" && parent.name.name === "svelte:component"))
) {
const scope = getScopeFromNode(scopeManager, snippet.id);
const variable = scope.upper
? scope.upper.set.get(snippet.id.name)
: null;
if (variable) {
// Add the virtual reference for reading.
const reference = addVirtualReference(snippet.id, variable, scope, {
read: true,
});
(reference as any).svelteSnippetReference = true;
}
const upperScope = scope.upper;
if (!upperScope) continue;
const variable = upperScope.set.get(snippet.id.name);
if (!variable) continue;
// Add the virtual reference for reading.
const reference = addVirtualReference(snippet.id, variable, upperScope, {
read: true,
});
(reference as any).svelteSnippetReference = true;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/parser/converts/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,10 +655,13 @@ export function convertSnippetBlock(
).end,
);

const scopeKind = parent.type === "Program" ? "snippet" : "render";

ctx.scriptLet.nestSnippetBlock(
node.expression,
closeParenIndex,
snippetBlock,
scopeKind,
(id, params) => {
snippetBlock.id = id;
snippetBlock.params = params;
Expand Down
24 changes: 19 additions & 5 deletions src/parser/converts/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function convertSvelteRoot(
...ctx.getConvertLocation({ start: 0, end: ctx.code.length }),
};
const body = ast.body;
const snippetChildren: Compiler.SnippetBlock[] = [];
const fragment = getFragmentFromRoot(svelteAst);
if (fragment) {
let children = getChildren(fragment);
Expand All @@ -63,11 +64,21 @@ export function convertSvelteRoot(
children.push(options);
}
}
body.push(...convertChildren({ nodes: children }, ast, ctx));
const nonSnippetChildren: typeof children = [];
for (const child of children) {
if (child.type === "SnippetBlock") {
snippetChildren.push(child);
} else {
nonSnippetChildren.push(child);
}
}

body.push(...convertChildren({ nodes: nonSnippetChildren }, ast, ctx));
}
let script: SvelteScriptElement | null = null;
const instance = getInstanceFromRoot(svelteAst);
if (instance) {
const script: SvelteScriptElement = {
script = {
type: "SvelteScriptElement",
name: null as any,
startTag: null as any,
Expand All @@ -77,15 +88,13 @@ export function convertSvelteRoot(
...ctx.getConvertLocation(instance),
};
extractAttributes(script, ctx);
if (ctx.parserOptions.svelteFeatures?.experimentalGenerics)
convertGenericsAttribute(script, ctx);
extractElementTags(script, ctx, {
buildNameNode: (openTokenRange) => {
ctx.addToken("HTMLIdentifier", openTokenRange);
const name: SvelteName = {
type: "SvelteName",
name: "script",
parent: script,
parent: script!,
...ctx.getConvertLocation(openTokenRange),
};
return name;
Expand Down Expand Up @@ -163,6 +172,9 @@ export function convertSvelteRoot(

body.push(style);
}
body.push(...convertChildren({ nodes: snippetChildren }, ast, ctx));
if (script && ctx.parserOptions.svelteFeatures?.experimentalGenerics)
convertGenericsAttribute(script, ctx);

// Set the scope of the Program node.
ctx.scriptLet.addProgramRestore(
Expand All @@ -188,6 +200,8 @@ export function convertSvelteRoot(
},
);

sortNodes(body);

return ast;
}

Expand Down
2 changes: 0 additions & 2 deletions src/parser/template.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type {} from "svelte"; // FIXME: Workaround to get type information for "svelte/compiler"

Check warning on line 1 in src/parser/template.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'fixme' comment: 'FIXME: Workaround to get type...'
import { parse } from "svelte/compiler";
import type * as Compiler from "svelte/compiler";
import type * as SvAST from "./svelte-ast-types";
import type { Context } from "../context";
import { convertSvelteRoot } from "./converts/index";
import { sortNodes } from "./sort";
import type { SvelteProgram } from "../ast";
import { ParseError } from "..";
import type { NormalizedParserOptions } from "./parser-options";
Expand All @@ -27,7 +26,6 @@
...(svelteVersion.gte(5) ? { modern: true } : {}),
}) as never as Compiler.Root | SvAST.AstLegacy;
const ast = convertSvelteRoot(svelteAst, ctx);
sortNodes(ast.body);

return {
ast,
Expand Down
Loading
Loading