Skip to content

Commit 2f9a6ac

Browse files
committed
refactor(linter): move lsp relevant code into own file (#13243)
Moving all code behind `#[cfg(feature = "language_server")]` into own file.
1 parent 47aefa8 commit 2f9a6ac

File tree

8 files changed

+203
-225
lines changed

8 files changed

+203
-225
lines changed

crates/oxc_linter/src/fixer/fix.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use bitflags::bitflags;
55
use oxc_allocator::{Allocator, CloneIn};
66
use oxc_span::{GetSpan, SPAN, Span};
77

8-
#[cfg(feature = "language_server")]
9-
use crate::service::offset_to_position::SpanPositionMessage;
10-
118
bitflags! {
129
/// Flags describing an automatic code fix.
1310
///
@@ -292,13 +289,6 @@ pub struct Fix<'a> {
292289
pub span: Span,
293290
}
294291

295-
#[cfg(feature = "language_server")]
296-
#[derive(Debug)]
297-
pub struct FixWithPosition<'a> {
298-
pub content: Cow<'a, str>,
299-
pub span: SpanPositionMessage<'a>,
300-
}
301-
302292
impl<'new> CloneIn<'new> for Fix<'_> {
303293
type Cloned = Fix<'new>;
304294

@@ -392,14 +382,6 @@ impl PossibleFixes<'_> {
392382
}
393383
}
394384

395-
#[cfg(feature = "language_server")]
396-
#[derive(Debug)]
397-
pub enum PossibleFixesWithPosition<'a> {
398-
None,
399-
Single(FixWithPosition<'a>),
400-
Multiple(Vec<FixWithPosition<'a>>),
401-
}
402-
403385
// NOTE (@DonIsaac): having these variants is effectively the same as interning
404386
// single or 0-element Vecs. I experimented with using smallvec here, but the
405387
// resulting struct size was larger (40 bytes vs 32). So, we're sticking with

crates/oxc_linter/src/fixer/mod.rs

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,6 @@ use oxc_span::{GetSpan, Span};
66

77
use crate::LintContext;
88

9-
#[cfg(feature = "language_server")]
10-
use crate::service::offset_to_position::SpanPositionMessage;
11-
#[cfg(feature = "language_server")]
12-
pub use fix::{FixWithPosition, PossibleFixesWithPosition};
13-
#[cfg(feature = "language_server")]
14-
use oxc_data_structures::rope::Rope;
15-
#[cfg(feature = "language_server")]
16-
use oxc_diagnostics::{OxcCode, Severity};
17-
189
mod fix;
1910
pub use fix::{CompositeFix, Fix, FixKind, PossibleFixes, RuleFix};
2011
use oxc_allocator::{Allocator, CloneIn};
@@ -235,95 +226,6 @@ pub struct Message<'a> {
235226
fixed: bool,
236227
}
237228

238-
#[cfg(feature = "language_server")]
239-
#[derive(Debug)]
240-
pub struct MessageWithPosition<'a> {
241-
pub message: Cow<'a, str>,
242-
pub labels: Option<Vec<SpanPositionMessage<'a>>>,
243-
pub help: Option<Cow<'a, str>>,
244-
pub severity: Severity,
245-
pub code: OxcCode,
246-
pub url: Option<Cow<'a, str>>,
247-
pub fixes: PossibleFixesWithPosition<'a>,
248-
}
249-
250-
#[cfg(feature = "language_server")]
251-
impl From<OxcDiagnostic> for MessageWithPosition<'_> {
252-
fn from(from: OxcDiagnostic) -> Self {
253-
Self {
254-
message: from.message.clone(),
255-
labels: None,
256-
help: from.help.clone(),
257-
severity: from.severity,
258-
code: from.code.clone(),
259-
url: from.url.clone(),
260-
fixes: PossibleFixesWithPosition::None,
261-
}
262-
}
263-
}
264-
265-
// clippy: the source field is checked and assumed to be less than 4GB, and
266-
// we assume that the fix offset will not exceed 2GB in either direction
267-
#[cfg(feature = "language_server")]
268-
#[expect(clippy::cast_possible_truncation)]
269-
pub fn message_to_message_with_position<'a>(
270-
message: &Message<'a>,
271-
source_text: &str,
272-
rope: &Rope,
273-
) -> MessageWithPosition<'a> {
274-
use crate::service::offset_to_position::offset_to_position;
275-
276-
let labels = message.error.labels.as_ref().map(|labels| {
277-
labels
278-
.iter()
279-
.map(|labeled_span| {
280-
let offset = labeled_span.offset() as u32;
281-
let start_position = offset_to_position(rope, offset, source_text);
282-
let end_position =
283-
offset_to_position(rope, offset + labeled_span.len() as u32, source_text);
284-
let message = labeled_span.label().map(|label| Cow::Owned(label.to_string()));
285-
286-
SpanPositionMessage::new(start_position, end_position).with_message(message)
287-
})
288-
.collect::<Vec<_>>()
289-
});
290-
291-
MessageWithPosition {
292-
message: message.error.message.clone(),
293-
severity: message.error.severity,
294-
help: message.error.help.clone(),
295-
url: message.error.url.clone(),
296-
code: message.error.code.clone(),
297-
labels,
298-
fixes: match &message.fixes {
299-
PossibleFixes::None => PossibleFixesWithPosition::None,
300-
PossibleFixes::Single(fix) => {
301-
PossibleFixesWithPosition::Single(fix_to_fix_with_position(fix, rope, source_text))
302-
}
303-
PossibleFixes::Multiple(fixes) => PossibleFixesWithPosition::Multiple(
304-
fixes.iter().map(|fix| fix_to_fix_with_position(fix, rope, source_text)).collect(),
305-
),
306-
},
307-
}
308-
}
309-
310-
#[cfg(feature = "language_server")]
311-
fn fix_to_fix_with_position<'a>(
312-
fix: &Fix<'a>,
313-
rope: &Rope,
314-
source_text: &str,
315-
) -> FixWithPosition<'a> {
316-
use crate::service::offset_to_position::offset_to_position;
317-
318-
let start_position = offset_to_position(rope, fix.span.start, source_text);
319-
let end_position = offset_to_position(rope, fix.span.end, source_text);
320-
FixWithPosition {
321-
content: fix.content.clone(),
322-
span: SpanPositionMessage::new(start_position, end_position)
323-
.with_message(fix.message.as_ref().map(|label| Cow::Owned(label.to_string()))),
324-
}
325-
}
326-
327229
impl<'new> CloneIn<'new> for Message<'_> {
328230
type Cloned = Message<'new>;
329231

crates/oxc_linter/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ mod external_plugin_store;
2121
mod fixer;
2222
mod frameworks;
2323
mod globals;
24+
#[cfg(feature = "language_server")]
25+
mod lsp;
2426
mod module_graph_visitor;
2527
mod module_record;
2628
mod options;
@@ -70,7 +72,7 @@ use crate::{
7072
};
7173

7274
#[cfg(feature = "language_server")]
73-
pub use crate::fixer::{FixWithPosition, MessageWithPosition, PossibleFixesWithPosition};
75+
pub use crate::lsp::{FixWithPosition, MessageWithPosition, PossibleFixesWithPosition};
7476

7577
#[cfg(target_pointer_width = "64")]
7678
#[test]

crates/oxc_linter/src/lsp.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use oxc_data_structures::rope::{Rope, get_line_column};
2+
use std::borrow::Cow;
3+
4+
use crate::fixer::{Fix, Message, PossibleFixes};
5+
use oxc_diagnostics::{OxcCode, OxcDiagnostic, Severity};
6+
7+
#[derive(Clone, Debug)]
8+
pub struct SpanPositionMessage<'a> {
9+
/// A brief suggestion message describing the fix. Will be shown in
10+
/// editors via code actions.
11+
message: Option<Cow<'a, str>>,
12+
13+
start: SpanPosition,
14+
end: SpanPosition,
15+
}
16+
17+
impl<'a> SpanPositionMessage<'a> {
18+
pub fn new(start: SpanPosition, end: SpanPosition) -> Self {
19+
Self { start, end, message: None }
20+
}
21+
22+
pub fn with_message(mut self, message: Option<Cow<'a, str>>) -> Self {
23+
self.message = message;
24+
self
25+
}
26+
27+
pub fn start(&self) -> &SpanPosition {
28+
&self.start
29+
}
30+
31+
pub fn end(&self) -> &SpanPosition {
32+
&self.end
33+
}
34+
35+
pub fn message(&self) -> Option<&Cow<'a, str>> {
36+
self.message.as_ref()
37+
}
38+
}
39+
40+
#[derive(Clone, Debug)]
41+
pub struct SpanPosition {
42+
pub line: u32,
43+
pub character: u32,
44+
}
45+
46+
impl SpanPosition {
47+
pub fn new(line: u32, column: u32) -> Self {
48+
Self { line, character: column }
49+
}
50+
}
51+
52+
pub fn offset_to_position(rope: &Rope, offset: u32, source_text: &str) -> SpanPosition {
53+
let (line, column) = get_line_column(rope, offset, source_text);
54+
SpanPosition::new(line, column)
55+
}
56+
57+
#[derive(Debug)]
58+
pub struct MessageWithPosition<'a> {
59+
pub message: Cow<'a, str>,
60+
pub labels: Option<Vec<SpanPositionMessage<'a>>>,
61+
pub help: Option<Cow<'a, str>>,
62+
pub severity: Severity,
63+
pub code: OxcCode,
64+
pub url: Option<Cow<'a, str>>,
65+
pub fixes: PossibleFixesWithPosition<'a>,
66+
}
67+
68+
impl From<OxcDiagnostic> for MessageWithPosition<'_> {
69+
fn from(from: OxcDiagnostic) -> Self {
70+
Self {
71+
message: from.message.clone(),
72+
labels: None,
73+
help: from.help.clone(),
74+
severity: from.severity,
75+
code: from.code.clone(),
76+
url: from.url.clone(),
77+
fixes: PossibleFixesWithPosition::None,
78+
}
79+
}
80+
}
81+
82+
// clippy: the source field is checked and assumed to be less than 4GB, and
83+
// we assume that the fix offset will not exceed 2GB in either direction
84+
#[expect(clippy::cast_possible_truncation)]
85+
pub fn message_to_message_with_position<'a>(
86+
message: &Message<'a>,
87+
source_text: &str,
88+
rope: &Rope,
89+
) -> MessageWithPosition<'a> {
90+
let labels = message.error.labels.as_ref().map(|labels| {
91+
labels
92+
.iter()
93+
.map(|labeled_span| {
94+
let offset = labeled_span.offset() as u32;
95+
let start_position = offset_to_position(rope, offset, source_text);
96+
let end_position =
97+
offset_to_position(rope, offset + labeled_span.len() as u32, source_text);
98+
let message = labeled_span.label().map(|label| Cow::Owned(label.to_string()));
99+
100+
SpanPositionMessage::new(start_position, end_position).with_message(message)
101+
})
102+
.collect::<Vec<_>>()
103+
});
104+
105+
MessageWithPosition {
106+
message: message.error.message.clone(),
107+
severity: message.error.severity,
108+
help: message.error.help.clone(),
109+
url: message.error.url.clone(),
110+
code: message.error.code.clone(),
111+
labels,
112+
fixes: match &message.fixes {
113+
PossibleFixes::None => PossibleFixesWithPosition::None,
114+
PossibleFixes::Single(fix) => {
115+
PossibleFixesWithPosition::Single(fix_to_fix_with_position(fix, rope, source_text))
116+
}
117+
PossibleFixes::Multiple(fixes) => PossibleFixesWithPosition::Multiple(
118+
fixes.iter().map(|fix| fix_to_fix_with_position(fix, rope, source_text)).collect(),
119+
),
120+
},
121+
}
122+
}
123+
124+
#[derive(Debug)]
125+
pub enum PossibleFixesWithPosition<'a> {
126+
None,
127+
Single(FixWithPosition<'a>),
128+
Multiple(Vec<FixWithPosition<'a>>),
129+
}
130+
131+
#[derive(Debug)]
132+
pub struct FixWithPosition<'a> {
133+
pub content: Cow<'a, str>,
134+
pub span: SpanPositionMessage<'a>,
135+
}
136+
137+
fn fix_to_fix_with_position<'a>(
138+
fix: &Fix<'a>,
139+
rope: &Rope,
140+
source_text: &str,
141+
) -> FixWithPosition<'a> {
142+
let start_position = offset_to_position(rope, fix.span.start, source_text);
143+
let end_position = offset_to_position(rope, fix.span.end, source_text);
144+
FixWithPosition {
145+
content: fix.content.clone(),
146+
span: SpanPositionMessage::new(start_position, end_position)
147+
.with_message(fix.message.as_ref().map(|label| Cow::Owned(label.to_string()))),
148+
}
149+
}
150+
151+
#[cfg(test)]
152+
mod test {
153+
use oxc_data_structures::rope::Rope;
154+
155+
use super::offset_to_position;
156+
157+
#[test]
158+
fn single_line() {
159+
let source = "foo.bar!;";
160+
assert_position(source, 0, (0, 0));
161+
assert_position(source, 4, (0, 4));
162+
assert_position(source, 9, (0, 9));
163+
}
164+
165+
#[test]
166+
fn multi_line() {
167+
let source = "console.log(\n foo.bar!\n);";
168+
assert_position(source, 0, (0, 0));
169+
assert_position(source, 12, (0, 12));
170+
assert_position(source, 13, (1, 0));
171+
assert_position(source, 23, (1, 10));
172+
assert_position(source, 24, (2, 0));
173+
assert_position(source, 26, (2, 2));
174+
}
175+
176+
#[test]
177+
fn multi_byte() {
178+
let source = "let foo = \n '👍';";
179+
assert_position(source, 10, (0, 10));
180+
assert_position(source, 11, (1, 0));
181+
assert_position(source, 14, (1, 3));
182+
assert_position(source, 18, (1, 5));
183+
assert_position(source, 19, (1, 6));
184+
}
185+
186+
#[test]
187+
#[should_panic(expected = "out of bounds")]
188+
fn out_of_bounds() {
189+
offset_to_position(&Rope::from_str("foo"), 100, "foo");
190+
}
191+
192+
fn assert_position(source: &str, offset: u32, expected: (u32, u32)) {
193+
let position = offset_to_position(&Rope::from_str(source), offset, source);
194+
assert_eq!(position.line, expected.0);
195+
assert_eq!(position.character, expected.1);
196+
}
197+
}

crates/oxc_linter/src/service/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ use crate::Linter;
1111
mod runtime;
1212
use runtime::Runtime;
1313
pub use runtime::RuntimeFileSystem;
14-
15-
#[cfg(feature = "language_server")]
16-
pub mod offset_to_position;
17-
1814
pub struct LintServiceOptions {
1915
/// Current working directory
2016
cwd: Box<Path>,

0 commit comments

Comments
 (0)