mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
refactor(language_server): move structs into own file (#8026)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
b2a4a786ae
commit
de8246b96f
6 changed files with 216 additions and 192 deletions
165
crates/oxc_language_server/src/linter/error_with_position.rs
Normal file
165
crates/oxc_language_server/src/linter/error_with_position.rs
Normal file
|
|
@ -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<FixedContent>,
|
||||||
|
pub labels_with_pos: Vec<LabeledSpanWithPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LabeledSpanWithPosition {
|
||||||
|
pub start_pos: Position,
|
||||||
|
pub end_pos: Position,
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiagnosticReport {
|
||||||
|
pub diagnostic: lsp_types::Diagnostic,
|
||||||
|
pub fixed_content: Option<FixedContent>,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ErrorReport {
|
||||||
|
pub error: Error,
|
||||||
|
pub fixed_content: Option<FixedContent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<FixedContent>,
|
||||||
|
start: usize,
|
||||||
|
) -> Self {
|
||||||
|
let labels = error.labels().map_or(vec![], Iterator::collect);
|
||||||
|
let labels_with_pos: Vec<LabeledSpanWithPosition> = 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<DiagnosticRelatedInformation>| {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,166 +2,26 @@ use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use cow_utils::CowUtils;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use tower_lsp::lsp_types::{
|
use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Range};
|
||||||
self, CodeDescription, DiagnosticRelatedInformation, DiagnosticSeverity, NumberOrString,
|
|
||||||
Position, Range, Url,
|
|
||||||
};
|
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_data_structures::rope::{get_line_column, Rope};
|
use oxc_diagnostics::{Error, NamedSource};
|
||||||
use oxc_diagnostics::{Error, NamedSource, Severity};
|
|
||||||
use oxc_linter::{
|
use oxc_linter::{
|
||||||
loader::{JavaScriptSource, Loader, LINT_PARTIAL_LOADER_EXT},
|
loader::{JavaScriptSource, Loader, LINT_PARTIAL_LOADER_EXT},
|
||||||
FixKind, Linter, ModuleRecord,
|
Linter, ModuleRecord,
|
||||||
};
|
};
|
||||||
use oxc_parser::{ParseOptions, Parser};
|
use oxc_parser::{ParseOptions, Parser};
|
||||||
use oxc_semantic::SemanticBuilder;
|
use oxc_semantic::SemanticBuilder;
|
||||||
use oxc_span::VALID_EXTENSIONS;
|
use oxc_span::VALID_EXTENSIONS;
|
||||||
|
|
||||||
const LINT_DOC_LINK_PREFIX: &str = "https://oxc.rs/docs/guide/usage/linter/rules";
|
use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition, FixedContent};
|
||||||
#[derive(Debug)]
|
use crate::linter::offset_to_position;
|
||||||
struct ErrorWithPosition {
|
use crate::DiagnosticReport;
|
||||||
pub start_pos: Position,
|
|
||||||
pub end_pos: Position,
|
|
||||||
pub miette_err: Error,
|
|
||||||
pub fixed_content: Option<FixedContent>,
|
|
||||||
pub labels_with_pos: Vec<LabeledSpanWithPosition>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct LabeledSpanWithPosition {
|
|
||||||
pub start_pos: Position,
|
|
||||||
pub end_pos: Position,
|
|
||||||
pub message: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorWithPosition {
|
|
||||||
pub fn new(
|
|
||||||
error: Error,
|
|
||||||
text: &str,
|
|
||||||
fixed_content: Option<FixedContent>,
|
|
||||||
start: usize,
|
|
||||||
) -> Self {
|
|
||||||
let labels = error.labels().map_or(vec![], Iterator::collect);
|
|
||||||
let labels_with_pos: Vec<LabeledSpanWithPosition> = 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<DiagnosticRelatedInformation>| {
|
|
||||||
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<FixedContent>,
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ErrorReport {
|
|
||||||
pub error: Error,
|
|
||||||
pub fixed_content: Option<FixedContent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FixedContent {
|
|
||||||
pub code: String,
|
|
||||||
pub range: Range,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IsolatedLintHandler {
|
pub struct IsolatedLintHandler {
|
||||||
linter: Arc<Linter>,
|
linter: Arc<Linter>,
|
||||||
|
|
@ -350,47 +210,3 @@ impl IsolatedLintHandler {
|
||||||
(path.to_path_buf(), diagnostics)
|
(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<Linter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String>) -> Option<Vec<DiagnosticReport>> {
|
|
||||||
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]))
|
|
||||||
}
|
|
||||||
14
crates/oxc_language_server/src/linter/mod.rs
Normal file
14
crates/oxc_language_server/src/linter/mod.rs
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
28
crates/oxc_language_server/src/linter/server_linter.rs
Normal file
28
crates/oxc_language_server/src/linter/server_linter.rs
Normal file
|
|
@ -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<Linter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>) -> Option<Vec<DiagnosticReport>> {
|
||||||
|
IsolatedLintHandler::new(Arc::clone(&self.linter))
|
||||||
|
.run_single(&uri.to_file_path().unwrap(), content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,8 @@ use tower_lsp::{
|
||||||
use oxc_linter::{FixKind, LinterBuilder, Oxlintrc};
|
use oxc_linter::{FixKind, LinterBuilder, Oxlintrc};
|
||||||
|
|
||||||
use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC};
|
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 capabilities;
|
||||||
mod linter;
|
mod linter;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"oxc"
|
"oxc"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.95.0"
|
"vscode": "^1.96.0"
|
||||||
},
|
},
|
||||||
"sponsor": {
|
"sponsor": {
|
||||||
"url": "https://github.com/sponsors/boshen"
|
"url": "https://github.com/sponsors/boshen"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue