From de8246b96feadb753310c9404545c6c4a7fec3c0 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Fri, 20 Dec 2024 03:29:44 +0100 Subject: [PATCH] refactor(language_server): move structs into own file (#8026) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/linter/error_with_position.rs | 165 +++++++++++++++ .../isolated_lint_handler.rs} | 196 +----------------- crates/oxc_language_server/src/linter/mod.rs | 14 ++ .../src/linter/server_linter.rs | 28 +++ crates/oxc_language_server/src/main.rs | 3 +- editors/vscode/package.json | 2 +- 6 files changed, 216 insertions(+), 192 deletions(-) create mode 100644 crates/oxc_language_server/src/linter/error_with_position.rs rename crates/oxc_language_server/src/{linter.rs => linter/isolated_lint_handler.rs} (53%) create mode 100644 crates/oxc_language_server/src/linter/mod.rs create mode 100644 crates/oxc_language_server/src/linter/server_linter.rs diff --git a/crates/oxc_language_server/src/linter/error_with_position.rs b/crates/oxc_language_server/src/linter/error_with_position.rs new file mode 100644 index 0000000000000..084887c85e052 --- /dev/null +++ b/crates/oxc_language_server/src/linter/error_with_position.rs @@ -0,0 +1,165 @@ +use std::{path::PathBuf, str::FromStr}; + +use tower_lsp::lsp_types::{ + self, CodeDescription, DiagnosticRelatedInformation, NumberOrString, Position, Range, Url, +}; + +use cow_utils::CowUtils; +use oxc_diagnostics::{Error, Severity}; + +use crate::linter::offset_to_position; + +const LINT_DOC_LINK_PREFIX: &str = "https://oxc.rs/docs/guide/usage/linter/rules"; + +#[derive(Debug)] +pub struct ErrorWithPosition { + pub start_pos: Position, + pub end_pos: Position, + pub miette_err: Error, + pub fixed_content: Option, + pub labels_with_pos: Vec, +} + +#[derive(Debug)] +pub struct LabeledSpanWithPosition { + pub start_pos: Position, + pub end_pos: Position, + pub message: Option, +} + +#[derive(Debug, Clone)] +pub struct DiagnosticReport { + pub diagnostic: lsp_types::Diagnostic, + pub fixed_content: Option, +} +#[derive(Debug)] +pub struct ErrorReport { + pub error: Error, + pub fixed_content: Option, +} + +#[derive(Debug, Clone)] +pub struct FixedContent { + pub code: String, + pub range: Range, +} + +fn cmp_range(first: &Range, other: &Range) -> std::cmp::Ordering { + match first.start.cmp(&other.start) { + std::cmp::Ordering::Equal => first.end.cmp(&other.end), + o => o, + } +} + +/// parse `OxcCode` to `Option<(scope, number)>` +fn parse_diagnostic_code(code: &str) -> Option<(&str, &str)> { + if !code.ends_with(')') { + return None; + } + let right_parenthesis_pos = code.rfind('(')?; + Some((&code[0..right_parenthesis_pos], &code[right_parenthesis_pos + 1..code.len() - 1])) +} + +impl ErrorWithPosition { + pub fn new( + error: Error, + text: &str, + fixed_content: Option, + start: usize, + ) -> Self { + let labels = error.labels().map_or(vec![], Iterator::collect); + let labels_with_pos: Vec = labels + .iter() + .map(|labeled_span| LabeledSpanWithPosition { + start_pos: offset_to_position(labeled_span.offset() + start, text), + end_pos: offset_to_position( + labeled_span.offset() + start + labeled_span.len(), + text, + ), + message: labeled_span.label().map(ToString::to_string), + }) + .collect(); + + let start_pos = labels_with_pos[0].start_pos; + let end_pos = labels_with_pos[labels_with_pos.len() - 1].end_pos; + + Self { miette_err: error, start_pos, end_pos, labels_with_pos, fixed_content } + } + + fn to_lsp_diagnostic(&self, path: &PathBuf) -> lsp_types::Diagnostic { + let severity = match self.miette_err.severity() { + Some(Severity::Error) => Some(lsp_types::DiagnosticSeverity::ERROR), + _ => Some(lsp_types::DiagnosticSeverity::WARNING), + }; + let related_information = Some( + self.labels_with_pos + .iter() + .map(|labeled_span| lsp_types::DiagnosticRelatedInformation { + location: lsp_types::Location { + uri: lsp_types::Url::from_file_path(path).unwrap(), + range: lsp_types::Range { + start: lsp_types::Position { + line: labeled_span.start_pos.line, + character: labeled_span.start_pos.character, + }, + end: lsp_types::Position { + line: labeled_span.end_pos.line, + character: labeled_span.end_pos.character, + }, + }, + }, + message: labeled_span.message.clone().unwrap_or_default(), + }) + .collect(), + ); + let range = related_information.as_ref().map_or( + Range { start: self.start_pos, end: self.end_pos }, + |infos: &Vec| { + let mut ret_range = Range { + start: Position { line: u32::MAX, character: u32::MAX }, + end: Position { line: u32::MAX, character: u32::MAX }, + }; + for info in infos { + if cmp_range(&ret_range, &info.location.range) == std::cmp::Ordering::Greater { + ret_range = info.location.range; + } + } + ret_range + }, + ); + let code = self.miette_err.code().map(|item| item.to_string()); + let code_description = code.as_ref().and_then(|code| { + let (scope, number) = parse_diagnostic_code(code)?; + Some(CodeDescription { + href: Url::from_str(&format!( + "{LINT_DOC_LINK_PREFIX}/{}/{number}", + scope.strip_prefix("eslint-plugin-").unwrap_or(scope).cow_replace("-", "_") + )) + .ok()?, + }) + }); + let message = self.miette_err.help().map_or_else( + || self.miette_err.to_string(), + |help| format!("{}\nhelp: {}", self.miette_err, help), + ); + + lsp_types::Diagnostic { + range, + severity, + code: code.map(NumberOrString::String), + message, + source: Some("oxc".into()), + code_description, + related_information, + tags: None, + data: None, + } + } + + pub fn into_diagnostic_report(self, path: &PathBuf) -> DiagnosticReport { + DiagnosticReport { + diagnostic: self.to_lsp_diagnostic(path), + fixed_content: self.fixed_content, + } + } +} diff --git a/crates/oxc_language_server/src/linter.rs b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs similarity index 53% rename from crates/oxc_language_server/src/linter.rs rename to crates/oxc_language_server/src/linter/isolated_lint_handler.rs index c2a0af67e9c35..fec99c4e32ee8 100644 --- a/crates/oxc_language_server/src/linter.rs +++ b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs @@ -2,166 +2,26 @@ use std::{ fs, path::{Path, PathBuf}, rc::Rc, - str::FromStr, sync::{Arc, OnceLock}, }; -use cow_utils::CowUtils; use log::debug; use rustc_hash::FxHashSet; -use tower_lsp::lsp_types::{ - self, CodeDescription, DiagnosticRelatedInformation, DiagnosticSeverity, NumberOrString, - Position, Range, Url, -}; +use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Range}; use oxc_allocator::Allocator; -use oxc_data_structures::rope::{get_line_column, Rope}; -use oxc_diagnostics::{Error, NamedSource, Severity}; +use oxc_diagnostics::{Error, NamedSource}; use oxc_linter::{ loader::{JavaScriptSource, Loader, LINT_PARTIAL_LOADER_EXT}, - FixKind, Linter, ModuleRecord, + Linter, ModuleRecord, }; use oxc_parser::{ParseOptions, Parser}; use oxc_semantic::SemanticBuilder; use oxc_span::VALID_EXTENSIONS; -const LINT_DOC_LINK_PREFIX: &str = "https://oxc.rs/docs/guide/usage/linter/rules"; -#[derive(Debug)] -struct ErrorWithPosition { - pub start_pos: Position, - pub end_pos: Position, - pub miette_err: Error, - pub fixed_content: Option, - pub labels_with_pos: Vec, -} - -#[derive(Debug)] -struct LabeledSpanWithPosition { - pub start_pos: Position, - pub end_pos: Position, - pub message: Option, -} - -impl ErrorWithPosition { - pub fn new( - error: Error, - text: &str, - fixed_content: Option, - start: usize, - ) -> Self { - let labels = error.labels().map_or(vec![], Iterator::collect); - let labels_with_pos: Vec = labels - .iter() - .map(|labeled_span| LabeledSpanWithPosition { - start_pos: offset_to_position(labeled_span.offset() + start, text), - end_pos: offset_to_position( - labeled_span.offset() + start + labeled_span.len(), - text, - ), - message: labeled_span.label().map(ToString::to_string), - }) - .collect(); - - let start_pos = labels_with_pos[0].start_pos; - let end_pos = labels_with_pos[labels_with_pos.len() - 1].end_pos; - - Self { miette_err: error, start_pos, end_pos, labels_with_pos, fixed_content } - } - - fn to_lsp_diagnostic(&self, path: &PathBuf) -> lsp_types::Diagnostic { - let severity = match self.miette_err.severity() { - Some(Severity::Error) => Some(lsp_types::DiagnosticSeverity::ERROR), - _ => Some(lsp_types::DiagnosticSeverity::WARNING), - }; - let related_information = Some( - self.labels_with_pos - .iter() - .map(|labeled_span| lsp_types::DiagnosticRelatedInformation { - location: lsp_types::Location { - uri: lsp_types::Url::from_file_path(path).unwrap(), - range: lsp_types::Range { - start: lsp_types::Position { - line: labeled_span.start_pos.line, - character: labeled_span.start_pos.character, - }, - end: lsp_types::Position { - line: labeled_span.end_pos.line, - character: labeled_span.end_pos.character, - }, - }, - }, - message: labeled_span.message.clone().unwrap_or_default(), - }) - .collect(), - ); - let range = related_information.as_ref().map_or( - Range { start: self.start_pos, end: self.end_pos }, - |infos: &Vec| { - let mut ret_range = Range { - start: Position { line: u32::MAX, character: u32::MAX }, - end: Position { line: u32::MAX, character: u32::MAX }, - }; - for info in infos { - if cmp_range(&ret_range, &info.location.range) == std::cmp::Ordering::Greater { - ret_range = info.location.range; - } - } - ret_range - }, - ); - let code = self.miette_err.code().map(|item| item.to_string()); - let code_description = code.as_ref().and_then(|code| { - let (scope, number) = parse_diagnostic_code(code)?; - Some(CodeDescription { - href: Url::from_str(&format!( - "{LINT_DOC_LINK_PREFIX}/{}/{number}", - scope.strip_prefix("eslint-plugin-").unwrap_or(scope).cow_replace("-", "_") - )) - .ok()?, - }) - }); - let message = self.miette_err.help().map_or_else( - || self.miette_err.to_string(), - |help| format!("{}\nhelp: {}", self.miette_err, help), - ); - - lsp_types::Diagnostic { - range, - severity, - code: code.map(NumberOrString::String), - message, - source: Some("oxc".into()), - code_description, - related_information, - tags: None, - data: None, - } - } - - fn into_diagnostic_report(self, path: &PathBuf) -> DiagnosticReport { - DiagnosticReport { - diagnostic: self.to_lsp_diagnostic(path), - fixed_content: self.fixed_content, - } - } -} - -#[derive(Debug, Clone)] -pub struct DiagnosticReport { - pub diagnostic: lsp_types::Diagnostic, - pub fixed_content: Option, -} -#[derive(Debug)] -struct ErrorReport { - pub error: Error, - pub fixed_content: Option, -} - -#[derive(Debug, Clone)] -pub struct FixedContent { - pub code: String, - pub range: Range, -} +use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition, FixedContent}; +use crate::linter::offset_to_position; +use crate::DiagnosticReport; pub struct IsolatedLintHandler { linter: Arc, @@ -350,47 +210,3 @@ impl IsolatedLintHandler { (path.to_path_buf(), diagnostics) } } - -#[allow(clippy::cast_possible_truncation)] -fn offset_to_position(offset: usize, source_text: &str) -> Position { - // TODO(perf): share a single instance of `Rope` - let rope = Rope::from_str(source_text); - let (line, column) = get_line_column(&rope, offset as u32, source_text); - Position::new(line, column) -} - -pub struct ServerLinter { - linter: Arc, -} - -impl ServerLinter { - pub fn new() -> Self { - let linter = Linter::default().with_fix(FixKind::SafeFix); - Self { linter: Arc::new(linter) } - } - - pub fn new_with_linter(linter: Linter) -> Self { - Self { linter: Arc::new(linter) } - } - - pub fn run_single(&self, uri: &Url, content: Option) -> Option> { - IsolatedLintHandler::new(Arc::clone(&self.linter)) - .run_single(&uri.to_file_path().unwrap(), content) - } -} - -fn cmp_range(first: &Range, other: &Range) -> std::cmp::Ordering { - match first.start.cmp(&other.start) { - std::cmp::Ordering::Equal => first.end.cmp(&other.end), - o => o, - } -} - -/// parse `OxcCode` to `Option<(scope, number)>` -fn parse_diagnostic_code(code: &str) -> Option<(&str, &str)> { - if !code.ends_with(')') { - return None; - } - let right_parenthesis_pos = code.rfind('(')?; - Some((&code[0..right_parenthesis_pos], &code[right_parenthesis_pos + 1..code.len() - 1])) -} diff --git a/crates/oxc_language_server/src/linter/mod.rs b/crates/oxc_language_server/src/linter/mod.rs new file mode 100644 index 0000000000000..01e2fd2c5226a --- /dev/null +++ b/crates/oxc_language_server/src/linter/mod.rs @@ -0,0 +1,14 @@ +use oxc_data_structures::rope::{get_line_column, Rope}; +use tower_lsp::lsp_types::Position; + +pub mod error_with_position; +mod isolated_lint_handler; +pub mod server_linter; + +#[allow(clippy::cast_possible_truncation)] +pub fn offset_to_position(offset: usize, source_text: &str) -> Position { + // TODO(perf): share a single instance of `Rope` + let rope = Rope::from_str(source_text); + let (line, column) = get_line_column(&rope, offset as u32, source_text); + Position::new(line, column) +} diff --git a/crates/oxc_language_server/src/linter/server_linter.rs b/crates/oxc_language_server/src/linter/server_linter.rs new file mode 100644 index 0000000000000..22104b9768385 --- /dev/null +++ b/crates/oxc_language_server/src/linter/server_linter.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; + +use tower_lsp::lsp_types::Url; + +use oxc_linter::{FixKind, Linter}; + +use crate::linter::error_with_position::DiagnosticReport; +use crate::linter::isolated_lint_handler::IsolatedLintHandler; + +pub struct ServerLinter { + linter: Arc, +} + +impl ServerLinter { + pub fn new() -> Self { + let linter = Linter::default().with_fix(FixKind::SafeFix); + Self { linter: Arc::new(linter) } + } + + pub fn new_with_linter(linter: Linter) -> Self { + Self { linter: Arc::new(linter) } + } + + pub fn run_single(&self, uri: &Url, content: Option) -> Option> { + IsolatedLintHandler::new(Arc::clone(&self.linter)) + .run_single(&uri.to_file_path().unwrap(), content) + } +} diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 1d5f4dac47c18..fe17ac070d31d 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -23,7 +23,8 @@ use tower_lsp::{ use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC}; -use crate::linter::{DiagnosticReport, ServerLinter}; +use crate::linter::error_with_position::DiagnosticReport; +use crate::linter::server_linter::ServerLinter; mod capabilities; mod linter; diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 0c0c0e6e212b2..ef8e09fe71f0f 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -22,7 +22,7 @@ "oxc" ], "engines": { - "vscode": "^1.95.0" + "vscode": "^1.96.0" }, "sponsor": { "url": "https://github.com/sponsors/boshen"