Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bb8a078

Browse filesBrowse files
committed
feat(language_server): use linter runtime (#10268)
closes #7118 Added tests for: - (language_server & VSCode) nested configuration - (language_server & VSCode) nested configuration extends - (language_server) without nested configuration The implementation requires at the moment a feature flag for `oxc_linter`. I added documentation in `runtime.rs` to let others know why this is needed. Maybe someone can refactor it to avoid the flag. My first idea is a Trait for file system, but you guys know what to do <3 ![grafik](https://github.com/user-attachments/assets/9bb3aa67-c376-4f51-8474-1d0837d78338)
1 parent 0a42695 commit bb8a078
Copy full SHA for bb8a078

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner

50 files changed

+822
-365
lines changed

‎Cargo.lock

Copy file name to clipboardExpand all lines: Cargo.lock
+1-4Lines changed: 1 addition & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/oxc_language_server/Cargo.toml

Copy file name to clipboardExpand all lines: crates/oxc_language_server/Cargo.toml
+2-5Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,10 @@ doctest = false
2323

2424
[dependencies]
2525
oxc_allocator = { workspace = true }
26-
oxc_data_structures = { workspace = true, features = ["rope"] }
2726
oxc_diagnostics = { workspace = true }
28-
oxc_linter = { workspace = true }
29-
oxc_parser = { workspace = true }
30-
oxc_semantic = { workspace = true }
27+
oxc_linter = { workspace = true, features = ["language_server"] }
3128

32-
cow-utils = { workspace = true }
29+
#
3330
env_logger = { workspace = true, features = ["humantime"] }
3431
futures = { workspace = true }
3532
globset = { workspace = true }
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": [
3+
"./config/.oxlintrc.json"
4+
]
5+
}
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": [
3+
"import"
4+
],
5+
"rules": {
6+
"import/no-cycle": "error"
7+
}
8+
}
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// should report cycle detected
2+
import { b } from './dep-b.ts';
3+
4+
b();
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// this file is also included in dep-a.ts and dep-a.ts should report a no-cycle diagnostic
2+
import './dep-a.ts';
3+
4+
export function b() { /* ... */ }
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// should report cycle detected
2+
import { b } from './dep-b.ts';
3+
4+
b();
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// this file is also included in dep-a.ts and dep-a.ts should report a no-cycle diagnostic
2+
import './dep-a.ts';
3+
4+
export function b() { /* ... */ }
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": [
3+
"import"
4+
],
5+
"rules": {
6+
"import/no-cycle": "error"
7+
}
8+
}
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// should report cycle detected
2+
import { b } from './folder-dep-b.ts';
3+
4+
b();
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// this file is also included in folder-dep-a.ts and folder-dep-a.ts should report a no-cycle diagnostic
2+
import './folder-dep-a.ts';
3+
4+
export function b() { /* ... */ }
+88-132Lines changed: 88 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,20 @@
1-
use std::{path::PathBuf, str::FromStr};
1+
use std::{borrow::Cow, path::PathBuf, str::FromStr};
22

3+
use oxc_linter::MessageWithPosition;
34
use tower_lsp_server::{
45
UriExt,
56
lsp_types::{
67
self, CodeDescription, DiagnosticRelatedInformation, NumberOrString, Position, Range, Uri,
78
},
89
};
910

10-
use cow_utils::CowUtils;
11-
use oxc_diagnostics::{Error, Severity};
12-
13-
use crate::linter::offset_to_position;
14-
15-
const LINT_DOC_LINK_PREFIX: &str = "https://oxc.rs/docs/guide/usage/linter/rules";
16-
17-
#[derive(Debug)]
18-
pub struct ErrorWithPosition {
19-
pub start_pos: Position,
20-
pub end_pos: Position,
21-
pub miette_err: Error,
22-
pub fixed_content: Option<FixedContent>,
23-
pub labels_with_pos: Vec<LabeledSpanWithPosition>,
24-
}
25-
26-
#[derive(Debug)]
27-
pub struct LabeledSpanWithPosition {
28-
pub start_pos: Position,
29-
pub end_pos: Position,
30-
pub message: Option<String>,
31-
}
11+
use oxc_diagnostics::Severity;
3212

3313
#[derive(Debug, Clone)]
3414
pub struct DiagnosticReport {
3515
pub diagnostic: lsp_types::Diagnostic,
3616
pub fixed_content: Option<FixedContent>,
3717
}
38-
#[derive(Debug)]
39-
pub struct ErrorReport {
40-
pub error: Error,
41-
pub fixed_content: Option<FixedContent>,
42-
}
4318

4419
#[derive(Debug, Clone)]
4520
pub struct FixedContent {
@@ -55,115 +30,96 @@ fn cmp_range(first: &Range, other: &Range) -> std::cmp::Ordering {
5530
}
5631
}
5732

58-
/// parse `OxcCode` to `Option<(scope, number)>`
59-
fn parse_diagnostic_code(code: &str) -> Option<(&str, &str)> {
60-
if !code.ends_with(')') {
61-
return None;
62-
}
63-
let right_parenthesis_pos = code.rfind('(')?;
64-
Some((&code[0..right_parenthesis_pos], &code[right_parenthesis_pos + 1..code.len() - 1]))
65-
}
66-
67-
impl ErrorWithPosition {
68-
pub fn new(
69-
error: Error,
70-
text: &str,
71-
fixed_content: Option<FixedContent>,
72-
start: usize,
73-
) -> Self {
74-
let labels = error.labels().map_or(vec![], Iterator::collect);
75-
let labels_with_pos: Vec<LabeledSpanWithPosition> = labels
33+
fn message_with_position_to_lsp_diagnostic(
34+
message: &MessageWithPosition<'_>,
35+
path: &PathBuf,
36+
) -> lsp_types::Diagnostic {
37+
let severity = match message.severity {
38+
Severity::Error => Some(lsp_types::DiagnosticSeverity::ERROR),
39+
_ => Some(lsp_types::DiagnosticSeverity::WARNING),
40+
};
41+
let uri = lsp_types::Uri::from_file_path(path).unwrap();
42+
43+
let related_information = message.labels.as_ref().map(|spans| {
44+
spans
7645
.iter()
77-
.map(|labeled_span| LabeledSpanWithPosition {
78-
start_pos: offset_to_position(labeled_span.offset() + start, text),
79-
end_pos: offset_to_position(
80-
labeled_span.offset() + start + labeled_span.len(),
81-
text,
82-
),
83-
message: labeled_span.label().map(ToString::to_string),
84-
})
85-
.collect();
86-
87-
let start_pos = labels_with_pos[0].start_pos;
88-
let end_pos = labels_with_pos[labels_with_pos.len() - 1].end_pos;
89-
90-
Self { miette_err: error, start_pos, end_pos, labels_with_pos, fixed_content }
91-
}
92-
93-
fn to_lsp_diagnostic(&self, path: &PathBuf) -> lsp_types::Diagnostic {
94-
let severity = match self.miette_err.severity() {
95-
Some(Severity::Error) => Some(lsp_types::DiagnosticSeverity::ERROR),
96-
_ => Some(lsp_types::DiagnosticSeverity::WARNING),
97-
};
98-
let related_information = Some(
99-
self.labels_with_pos
100-
.iter()
101-
.map(|labeled_span| lsp_types::DiagnosticRelatedInformation {
102-
location: lsp_types::Location {
103-
uri: lsp_types::Uri::from_file_path(path).unwrap(),
104-
range: lsp_types::Range {
105-
start: lsp_types::Position {
106-
line: labeled_span.start_pos.line,
107-
character: labeled_span.start_pos.character,
108-
},
109-
end: lsp_types::Position {
110-
line: labeled_span.end_pos.line,
111-
character: labeled_span.end_pos.character,
112-
},
46+
.map(|span| lsp_types::DiagnosticRelatedInformation {
47+
location: lsp_types::Location {
48+
uri: uri.clone(),
49+
range: lsp_types::Range {
50+
start: lsp_types::Position {
51+
line: span.start().line,
52+
character: span.start().character,
53+
},
54+
end: lsp_types::Position {
55+
line: span.end().line,
56+
character: span.end().character,
11357
},
11458
},
115-
message: labeled_span.message.clone().unwrap_or_default(),
116-
})
117-
.collect(),
118-
);
119-
let range = related_information.as_ref().map_or(
120-
Range { start: self.start_pos, end: self.end_pos },
121-
|infos: &Vec<DiagnosticRelatedInformation>| {
122-
let mut ret_range = Range {
123-
start: Position { line: u32::MAX, character: u32::MAX },
124-
end: Position { line: u32::MAX, character: u32::MAX },
125-
};
126-
for info in infos {
127-
if cmp_range(&ret_range, &info.location.range) == std::cmp::Ordering::Greater {
128-
ret_range = info.location.range;
129-
}
130-
}
131-
ret_range
132-
},
133-
);
134-
let code = self.miette_err.code().map(|item| item.to_string());
135-
let code_description = code.as_ref().and_then(|code| {
136-
let (scope, number) = parse_diagnostic_code(code)?;
137-
Some(CodeDescription {
138-
href: Uri::from_str(&format!(
139-
"{LINT_DOC_LINK_PREFIX}/{}/{number}",
140-
scope.strip_prefix("eslint-plugin-").unwrap_or(scope).cow_replace("-", "_")
141-
))
142-
.ok()?,
59+
},
60+
message: span.message().unwrap_or(&Cow::Borrowed("")).to_string(),
14361
})
144-
});
145-
let message = self.miette_err.help().map_or_else(
146-
|| self.miette_err.to_string(),
147-
|help| format!("{}\nhelp: {}", self.miette_err, help),
148-
);
149-
150-
lsp_types::Diagnostic {
151-
range,
152-
severity,
153-
code: code.map(NumberOrString::String),
154-
message,
155-
source: Some("oxc".into()),
156-
code_description,
157-
related_information,
158-
tags: None,
159-
data: None,
160-
}
62+
.collect()
63+
});
64+
65+
let range = related_information.as_ref().map_or(
66+
Range {
67+
start: Position { line: u32::MAX, character: u32::MAX },
68+
end: Position { line: u32::MAX, character: u32::MAX },
69+
},
70+
|infos: &Vec<DiagnosticRelatedInformation>| {
71+
let mut ret_range = Range {
72+
start: Position { line: u32::MAX, character: u32::MAX },
73+
end: Position { line: u32::MAX, character: u32::MAX },
74+
};
75+
for info in infos {
76+
if cmp_range(&ret_range, &info.location.range) == std::cmp::Ordering::Greater {
77+
ret_range = info.location.range;
78+
}
79+
}
80+
ret_range
81+
},
82+
);
83+
let code = message.code.to_string();
84+
let code_description =
85+
message.url.as_ref().map(|url| CodeDescription { href: Uri::from_str(url).ok().unwrap() });
86+
let message = message.help.as_ref().map_or_else(
87+
|| message.message.to_string(),
88+
|help| format!("{}\nhelp: {}", message.message, help),
89+
);
90+
91+
lsp_types::Diagnostic {
92+
range,
93+
severity,
94+
code: Some(NumberOrString::String(code)),
95+
message,
96+
source: Some("oxc".into()),
97+
code_description,
98+
related_information,
99+
tags: None,
100+
data: None,
161101
}
102+
}
162103

163-
pub fn into_diagnostic_report(self, path: &PathBuf) -> DiagnosticReport {
164-
DiagnosticReport {
165-
diagnostic: self.to_lsp_diagnostic(path),
166-
fixed_content: self.fixed_content,
167-
}
104+
pub fn message_with_position_to_lsp_diagnostic_report(
105+
message: &MessageWithPosition<'_>,
106+
path: &PathBuf,
107+
) -> DiagnosticReport {
108+
DiagnosticReport {
109+
diagnostic: message_with_position_to_lsp_diagnostic(message, path),
110+
fixed_content: message.fix.as_ref().map(|infos| FixedContent {
111+
message: infos.span.message().map(std::string::ToString::to_string),
112+
code: infos.content.to_string(),
113+
range: Range {
114+
start: Position {
115+
line: infos.span.start().line,
116+
character: infos.span.start().character,
117+
},
118+
end: Position {
119+
line: infos.span.end().line,
120+
character: infos.span.end().character,
121+
},
122+
},
123+
}),
168124
}
169125
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.