Skip to content

Commit e355d5c

Browse files
authored
fix: wrong variable scope in let directive (#271)
* fix: wrong variable scope in let directive * Create angry-pumpkins-ring.md * fix
1 parent 2274c23 commit e355d5c

File tree

14 files changed

+4676
-217
lines changed

14 files changed

+4676
-217
lines changed

.changeset/angry-pumpkins-ring.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
fix: wrong variable scope in let directive

src/context/let-directive-collection.ts

+12-40
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,13 @@
1-
import type { SvelteLetDirective, SvelteName, SvelteNode } from "../ast";
1+
import type { SvelteLetDirective, SvelteName } from "../ast";
22
import type * as ESTree from "estree";
3-
import type { ScriptLetCallback, ScriptLetCallbackOption } from "./script-let";
3+
import type { ScriptLetBlockParam, ScriptLetCallback } from "./script-let";
44

55
/** A class that collects pattern nodes for Let directives. */
66
export class LetDirectiveCollection {
7-
private readonly list: {
8-
pattern: ESTree.Pattern | SvelteName;
9-
directive: SvelteLetDirective;
10-
typing: string;
11-
callbacks: ScriptLetCallback<ESTree.Pattern>[];
12-
}[] = [];
7+
private readonly list: ScriptLetBlockParam[] = [];
138

14-
public isEmpty(): boolean {
15-
return this.list.length === 0;
16-
}
17-
18-
public getLetParams(): (ESTree.Pattern | SvelteName)[] {
19-
return this.list.map((d) => d.pattern);
20-
}
21-
22-
public getParents(): SvelteNode[] {
23-
return this.list.map((d) => d.directive);
24-
}
25-
26-
public getCallback(): (
27-
nodes: ESTree.Pattern[],
28-
options: ScriptLetCallbackOption
29-
) => void {
30-
return (nodes, options) => {
31-
for (let index = 0; index < nodes.length; index++) {
32-
const node = nodes[index];
33-
const { callbacks } = this.list[index];
34-
for (const callback of callbacks) {
35-
callback(node, options);
36-
}
37-
}
38-
};
39-
}
40-
41-
public getTypes(): string[] {
42-
return this.list.map((d) => d.typing);
9+
public getLetParams(): ScriptLetBlockParam[] {
10+
return this.list;
4311
}
4412

4513
public addPattern(
@@ -49,10 +17,14 @@ export class LetDirectiveCollection {
4917
...callbacks: ScriptLetCallback<ESTree.Pattern>[]
5018
): ScriptLetCallback<ESTree.Pattern>[] {
5119
this.list.push({
52-
pattern,
53-
directive,
20+
node: pattern,
21+
parent: directive,
5422
typing,
55-
callbacks,
23+
callback(node, options) {
24+
for (const callback of callbacks) {
25+
callback(node, options);
26+
}
27+
},
5628
});
5729
return callbacks;
5830
}

src/context/script-let.ts

+54-57
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ type ObjectShorthandProperty = ESTree.Property & {
113113
value: ESTree.Identifier;
114114
};
115115

116+
export type ScriptLetBlockParam = {
117+
node: ESTree.Pattern | SvelteName;
118+
parent: SvelteNode;
119+
typing: string;
120+
callback: (node: ESTree.Pattern, options: ScriptLetCallbackOption) => void;
121+
};
116122
/**
117123
* A class that handles script fragments.
118124
* The script fragment AST node remaps and connects to the original directive AST node.
@@ -408,62 +414,15 @@ export class ScriptLetContext {
408414
this.pushScope(restore, "});");
409415
}
410416

411-
public nestBlock(block: SvelteNode): void;
412-
413-
public nestBlock(
414-
block: SvelteNode,
415-
params: (ESTree.Pattern | SvelteName)[],
416-
parents: SvelteNode[],
417-
callback: (
418-
nodes: ESTree.Pattern[],
419-
options: ScriptLetCallbackOption
420-
) => void,
421-
typings:
422-
| string[]
423-
| ((helper: TypeGenHelper) => {
424-
typings: string[];
425-
preparationScript?: string[];
426-
})
427-
): void;
428-
429417
public nestBlock(
430418
block: SvelteNode,
431-
params?: (ESTree.Pattern | SvelteName)[],
432-
parents?: SvelteNode[],
433-
callback?: (
434-
nodes: ESTree.Pattern[],
435-
options: ScriptLetCallbackOption
436-
) => void,
437-
typings?:
438-
| string[]
439-
| ((helper: TypeGenHelper) => {
440-
typings: string[];
419+
params?:
420+
| ScriptLetBlockParam[]
421+
| ((helper: TypeGenHelper | null) => {
422+
param: ScriptLetBlockParam;
441423
preparationScript?: string[];
442424
})
443425
): void {
444-
let arrayTypings: string[] = [];
445-
if (typings && this.ctx.isTypeScript()) {
446-
if (Array.isArray(typings)) {
447-
arrayTypings = typings;
448-
} else {
449-
const generatedTypes = typings({
450-
generateUniqueId: (base) => this.generateUniqueId(base),
451-
});
452-
arrayTypings = generatedTypes.typings;
453-
if (generatedTypes.preparationScript) {
454-
for (const preparationScript of generatedTypes.preparationScript) {
455-
this.appendScriptWithoutOffset(
456-
preparationScript,
457-
(node, tokens, comments, result) => {
458-
tokens.length = 0;
459-
comments.length = 0;
460-
removeAllScopeAndVariableAndReference(node, result);
461-
}
462-
);
463-
}
464-
}
465-
}
466-
}
467426
if (!params) {
468427
const restore = this.appendScript(
469428
`{`,
@@ -483,7 +442,44 @@ export class ScriptLetContext {
483442
);
484443
this.pushScope(restore, "}");
485444
} else {
486-
const ranges = params.map(getNodeRange).sort(([a], [b]) => a - b);
445+
let resolvedParams;
446+
if (typeof params === "function") {
447+
if (this.ctx.isTypeScript()) {
448+
const generatedTypes = params({
449+
generateUniqueId: (base) => this.generateUniqueId(base),
450+
});
451+
resolvedParams = [generatedTypes.param];
452+
if (generatedTypes.preparationScript) {
453+
for (const preparationScript of generatedTypes.preparationScript) {
454+
this.appendScriptWithoutOffset(
455+
preparationScript,
456+
(node, tokens, comments, result) => {
457+
tokens.length = 0;
458+
comments.length = 0;
459+
removeAllScopeAndVariableAndReference(node, result);
460+
}
461+
);
462+
}
463+
}
464+
} else {
465+
const generatedTypes = params(null);
466+
resolvedParams = [generatedTypes.param];
467+
}
468+
} else {
469+
resolvedParams = params;
470+
}
471+
const sortedParams = [...resolvedParams]
472+
.map((d) => {
473+
return {
474+
...d,
475+
range: getNodeRange(d.node),
476+
};
477+
})
478+
.sort((a, b) => {
479+
const [pA] = a.range;
480+
const [pB] = b.range;
481+
return pA - pB;
482+
});
487483

488484
const maps: {
489485
index: number;
@@ -492,8 +488,9 @@ export class ScriptLetContext {
492488
}[] = [];
493489

494490
let source = "";
495-
for (let index = 0; index < ranges.length; index++) {
496-
const range = ranges[index];
491+
for (let index = 0; index < sortedParams.length; index++) {
492+
const param = sortedParams[index];
493+
const range = param.range;
497494
if (source) {
498495
source += ",";
499496
}
@@ -505,7 +502,7 @@ export class ScriptLetContext {
505502
range,
506503
});
507504
if (this.ctx.isTypeScript()) {
508-
source += `: (${arrayTypings[index]})`;
505+
source += `: (${param.typing})`;
509506
}
510507
}
511508
const restore = this.appendScript(
@@ -517,9 +514,9 @@ export class ScriptLetContext {
517514
const scope = result.getScope(fn.body);
518515

519516
// Process for nodes
520-
callback!(fn.params, result);
521517
for (let index = 0; index < fn.params.length; index++) {
522518
const p = fn.params[index];
519+
sortedParams[index].callback(p, result);
523520
if (this.ctx.isTypeScript()) {
524521
const typeAnnotation = (p as any).typeAnnotation;
525522
delete (p as any).typeAnnotation;
@@ -532,7 +529,7 @@ export class ScriptLetContext {
532529

533530
removeAllScopeAndVariableAndReference(typeAnnotation, result);
534531
}
535-
(p as any).parent = parents![index];
532+
(p as any).parent = sortedParams[index].parent;
536533
}
537534

538535
// Process for scope

src/parser/converts/block.ts

+51-34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
import type { Context } from "../../context";
1919
import { convertChildren } from "./element";
2020
import { getWithLoc, indexOf, lastIndexOf } from "./common";
21+
import type * as ESTree from "estree";
2122

2223
/** Get start index of block */
2324
function startBlockIndex(code: string, endIndex: number): number {
@@ -281,37 +282,52 @@ export function convertAwaitBlock(
281282
}),
282283
};
283284
if (node.value) {
284-
ctx.scriptLet.nestBlock(
285-
thenBlock,
286-
[node.value],
287-
[thenBlock],
288-
([value]) => {
285+
const baseParam = {
286+
node: node.value,
287+
parent: thenBlock,
288+
callback(value: ESTree.Pattern) {
289289
thenBlock.value = value;
290290
},
291-
({ generateUniqueId }) => {
292-
const expression = ctx.getText(node.expression);
293-
if (node.expression.type === "Literal") {
294-
return {
295-
typings: [expression],
296-
};
297-
}
298-
const idAwaitThenValue = generateUniqueId("AwaitThenValue");
299-
if (node.expression.type === "Identifier") {
300-
return {
301-
preparationScript: [generateAwaitThenValueType(idAwaitThenValue)],
302-
typings: [`${idAwaitThenValue}<(typeof ${expression})>`],
303-
};
304-
}
305-
const id = generateUniqueId(expression);
291+
typing: "any",
292+
};
293+
294+
ctx.scriptLet.nestBlock(thenBlock, (typeCtx) => {
295+
if (!typeCtx) {
306296
return {
307-
preparationScript: [
308-
`const ${id} = ${expression};`,
309-
generateAwaitThenValueType(idAwaitThenValue),
310-
],
311-
typings: [`${idAwaitThenValue}<(typeof ${id})>`],
297+
param: baseParam,
312298
};
313299
}
314-
);
300+
const expression = ctx.getText(node.expression);
301+
if (node.expression.type === "Literal") {
302+
return {
303+
param: {
304+
...baseParam,
305+
typing: expression,
306+
},
307+
};
308+
}
309+
const idAwaitThenValue = typeCtx.generateUniqueId("AwaitThenValue");
310+
if (node.expression.type === "Identifier") {
311+
return {
312+
preparationScript: [generateAwaitThenValueType(idAwaitThenValue)],
313+
param: {
314+
...baseParam,
315+
typing: `${idAwaitThenValue}<(typeof ${expression})>`,
316+
},
317+
};
318+
}
319+
const id = typeCtx.generateUniqueId(expression);
320+
return {
321+
preparationScript: [
322+
`const ${id} = ${expression};`,
323+
generateAwaitThenValueType(idAwaitThenValue),
324+
],
325+
param: {
326+
...baseParam,
327+
typing: `${idAwaitThenValue}<(typeof ${id})>`,
328+
},
329+
};
330+
});
315331
} else {
316332
ctx.scriptLet.nestBlock(thenBlock);
317333
}
@@ -351,15 +367,16 @@ export function convertAwaitBlock(
351367
} as SvelteAwaitCatchBlock;
352368

353369
if (node.error) {
354-
ctx.scriptLet.nestBlock(
355-
catchBlock,
356-
[node.error],
357-
[catchBlock],
358-
([error]) => {
359-
catchBlock.error = error;
370+
ctx.scriptLet.nestBlock(catchBlock, [
371+
{
372+
node: node.error,
373+
parent: catchBlock,
374+
typing: "Error",
375+
callback: (error) => {
376+
catchBlock.error = error;
377+
},
360378
},
361-
["Error"]
362-
);
379+
]);
363380
} else {
364381
ctx.scriptLet.nestBlock(catchBlock);
365382
}

0 commit comments

Comments
 (0)