Skip to content

Commit d92287d

Browse files
authored
feat: add support for $bindable() scope analysis (#527)
1 parent 7124bff commit d92287d

15 files changed

+2639
-62
lines changed

.changeset/kind-houses-work.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
feat: add support for `$bindable()` scope analysis

src/parser/analyze-scope.ts

+69-13
Original file line numberDiff line numberDiff line change
@@ -169,26 +169,82 @@ export function analyzePropsScope(
169169
}
170170

171171
for (const node of body.body) {
172-
if (node.type !== "ExportNamedDeclaration") {
173-
continue;
174-
}
175-
if (node.declaration) {
176-
if (node.declaration.type === "VariableDeclaration") {
177-
for (const decl of node.declaration.declarations) {
178-
if (decl.id.type === "Identifier") {
179-
addPropsReference(decl.id, moduleScope);
172+
if (node.type === "ExportNamedDeclaration") {
173+
// Process for Svelte v4 style props. e.g. `export let x`;
174+
if (node.declaration) {
175+
if (node.declaration.type === "VariableDeclaration") {
176+
for (const decl of node.declaration.declarations) {
177+
for (const pattern of extractPattern(decl.id)) {
178+
if (pattern.type === "Identifier") {
179+
addPropReference(pattern, moduleScope);
180+
}
181+
}
182+
}
183+
}
184+
} else {
185+
for (const spec of node.specifiers) {
186+
addPropReference(spec.local, moduleScope);
187+
}
188+
}
189+
} else if (node.type === "VariableDeclaration") {
190+
// Process for Svelte v5 Runes props. e.g. `let { x = $bindable() } = $props()`;
191+
for (const decl of node.declarations) {
192+
if (
193+
decl.init?.type === "CallExpression" &&
194+
decl.init.callee.type === "Identifier" &&
195+
decl.init.callee.name === "$props" &&
196+
decl.id.type === "ObjectPattern"
197+
) {
198+
for (const pattern of extractPattern(decl.id)) {
199+
if (
200+
pattern.type === "AssignmentPattern" &&
201+
pattern.left.type === "Identifier" &&
202+
pattern.right.type === "CallExpression" &&
203+
pattern.right.callee.type === "Identifier" &&
204+
pattern.right.callee.name === "$bindable"
205+
) {
206+
addPropReference(pattern.left, moduleScope);
207+
}
180208
}
181209
}
182210
}
183-
} else {
184-
for (const spec of node.specifiers) {
185-
addPropsReference(spec.local, moduleScope);
211+
}
212+
}
213+
214+
function* extractPattern(node: ESTree.Pattern): Iterable<ESTree.Pattern> {
215+
yield node;
216+
if (node.type === "Identifier") {
217+
return;
218+
}
219+
if (node.type === "ObjectPattern") {
220+
for (const prop of node.properties) {
221+
if (prop.type === "Property") {
222+
yield* extractPattern(prop.value);
223+
} else {
224+
yield* extractPattern(prop);
225+
}
226+
}
227+
return;
228+
}
229+
if (node.type === "ArrayPattern") {
230+
for (const elem of node.elements) {
231+
if (elem) {
232+
yield* extractPattern(elem);
233+
}
186234
}
235+
return;
236+
}
237+
if (node.type === "AssignmentPattern") {
238+
yield* extractPattern(node.left);
239+
return;
240+
}
241+
if (node.type === "RestElement") {
242+
yield* extractPattern(node.argument);
187243
}
188244
}
189245

190-
/** Add virtual props reference */
191-
function addPropsReference(node: ESTree.Identifier, scope: Scope) {
246+
/** Add virtual prop reference */
247+
function addPropReference(node: ESTree.Identifier, scope: Scope) {
192248
for (const variable of scope.variables) {
193249
if (variable.name !== node.name) {
194250
continue;

src/parser/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ function parseAsSvelte(
138138
analyzeStoreScope(resultScript.scopeManager!);
139139
analyzeReactiveScope(resultScript.scopeManager!);
140140
analyzeStoreScope(resultScript.scopeManager!); // for reactive vars
141-
analyzeSnippetsScope(ctx.snippets, resultScript.scopeManager!); // for reactive vars
141+
analyzeSnippetsScope(ctx.snippets, resultScript.scopeManager!);
142142

143143
// Add $$xxx variable
144144
addGlobalVariables(resultScript.scopeManager!, globals);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let { b = $bindable() } = $props();
3+
4+
function handler() {
5+
b++;
6+
}
7+
</script>
8+
9+
<button onclick={handler}>Click Me!</button>

0 commit comments

Comments
 (0)