Skip to content

fix(core): tracking of snippet and render function calls#9877

Merged
dyc3 merged 4 commits intomainfrom
fix/svelte-figure
Apr 9, 2026
Merged

fix(core): tracking of snippet and render function calls#9877
dyc3 merged 4 commits intomainfrom
fix/svelte-figure

Conversation

@ematipico
Copy link
Copy Markdown
Member

@ematipico ematipico commented Apr 9, 2026

Summary

Closes #9136
Closes #9653

I initially used AI, but then scrapped everything because it was really bad at tracking the correct solution. A waste of time lol. I used it only to create tests at the end, and to help me with updating the call sites with the new function signatures.

This PR fixes various bugs and holes in the Svelte tracking of bindings and references:

  • each blocks
  • const block
  • render block
  • snippet block
  • debug block

Fixed also the tracking of shorthand attributes e.g. <a {href}>

I introduced the concept of EmbedBlockKind, because snippet, const and render have special cases that need to be handled. I kept the concept generic, so other snippets can use it.

I also refactored how we parse and track nodes using a shared function, and moved everything inside html/parse_embedded_nodes.rs. The whole html.rs file was getting quite convoluted.

Test Plan

Added various new tests and made sure that they are correct.

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: 81c35cc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ematipico ematipico requested review from a team April 9, 2026 09:38
@github-actions github-actions Bot added A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Apr 9, 2026
@ematipico ematipico force-pushed the fix/svelte-figure branch from a555536 to 2ba3832 Compare April 9, 2026 09:39
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

Walkthrough

This PR refactors HTML embed parsing into a new module and adds embed block-kind metadata. It extends embedded binding extraction for Svelte: snippet declarations and render calls, {#each ... as ...} destructuring (including keyed/index patterns), {@const ...} assignments, {@debug ...} identifiers, and shorthand attribute expressions are now registered or analysed as bindings/expressions. Several Svelte correctness tests (valid and invalid) were added to verify the new binding/usage resolution.

Possibly related PRs

Suggested labels

A-Parser

Suggested reviewers

  • dyc3
  • siketyan
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing tracking of snippet and render function calls in Svelte, which is the primary objective of this PR.
Description check ✅ Passed The description clearly explains the motivation, scope, and implementation approach, directly addressing the issues and test coverage.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #9136 by fixing Svelte binding and reference tracking for each, const, render, snippet, and debug blocks, plus shorthand attributes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to Svelte binding/reference tracking and the refactoring of embed parsing logic into a shared module, which supports the primary objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/svelte-figure

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/shiny-moments-drive.md:
- Around line 7-19: The example still uses an undeclared variable "images" in
the {`#each` images as img} block which triggers noUndeclaredVariables; either
declare it (add a <script> block that defines let images = [] so "images" is
declared before use) or remove the {`#each` ...} / {`@render` figure(img)} portion
and keep only the {`#snippet` figure(image)} / {`@render` figure(...)} example so no
undeclared variable is referenced; update the example to ensure "images" is
declared or omitted accordingly.

In `@crates/biome_service/src/file_handlers/html.rs`:
- Around line 1290-1295: The current call to
ctx.builder.visit_js_source_snippet(&parse.tree(), host_file_source) inside the
is_source_level || host_file_source.is_svelte() branch causes every Svelte
embedded expression to be treated as a snippet declaration and registers their
identifiers in js_bindings; restrict this extra binding walk so it only runs for
snippet header/embed kinds (not arbitrary Svelte expressions). Change the
condition or the call site so that visit_js_source_snippet is invoked only when
the embed represents a snippet/header (e.g. check the embed kind or a
"is_snippet_header" predicate on host_file_source or parse) rather than whenever
host_file_source.is_svelte() is true, or add an argument to
visit_js_source_snippet to pass a specific embed kind so the builder only
records bindings for snippet/header embeds; update uses of is_source_level,
host_file_source.is_svelte(), ctx.builder.visit_js_source_snippet, and the
js_bindings code path accordingly.

In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`:
- Around line 149-155: The JsAssignmentExpression branch currently only handles
simple identifier assignments (using as_js_identifier_assignment and token) and
returns early for destructured patterns, so destructured defaults like { item: {
src } = fallback } are not collected; update the
AnyJsExpression::JsAssignmentExpression arm to: if assign.left() yields an
identifier, keep the existing logic (ident/name_token/token_text_trimmed);
otherwise, traverse the left pattern (object/array patterns) to recursively
extract all identifier name_tokens and insert them into self.js_bindings (reuse
or add a small helper that walks JsObjectBindingPattern/JsArrayBindingPattern
and JsIdentifierBinding nodes), ensuring every binding found from the
destructured pattern is registered the same way as the simple identifier case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fcc52331-cf6b-409f-800f-49385bcd133b

📥 Commits

Reviewing files that changed from the base of the PR and between 78bce77 and 2ba3832.

⛔ Files ignored due to path filters (2)
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid-svelte-snippet-params.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-svelte-snippet-params.svelte.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (5)
  • .changeset/shiny-moments-drive.md
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid-svelte-snippet-params.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-svelte-snippet-params.svelte
  • crates/biome_service/src/file_handlers/html.rs
  • crates/biome_service/src/workspace/document/services/embedded_bindings.rs

Comment thread .changeset/shiny-moments-drive.md Outdated
Comment thread crates/biome_service/src/file_handlers/html.rs Outdated
Comment on lines +149 to +155
AnyJsExpression::JsAssignmentExpression(assign) => {
let left = assign.left().ok()?;
let ident = left.as_any_js_assignment()?.as_js_identifier_assignment()?;
let token = ident.name_token().ok()?;
self.js_bindings
.insert(token.text_trimmed_range(), token.token_text_trimmed());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Destructured defaults still fall through the cracks.

This branch only handles identifier = default. Valid params such as figure({ item: { src } = fallback }) or figure([{ id } = fallback]) also arrive as JsAssignmentExpression, but here we bail before walking the object/array pattern, so those bindings never get registered. The new tests only cover image = fallback, so this one slips by quietly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`
around lines 149 - 155, The JsAssignmentExpression branch currently only handles
simple identifier assignments (using as_js_identifier_assignment and token) and
returns early for destructured patterns, so destructured defaults like { item: {
src } = fallback } are not collected; update the
AnyJsExpression::JsAssignmentExpression arm to: if assign.left() yields an
identifier, keep the existing logic (ident/name_token/token_text_trimmed);
otherwise, traverse the left pattern (object/array patterns) to recursively
extract all identifier name_tokens and insert them into self.js_bindings (reuse
or add a small helper that walks JsObjectBindingPattern/JsArrayBindingPattern
and JsIdentifierBinding nodes), ensuring every binding found from the
destructured pattern is registered the same way as the simple identifier case.

Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@ematipico
Copy link
Copy Markdown
Member Author

Not quite, I found some holes in the implementation.

It will take while, so feel free to start the release if this doesn't get fixed in time

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/biome_service/src/workspace/document/services/embedded_bindings.rs (1)

199-205: ⚠️ Potential issue | 🟠 Major

Destructured defaults still get dropped here.

This branch only records identifier = fallback. Shapes like figure({ item: { src } = fallback }) or figure([{ id } = fallback]) also arrive here, but the early as_js_identifier_assignment() exit skips the nested bindings entirely. That means valid snippet params can still be reported as undeclared/unused. Please recurse into object/array assignment targets as well, and add a test for one of those cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`
around lines 199 - 205, The JsAssignmentExpression arm currently only handles
identifier assignments via as_js_identifier_assignment() and skips nested
object/array destructured targets, so modify the
AnyJsExpression::JsAssignmentExpression branch to detect when assign.left() is
an object or array assignment (e.g., via as_any_js_assignment() helpers) and
recursively walk those assignment targets to collect inner identifier tokens
before inserting into self.js_bindings (reuse the existing token collection
logic used for identifier assignments). Ensure you keep the existing identifier
path (token.token_text_trimmed()) but add a recursive helper (e.g.,
collect_js_binding_identifiers) called from this arm to handle nested
ObjectPattern/ArrayPattern defaults; then add a unit test that exercises a
destructured default like figure({ item: { src } = fallback }) or figure([{ id }
= fallback]) to assert the inner binding is recorded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/shiny-moments-drive.md:
- Line 5: Update the changeset header line that currently reads "Fixed
[`#9653`](https://github.com/biomejs/biome/issues/9653):
[`noUndeclaredVariables`]..." to reference the correct issue `#9136` instead:
replace both the display number "#9653" and the URL path "/issues/9653" with
"#9136" and the corresponding "/issues/9136" so the user-facing note matches the
bug this PR actually closes; the target text to edit is the markdown fragment
starting with "Fixed [`#9653`](...)" in .changeset/shiny-moments-drive.md.

---

Duplicate comments:
In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`:
- Around line 199-205: The JsAssignmentExpression arm currently only handles
identifier assignments via as_js_identifier_assignment() and skips nested
object/array destructured targets, so modify the
AnyJsExpression::JsAssignmentExpression branch to detect when assign.left() is
an object or array assignment (e.g., via as_any_js_assignment() helpers) and
recursively walk those assignment targets to collect inner identifier tokens
before inserting into self.js_bindings (reuse the existing token collection
logic used for identifier assignments). Ensure you keep the existing identifier
path (token.token_text_trimmed()) but add a recursive helper (e.g.,
collect_js_binding_identifiers) called from this arm to handle nested
ObjectPattern/ArrayPattern defaults; then add a unit test that exercises a
destructured default like figure({ item: { src } = fallback }) or figure([{ id }
= fallback]) to assert the inner binding is recorded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b58e5c9-8d5a-4c1e-9206-f3b9521d04ef

📥 Commits

Reviewing files that changed from the base of the PR and between 2ba3832 and 3287306.

⛔ Files ignored due to path filters (6)
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_const_and_debug.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_each.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_svelte_snippet.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_const_and_debug.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_each.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_svelte_snippet.svelte.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (11)
  • .changeset/shiny-moments-drive.md
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_const_and_debug.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_each.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_svelte_snippet.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_const_and_debug.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_each.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_svelte_snippet.svelte
  • crates/biome_service/src/embed/types.rs
  • crates/biome_service/src/file_handlers/html.rs
  • crates/biome_service/src/file_handlers/html/parse_embedded_nodes.rs
  • crates/biome_service/src/workspace/document/services/embedded_bindings.rs
✅ Files skipped from review due to trivial changes (4)
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_svelte_snippet.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_const_and_debug.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_svelte_snippet.svelte
  • crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_each.svelte

Comment thread .changeset/shiny-moments-drive.md Outdated
@ematipico
Copy link
Copy Markdown
Member Author

@dyc3 The scope of the PR grew. As I was fixing stuff, I found other bugs

Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the tests, everything looks correct.

This fix is fine for now, but I'm still in favor of a dedicated svelte semantic model in the long term.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/biome_service/src/workspace/document/services/embedded_bindings.rs (1)

199-205: ⚠️ Potential issue | 🟡 Minor

Destructured defaults are still slipping through here.

This branch only handles identifier = default. If the left-hand side is an object or array pattern, we bail before walking it, so params like figure({ item: { src } = fallback }) or figure([{ id } = fallback]) still never register src / id. Please recurse into the left-hand pattern instead of assuming an identifier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`
around lines 199 - 205, The JsAssignmentExpression branch only handles simple
identifier assignments via as_js_identifier_assignment() and misses identifiers
inside destructured defaults (e.g., object/array patterns). Update the
AnyJsExpression::JsAssignmentExpression handling to recurse into the left-hand
pattern: if as_js_identifier_assignment() succeeds keep the current logic,
otherwise detect and walk JsObjectBindingPattern and JsArrayBindingPattern nodes
(or a helper like collect_identifiers_from_pattern) to find inner JsIdentifier
tokens (name_token()) and call
self.js_bindings.insert(token.text_trimmed_range(), token.token_text_trimmed())
for each found identifier; factor the recursion into a small helper function to
reuse elsewhere.
🧹 Nitpick comments (1)
.changeset/shiny-moments-drive.md (1)

5-12: Trim the implementation jargon from the lead sentence.

“Declare or reference bindings in the host grammar” reads like an internal note, not a release note. A plainer “used or declared in Svelte templates” sort of wording would land better for users.

As per coding guidelines, “Write changeset descriptions for end users, not developers.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/shiny-moments-drive.md around lines 5 - 12, Replace the lead
sentence that currently uses developer jargon ("declare or reference bindings in
the host grammar") with a plain, user-facing phrasing such as "used or declared
in Svelte templates" (keep the rest of the changeset and bullet list intact);
update the top-of-file lead sentence so it reads clearly for end users (e.g.,
"no longer report false positives on several Svelte template constructs that are
used or declared in Svelte templates") and ensure grammar and punctuation remain
consistent with the existing bullets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`:
- Around line 199-205: The JsAssignmentExpression branch only handles simple
identifier assignments via as_js_identifier_assignment() and misses identifiers
inside destructured defaults (e.g., object/array patterns). Update the
AnyJsExpression::JsAssignmentExpression handling to recurse into the left-hand
pattern: if as_js_identifier_assignment() succeeds keep the current logic,
otherwise detect and walk JsObjectBindingPattern and JsArrayBindingPattern nodes
(or a helper like collect_identifiers_from_pattern) to find inner JsIdentifier
tokens (name_token()) and call
self.js_bindings.insert(token.text_trimmed_range(), token.token_text_trimmed())
for each found identifier; factor the recursion into a small helper function to
reuse elsewhere.

---

Nitpick comments:
In @.changeset/shiny-moments-drive.md:
- Around line 5-12: Replace the lead sentence that currently uses developer
jargon ("declare or reference bindings in the host grammar") with a plain,
user-facing phrasing such as "used or declared in Svelte templates" (keep the
rest of the changeset and bullet list intact); update the top-of-file lead
sentence so it reads clearly for end users (e.g., "no longer report false
positives on several Svelte template constructs that are used or declared in
Svelte templates") and ensure grammar and punctuation remain consistent with the
existing bullets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e81b2e51-0634-4ddb-8293-31c4b45c5095

📥 Commits

Reviewing files that changed from the base of the PR and between 3287306 and 81c35cc.

📒 Files selected for processing (4)
  • .changeset/shiny-moments-drive.md
  • crates/biome_service/src/embed/types.rs
  • crates/biome_service/src/file_handlers/html/parse_embedded_nodes.rs
  • crates/biome_service/src/workspace/document/services/embedded_bindings.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/biome_service/src/file_handlers/html/parse_embedded_nodes.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

2 participants