Conversation
🦋 Changeset detectedLatest commit: 875123c The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 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 |
baba547 to
5fe1c4e
Compare
064c8a0 to
3086d1a
Compare
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughImplements end-to-end LSP “go to definition”: new workspace types and server method, LSP capability advertisement and handler, editor-feature gating propagated on open/change, file-handler resolvers for JS/HTML/CSS, module-graph tracking of CSS selector ranges and inline offsets, richer embedded-binding metadata, many tests and test utilities, and updates to Open/Change file params to accept optional Possibly related PRs
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: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/biome_service/src/workspace/document/services/embedded_bindings.rs (1)
471-509:⚠️ Potential issue | 🟠 MajorFinish the import-source wiring for all import forms.
default_specifiers()already registers the default binding, so the new default-clause branch adds a second copy. Meanwhile named imports still go throughregister_binding(...), which meansimport { Button } from "./Button.astro"never records asourceat all. That breaks the new source-based component lookup for the most common import shape and can also duplicate entries oncebindings_without_source()is used.🤖 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 471 - 509, The code currently registers default imports twice and omits source info for named imports; to fix, change the default-specifier branch (clause.default_specifiers()) to call register_binding_with_source(...) using import.source_text().ok()? instead of register_binding(...), and remove or collapse the separate as_js_import_default_clause() branch to avoid duplicate registration; also update the named-import handling logic (where individual named specifiers are processed) to call register_binding_with_source(...) with import.source_text().ok()? so every import form (default_specifiers, as_js_import_default_clause/as_js_import_namespace_clause, and named specifiers) records the source consistently.
🧹 Nitpick comments (1)
crates/biome_module_graph/src/module_graph.rs (1)
528-556: Avoid traversingstatic_import_pathstwice here.Lines 528-539 already add CSS imports from embedded
<script>blocks, then Lines 543-556 add the same static imports again viastatic_import_paths.chain(...). That means duplicateCssClassSteps for the same stylesheet, which is wasted work and can leak duplicate path entries into diagnostics/traversal output.♻️ One way to de-duplicate it
- // 2b. CSS files imported from embedded <script> blocks - // (e.g., `import "./index.css"` in Astro frontmatter). - for import_path in html_info.static_import_paths.values() { - if let Some(path) = import_path.as_path() - && let Some(css_info) = self.css_module_info_for_path(path) - { - linked_steps.push(CssClassStep { - css_path: path.to_path_buf(), - css_classes: css_info.classes.clone(), - }); - } - } - - // 3. CSS files imported via static imports from embedded scripts - // (e.g., Astro frontmatter `import "./styles.css"`). + // 3. CSS files imported via embedded scripts + // (e.g., Astro frontmatter `import "./styles.css"`). for import_path in html_info .static_import_paths .values() .chain(html_info.dynamic_import_paths.values())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_module_graph/src/module_graph.rs` around lines 528 - 556, Duplicate traversal: html_info.static_import_paths is iterated in the first loop and again in the chained loop, producing duplicate CssClassStep entries; change the second loop (which currently iterates html_info.static_import_paths.values().chain(html_info.dynamic_import_paths.values())) to only iterate html_info.dynamic_import_paths.values() (so use dynamic_import_paths alone) and keep using self.css_module_info_for_path, linked_steps.push(CssClassStep { css_path: path.to_path_buf(), css_classes: css_info.classes.clone(), }) unchanged to preserve behavior for dynamic imports.
🤖 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/dirty-lions-talk.md:
- Around line 5-14: Tighten the release note copy by rephrasing the clunky
sentence "When the cursor of the mouse is hovering an entity (variable, CSS
class, type, etc.), and the command CTRL + click is triggered" to something more
natural (e.g., "Ctrl+click an entity (variable, CSS class, type, etc.) to jump
to its definition") and finish/clarify the truncated bullet "Variables used in
HTML-ish files and defined in the same file or imported from another module
(JavaScript or HTML-ish)" so it reads as a complete, parallel item (e.g.,
"Variables used in HTML-ish files, defined in the same file or imported from
JavaScript or other HTML-ish files"). Make these edits in
.changeset/dirty-lions-talk.md replacing the exact phrases above to preserve
intent and parallel structure.
In `@crates/biome_lsp/src/handlers/navigation.rs`:
- Around line 60-69: The code currently fabricates a bogus Range::default() when
session.workspace.get_file_content(GetFileContentParams { ... }) fails, causing
a misleading jump; instead, on Err(_) do not produce a default range — propagate
the error or return None (so no Location is returned) and/or log the failure
with context; replace the Err(_) => Range::default() branch so it either returns
an Err with a descriptive message (bubbling the failure out of the surrounding
function) or yields no definition entry, rather than calling Range::default();
keep references to session.workspace.get_file_content, GetFileContentParams,
LineIndex::new, and to_proto::range when implementing the fix.
- Around line 8-15: The issue is a name collision between
biome_service::workspace::GotoDefinitionParams and the LSP GotoDefinitionParams
imported via tower_lsp_server::ls_types::*, causing
params.text_document_position_params (an LSP field) to be unavailable; fix by
aliasing the workspace types (e.g., import
biome_service::workspace::{GetFileContentParams as WsGetFileContentParams,
GotoDefinitionParams as WsGotoDefinitionParams}) and update usages in
goto_definition to refer to the LSP GotoDefinitionParams for the function
parameter and to WsGetFileContentParams/WsGotoDefinitionParams where you need
workspace types (also update the other occurrences that referenced the ambiguous
types around the later block that uses workspace types).
In `@crates/biome_lsp/src/server_goto.tests.rs`:
- Around line 263-515: Add tests covering the class/className → CSS definition
path: create new test functions in this file using the existing helpers
(goto_definition_single_file and goto_definition_cross_file) and assertions
(assert_definition, test_uri, file_uri, range, pos) that exercise (1) inline
<style> resolution in the same .astro/.html file (e.g. a component with <div
class="foo"> and a <style> block containing .foo { ... } and asserting the
definition range inside the style block) and (2) cross-file CSS resolution via
imports (e.g. an .astro/.jsx file with className="bar" importing a .css file
that defines .bar, or an imported .astro component with an inline style import)
making sure to set the same config used elsewhere ("html": {
"experimentalFullSupportEnabled": true }) where needed; name tests clearly like
goto_definition_css_inline_style and goto_definition_css_cross_file and use the
same pattern (tokio::test async fn, call helpers, then assert_definition) so
offsets and import-tree traversal for CSS are exercised.
In `@crates/biome_lsp/src/session.rs`:
- Around line 646-662: The current logic in can_register_goto_definition lets
any client with dynamic_registration enable go-to-definition regardless of the
feature toggle; change the boolean logic so the extension toggle gates
registration: read goto_definition_enabled() from extension_settings and only
allow registration when that is true and the client advertises dynamic
registration (i.e., compute the dynamic flag from
initialize_params.client_capabilities.text_document.definition.dynamic_registration
and return enabled && dynamic). Reference: can_register_goto_definition,
initialize_params, extension_settings, goto_definition_enabled.
In `@crates/biome_module_graph/src/css_module_info/mod.rs`:
- Around line 149-157: The classes map currently uses IndexMap<TokenText,
TextRange> which collapses repeated class selectors; change it to store all
ranges per class (e.g., IndexMap<TokenText, Vec<TextRange>> or another
collection) and update the code that populates it (the walker that records
CssClassSelector nodes) to push ranges into the vector instead of overwriting,
and update any call sites of the classes field (e.g., go-to-definition code) to
iterate/choose from the Vec<TextRange> rather than assuming a single TextRange
so callers can decide whether to return one or many locations.
In `@crates/biome_service/src/file_handlers/html/go_to.rs`:
- Around line 56-63: The hit-test for class names is inclusive of the end byte
so a caret on the following space still matches the previous class; in the loop
in go_to.rs that computes pos, start, end and checks relative_offset, change the
upper bound from inclusive to exclusive by replacing the condition
relative_offset >= start && relative_offset <= end with relative_offset >= start
&& relative_offset < end so DefinitionReference::CssClass only matches bytes
inside the class name.
In `@crates/biome_service/src/file_handlers/javascript.rs`:
- Around line 1670-1686: The cursor-in-class detection treats the class end as
inclusive, causing matches when the cursor is on the separating space or closing
quote; in the loop that iterates over text.split_ascii_whitespace() (using
variables relative_offset, pos, start, end, and class_name) change the
membership check to treat end as exclusive (i.e., require relative_offset >=
start && relative_offset < end) and keep end computed as start +
class_name.len() so the boundary at the character immediately after the class
does not count as inside the token.
In `@crates/biome_service/src/file_handlers/javascript/go_to.rs`:
- Around line 158-160: The HtmlComponent branch is passing the import specifier
`source` into `resolve_import_definition`, but that helper expects a local
binding name and looks up `static_imports`, so specifier paths (e.g.
"./Button.astro") never resolve; replace or augment the call in the
DefinitionReference::HtmlComponent arm to perform a specifier-based lookup
instead of the local-name lookup: consult the module graph/requests to map the
specifier `source` -> resolved module (or implement a new helper like
`resolve_definition_by_specifier` that queries the same index used for specifier
resolution), then return the appropriate DefinitionReference result; keep
`resolve_import_definition` for local-binding use and add/route HtmlComponent to
the new specifier resolver so tags backed by "./Button.astro" resolve correctly.
In `@crates/biome_service/src/workspace/server.rs`:
- Around line 2756-2777: The embedded-definition resolution currently calls
resolve_definition with each snippet's content_offset but doesn't record which
snippet produced the reference, so the first snippet that returns a location may
be unrelated; update the LocalEmbedded definition handling to carry the
originating snippet identity (e.g., file_source_index or content_offset) onto
the DefinitionRef when it's created and then use that stored origin when
building ResolveDefinitionParams in the block handling
definition_ref.is_embedded() (symbols: definition_ref, LocalEmbedded,
embedded_snippets, snippet.file_source_index(), snippet.content_offset(),
ResolveDefinitionParams, resolve_definition); ensure all places that construct
DefinitionRef for embedded bindings populate the origin field so
resolve_definition receives the correct offset from the actual originating
snippet.
- Around line 514-517: The code builds document services in open_file_internal()
when settings.is_linter_enabled() || settings.is_assist_enabled() ||
needs_document_services is true, but change_file() only recreates services based
on settings.is_linter_enabled() || settings.is_assist_enabled(), causing files
opened with only definition/navigation (needs_document_services) to lose their
semantic model after edits; fix by persisting the needs_document_services
decision into the file/workspace state when open_file_internal() runs (e.g., add
a flag like file.needs_document_services or store in the WorkspaceFile entry)
and update change_file() to consult that persisted flag (or the same composite
predicate) when deciding to rebuild document services so semantic models remain
available after edits.
- Around line 841-849: The current logic calls the host-language resolve_binding
and returns immediately even when it yields None, preventing the subsequent
embedded-snippet loop from running; change the block around
resolve_binding(ResolveBindingParams { parse, cursor_offset, services }) so you
only compute resolve_capabilities(&result, capabilities) and return Ok((result,
path.clone(), capabilities)) when result is Some(...) (i.e., a successful
binding), otherwise do not return and allow the snippet loop below to run and
attempt resolution inside embedded <script>/<style> regions; keep the same
variables (resolve_binding, ResolveBindingParams, resolve_capabilities, parse,
cursor_offset, services, path) to locate and adjust the logic.
---
Outside diff comments:
In `@crates/biome_service/src/workspace/document/services/embedded_bindings.rs`:
- Around line 471-509: The code currently registers default imports twice and
omits source info for named imports; to fix, change the default-specifier branch
(clause.default_specifiers()) to call register_binding_with_source(...) using
import.source_text().ok()? instead of register_binding(...), and remove or
collapse the separate as_js_import_default_clause() branch to avoid duplicate
registration; also update the named-import handling logic (where individual
named specifiers are processed) to call register_binding_with_source(...) with
import.source_text().ok()? so every import form (default_specifiers,
as_js_import_default_clause/as_js_import_namespace_clause, and named specifiers)
records the source consistently.
---
Nitpick comments:
In `@crates/biome_module_graph/src/module_graph.rs`:
- Around line 528-556: Duplicate traversal: html_info.static_import_paths is
iterated in the first loop and again in the chained loop, producing duplicate
CssClassStep entries; change the second loop (which currently iterates
html_info.static_import_paths.values().chain(html_info.dynamic_import_paths.values()))
to only iterate html_info.dynamic_import_paths.values() (so use
dynamic_import_paths alone) and keep using self.css_module_info_for_path,
linked_steps.push(CssClassStep { css_path: path.to_path_buf(), css_classes:
css_info.classes.clone(), }) unchanged to preserve behavior for dynamic imports.
🪄 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: 1ba7e0f3-79c6-490c-9ed4-42c8a126ca5b
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**
📒 Files selected for processing (62)
.changeset/dirty-lions-talk.mdcrates/biome_cli/src/execute/migrate.rscrates/biome_cli/src/runner/impls/process_file/format.rscrates/biome_cli/src/runner/impls/process_file/lint_and_assist.rscrates/biome_cli/src/runner/process_file.rscrates/biome_css_formatter/tests/quick_test.rscrates/biome_formatter_test/src/spec.rscrates/biome_graphql_formatter/tests/quick_test.rscrates/biome_grit_formatter/tests/quick_test.rscrates/biome_html_analyze/src/lint/nursery/no_undeclared_classes.rscrates/biome_html_formatter/tests/quick_test.rscrates/biome_js_analyze/src/lint/nursery/no_undeclared_classes.rscrates/biome_js_formatter/tests/quick_test.rscrates/biome_json_formatter/tests/quick_test.rscrates/biome_lsp/Cargo.tomlcrates/biome_lsp/src/capabilities.rscrates/biome_lsp/src/diagnostics.rscrates/biome_lsp/src/extension_settings.rscrates/biome_lsp/src/handlers.rscrates/biome_lsp/src/handlers/navigation.rscrates/biome_lsp/src/handlers/text_document.rscrates/biome_lsp/src/lib.rscrates/biome_lsp/src/server.rscrates/biome_lsp/src/server.tests.rscrates/biome_lsp/src/server_goto.tests.rscrates/biome_lsp/src/server_test_utils.rscrates/biome_lsp/src/session.rscrates/biome_module_graph/src/css_module_info/mod.rscrates/biome_module_graph/src/css_module_info/traverse.rscrates/biome_module_graph/src/css_module_info/visitor.rscrates/biome_module_graph/src/format_module_graph.rscrates/biome_module_graph/src/html_module_info/mod.rscrates/biome_module_graph/src/html_module_info/visitor.rscrates/biome_module_graph/src/js_module_info.rscrates/biome_module_graph/src/module_graph.rscrates/biome_module_graph/tests/spec_tests.rscrates/biome_service/Cargo.tomlcrates/biome_service/src/diagnostics.rscrates/biome_service/src/file_handlers/astro.rscrates/biome_service/src/file_handlers/css.rscrates/biome_service/src/file_handlers/graphql.rscrates/biome_service/src/file_handlers/grit.rscrates/biome_service/src/file_handlers/html.rscrates/biome_service/src/file_handlers/html/go_to.rscrates/biome_service/src/file_handlers/html/parse_embedded_nodes.tests.rscrates/biome_service/src/file_handlers/javascript.rscrates/biome_service/src/file_handlers/javascript/go_to.rscrates/biome_service/src/file_handlers/json.rscrates/biome_service/src/file_handlers/md.rscrates/biome_service/src/file_handlers/mod.rscrates/biome_service/src/file_handlers/svelte.rscrates/biome_service/src/file_handlers/vue.rscrates/biome_service/src/scanner/watcher.rscrates/biome_service/src/scanner/workspace_scanner_bridge.tests.rscrates/biome_service/src/scanner/workspace_watcher_bridge.tests.rscrates/biome_service/src/settings.rscrates/biome_service/src/workspace.rscrates/biome_service/src/workspace.tests.rscrates/biome_service/src/workspace/client.rscrates/biome_service/src/workspace/document/services/embedded_bindings.rscrates/biome_service/src/workspace/server.rscrates/biome_service/src/workspace/server.tests.rs
| The Biome Language server now supports the "go-to definition" feature. | ||
|
|
||
| When the cursor of the mouse is hovering an entity (variable, CSS class, type, etc.), and the command <kbd>CTRL</kbd> + click is triggered, the editor jumps to where this entity is defined, if the language server can find it. | ||
|
|
||
| Here's what Biome is able to resolve: | ||
| - Variables and types used in JavaScript modules, defined in the same file or imported from another module. | ||
| - JSX Components used in JavaScript modules, defined in the same file or imported from another module. | ||
| - CSS classes used in JSX and HTML-ish files (Vue, Svelte and Astro), and defined in CSS files. | ||
| - Components used in HTML-ish files and defined in other HTML-ish. | ||
| - Variables used in HTML-ish files and defined in the same file or imported from another module (JavaScript or HTML-ish). |
There was a problem hiding this comment.
Tighten the release note copy.
Line 7 is a bit clunky, and line 13 ends mid-sentence. A small rewrite would read more naturally.
Suggested wording
-When the cursor of the mouse is hovering an entity (variable, CSS class, type, etc.), and the command <kbd>CTRL</kbd> + click is triggered, the editor jumps to where this entity is defined, if the language server can find it.
+When hovering an entity (variable, CSS class, type, etc.) and pressing <kbd>CTRL</kbd> + click, the editor jumps to its definition if the language server can find it.
@@
-- Components used in HTML-ish files and defined in other HTML-ish.
+- Components used in HTML-ish files and defined in other HTML-ish files.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| The Biome Language server now supports the "go-to definition" feature. | |
| When the cursor of the mouse is hovering an entity (variable, CSS class, type, etc.), and the command <kbd>CTRL</kbd> + click is triggered, the editor jumps to where this entity is defined, if the language server can find it. | |
| Here's what Biome is able to resolve: | |
| - Variables and types used in JavaScript modules, defined in the same file or imported from another module. | |
| - JSX Components used in JavaScript modules, defined in the same file or imported from another module. | |
| - CSS classes used in JSX and HTML-ish files (Vue, Svelte and Astro), and defined in CSS files. | |
| - Components used in HTML-ish files and defined in other HTML-ish. | |
| - Variables used in HTML-ish files and defined in the same file or imported from another module (JavaScript or HTML-ish). | |
| The Biome Language server now supports the "go-to definition" feature. | |
| When hovering an entity (variable, CSS class, type, etc.) and pressing <kbd>CTRL</kbd> + click, the editor jumps to its definition if the language server can find it. | |
| Here's what Biome is able to resolve: | |
| - Variables and types used in JavaScript modules, defined in the same file or imported from another module. | |
| - JSX Components used in JavaScript modules, defined in the same file or imported from another module. | |
| - CSS classes used in JSX and HTML-ish files (Vue, Svelte and Astro), and defined in CSS files. | |
| - Components used in HTML-ish files and defined in other HTML-ish files. | |
| - Variables used in HTML-ish files and defined in the same file or imported from another module (JavaScript or HTML-ish). |
🧰 Tools
🪛 LanguageTool
[typographical] ~7-~7: Usually, there’s no comma before “if”.
Context: ...or jumps to where this entity is defined, if the language server can find it. Here'...
(IF_NO_COMMA)
[style] ~9-~9: As a shorter alternative for ‘able to’, consider using “can”.
Context: ... server can find it. Here's what Biome is able to resolve: - Variables and types used in ...
(BE_ABLE_TO)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/dirty-lions-talk.md around lines 5 - 14, Tighten the release note
copy by rephrasing the clunky sentence "When the cursor of the mouse is hovering
an entity (variable, CSS class, type, etc.), and the command CTRL + click is
triggered" to something more natural (e.g., "Ctrl+click an entity (variable, CSS
class, type, etc.) to jump to its definition") and finish/clarify the truncated
bullet "Variables used in HTML-ish files and defined in the same file or
imported from another module (JavaScript or HTML-ish)" so it reads as a
complete, parallel item (e.g., "Variables used in HTML-ish files, defined in the
same file or imported from JavaScript or other HTML-ish files"). Make these
edits in .changeset/dirty-lions-talk.md replacing the exact phrases above to
preserve intent and parallel structure.
|
Ok I'll need to send more changes. The bot review was really helpful. Getting there! |
7c2b242 to
b98fef0
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/biome_module_graph/src/module_graph.rs (1)
532-560:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid pushing embedded-script CSS twice.
static_import_pathsare appended in the new 2b loop and again in the loop below, so HTML-like files withimport "./styles.css"can emit duplicate CSS steps and duplicate go-to-definition targets.Suggested trim
- // 3. CSS files imported via static imports from embedded scripts - // (e.g., Astro frontmatter `import "./styles.css"`). - for import_path in html_info - .static_import_paths - .values() - .chain(html_info.dynamic_import_paths.values()) + // 3. CSS files imported dynamically from embedded scripts. + for import_path in html_info.dynamic_import_paths.values() { if let Some(path) = import_path.as_path() && let Some(css_info) = self.css_module_info_for_path(path) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_module_graph/src/module_graph.rs` around lines 532 - 560, The code currently iterates html_info.static_import_paths twice (once in the 2b loop and again in the 3rd loop), causing duplicate CssClassStep entries; update the 3rd loop to only iterate html_info.dynamic_import_paths.values() (remove the .values().chain(html_info.dynamic_import_paths.values()) usage) so that static imports are only handled in the 2b branch; keep the existing guard using css_module_info_for_path and the linked_steps.push(CssClassStep { css_path: ..., css_classes: ... }) logic unchanged.crates/biome_lsp/src/handlers/text_document.rs (1)
223-229:⚠️ Potential issue | 🟠 MajorConfirm editor_features regression: go-to-definition will disappear after first edit/save.
did_openstoreseditor_featuresviachange_file, butdid_changeanddid_savepassNone. Thechange_filemethod (workspace/server.rs:1917) useseditor_features.unwrap_or_default(), which replacesNonewith emptyEditorFeatures. Since theDocumentstruct stores noeditor_featuresfield, there's nothing to preserve—the feature is simply lost on the next file update.Pass the stored editor_features from the document, or update
did_change/did_saveto send the existing features instead ofNone.Also applies to: 255–261
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_lsp/src/handlers/text_document.rs` around lines 223 - 229, The code is dropping editor_features by passing None to session.workspace.change_file in handlers for did_change/did_save (and similarly at lines 255–261); update those calls to pass the document's stored editor_features instead of None (or retrieve existing features from the Document before constructing ChangeFileParams) so ChangeFileParams.editor_features is populated rather than defaulted via editor_features.unwrap_or_default(); reference session.workspace.change_file, ChangeFileParams, Document, and the did_open/did_change/did_save handlers to locate and fix the calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/biome_lsp/src/handlers/navigation.rs`:
- Around line 72-85: The multi-match branch currently uses flat_map over
definition.matches which swallows errors from to_location; change the iterator
to map each (definition_path, range) to the Result returned by to_location, then
collect that iterator into a Result<Vec<_>, E> (e.g.
.map(...).collect::<Result<Vec<_>, _>>() ?) and propagate the error with ? so
failures mirror the single-match branch behavior; after successful collection,
flatten or extend the resulting locations into the final locations Vec as
needed. Reference: to_location, definition.matches, locations.
In `@crates/biome_service/src/file_handlers/css/go_to.rs`:
- Around line 29-36: The selector matching currently uses
selector.resolved().normalize().contains(class_name) which does substring
matching; change it to iterate selector.resolved().tokens() and check for an
exact token pair where the kind is CssSyntaxKind::DOT and the token text equals
class_name (match TokenText or string value exactly) before computing range and
calling range.add(offset) and result.store(BiomePath::new(path), range); update
the matching logic inside the loop over model.rules()/rule.selectors() to use
tokens() rather than normalize().contains(...), and add a regression test that
verifies a lookup for ".foo" does not match a selector like ".foobar".
In `@crates/biome_service/src/file_handlers/javascript/go_to.rs`:
- Around line 26-35: The code currently bails out early by calling
params.services.as_js_services()?.semantic_model.as_ref()? which returns None
when the JS semantic model is missing, preventing the JSX className/class path
from running; instead keep the JSX class-name path syntax-only by making
semantic_model optional (do not use the ? that returns early) — e.g. obtain
semantic_model as an Option (use as_js_services().and_then(|s|
s.semantic_model.as_ref()) or delay fetching the model) and move or check
semantic_model only when needed for other branches, so
extract_css_class_at_offset(jsx_attribute, params.cursor_offset) and the
JsxAttribute -> DefinitionReference::CssClass branch run even if semantic_model
is None.
In `@crates/biome_service/src/workspace/server.rs`:
- Around line 867-895: The loop currently probes the first snippet with a
resolve_binding and returns immediately, even when the cursor isn't in that
snippet or no binding is found; update the logic in the embedded_snippets
iteration so you first check snippet bounds/ownership (use
snippet.content_offset() and snippet length/extent) to ensure cursor_offset lies
inside the snippet before computing local_cursor and calling resolve, call
resolve(ResolveBindingParams { ... }) only for the snippet that contains the
cursor, treat a None/empty resolve result as "not found" and continue to the
next snippet instead of returning, and only adjust the
DefinitionReference::Local range (range + snippet_offset) and return
Ok((adjusted, path.clone(), capabilities)) when a concrete binding/result was
actually found; otherwise let the loop continue until all embedded_snippets are
exhausted.
---
Outside diff comments:
In `@crates/biome_lsp/src/handlers/text_document.rs`:
- Around line 223-229: The code is dropping editor_features by passing None to
session.workspace.change_file in handlers for did_change/did_save (and similarly
at lines 255–261); update those calls to pass the document's stored
editor_features instead of None (or retrieve existing features from the Document
before constructing ChangeFileParams) so ChangeFileParams.editor_features is
populated rather than defaulted via editor_features.unwrap_or_default();
reference session.workspace.change_file, ChangeFileParams, Document, and the
did_open/did_change/did_save handlers to locate and fix the calls.
In `@crates/biome_module_graph/src/module_graph.rs`:
- Around line 532-560: The code currently iterates html_info.static_import_paths
twice (once in the 2b loop and again in the 3rd loop), causing duplicate
CssClassStep entries; update the 3rd loop to only iterate
html_info.dynamic_import_paths.values() (remove the
.values().chain(html_info.dynamic_import_paths.values()) usage) so that static
imports are only handled in the 2b branch; keep the existing guard using
css_module_info_for_path and the linked_steps.push(CssClassStep { css_path: ...,
css_classes: ... }) logic unchanged.
🪄 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: 0cd14ea5-54c5-46fc-88ba-80ceb6f6e9be
📒 Files selected for processing (36)
crates/biome_cli/src/execute/migrate.rscrates/biome_cli/src/runner/impls/process_file/format.rscrates/biome_cli/src/runner/impls/process_file/lint_and_assist.rscrates/biome_cli/src/runner/process_file.rscrates/biome_css_formatter/tests/quick_test.rscrates/biome_formatter_test/src/spec.rscrates/biome_graphql_formatter/tests/quick_test.rscrates/biome_grit_formatter/tests/quick_test.rscrates/biome_html_formatter/tests/quick_test.rscrates/biome_js_formatter/tests/quick_test.rscrates/biome_json_formatter/tests/quick_test.rscrates/biome_lsp/src/extension_settings.rscrates/biome_lsp/src/handlers/navigation.rscrates/biome_lsp/src/handlers/text_document.rscrates/biome_lsp/src/server.tests.rscrates/biome_lsp/src/server_goto.tests.rscrates/biome_lsp/src/session.rscrates/biome_module_graph/src/module_graph.rscrates/biome_service/Cargo.tomlcrates/biome_service/src/file_handlers/css.rscrates/biome_service/src/file_handlers/css/go_to.rscrates/biome_service/src/file_handlers/html/go_to.rscrates/biome_service/src/file_handlers/html/parse_embedded_nodes.rscrates/biome_service/src/file_handlers/html/parse_embedded_nodes.tests.rscrates/biome_service/src/file_handlers/javascript.rscrates/biome_service/src/file_handlers/javascript/go_to.rscrates/biome_service/src/file_handlers/mod.rscrates/biome_service/src/scanner/workspace_scanner_bridge.tests.rscrates/biome_service/src/scanner/workspace_watcher_bridge.tests.rscrates/biome_service/src/settings.rscrates/biome_service/src/workspace.rscrates/biome_service/src/workspace.tests.rscrates/biome_service/src/workspace/document/mod.rscrates/biome_service/src/workspace/server.rscrates/biome_service/src/workspace/server.tests.rscrates/biome_test_utils/src/lib.rs
✅ Files skipped from review due to trivial changes (11)
- crates/biome_service/src/file_handlers/html/parse_embedded_nodes.tests.rs
- crates/biome_css_formatter/tests/quick_test.rs
- crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs
- crates/biome_json_formatter/tests/quick_test.rs
- crates/biome_service/src/workspace/document/mod.rs
- crates/biome_html_formatter/tests/quick_test.rs
- crates/biome_graphql_formatter/tests/quick_test.rs
- crates/biome_service/src/scanner/workspace_watcher_bridge.tests.rs
- crates/biome_cli/src/runner/process_file.rs
- crates/biome_js_formatter/tests/quick_test.rs
- crates/biome_service/src/scanner/workspace_scanner_bridge.tests.rs
🚧 Files skipped from review as they are similar to previous changes (9)
- crates/biome_cli/src/runner/impls/process_file/format.rs
- crates/biome_formatter_test/src/spec.rs
- crates/biome_grit_formatter/tests/quick_test.rs
- crates/biome_cli/src/execute/migrate.rs
- crates/biome_lsp/src/extension_settings.rs
- crates/biome_lsp/src/session.rs
- crates/biome_service/src/file_handlers/html/go_to.rs
- crates/biome_lsp/src/server_goto.tests.rs
- crates/biome_service/src/workspace/server.tests.rs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.changeset/dirty-lions-talk.md (1)
5-14:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPlease tighten and complete the release note copy.
Line 7 reads clunky, and Line 13 ends abruptly (“other HTML-ish”). The entry is also longer than the 1–3 sentence changeset guidance.
✍️ Suggested rewrite
-The Biome Language server now supports the "go-to definition" feature. - -When the cursor of the mouse is hovering an entity (variable, CSS class, type, etc.), and the command <kbd>CTRL</kbd> + click is triggered, the editor jumps to where this entity is defined, if the language server can find it. - -Here's what Biome is able to resolve: -- Variables and types used in JavaScript modules, defined in the same file or imported from another module. -- JSX Components used in JavaScript modules, defined in the same file or imported from another module. -- CSS classes used in JSX and HTML-ish files (Vue, Svelte and Astro), and defined in CSS files. -- Components used in HTML-ish files and defined in other HTML-ish. -- Variables used in HTML-ish files and defined in the same file or imported from another module (JavaScript or HTML-ish). +Biome Language Server gained support for `go-to definition`. +When hovering an entity (variable, CSS class, type, etc.) and pressing <kbd>CTRL</kbd> + click, the editor now jumps to its definition when the language server can resolve it. +Biome now resolves definitions for JavaScript and HTML-ish variables/types/components (including imports), and CSS classes used in JSX/HTML-ish files and defined in CSS files.As per coding guidelines: “Write changeset descriptions concisely and clearly (1-3 sentences) … End every sentence in changeset descriptions with a full stop (period).”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.changeset/dirty-lions-talk.md around lines 5 - 14, Replace the long bulleted explanation with a concise 1–3 sentence changeset that ends every sentence with a period; rewrite the CTRL + click sentence (currently "When the cursor of the mouse is hovering an entity...") to a shorter, clearer sentence like "Use Ctrl+click to jump to an entity's definition when available." and fix the incomplete phrase "other HTML-ish" to "other HTML-ish files." Ensure you keep a brief list of supported resolutions (variables/types, JSX components, CSS classes, and components/variables in HTML-ish files) compressed into one sentence or two, and terminate all sentences with periods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.changeset/dirty-lions-talk.md:
- Around line 5-14: Replace the long bulleted explanation with a concise 1–3
sentence changeset that ends every sentence with a period; rewrite the CTRL +
click sentence (currently "When the cursor of the mouse is hovering an
entity...") to a shorter, clearer sentence like "Use Ctrl+click to jump to an
entity's definition when available." and fix the incomplete phrase "other
HTML-ish" to "other HTML-ish files." Ensure you keep a brief list of supported
resolutions (variables/types, JSX components, CSS classes, and
components/variables in HTML-ish files) compressed into one sentence or two, and
terminate all sentences with periods.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 96d0ad24-6496-4cf8-8f97-01df365dd39e
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lockand included by**
📒 Files selected for processing (12)
.changeset/dirty-lions-talk.mdcrates/biome_cli/src/execute/migrate.rscrates/biome_cli/src/runner/impls/process_file/format.rscrates/biome_cli/src/runner/impls/process_file/lint_and_assist.rscrates/biome_cli/src/runner/process_file.rscrates/biome_css_formatter/tests/quick_test.rscrates/biome_formatter_test/src/spec.rscrates/biome_graphql_formatter/tests/quick_test.rscrates/biome_grit_formatter/tests/quick_test.rscrates/biome_html_analyze/src/lint/nursery/no_undeclared_classes.rscrates/biome_html_formatter/tests/quick_test.rscrates/biome_js_analyze/src/lint/nursery/no_undeclared_classes.rs
✅ Files skipped from review due to trivial changes (7)
- crates/biome_cli/src/runner/process_file.rs
- crates/biome_cli/src/runner/impls/process_file/lint_and_assist.rs
- crates/biome_html_formatter/tests/quick_test.rs
- crates/biome_graphql_formatter/tests/quick_test.rs
- crates/biome_html_analyze/src/lint/nursery/no_undeclared_classes.rs
- crates/biome_css_formatter/tests/quick_test.rs
- crates/biome_formatter_test/src/spec.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- crates/biome_cli/src/runner/impls/process_file/format.rs
- crates/biome_grit_formatter/tests/quick_test.rs
- crates/biome_cli/src/execute/migrate.rs
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/biome_module_graph/src/module_graph.rs (1)
532-560:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid collecting embedded-script CSS imports twice.
The new
static_import_pathspass is redundant with the loop just below, which already walksstatic_import_pathsanddynamic_import_paths. Static CSS imports from embedded<script>blocks therefore land inlinked_stepstwice, which can bubble up as duplicate available classes and duplicate go-to targets. One pass is plenty.✂️ Minimal tidy-up
- // 2b. CSS files imported from embedded <script> blocks - // (e.g., `import "./index.css"` in Astro frontmatter). - for import_path in html_info.static_import_paths.values() { - if let Some(path) = import_path.as_path() - && let Some(css_info) = self.css_module_info_for_path(path) - { - linked_steps.push(CssClassStep { - css_path: path.to_path_buf(), - css_classes: css_info.classes.clone(), - }); - } - } - // 3. CSS files imported via static imports from embedded scripts // (e.g., Astro frontmatter `import "./styles.css"`). for import_path in html_info .static_import_paths .values()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_module_graph/src/module_graph.rs` around lines 532 - 560, The first loop that iterates html_info.static_import_paths and pushes CssClassStep entries duplicates work done by the subsequent loop that walks html_info.static_import_paths.chain(html_info.dynamic_import_paths); remove the earlier loop (the one labeled "2b. CSS files imported from embedded <script> blocks") so css_module_info_for_path results are only collected once into linked_steps (keep the combined loop that uses static_import_paths.chain(dynamic_import_paths) and ensure it still pushes CssClassStep with css_path and css_classes as before).
♻️ Duplicate comments (1)
crates/biome_service/src/workspace/server.rs (1)
867-874:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCheck snippet ownership before offset arithmetic.
Line 869 subtracts
snippet_offsetbefore Line 872 proves the cursor is inside that snippet. In multi-embed files, a cursor before the current snippet can underflow here and send resolution off into the weeds. Tiny bug, surprisingly pointy.Possible fix
for snippet in embedded_snippets { - let snippet_offset = snippet.content_offset(); - let local_cursor = cursor_offset - snippet_offset; - let snippet_range = snippet.content_range(); if cursor_offset < snippet_range.start() || cursor_offset >= snippet_range.end() { continue; } + + let snippet_offset = snippet.content_offset(); + let local_cursor = cursor_offset - snippet_offset; let Some(file_source) = self.get_source(snippet.file_source_index()) else { continue; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_service/src/workspace/server.rs` around lines 867 - 874, The code performs subtraction of snippet.content_offset() into local_cursor before verifying the cursor lies within the snippet, which can underflow for earlier snippets; fix by moving the calculation of local_cursor (or using checked_sub) to after the range check that uses snippet.content_range(), or use snippet.content_offset().checked_sub(...) / cursor_offset.checked_sub(snippet_offset) and continue if None, ensuring you reference embedded_snippets, snippet.content_offset(), snippet.content_range(), local_cursor and cursor_offset when applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/biome_lsp/src/server_goto.tests.rs`:
- Around line 61-85: The test currently calls .context("goto_definition returned
None")? after awaiting server.request which converts
Option<lsp::GotoDefinitionResponse> into a Result<lsp::GotoDefinitionResponse>
and makes res non-optional; remove the .context("goto_definition returned
None")? suffix and simply use .await? so that res retains the
Option<lsp::GotoDefinitionResponse> type expected by the function and tests
(locate the call to server.request(...) for "textDocument/definition" /
"goto_definition" and remove the .context(...) chain).
In `@crates/biome_module_graph/src/module_graph.rs`:
- Around line 315-344: find_css_class_definition currently only checks CSS files
directly reached from the JS/HTML traversals and ignores transitive
CssModuleInfo.imports, so classes in CSS files imported by other CSS files are
missed; update the lookup to recursively follow CssModuleInfo.imports (e.g.,
add/extend a helper such as traverse_css_imports or augment
traverse_import_tree_for_classes/for_html_classes) and maintain a visited
HashSet to prevent cycles, collecting matching class ranges from each transitive
css_path; also add a regression test that exercises a chain like App.tsx ->
app.css -> components.css and verifies go-to-definition finds classes in
components.css.
In `@crates/biome_service/src/file_handlers/javascript/go_to.rs`:
- Around line 143-186: resolve_definition currently always returns Some(result)
even when no definitions were stored by resolve_import_definition,
resolve_dynamic_import_definition, or the LocalEmbedded branch without an
offset; change it to return None when the resulting GoToDefinitionResult is
empty. After the match block in resolve_definition, check whether the
accumulated result is empty (e.g. result.is_empty() or
result.matches.is_empty()) and return None in that case, otherwise return
Some(result); keep all existing calls to result.store and the
DefinitionReference branches unchanged.
In `@crates/biome_service/src/workspace/server.rs`:
- Around line 2751-2763: The code currently adds GotoDefinition to the
capability and only uses params.enabled in the capability calculation and
needs_document_services() path, but callers can still run document services when
params.enabled is false if lint or assist are enabled; to fix, short-circuit
early by returning Ok(None) when params.enabled is false (i.e., add a guard
checking params.enabled before computing capability or evaluating
has_document_services), or incorporate params.enabled into the
has_document_services check; update the logic around params.enabled,
EditorFeatures, and the call to self.settings_handle_with_features(&settings,
None, capability) (and the subsequent has_document_services =
settings.needs_document_services() || settings.as_ref().is_linter_enabled() ||
settings.as_ref().is_assist_enabled()) so that when params.enabled == false the
function exits without running go-to-definition or other document-resolution
code.
---
Outside diff comments:
In `@crates/biome_module_graph/src/module_graph.rs`:
- Around line 532-560: The first loop that iterates
html_info.static_import_paths and pushes CssClassStep entries duplicates work
done by the subsequent loop that walks
html_info.static_import_paths.chain(html_info.dynamic_import_paths); remove the
earlier loop (the one labeled "2b. CSS files imported from embedded <script>
blocks") so css_module_info_for_path results are only collected once into
linked_steps (keep the combined loop that uses
static_import_paths.chain(dynamic_import_paths) and ensure it still pushes
CssClassStep with css_path and css_classes as before).
---
Duplicate comments:
In `@crates/biome_service/src/workspace/server.rs`:
- Around line 867-874: The code performs subtraction of snippet.content_offset()
into local_cursor before verifying the cursor lies within the snippet, which can
underflow for earlier snippets; fix by moving the calculation of local_cursor
(or using checked_sub) to after the range check that uses
snippet.content_range(), or use snippet.content_offset().checked_sub(...) /
cursor_offset.checked_sub(snippet_offset) and continue if None, ensuring you
reference embedded_snippets, snippet.content_offset(), snippet.content_range(),
local_cursor and cursor_offset when applying the change.
🪄 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: 7c71ef3e-fdd4-4f7c-8b3e-29f03d457d99
⛔ Files ignored due to path filters (1)
packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**
📒 Files selected for processing (16)
crates/biome_html_analyze/src/lint/nursery/no_undeclared_classes.rscrates/biome_js_analyze/src/lint/nursery/no_undeclared_classes.rscrates/biome_lsp/src/handlers/navigation.rscrates/biome_lsp/src/server_goto.tests.rscrates/biome_module_graph/src/css_module_info/mod.rscrates/biome_module_graph/src/css_module_info/traverse.rscrates/biome_module_graph/src/css_module_info/visitor.rscrates/biome_module_graph/src/format_module_graph.rscrates/biome_module_graph/src/module_graph.rscrates/biome_module_graph/tests/spec_tests.rscrates/biome_service/src/file_handlers/css/go_to.rscrates/biome_service/src/file_handlers/javascript/go_to.rscrates/biome_service/src/settings.rscrates/biome_service/src/workspace.rscrates/biome_service/src/workspace/server.rscrates/biome_service/src/workspace/server.tests.rs
✅ Files skipped from review due to trivial changes (2)
- crates/biome_html_analyze/src/lint/nursery/no_undeclared_classes.rs
- crates/biome_js_analyze/src/lint/nursery/no_undeclared_classes.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_service/src/settings.rs
dyc3
left a comment
There was a problem hiding this comment.
I just tried this on my vue project.
It worked for:
- Components used in the template section, where the component was imported with a relative import like
./Foo.vue - All cases where a js variable was declared in the same contiguous js snippet (so the
<script>section in a vue component, or a whole .ts file) - CSS Classes used in the template section that are defined in the same file's
<style>section.
It did not work for these cases:
- All cases where the definition was in another package and the file was a .vue component.
- Components used in the template section, where the component was imported with a path alias like
@/components/Foo.vue(seems more like a bug in the module graph) - variables used in embedded expressions like
v-if,v-else-if, or interpolation.
I'm also concerned that this will override or conflict with the functionality of existing tools that already implement this. Does the lsp spec say anything about it? We should probably provide a way to disable it.
The client is in charge of it, there's no override, the signals are joined together. The client shows multiple definitions and the user decides which one to follow.
It's already there and implemented |
|
Ah ok, cool. What about the rest of the stuff I mentioned? Or do we want to defer it to another PR https://github.com/dyc3/opentogethertube is the repo i tested on. |
|
I'll look into those. First should be easy, second might be a bug, second probably to defer to later |
7f24c7f to
875123c
Compare
|
@dyc3 Addressed 1 and 2, which belonged to the same bug: when the extension setting was on, we didn't enable the module graph, hence 1 wasn't properly resolved (plus some other bug). Now that the module graph is enabled, 2 should work now. |
Note
I used AI mostly for tests and bootstrapped the initial code, but rewrote most of it manually as I added more features.
Summary
Part of #9618
This is a follow-up of #9619 to complete the feature.
The majority of the architectural work is described in the PR. This PR implements the remaining features that were on the list.
I had to create a new function called
get_real_capabilitiesbecause the old one wouldn't work for this feature, and changing it would break existing functionality.I also refactored how the
SettingsHandleis computed. It now accepts both inline config, and the newEditorFeatures. Cleaner and more extensible, and we don't need to use two handles.I want to keep the issue open so that users can leave feedback there.
Test Plan
Added other tests.
Docs
biomejs/website#4197