Skip to content

Commit 4e3686f

Browse files
committed
fix: actually resolve module scope for getScope
1 parent cf2a66c commit 4e3686f

File tree

3 files changed

+69
-54
lines changed

3 files changed

+69
-54
lines changed

src/parser/analyze-scope.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type ESTree from "estree";
22
import type { Scope, ScopeManager } from "eslint-scope";
33
import { Variable, Reference, analyze } from "eslint-scope";
44
import { getFallbackKeys } from "../traverse";
5-
import type { SvelteReactiveStatement, SvelteScriptElement } from "../ast";
5+
import type { SvelteHTMLNode, SvelteReactiveStatement, SvelteScriptElement } from "../ast";
66
import { addReference, addVariable } from "../scope";
77
import { addElementToSortedArray } from "../utils";
88
/**
@@ -25,14 +25,25 @@ export function analyzeScope(
2525
sourceType,
2626
};
2727

28-
return analyze(root, {
28+
let scopeManager = analyze(root, {
2929
ignoreEval: true,
3030
nodejsScope: false,
3131
impliedStrict: ecmaFeatures.impliedStrict,
3232
ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 2022,
3333
sourceType,
3434
fallback: getFallbackKeys,
3535
});
36+
let originalAcquire = scopeManager.acquire;
37+
scopeManager.acquire = function(node: ESTree.Node | SvelteHTMLNode, inner: boolean) {
38+
if (scopeManager.__get(node) === undefined && node.type !== 'Program' && node.parent.type === 'SvelteScriptElement') {
39+
// No deeper scope matched --> use module for nodes besides Program or SvelteScriptElement
40+
return scopeManager.globalScope.childScopes.find((s) => s.type === 'module') || scopeManager.globalScope;
41+
}
42+
43+
return originalAcquire.call(scopeManager, node, inner);
44+
};
45+
46+
return scopeManager;
3647
}
3748

3849
/** Analyze reactive scope */

src/scope/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type * as ESTree from "estree";
33
import type { TSESTree } from "@typescript-eslint/types";
44
import { traverseNodes } from "../traverse";
55
import { addElementsToSortedArray, addElementToSortedArray } from "../utils";
6-
import type { SvelteHTMLNode } from "../ast";
76

87
/** Remove all scope, variable, and reference */
98
export function removeAllScopeAndVariableAndReference(
@@ -59,9 +58,9 @@ export function removeAllScopeAndVariableAndReference(
5958
*/
6059
export function getScopeFromNode(
6160
scopeManager: ScopeManager,
62-
currentNode: ESTree.Node | SvelteHTMLNode
61+
currentNode: ESTree.Node
6362
): Scope {
64-
let node: ESTree.Node | SvelteHTMLNode | null = currentNode;
63+
let node: ESTree.Node | null = currentNode;
6564
for (; node; node = (node as any).parent || null) {
6665
const scope = scopeManager.acquire(node, false);
6766
if (scope) {
@@ -79,10 +78,7 @@ export function getScopeFromNode(
7978
}
8079
}
8180
const global = scopeManager.globalScope;
82-
83-
return currentNode.type === "Program" || currentNode.type === "SvelteScriptElement" ?
84-
global :
85-
getProgramScope(scopeManager);
81+
return global;
8682
}
8783
/**
8884
* Gets the scope for the Program node

tests/src/scope/scope.ts

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,69 @@
11
import assert from "assert";
2-
import { parseForESLint } from "../../../src";
3-
import { getScopeFromNode } from "../../../src/scope";
4-
5-
describe('getScopeFromNode', () => {
6-
it('returns the global scope for the root node', () => {
7-
const { ast, scopeManager } = parseForESLint('');
8-
9-
assert.strictEqual(getScopeFromNode(scopeManager, ast), scopeManager.globalScope);
2+
import * as svelte from "../../../src";
3+
import { FlatESLint } from 'eslint/use-at-your-own-risk';
4+
5+
async function generateScopeTestCase(code, selector, type) {
6+
const eslint = new FlatESLint({
7+
overrideConfigFile: true,
8+
overrideConfig: {
9+
languageOptions: {
10+
parser: svelte,
11+
},
12+
plugins: {
13+
local: {
14+
rules: {
15+
rule: generateScopeRule(selector, type),
16+
}
17+
}
18+
},
19+
rules: {
20+
'local/rule': 'error',
21+
}
22+
}
1023
});
11-
12-
it('returns the global scope for the script element', () => {
13-
const { ast, scopeManager } = parseForESLint('<script></script>');
14-
const script = ast.body[0];
15-
16-
assert.strictEqual(getScopeFromNode(scopeManager, script), scopeManager.globalScope);
24+
await eslint.lintText(code);
25+
}
26+
27+
function generateScopeRule(selector, type) {
28+
return {
29+
create(context) {
30+
return {
31+
[selector]() {
32+
const scope = context.getScope();
33+
34+
assert.strictEqual(scope.type, type);
35+
}
36+
};
37+
}
38+
}
39+
}
40+
41+
describe('context.getScope', () => {
42+
it('returns the global scope for the root node', async () => {
43+
await generateScopeTestCase('', 'Program', 'global');
1744
});
1845

19-
it('returns the module scope for nodes for top level nodes of script', () => {
20-
const { ast, scopeManager } = parseForESLint('<script>import mod from "mod";</script>');
21-
const importStatement = ast.body[0].body[0];
22-
23-
assert.strictEqual(getScopeFromNode(scopeManager, importStatement), scopeManager.globalScope.childScopes[0]);
46+
it('returns the global scope for the script element', async () => {
47+
await generateScopeTestCase('<script></script>', 'SvelteScriptElement', 'global');
2448
});
2549

26-
it('returns the module scope for nested nodes without their own scope', () => {
27-
const { ast, scopeManager } = parseForESLint('<script>a || b</script>');
28-
const importStatement = ast.body[0].body[0].expression.right;
29-
30-
assert.strictEqual(getScopeFromNode(scopeManager, importStatement), scopeManager.globalScope.childScopes[0]);
50+
it.only('returns the module scope for nodes for top level nodes of script', async () => {
51+
await generateScopeTestCase('<script>import mod from "mod";</script>', 'ImportDeclaration', 'module');
3152
});
3253

33-
it('returns the module scope for nested nodes for non-modules', () => {
34-
const { ast, scopeManager } = parseForESLint('<script>a || b</script>', { sourceType: 'script' });
35-
const importStatement = ast.body[0].body[0].expression.right;
36-
37-
assert.strictEqual(getScopeFromNode(scopeManager, importStatement), scopeManager.globalScope.childScopes[0]);
54+
it('returns the module scope for nested nodes without their own scope', async () => {
55+
await generateScopeTestCase('<script>a || b</script>', 'LogicalExpression', 'module');
3856
});
3957

40-
it('returns the the child scope of top level nodes with their own scope', () => {
41-
const { ast, scopeManager } = parseForESLint('<script>function fn() {}</script>');
42-
const fnNode = ast.body[0].body[0];
43-
44-
assert.strictEqual(getScopeFromNode(scopeManager, fnNode), scopeManager.globalScope.childScopes[0].childScopes[0]);
58+
it('returns the the child scope of top level nodes with their own scope', async () => {
59+
await generateScopeTestCase('<script>function fn() {}</script>', 'FunctionDeclaration', 'function');
4560
});
4661

47-
it('returns the own scope for nested nodes', () => {
48-
const { ast, scopeManager } = parseForESLint('<script>a || (() => {})</script>');
49-
const importStatement = ast.body[0].body[0].expression.right;
50-
51-
assert.strictEqual(getScopeFromNode(scopeManager, importStatement), scopeManager.globalScope.childScopes[0].childScopes[0]);
62+
it('returns the own scope for nested nodes', async () => {
63+
await generateScopeTestCase('<script>a || (() => {})</script>', 'ArrowFunctionExpression', 'function');
5264
});
5365

54-
it('returns the the nearest child scope for statements inside non-global scopes', () => {
55-
const { ast, scopeManager } = parseForESLint('<script>function fn() { nested; }</script>');
56-
const fnNode = ast.body[0].body[0];
57-
const nestedStatement = fnNode.body.body[0];
58-
59-
assert.strictEqual(getScopeFromNode(scopeManager, nestedStatement), scopeManager.globalScope.childScopes[0].childScopes[0]);
66+
it('returns the the nearest child scope for statements inside non-global scopes', async () => {
67+
await generateScopeTestCase('<script>function fn() { nested; }</script>', 'ExpressionStatement', 'function');
6068
});
6169
});

0 commit comments

Comments
 (0)