fix(core): tracking of snippet and render function calls#9877
Conversation
🦋 Changeset detectedLatest commit: 81c35cc The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
a555536 to
2ba3832
Compare
WalkthroughThis 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, Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid-svelte-snippet-params.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-svelte-snippet-params.svelte.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (5)
.changeset/shiny-moments-drive.mdcrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid-svelte-snippet-params.sveltecrates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/valid-svelte-snippet-params.sveltecrates/biome_service/src/file_handlers/html.rscrates/biome_service/src/workspace/document/services/embedded_bindings.rs
| 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()); | ||
| } |
There was a problem hiding this comment.
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.
|
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
crates/biome_service/src/workspace/document/services/embedded_bindings.rs (1)
199-205:⚠️ Potential issue | 🟠 MajorDestructured defaults still get dropped here.
This branch only records
identifier = fallback. Shapes likefigure({ item: { src } = fallback })orfigure([{ id } = fallback])also arrive here, but the earlyas_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
⛔ Files ignored due to path filters (6)
crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_const_and_debug.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_each.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_svelte_snippet.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_const_and_debug.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_each.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_svelte_snippet.svelte.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (11)
.changeset/shiny-moments-drive.mdcrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_const_and_debug.sveltecrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_each.sveltecrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalid_svelte_snippet.sveltecrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_const_and_debug.sveltecrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_each.sveltecrates/biome_js_analyze/tests/specs/correctness/noUndeclaredVariables/valid_svelte_snippet.sveltecrates/biome_service/src/embed/types.rscrates/biome_service/src/file_handlers/html.rscrates/biome_service/src/file_handlers/html/parse_embedded_nodes.rscrates/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
|
@dyc3 The scope of the PR grew. As I was fixing stuff, I found other bugs |
dyc3
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
crates/biome_service/src/workspace/document/services/embedded_bindings.rs (1)
199-205:⚠️ Potential issue | 🟡 MinorDestructured 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 likefigure({ item: { src } = fallback })orfigure([{ id } = fallback])still never registersrc/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
📒 Files selected for processing (4)
.changeset/shiny-moments-drive.mdcrates/biome_service/src/embed/types.rscrates/biome_service/src/file_handlers/html/parse_embedded_nodes.rscrates/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
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:
Fixed also the tracking of shorthand attributes e.g.
<a {href}>I introduced the concept of
EmbedBlockKind, becausesnippet,constandrenderhave 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 wholehtml.rsfile was getting quite convoluted.Test Plan
Added various new tests and made sure that they are correct.
Docs