From e2b623328421ca9fc413f8301ef0c602320a4bae Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Mon, 16 Dec 2024 03:25:26 +0100 Subject: [PATCH] refactor(language_server): add capabilities struct (#7906) vscode test from local, intellij test from https://github.com/oxc-project/oxc/pull/7445#issuecomment-2496950216 --- .../oxc_language_server/src/capabilities.rs | 132 ++++++++++++++++++ crates/oxc_language_server/src/main.rs | 53 ++----- 2 files changed, 140 insertions(+), 45 deletions(-) create mode 100644 crates/oxc_language_server/src/capabilities.rs diff --git a/crates/oxc_language_server/src/capabilities.rs b/crates/oxc_language_server/src/capabilities.rs new file mode 100644 index 0000000000000..5dd476b3d93ec --- /dev/null +++ b/crates/oxc_language_server/src/capabilities.rs @@ -0,0 +1,132 @@ +use tower_lsp::lsp_types::{ + ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, OneOf, + ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions, + WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, +}; + +pub const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind = + CodeActionKind::new("source.fixAll.oxc"); + +pub struct Capabilities { + pub code_action_provider: bool, +} + +impl From for Capabilities { + fn from(value: ClientCapabilities) -> Self { + // check if the client support some code action literal support + let code_action_provider = value.text_document.is_some_and(|capability| { + capability.code_action.is_some_and(|code_action| { + code_action.code_action_literal_support.is_some_and(|literal_support| { + !literal_support.code_action_kind.value_set.is_empty() + }) + }) + }); + + Self { code_action_provider } + } +} + +impl From for ServerCapabilities { + fn from(value: Capabilities) -> Self { + Self { + text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + file_operations: None, + }), + code_action_provider: if value.code_action_provider { + Some(CodeActionProviderCapability::Options(CodeActionOptions { + code_action_kinds: Some(vec![ + CodeActionKind::QUICKFIX, + CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, + ]), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + resolve_provider: None, + })) + } else { + None + }, + ..ServerCapabilities::default() + } + } +} + +#[cfg(test)] +mod test { + use tower_lsp::lsp_types::{ + ClientCapabilities, CodeActionClientCapabilities, CodeActionKindLiteralSupport, + CodeActionLiteralSupport, TextDocumentClientCapabilities, + }; + + use super::Capabilities; + + #[test] + fn test_code_action_provider_vscode() { + let client_capabilities = ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + code_action: Some(CodeActionClientCapabilities { + code_action_literal_support: Some(CodeActionLiteralSupport { + code_action_kind: CodeActionKindLiteralSupport { + // this is from build (see help, about): + // Version: 1.95.3 (user setup) + // Commit: f1a4fb101478ce6ec82fe9627c43efbf9e98c813 + value_set: vec![ + #[allow(clippy::manual_string_new)] + "".into(), + "quickfix".into(), + "refactor".into(), + "refactor.extract".into(), + "refactor.inline".into(), + "refactor.rewrite".into(), + "source".into(), + "source.organizeImports".into(), + ], + }, + }), + ..CodeActionClientCapabilities::default() + }), + ..TextDocumentClientCapabilities::default() + }), + ..ClientCapabilities::default() + }; + + let capabilities = Capabilities::from(client_capabilities); + + assert!(capabilities.code_action_provider); + } + + #[test] + fn test_code_action_provider_intellij() { + let client_capabilities = ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + code_action: Some(CodeActionClientCapabilities { + code_action_literal_support: Some(CodeActionLiteralSupport { + code_action_kind: CodeActionKindLiteralSupport { + // this is from build (see help, about): + // Build #IU-243.22562.145, built on December 8, 2024 + value_set: vec![ + "quickfix".into(), + #[allow(clippy::manual_string_new)] + "".into(), + "source".into(), + "refactor".into(), + ], + }, + }), + ..CodeActionClientCapabilities::default() + }), + ..TextDocumentClientCapabilities::default() + }), + ..ClientCapabilities::default() + }; + + let capabilities = Capabilities::from(client_capabilities); + + assert!(capabilities.code_action_provider); + } +} diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index b0d067bbbe6e1..1d5f4dac47c18 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -11,22 +11,21 @@ use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::{ jsonrpc::{Error, ErrorCode, Result}, lsp_types::{ - CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, - CodeActionProviderCapability, CodeActionResponse, ConfigurationItem, Diagnostic, - DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, - DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - InitializeParams, InitializeResult, InitializedParams, NumberOrString, OneOf, Position, - Range, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, - TextEdit, Url, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFoldersServerCapabilities, - WorkspaceServerCapabilities, + CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, + ConfigurationItem, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams, + DidChangeWatchedFilesParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams, + NumberOrString, Position, Range, ServerInfo, TextEdit, Url, WorkspaceEdit, }, Client, LanguageServer, LspService, Server, }; use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; +use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC}; use crate::linter::{DiagnosticReport, ServerLinter}; +mod capabilities; mod linter; type FxDashMap = DashMap; @@ -88,9 +87,6 @@ enum SyntheticRunLevel { OnType, } -const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind = - CodeActionKind::new("source.fixAll.oxc"); - #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { @@ -106,45 +102,12 @@ impl LanguageServer for Backend { *self.options.lock().await = value; } - // check if the client support some code action literal support - let code_action_provider = if params.capabilities.text_document.is_some_and(|capability| { - capability.code_action.is_some_and(|code_action| { - code_action.code_action_literal_support.is_some_and(|literal_support| { - !literal_support.code_action_kind.value_set.is_empty() - }) - }) - }) { - Some(CodeActionProviderCapability::Options(CodeActionOptions { - code_action_kinds: Some(vec![ - CodeActionKind::QUICKFIX, - CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, - ]), - work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, - resolve_provider: None, - })) - } else { - None - }; - let oxlintrc = self.init_linter_config().await; self.init_ignore_glob(oxlintrc).await; Ok(InitializeResult { server_info: Some(ServerInfo { name: "oxc".into(), version: None }), offset_encoding: None, - capabilities: ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Kind( - TextDocumentSyncKind::FULL, - )), - workspace: Some(WorkspaceServerCapabilities { - workspace_folders: Some(WorkspaceFoldersServerCapabilities { - supported: Some(true), - change_notifications: Some(OneOf::Left(true)), - }), - file_operations: None, - }), - code_action_provider, - ..ServerCapabilities::default() - }, + capabilities: Capabilities::from(params.capabilities).into(), }) }