Skip to content

Commit b45d9ec

Browse files
committed
fix(no-shadow): ignore {#snippet} if it uses under component.
1 parent 2bd1799 commit b45d9ec

File tree

12 files changed

+168
-4
lines changed

12 files changed

+168
-4
lines changed

.changeset/silly-lions-build.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': patch
3+
---
4+
5+
fix(no-shadow): ignore `{#snippet}` if it uses under component.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { createRule } from '../utils/index.js';
2+
import { defineWrapperListener, getProxyContent, getCoreRule } from '../utils/eslint-core.js';
3+
import type { TSESTree } from '@typescript-eslint/types';
4+
import type { Scope } from '@typescript-eslint/scope-manager';
5+
import type { Range } from 'svelte-eslint-parser/lib/ast/common.js';
6+
import { getScope as getScopeUtil } from '../utils/ast-utils.js';
7+
import { getSourceCode as getSourceCodeCompat } from '../utils/compat.js';
8+
9+
const coreRule = getCoreRule('no-shadow');
10+
11+
function removeSnippetIdentifiers(snippetIdentifierNodeLocations: Range[], scope: Scope): Scope {
12+
return {
13+
...scope,
14+
variables: scope.variables.filter((variable) => {
15+
return !snippetIdentifierNodeLocations.some(([start, end]) => {
16+
return variable.identifiers.every((identifier) => {
17+
const { range } = identifier;
18+
return range[0] === start && range[1] === end;
19+
});
20+
});
21+
}),
22+
childScopes: scope.childScopes.map((scope) => {
23+
return removeSnippetIdentifiers(snippetIdentifierNodeLocations, scope);
24+
})
25+
} as Scope;
26+
}
27+
28+
export default createRule('no-shadow', {
29+
meta: {
30+
...coreRule.meta,
31+
docs: {
32+
description: coreRule.meta.docs.description,
33+
category: 'Best Practices',
34+
recommended: false,
35+
extensionRule: 'no-shadow'
36+
}
37+
},
38+
create(context) {
39+
const snippetIdentifierNodeLocations: Range[] = [];
40+
41+
function getScope(node: TSESTree.Node) {
42+
const scope = getScopeUtil(context, node);
43+
return removeSnippetIdentifiers(snippetIdentifierNodeLocations, scope);
44+
}
45+
46+
function getSourceCode() {
47+
const sourceCode = getSourceCodeCompat(context);
48+
return new Proxy(sourceCode, {
49+
get(target, key) {
50+
if (key === 'getScope') {
51+
return getScope;
52+
}
53+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
54+
return (target as any)[key];
55+
}
56+
});
57+
}
58+
59+
return defineWrapperListener(
60+
coreRule,
61+
getProxyContent(context, {
62+
sourceCode: getSourceCode()
63+
}),
64+
{
65+
createListenerProxy(coreListener) {
66+
return {
67+
...coreListener,
68+
SvelteSnippetBlock(node) {
69+
const parent = node.parent;
70+
if (parent.type === 'SvelteElement' && parent.kind === 'component') {
71+
snippetIdentifierNodeLocations.push(node.id.range);
72+
}
73+
coreListener.SvelteSnippetBlock?.(node);
74+
},
75+
'Program:exit'(node) {
76+
coreListener['Program:exit']?.(node);
77+
}
78+
};
79+
}
80+
}
81+
);
82+
}
83+
});

packages/eslint-plugin-svelte/src/types-for-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export type ASTNodeListener = {
211211
TSUnknownKeyword?: (node: TSESTree.TSUnknownKeyword & ASTNodeWithParent) => void;
212212
TSVoidKeyword?: (node: TSESTree.TSVoidKeyword & ASTNodeWithParent) => void;
213213
Program?: (node: AST.SvelteProgram & ASTNodeWithParent) => void;
214+
'Program:exit'?: (node: AST.SvelteProgram & ASTNodeWithParent) => void;
214215
SvelteScriptElement?: (node: AST.SvelteScriptElement & ASTNodeWithParent) => void;
215216
SvelteStyleElement?: (node: AST.SvelteStyleElement & ASTNodeWithParent) => void;
216217
SvelteElement?: (node: AST.SvelteElement & ASTNodeWithParent) => void;

packages/eslint-plugin-svelte/src/utils/eslint-core.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ import { Linter } from 'eslint';
66
import Module from 'module';
77

88
const require = Module.createRequire(import.meta.url);
9+
10+
export function getProxyContent(context: RuleContext, overrides: any): RuleContext {
11+
const cache: any = {};
12+
return new Proxy(context, {
13+
get(_t, key) {
14+
if (key in cache) {
15+
return cache[key];
16+
}
17+
if (key in overrides) {
18+
return (cache[key] = overrides[key]);
19+
}
20+
return (context as any)[key];
21+
}
22+
});
23+
}
24+
925
/**
1026
* Define the wrapped core rule.
1127
*/
@@ -17,10 +33,7 @@ export function defineWrapperListener(
1733
}
1834
): RuleListener {
1935
const listener = coreRule.create(context as any);
20-
21-
const svelteListener = proxyOptions.createListenerProxy?.(listener) ?? listener;
22-
23-
return svelteListener;
36+
return proxyOptions.createListenerProxy?.(listener) ?? listener;
2437
}
2538

2639
/**

packages/eslint-plugin-svelte/src/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import noReactiveFunctions from '../rules/no-reactive-functions.js';
4242
import noReactiveLiterals from '../rules/no-reactive-literals.js';
4343
import noReactiveReassign from '../rules/no-reactive-reassign.js';
4444
import noRestrictedHtmlElements from '../rules/no-restricted-html-elements.js';
45+
import noShadow from '../rules/no-shadow.js';
4546
import noShorthandStylePropertyOverrides from '../rules/no-shorthand-style-property-overrides.js';
4647
import noSpacesAroundEqualSignsInAttribute from '../rules/no-spaces-around-equal-signs-in-attribute.js';
4748
import noStoreAsync from '../rules/no-store-async.js';
@@ -113,6 +114,7 @@ export const rules = [
113114
noReactiveLiterals,
114115
noReactiveReassign,
115116
noRestrictedHtmlElements,
117+
noShadow,
116118
noShorthandStylePropertyOverrides,
117119
noSpacesAroundEqualSignsInAttribute,
118120
noStoreAsync,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: "'x' is already declared in the upper scope on line 2 column 13."
2+
line: 4
3+
column: 8
4+
suggestions: null
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
function a(x) {
3+
var b = function c() {
4+
var x = 'foo';
5+
};
6+
}
7+
</script>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: "'foo' is already declared in the upper scope on line 6 column 10."
2+
line: 8
3+
column: 11
4+
suggestions: null
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import ComponentWithSnippet from './ComponentWithSnippet.svelte';
3+
</script>
4+
5+
<ComponentWithSnippet>
6+
{@const foo = 1}
7+
<ComponentWithSnippet>
8+
{@const foo = 2}
9+
</ComponentWithSnippet>
10+
</ComponentWithSnippet>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
var a = 3;
3+
var b = (x) => {
4+
a++;
5+
return x + a;
6+
};
7+
setTimeout(() => {
8+
b(a);
9+
}, 0);
10+
</script>

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