Skip to content

Commit 35fcbd7

Browse files
committed
fix(language_server): split run logic for oxlint and tsgolint
1 parent 6431033 commit 35fcbd7

File tree

13 files changed

+239
-62
lines changed

13 files changed

+239
-62
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
debugger;
3+
4+
async function returnsPromise() {
5+
return "value";
6+
}
7+
8+
returnsPromise().then(() => {});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
debugger;
3+
4+
async function returnsPromise() {
5+
return "value";
6+
}
7+
8+
returnsPromise().then(() => {});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
debugger;
3+
4+
async function returnsPromise() {
5+
return "value";
6+
}
7+
8+
returnsPromise().then(() => {});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
debugger;
3+
4+
async function returnsPromise() {
5+
return "value";
6+
}
7+
8+
returnsPromise().then(() => {});

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@ use crate::linter::{
1919
isolated_lint_handler::{IsolatedLintHandler, IsolatedLintHandlerOptions},
2020
tsgo_linter::TsgoLinter,
2121
};
22-
use crate::options::UnusedDisableDirectives;
22+
use crate::options::{Run, UnusedDisableDirectives};
2323
use crate::{ConcurrentHashMap, OXC_CONFIG_FILE, Options};
2424

2525
use super::config_walker::ConfigWalker;
2626

27+
#[derive(Debug, PartialEq, Eq)]
28+
pub enum ServerLinterRun {
29+
OnType,
30+
OnSave,
31+
Force,
32+
}
33+
2734
pub struct ServerLinter {
2835
isolated_linter: Arc<Mutex<IsolatedLintHandler>>,
2936
tsgo_linter: Arc<Option<TsgoLinter>>,
3037
ignore_matcher: LintIgnoreMatcher,
3138
gitignore_glob: Vec<Gitignore>,
39+
lint_on_run: Run,
3240
pub extended_paths: Vec<PathBuf>,
3341
}
3442

@@ -125,6 +133,7 @@ impl ServerLinter {
125133
),
126134
gitignore_glob: Self::create_ignore_glob(&root_path),
127135
extended_paths,
136+
lint_on_run: options.run,
128137
tsgo_linter: if options.type_aware {
129138
Arc::new(Some(TsgoLinter::new(&root_path, config_store)))
130139
} else {
@@ -243,14 +252,42 @@ impl ServerLinter {
243252
&self,
244253
uri: &Uri,
245254
content: Option<String>,
255+
run_type: ServerLinterRun,
246256
) -> Option<Vec<DiagnosticReport>> {
257+
let (oxlint, tsgolint) = match (run_type, self.lint_on_run) {
258+
// run everything on save, or force
259+
(ServerLinterRun::Force, _) | (ServerLinterRun::OnSave, Run::OnSave) => (true, true),
260+
// run only oxlint on type
261+
// tsgolint does not support memory source_text
262+
(ServerLinterRun::OnType, Run::OnType) => (true, false),
263+
// it does not match, run nothing
264+
(ServerLinterRun::OnType, Run::OnSave) => (false, false),
265+
// run only tsglint on save, even if the user wants it with type
266+
// tsgolint only supports the OS file system.
267+
(ServerLinterRun::OnSave, Run::OnType) => (false, true),
268+
};
269+
270+
// return `None` when both tools doe not want to be used
271+
if !oxlint && !tsgolint {
272+
return None;
273+
}
274+
247275
if self.is_ignored(uri) {
248276
return None;
249277
}
250278

251-
// when `IsolatedLintHandler` returns `None`, it means it does not want to lint.
252-
// Do not try `tsgolint` because it could be ignored or is not supported.
253-
let mut reports = self.isolated_linter.lock().await.run_single(uri, content.clone())?;
279+
let mut reports = Vec::with_capacity(0);
280+
281+
if oxlint
282+
&& let Some(oxlint_reports) =
283+
self.isolated_linter.lock().await.run_single(uri, content.clone())
284+
{
285+
reports.extend(oxlint_reports);
286+
}
287+
288+
if !tsgolint {
289+
return Some(reports);
290+
}
254291

255292
let Some(tsgo_linter) = &*self.tsgo_linter else {
256293
return Some(reports);
@@ -296,6 +333,7 @@ mod test {
296333
use crate::{
297334
Options,
298335
linter::server_linter::{ServerLinter, normalize_path},
336+
options::Run,
299337
tester::{Tester, get_file_path},
300338
};
301339
use rustc_hash::FxHashMap;
@@ -342,70 +380,110 @@ mod test {
342380
assert!(configs_dirs[0].ends_with("init_nested_configs"));
343381
}
344382

383+
#[test]
384+
fn test_lint_on_run_on_type_on_type() {
385+
Tester::new(
386+
"fixtures/linter/lint_on_run/on_type",
387+
Some(Options { type_aware: true, run: Run::OnType, ..Default::default() }),
388+
)
389+
.test_and_snapshot_single_file("on-type.ts", Run::OnType);
390+
}
391+
392+
#[test]
393+
fn test_lint_on_run_on_type_on_save() {
394+
Tester::new(
395+
"fixtures/linter/lint_on_run/on_save",
396+
Some(Options { type_aware: true, run: Run::OnType, ..Default::default() }),
397+
)
398+
.test_and_snapshot_single_file("on-save.ts", Run::OnSave);
399+
}
400+
401+
#[test]
402+
fn test_lint_on_run_on_save_on_type() {
403+
Tester::new(
404+
"fixtures/linter/lint_on_run/on_save",
405+
Some(Options { type_aware: true, run: Run::OnSave, ..Default::default() }),
406+
)
407+
.test_and_snapshot_single_file("on-type.ts", Run::OnType);
408+
}
409+
410+
#[test]
411+
fn test_lint_on_run_on_save_on_save() {
412+
Tester::new(
413+
"fixtures/linter/lint_on_run/on_type",
414+
Some(Options { type_aware: true, run: Run::OnSave, ..Default::default() }),
415+
)
416+
.test_and_snapshot_single_file("on-save.ts", Run::OnSave);
417+
}
418+
345419
#[test]
346420
fn test_no_errors() {
347421
Tester::new("fixtures/linter/no_errors", None)
348-
.test_and_snapshot_single_file("hello_world.js");
422+
.test_and_snapshot_single_file("hello_world.js", Run::default());
349423
}
350424

351425
#[test]
352426
fn test_no_console() {
353427
Tester::new("fixtures/linter/deny_no_console", None)
354-
.test_and_snapshot_single_file("hello_world.js");
428+
.test_and_snapshot_single_file("hello_world.js", Run::default());
355429
}
356430

357431
// Test case for https://github.com/oxc-project/oxc/issues/9958
358432
#[test]
359433
fn test_issue_9958() {
360-
Tester::new("fixtures/linter/issue_9958", None).test_and_snapshot_single_file("issue.ts");
434+
Tester::new("fixtures/linter/issue_9958", None)
435+
.test_and_snapshot_single_file("issue.ts", Run::default());
361436
}
362437

363438
// Test case for https://github.com/oxc-project/oxc/issues/9957
364439
#[test]
365440
fn test_regexp() {
366441
Tester::new("fixtures/linter/regexp_feature", None)
367-
.test_and_snapshot_single_file("index.ts");
442+
.test_and_snapshot_single_file("index.ts", Run::default());
368443
}
369444

370445
#[test]
371446
fn test_frameworks() {
372-
Tester::new("fixtures/linter/astro", None).test_and_snapshot_single_file("debugger.astro");
373-
Tester::new("fixtures/linter/vue", None).test_and_snapshot_single_file("debugger.vue");
447+
Tester::new("fixtures/linter/astro", None)
448+
.test_and_snapshot_single_file("debugger.astro", Run::default());
449+
Tester::new("fixtures/linter/vue", None)
450+
.test_and_snapshot_single_file("debugger.vue", Run::default());
374451
Tester::new("fixtures/linter/svelte", None)
375-
.test_and_snapshot_single_file("debugger.svelte");
452+
.test_and_snapshot_single_file("debugger.svelte", Run::default());
376453
// ToDo: fix Tester to work only with Uris and do not access the file system
377454
// Tester::new("fixtures/linter/nextjs").test_and_snapshot_single_file("%5B%5B..rest%5D%5D/debugger.ts");
378455
}
379456

380457
#[test]
381458
fn test_invalid_syntax_file() {
382459
Tester::new("fixtures/linter/invalid_syntax", None)
383-
.test_and_snapshot_single_file("debugger.ts");
460+
.test_and_snapshot_single_file("debugger.ts", Run::default());
384461
}
385462

386463
#[test]
387464
fn test_cross_module_debugger() {
388465
Tester::new("fixtures/linter/cross_module", None)
389-
.test_and_snapshot_single_file("debugger.ts");
466+
.test_and_snapshot_single_file("debugger.ts", Run::default());
390467
}
391468

392469
#[test]
393470
fn test_cross_module_no_cycle() {
394-
Tester::new("fixtures/linter/cross_module", None).test_and_snapshot_single_file("dep-a.ts");
471+
Tester::new("fixtures/linter/cross_module", None)
472+
.test_and_snapshot_single_file("dep-a.ts", Run::default());
395473
}
396474

397475
#[test]
398476
fn test_cross_module_no_cycle_nested_config() {
399477
Tester::new("fixtures/linter/cross_module_nested_config", None)
400-
.test_and_snapshot_single_file("dep-a.ts");
478+
.test_and_snapshot_single_file("dep-a.ts", Run::default());
401479
Tester::new("fixtures/linter/cross_module_nested_config", None)
402-
.test_and_snapshot_single_file("folder/folder-dep-a.ts");
480+
.test_and_snapshot_single_file("folder/folder-dep-a.ts", Run::default());
403481
}
404482

405483
#[test]
406484
fn test_cross_module_no_cycle_extended_config() {
407485
Tester::new("fixtures/linter/cross_module_extended_config", None)
408-
.test_and_snapshot_single_file("dep-a.ts");
486+
.test_and_snapshot_single_file("dep-a.ts", Run::default());
409487
}
410488

411489
#[test]
@@ -420,7 +498,7 @@ mod test {
420498
..Options::default()
421499
}),
422500
)
423-
.test_and_snapshot_single_file("forward_ref.ts");
501+
.test_and_snapshot_single_file("forward_ref.ts", Run::default());
424502
}
425503

426504
#[test]
@@ -433,14 +511,14 @@ mod test {
433511
..Default::default()
434512
}),
435513
)
436-
.test_and_snapshot_single_file("test.js");
514+
.test_and_snapshot_single_file("test.js", Run::default());
437515
}
438516

439517
#[test]
440518
fn test_root_ignore_patterns() {
441519
let tester = Tester::new("fixtures/linter/ignore_patterns", None);
442-
tester.test_and_snapshot_single_file("ignored-file.ts");
443-
tester.test_and_snapshot_single_file("another_config/not-ignored-file.ts");
520+
tester.test_and_snapshot_single_file("ignored-file.ts", Run::default());
521+
tester.test_and_snapshot_single_file("another_config/not-ignored-file.ts", Run::default());
444522
}
445523

446524
#[test]
@@ -452,7 +530,7 @@ mod test {
452530
..Default::default()
453531
}),
454532
)
455-
.test_and_snapshot_single_file("deep/src/dep-a.ts");
533+
.test_and_snapshot_single_file("deep/src/dep-a.ts", Run::default());
456534
}
457535

458536
#[test]
@@ -462,6 +540,6 @@ mod test {
462540
"fixtures/linter/tsgolint",
463541
Some(Options { type_aware: true, ..Default::default() }),
464542
);
465-
tester.test_and_snapshot_single_file("no-floating-promises/index.ts");
543+
tester.test_and_snapshot_single_file("no-floating-promises/index.ts", Run::OnSave);
466544
}
467545
}

crates/oxc_language_server/src/main.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ mod worker;
3030
use capabilities::Capabilities;
3131
use code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC;
3232
use commands::{FIX_ALL_COMMAND_ID, FixAllCommandArgs};
33-
use options::{Options, Run, WorkspaceOption};
33+
use linter::server_linter::ServerLinterRun;
34+
use options::{Options, WorkspaceOption};
3435
use worker::WorkspaceWorker;
3536

3637
type ConcurrentHashMap<K, V> = papaya::HashMap<K, V, FxBuildHasher>;
@@ -441,10 +442,7 @@ impl LanguageServer for Backend {
441442
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
442443
return;
443444
};
444-
if !worker.should_lint_on_run_type(Run::OnSave).await {
445-
return;
446-
}
447-
if let Some(diagnostics) = worker.lint_file(uri, None).await {
445+
if let Some(diagnostics) = worker.lint_file(uri, None, ServerLinterRun::OnSave).await {
448446
self.client
449447
.publish_diagnostics(
450448
uri.clone(),
@@ -463,11 +461,8 @@ impl LanguageServer for Backend {
463461
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
464462
return;
465463
};
466-
if !worker.should_lint_on_run_type(Run::OnType).await {
467-
return;
468-
}
469464
let content = params.content_changes.first().map(|c| c.text.clone());
470-
if let Some(diagnostics) = worker.lint_file(uri, content).await {
465+
if let Some(diagnostics) = worker.lint_file(uri, content, ServerLinterRun::OnType).await {
471466
self.client
472467
.publish_diagnostics(
473468
uri.clone(),
@@ -486,7 +481,9 @@ impl LanguageServer for Backend {
486481
};
487482

488483
let content = params.text_document.text;
489-
if let Some(diagnostics) = worker.lint_file(uri, Some(content)).await {
484+
if let Some(diagnostics) =
485+
worker.lint_file(uri, Some(content), ServerLinterRun::Force).await
486+
{
490487
self.client
491488
.publish_diagnostics(
492489
uri.clone(),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
source: crates/oxc_language_server/src/tester.rs
3+
input_file: crates/oxc_language_server/fixtures/linter/lint_on_run/on_save/on-save.ts
4+
---
5+
code: "typescript-eslint(no-floating-promises)"
6+
code_description.href: "None"
7+
message: "Promises must be awaited.\nhelp: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator."
8+
range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 32 } }
9+
related_information[0].message: ""
10+
related_information[0].location.uri: "file://<variable>/fixtures/linter/lint_on_run/on_save/on-save.ts"
11+
related_information[0].location.range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 32 } }
12+
severity: Some(Warning)
13+
source: Some("oxc")
14+
tags: None
15+
fixed: Multiple([FixedContent { message: Some("Promises must be awaited."), code: "void ", range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 0 } } }, FixedContent { message: Some("Promises must be awaited."), code: "await ", range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 0 } } }])
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: crates/oxc_language_server/src/tester.rs
3+
input_file: crates/oxc_language_server/fixtures/linter/lint_on_run/on_save/on-type.ts
4+
---
5+
File is ignored
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: crates/oxc_language_server/src/tester.rs
3+
input_file: crates/oxc_language_server/fixtures/linter/lint_on_run/on_type/on-save.ts
4+
---
5+
code: "eslint(no-debugger)"
6+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html"
7+
message: "`debugger` statement is not allowed\nhelp: Remove the debugger statement"
8+
range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 9 } }
9+
related_information[0].message: ""
10+
related_information[0].location.uri: "file://<variable>/fixtures/linter/lint_on_run/on_type/on-save.ts"
11+
related_information[0].location.range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 9 } }
12+
severity: Some(Warning)
13+
source: Some("oxc")
14+
tags: None
15+
fixed: Single(FixedContent { message: Some("Remove the debugger statement"), code: "", range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 9 } } })
16+
17+
18+
code: "typescript-eslint(no-floating-promises)"
19+
code_description.href: "None"
20+
message: "Promises must be awaited.\nhelp: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator."
21+
range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 32 } }
22+
related_information[0].message: ""
23+
related_information[0].location.uri: "file://<variable>/fixtures/linter/lint_on_run/on_type/on-save.ts"
24+
related_information[0].location.range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 32 } }
25+
severity: Some(Warning)
26+
source: Some("oxc")
27+
tags: None
28+
fixed: Multiple([FixedContent { message: Some("Promises must be awaited."), code: "void ", range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 0 } } }, FixedContent { message: Some("Promises must be awaited."), code: "await ", range: Range { start: Position { line: 7, character: 0 }, end: Position { line: 7, character: 0 } } }])

0 commit comments

Comments
 (0)