Skip to content

Commit 67cfa58

Browse files
committed
perf(lsp): avoid some clones (#16519)
> This PR improves performance in the language server by systematically reducing unnecessary clone operations across multiple components. The changes primarily involve refactoring function signatures to take ownership where beneficial and restructuring code to avoid cloning data that can be moved instead. These clones were easy to fix, looking for more bad tech deps
1 parent 2a09665 commit 67cfa58

File tree

4 files changed

+63
-56
lines changed

4 files changed

+63
-56
lines changed

crates/oxc_language_server/src/backend.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ impl LanguageServer for Backend {
111111
let capabilities = Capabilities::from(params.capabilities);
112112

113113
// client sent workspace folders
114-
let workers = if let Some(workspace_folders) = &params.workspace_folders {
114+
let workers = if let Some(workspace_folders) = params.workspace_folders {
115115
workspace_folders
116-
.iter()
117-
.map(|workspace_folder| WorkspaceWorker::new(workspace_folder.uri.clone()))
116+
.into_iter()
117+
.map(|workspace_folder| WorkspaceWorker::new(workspace_folder.uri))
118118
.collect()
119119
// client sent deprecated root uri
120120
} else if let Some(root_uri) = params.root_uri {
@@ -130,8 +130,8 @@ impl LanguageServer for Backend {
130130
// we will init the linter after requesting for the workspace configuration.
131131
if !capabilities.workspace_configuration || options.is_some() {
132132
for worker in &workers {
133-
let option = &options
134-
.clone()
133+
let option = options
134+
.as_deref()
135135
.unwrap_or_default()
136136
.iter()
137137
.find(|workspace_option| {
@@ -140,7 +140,7 @@ impl LanguageServer for Backend {
140140
.map(|workspace_options| workspace_options.options.clone())
141141
.unwrap_or_default();
142142

143-
worker.start_worker(option.clone(), &self.tool_builders).await;
143+
worker.start_worker(option, &self.tool_builders).await;
144144
}
145145
}
146146

@@ -319,11 +319,11 @@ impl LanguageServer for Backend {
319319

320320
// Only create WorkspaceOption when the config is Some
321321
configs
322-
.iter()
322+
.into_iter()
323323
.enumerate()
324-
.map(|(index, config)| WorkspaceOption {
324+
.map(|(index, options)| WorkspaceOption {
325325
workspace_uri: workers[index].get_root_uri().clone(),
326-
options: config.clone(),
326+
options,
327327
})
328328
.collect::<Vec<_>>()
329329
} else {
@@ -456,8 +456,8 @@ impl LanguageServer for Backend {
456456
)
457457
.await;
458458

459-
for (index, folder) in params.event.added.iter().enumerate() {
460-
let worker = WorkspaceWorker::new(folder.uri.clone());
459+
for (index, folder) in params.event.added.into_iter().enumerate() {
460+
let worker = WorkspaceWorker::new(folder.uri);
461461
// get the configuration from the response and init the linter
462462
let options = configurations.get(index).unwrap_or(&serde_json::Value::Null);
463463
worker.start_worker(options.clone(), &self.tool_builders).await;
@@ -498,36 +498,36 @@ impl LanguageServer for Backend {
498498
/// See: <https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_didSave>
499499
async fn did_save(&self, params: DidSaveTextDocumentParams) {
500500
debug!("oxc server did save");
501-
let uri = &params.text_document.uri;
501+
let uri = params.text_document.uri;
502502
let workers = self.workspace_workers.read().await;
503-
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
503+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(&uri)) else {
504504
return;
505505
};
506506

507-
if let Some(diagnostics) = worker.run_diagnostic_on_save(uri, params.text.as_deref()).await
507+
if let Some(diagnostics) = worker.run_diagnostic_on_save(&uri, params.text.as_deref()).await
508508
{
509-
self.client.publish_diagnostics(uri.clone(), diagnostics, None).await;
509+
self.client.publish_diagnostics(uri, diagnostics, None).await;
510510
}
511511
}
512512
/// It will update the in-memory file content if the client supports dynamic formatting.
513513
/// It will re-lint the file and send updated diagnostics, if necessary.
514514
///
515515
/// See: <https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_didChange>
516516
async fn did_change(&self, params: DidChangeTextDocumentParams) {
517-
let uri = &params.text_document.uri;
517+
let uri = params.text_document.uri;
518518
let workers = self.workspace_workers.read().await;
519-
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
519+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(&uri)) else {
520520
return;
521521
};
522522
let content = params.content_changes.first().map(|c| c.text.clone());
523523

524524
if let Some(content) = &content {
525-
self.file_system.write().await.set(uri, content.clone());
525+
self.file_system.write().await.set(&uri, content.clone());
526526
}
527527

528-
if let Some(diagnostics) = worker.run_diagnostic_on_change(uri, content.as_deref()).await {
528+
if let Some(diagnostics) = worker.run_diagnostic_on_change(&uri, content.as_deref()).await {
529529
self.client
530-
.publish_diagnostics(uri.clone(), diagnostics, Some(params.text_document.version))
530+
.publish_diagnostics(uri, diagnostics, Some(params.text_document.version))
531531
.await;
532532
}
533533
}
@@ -537,19 +537,19 @@ impl LanguageServer for Backend {
537537
///
538538
/// See: <https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_didOpen>
539539
async fn did_open(&self, params: DidOpenTextDocumentParams) {
540-
let uri = &params.text_document.uri;
540+
let uri = params.text_document.uri;
541541
let workers = self.workspace_workers.read().await;
542-
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
542+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(&uri)) else {
543543
return;
544544
};
545545

546546
let content = params.text_document.text;
547547

548-
self.file_system.write().await.set(uri, content.clone());
548+
self.file_system.write().await.set(&uri, content.clone());
549549

550-
if let Some(diagnostics) = worker.run_diagnostic(uri, Some(&content)).await {
550+
if let Some(diagnostics) = worker.run_diagnostic(&uri, Some(&content)).await {
551551
self.client
552-
.publish_diagnostics(uri.clone(), diagnostics, Some(params.text_document.version))
552+
.publish_diagnostics(uri, diagnostics, Some(params.text_document.version))
553553
.await;
554554
}
555555
}

crates/oxc_language_server/src/linter/code_actions.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ pub const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind =
77
CodeActionKind::new("source.fixAll.oxc");
88

99
fn fix_content_to_code_action(
10-
fixed_content: &FixedContent,
11-
uri: &Uri,
10+
fixed_content: FixedContent,
11+
uri: Uri,
1212
is_preferred: bool,
1313
) -> CodeAction {
1414
CodeAction {
15-
title: fixed_content.message.clone(),
15+
title: fixed_content.message,
1616
kind: Some(CodeActionKind::QUICKFIX),
1717
is_preferred: Some(is_preferred),
1818
edit: Some(WorkspaceEdit {
1919
#[expect(clippy::disallowed_types)]
2020
changes: Some(std::collections::HashMap::from([(
21-
uri.clone(),
22-
vec![TextEdit { range: fixed_content.range, new_text: fixed_content.code.clone() }],
21+
uri,
22+
vec![TextEdit { range: fixed_content.range, new_text: fixed_content.code }],
2323
)])),
2424
..WorkspaceEdit::default()
2525
}),
@@ -30,13 +30,13 @@ fn fix_content_to_code_action(
3030
}
3131
}
3232

33-
pub fn apply_fix_code_actions(action: &LinterCodeAction, uri: &Uri) -> Vec<CodeAction> {
33+
pub fn apply_fix_code_actions(action: LinterCodeAction, uri: &Uri) -> Vec<CodeAction> {
3434
let mut code_actions = vec![];
3535

3636
// only the first code action is preferred
3737
let mut preferred = true;
38-
for fixed in &action.fixed_content {
39-
let action = fix_content_to_code_action(fixed, uri, preferred);
38+
for fixed in action.fixed_content {
39+
let action = fix_content_to_code_action(fixed, uri.clone(), preferred);
4040
preferred = false;
4141
code_actions.push(action);
4242
}
@@ -46,7 +46,7 @@ pub fn apply_fix_code_actions(action: &LinterCodeAction, uri: &Uri) -> Vec<CodeA
4646

4747
pub fn apply_all_fix_code_action(
4848
actions: impl Iterator<Item = LinterCodeAction>,
49-
uri: &Uri,
49+
uri: Uri,
5050
) -> Option<CodeAction> {
5151
let quick_fixes: Vec<TextEdit> = fix_all_text_edit(actions);
5252

@@ -60,7 +60,7 @@ pub fn apply_all_fix_code_action(
6060
is_preferred: Some(true),
6161
edit: Some(WorkspaceEdit {
6262
#[expect(clippy::disallowed_types)]
63-
changes: Some(std::collections::HashMap::from([(uri.clone(), quick_fixes)])),
63+
changes: Some(std::collections::HashMap::from([(uri, quick_fixes)])),
6464
..WorkspaceEdit::default()
6565
}),
6666
disabled: None,
@@ -98,8 +98,11 @@ pub fn fix_all_text_edit(actions: impl Iterator<Item = LinterCodeAction>) -> Vec
9898
// and return them as one workspace edit.
9999
// it is possible that one fix will change the range for the next fix
100100
// see oxc-project/oxc#10422
101-
text_edits
102-
.push(TextEdit { range: fixed_content.range, new_text: fixed_content.code.clone() });
101+
text_edits.push(TextEdit {
102+
range: fixed_content.range,
103+
// TODO: avoid cloning here
104+
new_text: fixed_content.code.clone(),
105+
});
103106
}
104107

105108
text_edits

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl ServerLinterBuilder {
8383

8484
// TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
8585
let use_nested_config = options.use_nested_configs();
86-
let fix_kind = FixKind::from(options.fix_kind.clone());
86+
let fix_kind = FixKind::from(options.fix_kind);
8787

8888
let use_cross_module = config_builder.plugins().has_import()
8989
|| (use_nested_config
@@ -124,7 +124,7 @@ impl ServerLinterBuilder {
124124
&IsolatedLintHandlerOptions {
125125
use_cross_module,
126126
type_aware: options.type_aware,
127-
fix_kind: FixKind::from(options.fix_kind.clone()),
127+
fix_kind,
128128
root_path: root_path.to_path_buf(),
129129
tsconfig_path: options.ts_config_path.as_ref().map(|path| {
130130
let path = Path::new(path).to_path_buf();
@@ -428,13 +428,13 @@ impl Tool for ServerLinter {
428428
}
429429

430430
let args = FixAllCommandArgs::try_from(arguments).map_err(|_| ErrorCode::InvalidParams)?;
431-
let uri = &Uri::from_str(&args.uri).map_err(|_| ErrorCode::InvalidParams)?;
431+
let uri = Uri::from_str(&args.uri).map_err(|_| ErrorCode::InvalidParams)?;
432432

433-
if !self.is_responsible_for_uri(uri) {
433+
if !self.is_responsible_for_uri(&uri) {
434434
return Ok(None);
435435
}
436436

437-
let actions = self.get_code_actions_for_uri(uri);
437+
let actions = self.get_code_actions_for_uri(&uri);
438438

439439
let Some(actions) = actions else {
440440
return Ok(None);
@@ -448,7 +448,7 @@ impl Tool for ServerLinter {
448448

449449
Ok(Some(WorkspaceEdit {
450450
#[expect(clippy::disallowed_types)]
451-
changes: Some(std::collections::HashMap::from([(uri.clone(), text_edits)])),
451+
changes: Some(std::collections::HashMap::from([(uri, text_edits)])),
452452
document_changes: None,
453453
change_annotations: None,
454454
}))
@@ -476,15 +476,16 @@ impl Tool for ServerLinter {
476476
.is_some_and(|only| only.contains(&CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC));
477477

478478
if is_source_fix_all_oxc {
479-
return apply_all_fix_code_action(actions, uri).map_or(vec![], |code_actions| {
480-
vec![CodeActionOrCommand::CodeAction(code_actions)]
481-
});
479+
return apply_all_fix_code_action(actions, uri.clone())
480+
.map_or(vec![], |code_actions| {
481+
vec![CodeActionOrCommand::CodeAction(code_actions)]
482+
});
482483
}
483484

484485
let mut code_actions_vec: Vec<CodeActionOrCommand> = vec![];
485486

486487
for action in actions {
487-
let fix_actions = apply_fix_code_actions(&action, uri);
488+
let fix_actions = apply_fix_code_actions(action, uri);
488489
code_actions_vec.extend(fix_actions.into_iter().map(CodeActionOrCommand::CodeAction));
489490
}
490491

crates/oxc_language_server/src/worker.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,19 +264,22 @@ impl WorkspaceWorker {
264264
"
265265
);
266266

267+
let result = self
268+
.handle_tool_changes(file_system, |tool| {
269+
tool.handle_configuration_change(
270+
&self.root_uri,
271+
&old_options,
272+
changed_options_json.clone(),
273+
)
274+
})
275+
.await;
276+
267277
{
268278
let mut options_guard = self.options.lock().await;
269-
*options_guard = Some(changed_options_json.clone());
279+
*options_guard = Some(changed_options_json);
270280
}
271281

272-
self.handle_tool_changes(file_system, |tool| {
273-
tool.handle_configuration_change(
274-
&self.root_uri,
275-
&old_options,
276-
changed_options_json.clone(),
277-
)
278-
})
279-
.await
282+
result
280283
}
281284

282285
/// Common implementation for handling tool changes that may result in

0 commit comments

Comments
 (0)