From da6336c927234394c303e31119ed238663f9df9b Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Wed, 26 Mar 2025 17:35:22 +0000 Subject: [PATCH] refactor(language_server)!: remove `enable` configuration, the client should shutdown the server instead (#9990) --- crates/oxc_language_server/README.md | 5 +- crates/oxc_language_server/src/main.rs | 84 +++++++++----------------- editors/vscode/client/Config.ts | 2 - editors/vscode/client/extension.ts | 33 ++++++++-- 4 files changed, 59 insertions(+), 65 deletions(-) diff --git a/crates/oxc_language_server/README.md b/crates/oxc_language_server/README.md index f8b45bd16c7f1..ca37e4ef8445b 100644 --- a/crates/oxc_language_server/README.md +++ b/crates/oxc_language_server/README.md @@ -25,7 +25,6 @@ Initialization Options: | Option Key | Value(s) | Default | Description | | ------------ | ---------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- | | `run` | `"onSave" \| "onType"` | `"onType"` | Should the server lint the files when the user is typing or saving | -| `enable` | `true \| false` | `true` | Should the server lint files | | `configPath` | `` | `.oxlintrc.json` | Path to a oxlint configuration file, pass '' to enable nested configuration | | `flags` | `Map` | `` | Special oxc language server flags, currently only one flag key is supported: `disable_nested_config` | @@ -33,11 +32,13 @@ Initialization Options: ### [shutdown](https://microsoft.github.io/language-server-protocol/specification#shutdown) +The server will reset the diagnostics for all open files and send one or more [textDocument/publishDiagnostics](#textdocumentpublishdiagnostics) requests to the client. + ### Workspace #### [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeConfiguration) -The server expects this request when settings like `run`, `enable`, `flags` or `configPath` are changed. +The server expects this request when settings like `run`, `flags` or `configPath` are changed. The server will revalidate or reset the diagnostics for all open files and send one or more [textDocument/publishDiagnostics](#textdocumentpublishdiagnostics) requests to the client. #### [workspace/didChangeWatchedFiles](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles) diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 444d0beaa65e7..6f2ed02b5a3ba 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -54,7 +54,6 @@ enum Run { #[serde(rename_all = "camelCase")] struct Options { run: Run, - enable: bool, config_path: String, flags: FxHashMap, } @@ -62,7 +61,6 @@ struct Options { impl Default for Options { fn default() -> Self { Self { - enable: true, run: Run::default(), config_path: OXC_CONFIG_FILE.into(), flags: FxHashMap::default(), @@ -71,17 +69,6 @@ impl Default for Options { } impl Options { - fn get_lint_level(&self) -> SyntheticRunLevel { - if self.enable { - match self.run { - Run::OnSave => SyntheticRunLevel::OnSave, - Run::OnType => SyntheticRunLevel::OnType, - } - } else { - SyntheticRunLevel::Disable - } - } - fn get_config_path(&self) -> Option { if self.config_path.is_empty() { None } else { Some(PathBuf::from(&self.config_path)) } } @@ -91,13 +78,6 @@ impl Options { } } -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] -enum SyntheticRunLevel { - Disable, - OnSave, - OnType, -} - #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { @@ -155,40 +135,16 @@ impl LanguageServer for Backend { " ); - if current_option.get_lint_level() != changed_options.get_lint_level() - && changed_options.get_lint_level() == SyntheticRunLevel::Disable - { - debug!("lint level change detected {:?}", &changed_options.get_lint_level()); - // clear all exists diagnostics when linter is disabled - let cleared_diagnostics = self - .diagnostics_report_map - .pin() - .keys() - .map(|uri| { - ( - // should convert successfully, case the key is from `params.document.uri` - Url::from_str(uri) - .ok() - .and_then(|url| url.to_file_path().ok()) - .expect("should convert to path"), - vec![], - ) - }) - .collect::>(); - self.publish_all_diagnostics(&cleared_diagnostics).await; - } - if changed_options.disable_nested_configs() { self.nested_configs.pin().clear(); } *self.options.lock().await = changed_options.clone(); - // revalidate the config and all open files, when lint level is not disabled and the config path is changed - if changed_options.get_lint_level() != SyntheticRunLevel::Disable - && changed_options - .get_config_path() - .is_some_and(|path| path.to_str().unwrap() != current_option.config_path) + // revalidate the config and all open files + if changed_options + .get_config_path() + .is_some_and(|path| path.to_str().unwrap() != current_option.config_path) { info!("config path change detected {:?}", &changed_options.get_config_path()); self.init_linter_config().await; @@ -247,14 +203,15 @@ impl LanguageServer for Backend { } async fn shutdown(&self) -> Result<()> { + self.clear_all_diagnostics().await; Ok(()) } async fn did_save(&self, params: DidSaveTextDocumentParams) { debug!("oxc server did save"); // drop as fast as possible - let run_level = { self.options.lock().await.get_lint_level() }; - if run_level < SyntheticRunLevel::OnSave { + let run_level = { self.options.lock().await.run }; + if run_level != Run::OnSave { return; } let uri = params.text_document.uri; @@ -267,8 +224,8 @@ impl LanguageServer for Backend { /// When the document changed, it may not be written to disk, so we should /// get the file context from the language client async fn did_change(&self, params: DidChangeTextDocumentParams) { - let run_level = { self.options.lock().await.get_lint_level() }; - if run_level < SyntheticRunLevel::OnType { + let run_level = { self.options.lock().await.run }; + if run_level != Run::OnType { return; } @@ -286,10 +243,6 @@ impl LanguageServer for Backend { } async fn did_open(&self, params: DidOpenTextDocumentParams) { - let run_level = { self.options.lock().await.get_lint_level() }; - if run_level <= SyntheticRunLevel::Disable { - return; - } if self.is_ignored(¶ms.text_document.uri).await { return; } @@ -515,6 +468,25 @@ impl Backend { } } + async fn clear_all_diagnostics(&self) { + let cleared_diagnostics = self + .diagnostics_report_map + .pin() + .keys() + .map(|uri| { + ( + // should convert successfully, case the key is from `params.document.uri` + Url::from_str(uri) + .ok() + .and_then(|url| url.to_file_path().ok()) + .expect("should convert to path"), + vec![], + ) + }) + .collect::>(); + self.publish_all_diagnostics(&cleared_diagnostics).await; + } + #[expect(clippy::ptr_arg)] async fn publish_all_diagnostics(&self, result: &Vec<(PathBuf, Vec)>) { join_all(result.iter().map(|(path, diagnostics)| { diff --git a/editors/vscode/client/Config.ts b/editors/vscode/client/Config.ts index 3a6326f846664..aeba997e93b37 100644 --- a/editors/vscode/client/Config.ts +++ b/editors/vscode/client/Config.ts @@ -98,7 +98,6 @@ export class Config implements ConfigInterface { public toLanguageServerConfig(): LanguageServerConfig { return { run: this.runTrigger, - enable: this.enable, configPath: this.configPath, flags: this.flags, }; @@ -107,7 +106,6 @@ export class Config implements ConfigInterface { interface LanguageServerConfig { configPath: string; - enable: boolean; run: Trigger; flags: Record; } diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index 085cdd6c53217..ae1e5b1c20260 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -58,10 +58,14 @@ export async function activate(context: ExtensionContext) { try { if (client.isRunning()) { await client.restart(); + // ToDo: refactor it on the server side. + // Do not touch watchers on client side, just simplify the restart of the server. + const configFiles = await findOxlintrcConfigFiles(); + await sendDidChangeWatchedFilesNotificationWith(client, configFiles); window.showInformationMessage('oxc server restarted.'); } else { - await client.start(); + await startClient(); } } catch (err) { client.error('Restarting client failed', err, 'force'); @@ -78,8 +82,18 @@ export async function activate(context: ExtensionContext) { const toggleEnable = commands.registerCommand( OxcCommands.ToggleEnable, - () => { - configService.config.updateEnable(!configService.config.enable); + async () => { + await configService.config.updateEnable(!configService.config.enable); + + if (client.isRunning()) { + if (!configService.config.enable) { + await client.stop(); + } + } else { + if (configService.config.enable) { + await startClient(); + } + } }, ); @@ -232,7 +246,7 @@ export async function activate(context: ExtensionContext) { configService.onConfigChange = function onConfigChange(event) { let settings = this.config.toLanguageServerConfig(); - updateStatsBar(settings.enable); + updateStatsBar(this.config.enable); client.sendNotification('workspace/didChangeConfiguration', { settings }); if (event.affectsConfiguration('oxc.configPath')) { @@ -265,8 +279,17 @@ export async function activate(context: ExtensionContext) { myStatusBarItem.backgroundColor = bgColor; } updateStatsBar(configService.config.enable); - await client.start(); + if (configService.config.enable) { + await startClient(); + } +} + +// Starts the client, it does not check if it is already started +async function startClient() { + await client.start(); + // ToDo: refactor it on the server side. + // Do not touch watchers on client side, just simplify the start of the server. const configFiles = await findOxlintrcConfigFiles(); await sendDidChangeWatchedFilesNotificationWith(client, configFiles); }