Skip to content

Conversation

@ho991217
Copy link
Contributor

@ho991217 ho991217 commented Nov 25, 2025

This PR was written primarily by Claude Sonnet 4.5.

Summary

Fixed stale diagnostics when the same file is opened in multiple VSCode instances with nested workspaces.

Problem

Closes biomejs/biome-vscode#817

When the same file is opened in multiple VSCode instances (e.g., repo/packages/package-a/test.json in one instance and package-a/test.json in another nested workspace):

  1. Editing the file in one instance and introducing a syntax error
  2. Then fixing the error
  3. Results in one of the instances showing diagnostics at wrong positions based on stale file content, even though the actual file has no errors

This happens because the LSP server's internal document state could become out of sync with the actual file content on disk when external modifications occur.

Solution

Implemented the textDocument/didSave handler to reload file content from disk on save:

  • Closes and reopens the file with FileContent::FromServer to force a fresh read from disk
  • Syncs the internal Document object with the reloaded content
  • Updates diagnostics to reflect the actual file state

This ensures that on every save, the LSP server's state is synchronized with the disk, resolving any stale state issues caused by concurrent edits from multiple editors.

Test Plan

  1. Open repo/packages/package-a/test.json in VSCode instance A
  2. Open the same file via package-a/test.json in VSCode instance B (nested workspace)
  3. In one instance, introduce a JSON syntax error (e.g., replace a comma with a period)
  4. Save the file
  5. Fix the syntax error and save again
  6. Verify that both instances show correct diagnostics (no false positives from stale content)

Docs

No documentation changes required. This is an internal LSP handler fix.

@changeset-bot
Copy link

changeset-bot bot commented Nov 25, 2025

🦋 Changeset detected

Latest commit: c689419

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

@github-actions github-actions bot added the A-LSP Area: language server protocol label Nov 25, 2025
@ho991217 ho991217 changed the title Handle multiple lsp instances fix: reload file from disk on save to fix stale diagnostics Nov 25, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds a changelog entry and implements LSP textDocument/didSave handling. Introduces a public did_save(session: &Session, params: lsp_types::DidSaveTextDocumentParams) -> Result<(), LspError> handler that, when text is provided, updates the on‑disk file via workspace change, replaces the in‑memory Document with that text, and attempts to refresh diagnostics (errors logged). LSPServer gains an async did_save(&self, params: DidSaveTextDocumentParams) method that forwards to the handler and swallows errors. Adds a test did_save_syncs_content_from_text_parameter.

Suggested reviewers

  • ematipico
  • dyc3

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning There is a mismatch between the PR's implementation focus and the linked issue #817. The PR implements didSave to reload from disk, but issue #817 concerns multiple Biome entries in formatter selection and file corruption from formatter conflicts, not stale diagnostics from concurrent edits. Clarify whether this PR addresses the actual problem in #817 (multiple formatter entries causing corruption) or if it's solving a different stale diagnostics issue. The linked issue may need a different fix approach.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing didSave handler to reload files and fix stale diagnostics.
Description check ✅ Passed The description is well-structured, explaining the problem (stale diagnostics with multiple VSCode instances), solution (didSave handler), and test plan.
Out of Scope Changes check ✅ Passed All changes (didSave handler, tests, imports) are directly in scope for implementing the textDocument/didSave synchronisation feature described in the PR objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57dd6d3 and c689419.

📒 Files selected for processing (1)
  • crates/biome_lsp/src/server.tests.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust dbg!() macro for debugging output during test execution, and pass the --show-output flag to cargo test to display debug output.
Use snapshot testing with the insta crate for testing in Rust projects. Accept or reject snapshots using cargo insta accept, cargo insta reject, or cargo insta review.
Write doc comments as doc tests in Rust using code blocks with assertions that will be executed during the testing phase.
Use rustdoc inline documentation for rules, assists, and their options. Create corresponding documentation PRs for other documentation updates against the next branch of the website repository.
Set the version metadata field in linter rule implementations to 'next' for newly created rules. Update this field to the new version number when releasing a minor or major version.

Files:

  • crates/biome_lsp/src/server.tests.rs
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:05:20.371Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:20.371Z
Learning: Applies to crates/biome_formatter/**/biome_*_formatter/tests/language.rs : Implement `TestFormatLanguage` trait in `tests/language.rs` for the formatter's test language

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:03:52.024Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:03:52.024Z
Learning: Applies to **/*.rs : Write doc comments as doc tests in Rust using code blocks with assertions that will be executed during the testing phase.

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace*.rs : Implement the Workspace trait in the Biome Service to manage internal state of projects, including open documents, project layout instances, and module graph instances

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/client.rs : Use WorkspaceClient implementation for creating connections to the daemon and communicating with WorkspaceServer

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/server.rs : Use WorkspaceServer implementation for maintaining workspace state in daemon mode and CLI daemonless mode

Applied to files:

  • crates/biome_lsp/src/server.tests.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
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

🧹 Nitpick comments (1)
crates/biome_lsp/src/handlers/text_document.rs (1)

194-239: did_save handler logic looks good; consider adding a regression test

The save handler nicely forces a reindex from disk (close → reopen with FileContent::FromServer → refresh Document → update diagnostics), which matches the described stale-diagnostics bug and aligns with the existing did_open/did_change patterns.

Given this is a subtle multi-instance / on-disk synchronisation issue, it would be great (when you have the patience) to back it with an LSP-level regression test in the existing server.tests.rs/LSP test harness, so we don’t accidentally break this flow in future. Based on learnings, similar workspace behaviours are usually covered with dedicated watcher/LSP tests.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a9139b and f8175b2.

📒 Files selected for processing (3)
  • .changeset/wet-keys-enjoy.md (1 hunks)
  • crates/biome_lsp/src/handlers/text_document.rs (1 hunks)
  • crates/biome_lsp/src/server.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Changeset descriptions should be user-facing, use past tense for actions taken (e.g., 'Added new feature'), and present tense for Biome behavior (e.g., 'Biome now supports...'). Include issue links, rule links, and code examples where applicable.

Files:

  • .changeset/wet-keys-enjoy.md
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust dbg!() macro for debugging output during test execution, and pass the --show-output flag to cargo test to display debug output.
Use snapshot testing with the insta crate for testing in Rust projects. Accept or reject snapshots using cargo insta accept, cargo insta reject, or cargo insta review.
Write doc comments as doc tests in Rust using code blocks with assertions that will be executed during the testing phase.
Use rustdoc inline documentation for rules, assists, and their options. Create corresponding documentation PRs for other documentation updates against the next branch of the website repository.
Set the version metadata field in linter rule implementations to 'next' for newly created rules. Update this field to the new version number when releasing a minor or major version.

Files:

  • crates/biome_lsp/src/server.rs
  • crates/biome_lsp/src/handlers/text_document.rs
🧠 Learnings (3)
📚 Learning: 2025-11-24T18:03:52.014Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:03:52.014Z
Learning: For bugfix/feature PRs visible to Biome toolchain users or affecting published crates, create a changeset using the `just new-changeset` command with appropriate package selection, change type (major/minor/patch), and description.

Applied to files:

  • .changeset/wet-keys-enjoy.md
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.

Applied to files:

  • .changeset/wet-keys-enjoy.md
📚 Learning: 2025-11-24T18:06:12.017Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.017Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests

Applied to files:

  • crates/biome_lsp/src/server.rs
🧬 Code graph analysis (2)
crates/biome_lsp/src/server.rs (1)
crates/biome_lsp/src/handlers/text_document.rs (1)
  • did_save (196-239)
crates/biome_lsp/src/handlers/text_document.rs (2)
crates/biome_lsp/src/server.rs (4)
  • params (367-371)
  • did_save (408-412)
  • new (49-61)
  • new (580-596)
crates/biome_lsp/src/documents.rs (1)
  • new (15-21)
🔇 Additional comments (1)
crates/biome_lsp/src/server.rs (1)

408-412: did_save wiring matches existing textDocument handlers

Forwarding to handlers::text_document::did_save and swallowing the result with .await.ok() is consistent with did_open / did_change / did_close, so the integration looks spot on.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Can you please add a test so we make sure that the new function does we expect?

@ho991217
Copy link
Contributor Author

Can you please add a test so we make sure that the new function does we expect?

Added at 8670c1f

Copy link
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: 0

🧹 Nitpick comments (1)
crates/biome_lsp/src/server.tests.rs (1)

4149-4237: Well-structured test for the content-sync mechanism.

The test properly validates that didSave triggers a reload from disk, which is the core fix for the stale diagnostics issue. The use of MemoryFileSystem to simulate external edits is appropriate, and the test follows established patterns in this file.

One optional consideration: whilst this test verifies content syncing, it doesn't verify that diagnostics are updated post-save. This may be intentional (focusing the unit test on the sync mechanism), but you might want to ensure diagnostic updates are covered in integration tests or add a follow-up assertion here checking that diagnostics reflect the new content.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc016c2 and 8670c1f.

📒 Files selected for processing (1)
  • crates/biome_lsp/src/server.tests.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust dbg!() macro for debugging output during test execution, and pass the --show-output flag to cargo test to display debug output.
Use snapshot testing with the insta crate for testing in Rust projects. Accept or reject snapshots using cargo insta accept, cargo insta reject, or cargo insta review.
Write doc comments as doc tests in Rust using code blocks with assertions that will be executed during the testing phase.
Use rustdoc inline documentation for rules, assists, and their options. Create corresponding documentation PRs for other documentation updates against the next branch of the website repository.
Set the version metadata field in linter rule implementations to 'next' for newly created rules. Update this field to the new version number when releasing a minor or major version.

Files:

  • crates/biome_lsp/src/server.tests.rs
🧠 Learnings (4)
📚 Learning: 2025-11-24T18:06:12.017Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.017Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.017Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.017Z
Learning: Applies to crates/biome_service/src/workspace*.rs : Implement the Workspace trait in the Biome Service to manage internal state of projects, including open documents, project layout instances, and module graph instances

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.017Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.017Z
Learning: Applies to crates/biome_service/src/workspace/client.rs : Use WorkspaceClient implementation for creating connections to the daemon and communicating with WorkspaceServer

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.017Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.017Z
Learning: Applies to crates/biome_service/src/workspace/server.rs : Use WorkspaceServer implementation for maintaining workspace state in daemon mode and CLI daemonless mode

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
🔇 Additional comments (2)
crates/biome_lsp/src/server.tests.rs (2)

34-37: LGTM!

Import addition is necessary for the new didSave functionality and properly integrated into the existing import list.


374-383: LGTM!

The helper method follows the established pattern of other document lifecycle methods (didOpen, didChange, didClose) and correctly sets text: None for standard didSave behavior.

Comment on lines 4135 to 4153

// #endregion

#[tokio::test]
async fn did_save_syncs_content_from_disk() -> Result<()> {
// ARRANGE: Set up file system with initial content
const INITIAL_CONTENT: &str = "const a = 1;";
const DISK_CONTENT: &str = "const b = 2;";
Copy link
Member

Choose a reason for hiding this comment

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

@ho991217 I would appreciate it if you could look at what the AI is doing. It placed the tests in the wrong section of the file. See the region comments? Please make sure the test is placed before line 4147

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved the test to just before the “MONOREPO TESTS” region, which feels more appropriate. 280c3ff

Comment on lines 207 to 220
// Trigger reindexing from disk to sync with saved content
session.workspace.close_file(CloseFileParams {
project_key: doc.project_key,
path: path.clone(),
})?;

// Reopen will load from disk
session.workspace.open_file(OpenFileParams {
project_key: doc.project_key,
path: path.clone(),
content: FileContent::FromServer,
document_file_source: None,
persist_node_cache: true,
})?;
Copy link
Member

Choose a reason for hiding this comment

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

Why do we close and open the file? Wouldn't it be better to use change_file?

Copy link
Contributor Author

@ho991217 ho991217 Nov 27, 2025

Choose a reason for hiding this comment

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

We use close_file and open_file instead of change_file to ensure synchronization when multiple LSP instances watch the same file.

Using change_file would only update with the in-memory content, which might not match what's actually on disk.

image

Comment on lines 222 to 231
// Sync the Document object with the content reloaded from disk
let fresh_content = session.workspace.get_file_content(GetFileContentParams {
project_key: doc.project_key,
path,
})?;

session.insert_document(
url.clone(),
Document::new(doc.project_key, doc.version, &fresh_content),
);
Copy link
Member

Choose a reason for hiding this comment

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

As per spec, the notification could have the new text: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didSave

If so, we should use that text instead

Copy link
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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 280c3ff and fffa606.

📒 Files selected for processing (2)
  • crates/biome_lsp/src/handlers/text_document.rs (1 hunks)
  • crates/biome_lsp/src/server.tests.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_lsp/src/handlers/text_document.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the Rust dbg!() macro for debugging output during test execution, and pass the --show-output flag to cargo test to display debug output.
Use snapshot testing with the insta crate for testing in Rust projects. Accept or reject snapshots using cargo insta accept, cargo insta reject, or cargo insta review.
Write doc comments as doc tests in Rust using code blocks with assertions that will be executed during the testing phase.
Use rustdoc inline documentation for rules, assists, and their options. Create corresponding documentation PRs for other documentation updates against the next branch of the website repository.
Set the version metadata field in linter rule implementations to 'next' for newly created rules. Update this field to the new version number when releasing a minor or major version.

Files:

  • crates/biome_lsp/src/server.tests.rs
🧠 Learnings (4)
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace*.rs : Implement the Workspace trait in the Biome Service to manage internal state of projects, including open documents, project layout instances, and module graph instances

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/client.rs : Use WorkspaceClient implementation for creating connections to the daemon and communicating with WorkspaceServer

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/server.rs : Use WorkspaceServer implementation for maintaining workspace state in daemon mode and CLI daemonless mode

Applied to files:

  • crates/biome_lsp/src/server.tests.rs
🧬 Code graph analysis (1)
crates/biome_lsp/src/server.tests.rs (6)
crates/biome_service/src/workspace/client.rs (1)
  • fs (210-212)
crates/biome_lsp/src/session.rs (1)
  • new (199-223)
crates/biome_formatter/src/lib.rs (10)
  • new (890-892)
  • new (965-967)
  • new (1053-1065)
  • new (1290-1292)
  • new (1340-1342)
  • new (2109-2117)
  • new (2236-2238)
  • default (306-308)
  • default (408-410)
  • default (675-677)
crates/biome_fs/src/fs/os.rs (3)
  • new (33-37)
  • new (416-433)
  • default (41-46)
crates/biome_lsp/src/server.rs (2)
  • default (573-575)
  • new_with_fs (599-611)
crates/biome_configuration/src/analyzer/mod.rs (4)
  • default (86-88)
  • default (132-134)
  • default (363-365)
  • default (1120-1122)
🔇 Additional comments (1)
crates/biome_lsp/src/server.tests.rs (1)

34-34: LGTM!

The import is correctly placed and necessary for the new didSave test.

Comment on lines 3900 to 3971
#[tokio::test]
async fn did_save_syncs_content_from_text_parameter() -> Result<()> {
const INITIAL_CONTENT: &str = "const a=1;";
const SAVED_CONTENT: &str = "const b=2;";

let fs = Arc::new(MemoryFileSystem::default());
let file_path = to_utf8_file_path_buf(uri!("document.js"));
fs.insert(file_path, INITIAL_CONTENT);

let factory = ServerFactory::new_with_fs(fs.clone());
let (service, client) = factory.create().into_inner();
let (stream, sink) = client.split();
let mut server = Server::new(service);

let (sender, _) = channel(CHANNEL_BUFFER_SIZE);
let reader = tokio::spawn(client_handler(stream, sink, sender));

server.initialize().await?;
server.initialized().await?;

server.open_document(INITIAL_CONTENT).await?;

// Send didSave with text parameter (as per LSP spec)
server
.notify(
"textDocument/didSave",
DidSaveTextDocumentParams {
text_document: TextDocumentIdentifier {
uri: uri!("document.js"),
},
text: Some(SAVED_CONTENT.to_string()),
},
)
.await?;

// Format the document to verify the content was updated
let edits: Option<Vec<TextEdit>> = server
.request(
"textDocument/formatting",
"formatting",
DocumentFormattingParams {
text_document: TextDocumentIdentifier {
uri: uri!("document.js"),
},
options: FormattingOptions {
tab_size: 4,
insert_spaces: false,
properties: HashMap::default(),
trim_trailing_whitespace: None,
insert_final_newline: None,
trim_final_newlines: None,
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
},
)
.await?
.context("formatting returned None")?;

// If content was properly updated to "const b=2;",
// formatting should add spaces around = and ;
let edits = edits.context("formatting did not return edits")?;
assert!(!edits.is_empty(), "Formatting should produce edits for 'const b=2;'");

server.close_document().await?;

server.shutdown().await?;
reader.abort();

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Strengthen the test assertion to verify content was actually updated.

The test assertion at line 3963 only checks that formatting produces edits, but both INITIAL_CONTENT and SAVED_CONTENT would produce formatting edits. If the didSave handler fails to update the content, the test would still pass by formatting the old content.

To properly verify that didSave updated the content, check that the formatting edits reference the new variable name 'b' (from SAVED_CONTENT) rather than 'a' (from INITIAL_CONTENT). For example:

// Verify edits are for the saved content (variable 'b', not 'a')
let formatted_text = edits.iter()
    .fold(SAVED_CONTENT.to_string(), |text, edit| {
        // Apply edits or check edit positions correspond to SAVED_CONTENT structure
        text
    });
assert!(formatted_text.contains("const b"), "Formatted text should contain variable 'b' from saved content");

Alternatively, use get_file_content to verify the document content matches SAVED_CONTENT before formatting, similar to the pattern in change_document_remove_line (lines 2606-2622).

🤖 Prompt for AI Agents
In crates/biome_lsp/src/server.tests.rs around lines 3900 to 3971, the test only
asserts that formatting produced edits but does not confirm the document was
actually updated to SAVED_CONTENT; update the test to verify the saved content
is present before/after formatting. After sending the didSave notification,
either (1) read the file content from the MemoryFileSystem (or use the existing
get_file_content helper pattern around lines 2606-2622) and assert it equals
SAVED_CONTENT, or (2) apply the returned TextEdits to SAVED_CONTENT (or fold
edits starting from SAVED_CONTENT) and assert the resulting text contains "const
b" (or "const b=") to ensure the edits target the saved content rather than the
initial content.

@ho991217
Copy link
Contributor Author

On second thought, it’s likely better to update the document from the ‎textDocument/didSave notification’s text parameter instead of reloading from the fs.

In parallel, the VS Code extension needs a change to include the saved buffer text in ‎didSave, which would resolve biomejs/biome-vscode#817.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Member

@ematipico ematipico 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! Thank you for addressing the issue. Users will be pleased!

@ematipico ematipico merged commit a226b28 into biomejs:main Nov 27, 2025
13 checks passed
@github-actions github-actions bot mentioned this pull request Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-LSP Area: language server protocol

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Multiple biome entries from vscode workspace causes corrupted formatting

3 participants