refactor(lsp): ToolTrait consume TextDocument & CodeActionParams (#26339)

Refactors the `oxc_language_server::Tool` trait and associated LSP plumbing to pass `TextDocument` and `CodeActionParams` by value, aligning tool APIs with “consume” semantics and simplifying call sites that previously had to keep borrowed values alive.
This commit is contained in:
Sysix committed 2026-09-05 18:33:58 +00:00
1 parent 2c9a947363
commit a5a4ac8811
7 files changed
+56 -59

No files matched your search

+1 -1
View File
@@ -234,7 +234,7 @@ impl Tool for ServerFormatter {
ToolRestartChanges { tool: None, watch_patterns: None, client_messages: Vec::new() }
}
fn run_format(&self, document: &TextDocument) -> Result<Vec<TextEdit>, String> {
fn run_format(&self, document: TextDocument) -> Result<Vec<TextEdit>, String> {
let file_content;
let (result, source_text) = if document.uri.scheme().as_str() == "file" {
let Some(path) = document.uri.to_file_path() else {
+13 -13
View File
@@ -578,7 +578,7 @@ impl Tool for ServerLinter {
}))
}
fn get_code_actions_or_commands(&self, params: &CodeActionParams) -> Vec<CodeActionOrCommand> {
fn get_code_actions_or_commands(&self, params: CodeActionParams) -> Vec<CodeActionOrCommand> {
let actions = self.get_code_actions_for_uri(
&params.uri,
params.context.trigger_kind,
@@ -671,14 +671,14 @@ impl Tool for ServerLinter {
/// Lint a file with the current linter
/// - If the file is not lintable or ignored, an empty vector is returned
fn run_diagnostic(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic(&self, document: TextDocument) -> DiagnosticResult {
Ok(vec![(document.uri.clone(), self.run_file(document.uri, document.text.as_deref())?)])
}
/// Lint a file with the current linter
/// - If the file is not lintable or ignored, an empty vector is returned
/// - If the linter is not set to `OnType`, an empty vector is returned
fn run_diagnostic_on_change(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_change(&self, document: TextDocument) -> DiagnosticResult {
if self.run != Run::OnType {
return Ok(vec![]);
}
@@ -688,7 +688,7 @@ impl Tool for ServerLinter {
/// Lint a file with the current linter
/// - If the file is not lintable or ignored, an empty vector is returned
/// - If the linter is not set to `OnSave`, an empty vector is returned
fn run_diagnostic_on_save(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_save(&self, document: TextDocument) -> DiagnosticResult {
if self.run != Run::OnSave {
return Ok(vec![]);
}
@@ -1203,7 +1203,7 @@ mod test {
let _ = linter.run_file(&uri, Some("let a = 1;")).unwrap();
// source.fixAll should only return safe fixes, not dangerous ones
let safe_actions = linter.get_code_actions_or_commands(&code_action_params(
let safe_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext {
@@ -1214,7 +1214,7 @@ mod test {
assert!(safe_actions.is_empty(), "source.fixAll should not apply dangerous fixes");
// source.fixAllDangerous.oxc should return dangerous fix actions when fix_kind is dangerous
let dangerous_actions = linter.get_code_actions_or_commands(&code_action_params(
let dangerous_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext {
@@ -1236,7 +1236,7 @@ mod test {
let range = Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX));
let uri = tester.get_file_uri("quickfix.js");
let _ = linter.run_file(&uri, Some("if (foo == NaN) {}")).unwrap();
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext::default(),
@@ -1247,7 +1247,7 @@ mod test {
"Default Context: Should return 3 code actions: 1 rule fix + 2 ignore actions"
);
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext { only: Some(vec![CodeActionKind::QUICKFIX]), ..Default::default() },
@@ -1259,7 +1259,7 @@ mod test {
"Quickfix Context: Should return 3 code actions: 1 rule fix + 2 ignore actions"
);
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext {
@@ -1274,7 +1274,7 @@ mod test {
"Quickfix & FixAll Context: Should return 4 code actions: 1 rule fix + 2 ignore actions, and 1 fix all action"
);
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext {
@@ -1302,7 +1302,7 @@ mod test {
let linter = tester.create_linter();
let range = Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX));
let uri = tester.get_file_uri("quickfix.js");
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext::default(),
@@ -1314,7 +1314,7 @@ mod test {
);
let _ = linter.run_file(&uri, Some("debugger;")).unwrap();
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext::default(),
@@ -1333,7 +1333,7 @@ mod test {
let linter = tester.create_linter();
let range = Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX));
let uri = tester.get_file_uri("trigger-kind-invoked.js");
let code_actions = linter.get_code_actions_or_commands(&code_action_params(
let code_actions = linter.get_code_actions_or_commands(code_action_params(
&uri,
range,
CodeActionContext {
+3 -3
View File
@@ -226,13 +226,13 @@ impl Tester<'_> {
let linter = self.create_linter();
let range = Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX));
let reports = FileResult {
diagnostic: linter.run_diagnostic(&TextDocument::new(
diagnostic: linter.run_diagnostic(TextDocument::new(
&uri,
oxc_language_server::LanguageId::default(),
None,
)),
actions: linter.get_code_actions_or_commands(
&oxc_language_server::CodeActionParams {
oxc_language_server::CodeActionParams {
uri: uri.clone(),
range,
context: context.clone(),
@@ -240,7 +240,7 @@ impl Tester<'_> {
},
),
fix_all_action: linter
.get_code_actions_or_commands(&oxc_language_server::CodeActionParams {
.get_code_actions_or_commands(oxc_language_server::CodeActionParams {
uri: uri.clone(),
range,
context: fix_all_context.clone(),
+8 -11
View File
@@ -251,13 +251,10 @@ impl LanguageServer for Backend {
continue;
};
let document = self.file_system.get_document(uri);
let diagnostics = worker.run_diagnostic(&document).await;
let diagnostics = worker.run_diagnostic(document).await;
match diagnostics {
Err(err) => {
error!(
"running diagnostics for {} failed: {err}",
document.uri.as_str()
);
error!("running diagnostics for {} failed: {err}", uri.as_str());
client_messages
.push(ClientMessage { r#type: MessageType::ERROR, message: err });
}
@@ -607,7 +604,7 @@ impl LanguageServer for Backend {
return;
};
let document = self.file_system.get_document(&uri);
match worker.run_diagnostic_on_save(&document).await {
match worker.run_diagnostic_on_save(document).await {
Err(err) => {
error!("running diagnostics for {} failed: {err}", uri.as_str());
self.client.show_message(MessageType::ERROR, err).await;
@@ -649,7 +646,7 @@ impl LanguageServer for Backend {
worker.remove_uri_cache(&uri).await;
if self.capabilities.get().is_some_and(|cap| cap.diagnostic_mode == DiagnosticMode::Push) {
match worker.run_diagnostic_on_change(&document).await {
match worker.run_diagnostic_on_change(document).await {
Err(err) => {
error!("running diagnostics for {} failed: {err}", uri.as_str());
self.client.show_message(MessageType::ERROR, err).await;
@@ -711,7 +708,7 @@ impl LanguageServer for Backend {
let document = self.file_system.get_document(&uri);
match worker.run_diagnostic(&document).await {
match worker.run_diagnostic(document).await {
Err(err) => {
error!("running diagnostics for {} failed: {err}", uri.as_str());
self.client.show_message(MessageType::ERROR, err).await;
@@ -800,7 +797,7 @@ impl LanguageServer for Backend {
is_open_document,
};
let code_actions = worker.get_code_actions_or_commands(&params).await;
let code_actions = worker.get_code_actions_or_commands(params).await;
if code_actions.is_empty() {
return Ok(None);
@@ -873,7 +870,7 @@ impl LanguageServer for Backend {
};
let document = self.file_system.get_document(uri);
let diagnostics = worker.run_diagnostic(&document).await;
let diagnostics = worker.run_diagnostic(document).await;
let diagnostics = match diagnostics {
Err(err) => {
@@ -936,7 +933,7 @@ impl LanguageServer for Backend {
};
let document = self.file_system.get_document(uri);
match worker.format_file(&document).await {
match worker.format_file(document).await {
Ok(edits) => {
if edits.is_empty() {
return Ok(None);
+4 -4
View File
@@ -170,7 +170,7 @@ impl Tool for FakeTool {
fn get_code_actions_or_commands(
&self,
params: &crate::CodeActionParams,
params: crate::CodeActionParams,
) -> Vec<CodeActionOrCommand> {
if params.uri.as_str().ends_with("code_action.config") {
return vec![CodeActionOrCommand::CodeAction(CodeAction {
@@ -184,7 +184,7 @@ impl Tool for FakeTool {
vec![]
}
fn run_diagnostic(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic(&self, document: TextDocument) -> DiagnosticResult {
if let Some(cache_uris) = &self.cache_uris {
cache_uris.lock().unwrap().push(document.uri.clone());
}
@@ -208,12 +208,12 @@ impl Tool for FakeTool {
Ok(Vec::new())
}
fn run_diagnostic_on_change(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_change(&self, document: TextDocument) -> DiagnosticResult {
// For this fake tool, we use the same logic as run_diagnostic
self.run_diagnostic(document)
}
fn run_diagnostic_on_save(&self, document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_save(&self, document: TextDocument) -> DiagnosticResult {
// For this fake tool, we use the same logic as run_diagnostic
self.run_diagnostic(document)
}
+5 -5
View File
@@ -77,7 +77,7 @@ pub trait Tool: Send + Sync {
/// The tool should filter the code actions based on the requested range.
/// The context can be used to further filter the code actions,
/// for example by the `only` field which indicates that only code actions of certain kinds are requested.
fn get_code_actions_or_commands(&self, _params: &CodeActionParams) -> Vec<CodeActionOrCommand> {
fn get_code_actions_or_commands(&self, _params: CodeActionParams) -> Vec<CodeActionOrCommand> {
Vec::new()
}
@@ -91,7 +91,7 @@ pub trait Tool: Send + Sync {
///
/// # Errors
/// Return [`Err`] when an error occurs; ignoring formatting should return [`Ok`] with an empty vector.
fn run_format(&self, _document: &TextDocument) -> Result<Vec<TextEdit>, String> {
fn run_format(&self, _document: TextDocument) -> Result<Vec<TextEdit>, String> {
Ok(Vec::new())
}
@@ -103,7 +103,7 @@ pub trait Tool: Send + Sync {
///
/// # Errors
/// Return [`Err`] when an error occurs; ignoring diagnostics should return [`Ok`] with an empty vector.
fn run_diagnostic(&self, _document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic(&self, _document: TextDocument) -> DiagnosticResult {
Ok(Vec::new())
}
@@ -116,7 +116,7 @@ pub trait Tool: Send + Sync {
///
/// # Errors
/// Return [`Err`] when an error occurs; ignoring diagnostics should return [`Ok`] with an empty vector.
fn run_diagnostic_on_save(&self, _document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_save(&self, _document: TextDocument) -> DiagnosticResult {
Ok(Vec::new())
}
@@ -129,7 +129,7 @@ pub trait Tool: Send + Sync {
///
/// # Errors
/// Return [`Err`] when an error occurs; ignoring diagnostics should return [`Ok`] with an empty vector.
fn run_diagnostic_on_change(&self, _document: &TextDocument) -> DiagnosticResult {
fn run_diagnostic_on_change(&self, _document: TextDocument) -> DiagnosticResult {
Ok(Vec::new())
}
+22 -22
View File
@@ -117,11 +117,11 @@ impl WorkspaceWorker {
/// Common aggregator for tool-provided diagnostics.
async fn collect_diagnostics_with<F>(
&self,
document: &TextDocument<'_>,
document: TextDocument<'_>,
run: F,
) -> Result<Vec<(Uri, Vec<Diagnostic>)>, String>
where
F: Fn(&Box<dyn Tool>, &TextDocument) -> DiagnosticResult,
F: Fn(&Box<dyn Tool>, TextDocument) -> DiagnosticResult,
{
let tool_diagnostics = {
let tool_guard = self.tool.read().await;
@@ -156,7 +156,7 @@ impl WorkspaceWorker {
/// When calling `Tool::run_diagnostic` results into an error.
pub async fn run_diagnostic(
&self,
document: &TextDocument<'_>,
document: TextDocument<'_>,
) -> Result<Vec<(Uri, Vec<Diagnostic>)>, String> {
self.collect_diagnostics_with(document, |tool, document| tool.run_diagnostic(document))
.await
@@ -168,7 +168,7 @@ impl WorkspaceWorker {
/// When calling `Tool::run_diagnostic_on_change` results into an error.
pub async fn run_diagnostic_on_change(
&self,
document: &TextDocument<'_>,
document: TextDocument<'_>,
) -> Result<Vec<(Uri, Vec<Diagnostic>)>, String> {
self.collect_diagnostics_with(document, |tool, document| {
tool.run_diagnostic_on_change(document)
@@ -182,7 +182,7 @@ impl WorkspaceWorker {
/// When calling `Tool::run_diagnostic_on_save` results into an error.
pub async fn run_diagnostic_on_save(
&self,
document: &TextDocument<'_>,
document: TextDocument<'_>,
) -> Result<Vec<(Uri, Vec<Diagnostic>)>, String> {
self.collect_diagnostics_with(document, |tool, document| {
tool.run_diagnostic_on_save(document)
@@ -197,7 +197,7 @@ impl WorkspaceWorker {
///
/// # Errors
/// When calling `Tool::run_format` results into an error.
pub async fn format_file(&self, document: &TextDocument<'_>) -> Result<Vec<TextEdit>, String> {
pub async fn format_file(&self, document: TextDocument<'_>) -> Result<Vec<TextEdit>, String> {
let tool_guard = self.tool.read().await;
let Some(tool) = tool_guard.as_ref() else {
return Ok(Vec::new());
@@ -230,7 +230,7 @@ impl WorkspaceWorker {
/// It calls all tools and collects their code actions or commands.
pub async fn get_code_actions_or_commands(
&self,
params: &CodeActionParams,
params: CodeActionParams,
) -> Vec<CodeActionOrCommand> {
let mut actions = Vec::new();
if let Some(tool) = self.tool.read().await.as_ref() {
@@ -350,7 +350,7 @@ impl WorkspaceWorker {
for uri in file_system.keys() {
let document = file_system.get_document(&uri);
let Ok(mut reports) = tool.run_diagnostic(&document) else {
let Ok(mut reports) = tool.run_diagnostic(document) else {
// If diagnostics could not be run, skip this URI, but continue with others
// TODO: Should we aggregate errors instead? One by one, or all together?
continue;
@@ -768,7 +768,7 @@ mod tests {
worker.start_worker(serde_json::Value::Null).await;
let actions = worker
.get_code_actions_or_commands(&CodeActionParams {
.get_code_actions_or_commands(CodeActionParams {
uri: Uri::from_str("file:///root/file.js").unwrap(),
range: Range::default(),
context: CodeActionContext::default(),
@@ -779,7 +779,7 @@ mod tests {
assert_eq!(actions.len(), 0);
let actions = worker
.get_code_actions_or_commands(&CodeActionParams {
.get_code_actions_or_commands(CodeActionParams {
uri: Uri::from_str("file:///root/code_action.config").unwrap(),
range: Range::default(),
context: CodeActionContext::default(),
@@ -807,7 +807,7 @@ mod tests {
worker.start_worker(serde_json::Value::Null).await;
let diagnostics_no_content = worker
.run_diagnostic(&TextDocument::new(&uri, LanguageId::default(), None))
.run_diagnostic(TextDocument::new(&uri, LanguageId::default(), None))
.await
.unwrap();
@@ -820,7 +820,7 @@ mod tests {
);
let diagnostics_with_content = worker
.run_diagnostic(&TextDocument::new(
.run_diagnostic(TextDocument::new(
&uri,
LanguageId::default(),
Some(Arc::from("helloworld")),
@@ -837,7 +837,7 @@ mod tests {
);
let no_diagnostics = worker
.run_diagnostic(&TextDocument::new(
.run_diagnostic(TextDocument::new(
&Uri::from_str("file:///root/unknown.file").unwrap(),
LanguageId::default(),
None,
@@ -848,7 +848,7 @@ mod tests {
assert!(no_diagnostics.is_empty());
let error = worker
.run_diagnostic(&TextDocument::new(
.run_diagnostic(TextDocument::new(
&Uri::from_str("file:///root/error.config").unwrap(),
LanguageId::default(),
None,
@@ -871,7 +871,7 @@ mod tests {
worker.start_worker(serde_json::Value::Null).await;
let diagnostics_no_content = worker
.run_diagnostic_on_change(&TextDocument::new(&uri, LanguageId::default(), None))
.run_diagnostic_on_change(TextDocument::new(&uri, LanguageId::default(), None))
.await
.unwrap();
@@ -884,7 +884,7 @@ mod tests {
);
let diagnostics_with_content = worker
.run_diagnostic_on_change(&TextDocument::new(
.run_diagnostic_on_change(TextDocument::new(
&uri,
LanguageId::default(),
Some(Arc::from("helloworld")),
@@ -901,7 +901,7 @@ mod tests {
);
let no_diagnostics = worker
.run_diagnostic_on_change(&TextDocument::new(
.run_diagnostic_on_change(TextDocument::new(
&Uri::from_str("file:///root/unknown.file").unwrap(),
LanguageId::default(),
None,
@@ -912,7 +912,7 @@ mod tests {
assert!(no_diagnostics.is_empty());
let error = worker
.run_diagnostic_on_change(&TextDocument::new(
.run_diagnostic_on_change(TextDocument::new(
&Uri::from_str("file:///root/error.config").unwrap(),
LanguageId::default(),
None,
@@ -934,7 +934,7 @@ mod tests {
worker.start_worker(serde_json::Value::Null).await;
let diagnostics_no_content = worker
.run_diagnostic_on_save(&TextDocument::new(&uri, LanguageId::default(), None))
.run_diagnostic_on_save(TextDocument::new(&uri, LanguageId::default(), None))
.await
.unwrap();
@@ -947,7 +947,7 @@ mod tests {
);
let diagnostics_with_content = worker
.run_diagnostic_on_save(&TextDocument::new(
.run_diagnostic_on_save(TextDocument::new(
&uri,
LanguageId::default(),
Some(Arc::from("helloworld")),
@@ -964,7 +964,7 @@ mod tests {
);
let no_diagnostics = worker
.run_diagnostic_on_save(&TextDocument::new(
.run_diagnostic_on_save(TextDocument::new(
&Uri::from_str("file:///root/unknown.file").unwrap(),
LanguageId::default(),
None,
@@ -975,7 +975,7 @@ mod tests {
assert!(no_diagnostics.is_empty());
let error = worker
.run_diagnostic_on_save(&TextDocument::new(
.run_diagnostic_on_save(TextDocument::new(
&Uri::from_str("file:///root/error.config").unwrap(),
LanguageId::default(),
None,