fix(oxlint): skip type-aware lint rules in type-check-only mode (#27076)

Pass `type_check_only` through to the TSGolint payload and send files with empty rule lists. Previously, type-aware rules still ran after Oxlint skipped the lint pass that collects disable directives, so suppressed lint errors leaked into `--type-check-only` output and triggered an assertion in debug builds.

Regression snapshots cover suppressed and unsuppressed promises, rule overrides, and a real TypeScript error that must still be reported.

Fixes voidzero-dev/vite-plus#2830.
This commit is contained in:
camc314 committed 2026-09-26 09:38:39 +00:00
1 parent d0b2462be3
commit 571cfa3365
8 files changed
+104 -2

No files matched your search

@@ -0,0 +1,17 @@
{
"categories": {
"correctness": "off"
},
"rules": {
"typescript/no-floating-promises": "error",
"no-debugger": "error"
},
"overrides": [
{
"files": ["type-error.ts"],
"rules": {
"typescript/no-floating-promises": "warn"
}
}
]
}
@@ -0,0 +1,4 @@
// oxlint-disable-next-line typescript/no-floating-promises -- fire and forget
Promise.resolve();
Promise.resolve();
debugger;
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "esnext"
}
}
@@ -0,0 +1,2 @@
const n: number = "x";
Promise.resolve();
+11
View File
@@ -1793,6 +1793,17 @@ mod test {
Tester::new().with_cwd("fixtures/cli/tsgolint_type_error".into()).test_and_snapshot(args);
}
#[test]
#[cfg(not(target_endian = "big"))]
fn test_tsgolint_type_check_only_skips_rules() {
Tester::new()
.with_cwd("fixtures/cli/tsgolint_type_check_only_rules".into())
.test_and_snapshot_multiple(&[
&["--type-check-only", "index.ts"],
&["--type-check-only", "type-error.ts"],
]);
}
#[test]
#[cfg(not(target_endian = "big"))]
fn test_tsgolint_type_check_only_reports_syntax_errors() {
@@ -0,0 +1,30 @@
---
source: apps/oxlint/src/tester.rs
---
##########
arguments: --type-check-only index.ts
working directory: fixtures/cli/tsgolint_type_check_only_rules
----------
Found 0 warnings and 0 errors.
Finished in <variable>ms on 1 file using 1 threads.
----------
CLI result: LintSucceeded
----------
##########
arguments: --type-check-only type-error.ts
working directory: fixtures/cli/tsgolint_type_check_only_rules
----------
x typescript(TS2322): Type 'string' is not assignable to type 'number'.
,-[type-error.ts:1:7]
1 | const n: number = "x";
: ^
2 | Promise.resolve();
`----
Found 0 warnings and 1 error.
Finished in <variable>ms on 1 file using 1 threads.
----------
CLI result: LintFoundErrors
----------
+1
View File
@@ -228,6 +228,7 @@ impl LintRunnerBuilder {
state
.with_silent(self.silent)
.with_type_check(self.type_check)
.with_type_check_only(self.type_check_only)
.with_timings(self.timings)
.with_ignore_fixes(self.with_ignore_fixes),
),
+33 -2
View File
@@ -41,6 +41,8 @@ pub struct TsGoLintState {
fix_suggestions: bool,
/// If `true`, include TypeScript compiler syntactic and semantic diagnostics.
type_check: bool,
/// If `true`, skip type-aware lint rules while retaining TypeScript diagnostics.
type_check_only: bool,
/// If `true`, request that per-rule debug timings be returned from `tsgolint`.
timings: bool,
/// If `true`, the linter will create "ignore this section / line" fixes for all diagnostics
@@ -60,6 +62,7 @@ impl TsGoLintState {
fix: fix_kind.contains(FixKind::Fix),
fix_suggestions: fix_kind.contains(FixKind::Suggestion),
type_check: false,
type_check_only: false,
timings: false,
with_ignore_fixes: false,
}
@@ -84,6 +87,7 @@ impl TsGoLintState {
fix: fix_kind.contains(FixKind::Fix),
fix_suggestions: fix_kind.contains(FixKind::Suggestion),
type_check: false,
type_check_only: false,
timings: false,
with_ignore_fixes: false,
})
@@ -108,6 +112,15 @@ impl TsGoLintState {
self
}
/// Set to `true` to skip type-aware lint rules.
///
/// Default is `false`.
#[must_use]
pub fn with_type_check_only(mut self, yes: bool) -> Self {
self.type_check_only = yes;
self
}
/// Set to `true` to request that per-rule debug timings be returned from `tsgolint`.
///
/// Default is `false`.
@@ -560,13 +573,31 @@ impl TsGoLintState {
source_overrides: Option<FxHashMap<String, String>>,
resolved_configs: &mut FxHashMap<PathBuf, ResolvedLinterState>,
) -> Payload {
if self.type_check_only {
let file_paths: Vec<String> = paths
.iter()
.filter(|path| SourceType::from_path(Path::new(path)).is_ok())
.map(|path| path.to_string_lossy().into_owned())
.collect();
return Payload {
version: 2,
configs: if file_paths.is_empty() {
vec![]
} else {
vec![Config { file_paths, rules: vec![] }]
},
source_overrides,
report_syntactic: self.type_check,
report_semantic: self.type_check,
};
}
let mut config_groups: FxHashMap<BTreeSet<Rule>, Vec<String>> = FxHashMap::default();
for path in paths {
if SourceType::from_path(Path::new(path)).is_ok() {
let path_buf = PathBuf::from(path);
let file_path = path.to_string_lossy().to_string();
let path_buf = PathBuf::from(path);
let resolved_config = resolved_configs
.entry(path_buf.clone())
.or_insert_with(|| self.config_store.resolve(&path_buf));