From f34f6fad28a1c4e2b505c9613b013927f182fbe0 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:54:51 +0000 Subject: [PATCH] feat(linter): introduce typeCheck config option (#19764) --- .../config-type-check-false.json | 6 + .../config-type-check.json | 6 + .../oxlint/src-js/package/config.generated.ts | 6 + apps/oxlint/src/config_loader.rs | 126 ++++++++++++++++-- apps/oxlint/src/lint.rs | 24 +++- ...c config-type-check-false.json@oxlint.snap | 36 +++++ ...c config-type-check-false.json@oxlint.snap | 30 +++++ ...rror_-c config-type-check.json@oxlint.snap | 36 +++++ .../files/test.ts | 5 + .../output.snap.md | 29 ++++ .../oxlint.config.ts | 14 ++ .../options/type_check_false.json | 5 + .../options/type_check_true.json | 5 + .../oxc_linter/src/config/config_builder.rs | 25 ++++ crates/oxc_linter/src/config/config_store.rs | 36 ++++- crates/oxc_linter/src/config/oxlintrc.rs | 37 ++++- npm/oxlint/configuration_schema.json | 10 +- .../src/snapshots/schema_json.snap | 8 ++ .../src/snapshots/schema_markdown.snap | 10 ++ 19 files changed, 434 insertions(+), 20 deletions(-) create mode 100644 apps/oxlint/fixtures/tsgolint_type_error/config-type-check-false.json create mode 100644 apps/oxlint/fixtures/tsgolint_type_error/config-type-check.json create mode 100644 apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_--type-check -c config-type-check-false.json@oxlint.snap create mode 100644 apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check-false.json@oxlint.snap create mode 100644 apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check.json@oxlint.snap create mode 100644 apps/oxlint/test/fixtures/js_config_dynamic_type_check/files/test.ts create mode 100644 apps/oxlint/test/fixtures/js_config_dynamic_type_check/output.snap.md create mode 100644 apps/oxlint/test/fixtures/js_config_dynamic_type_check/oxlint.config.ts create mode 100644 crates/oxc_linter/fixtures/extends_config/options/type_check_false.json create mode 100644 crates/oxc_linter/fixtures/extends_config/options/type_check_true.json diff --git a/apps/oxlint/fixtures/tsgolint_type_error/config-type-check-false.json b/apps/oxlint/fixtures/tsgolint_type_error/config-type-check-false.json new file mode 100644 index 0000000000000..68b9b1750d795 --- /dev/null +++ b/apps/oxlint/fixtures/tsgolint_type_error/config-type-check-false.json @@ -0,0 +1,6 @@ +{ + "options": { + "typeAware": true, + "typeCheck": false + } +} diff --git a/apps/oxlint/fixtures/tsgolint_type_error/config-type-check.json b/apps/oxlint/fixtures/tsgolint_type_error/config-type-check.json new file mode 100644 index 0000000000000..52810e6ab0d10 --- /dev/null +++ b/apps/oxlint/fixtures/tsgolint_type_error/config-type-check.json @@ -0,0 +1,6 @@ +{ + "options": { + "typeAware": true, + "typeCheck": true + } +} diff --git a/apps/oxlint/src-js/package/config.generated.ts b/apps/oxlint/src-js/package/config.generated.ts index 1222f0fa4557a..46281a1661b9c 100644 --- a/apps/oxlint/src-js/package/config.generated.ts +++ b/apps/oxlint/src-js/package/config.generated.ts @@ -318,6 +318,12 @@ export interface OxlintOptions { * Equivalent to passing `--type-aware` on the CLI. */ typeAware?: boolean | null; + /** + * Enable experimental type checking (includes TypeScript compiler diagnostics). + * + * Equivalent to passing `--type-check` on the CLI. + */ + typeCheck?: boolean | null; } export interface OxlintOverride { /** diff --git a/apps/oxlint/src/config_loader.rs b/apps/oxlint/src/config_loader.rs index 352767fc70fd7..64eadb810fa50 100644 --- a/apps/oxlint/src/config_loader.rs +++ b/apps/oxlint/src/config_loader.rs @@ -376,9 +376,17 @@ impl<'a> ConfigLoader<'a> { .and_then(|root| path.parent().map(|parent| parent == root)) .unwrap_or(false); - if builder.type_aware().is_some() && !is_root_config { - errors.push(ConfigLoadError::Diagnostic(nested_type_aware_not_supported(&path))); - continue; + if !is_root_config { + if builder.type_aware().is_some() { + errors + .push(ConfigLoadError::Diagnostic(nested_type_aware_not_supported(&path))); + continue; + } + if builder.type_check().is_some() { + errors + .push(ConfigLoadError::Diagnostic(nested_type_check_not_supported(&path))); + continue; + } } let extended_paths = builder.extended_paths.clone(); @@ -633,6 +641,14 @@ fn nested_type_aware_not_supported(path: &Path) -> OxcDiagnostic { .with_help("Move `options.typeAware` to the root configuration file.") } +fn nested_type_check_not_supported(path: &Path) -> OxcDiagnostic { + OxcDiagnostic::error(format!( + "The `options.typeCheck` option is only supported in the root config, but it was found in {}.", + path.display() + )) + .with_help("Move `options.typeCheck` to the root configuration file.") +} + #[cfg(test)] mod test { use std::path::{Path, PathBuf}; @@ -655,10 +671,15 @@ mod test { } #[cfg(feature = "napi")] - fn make_js_config(path: PathBuf, type_aware: Option) -> JsConfigResult { - let mut config: oxc_linter::Oxlintrc = - serde_json::from_value(serde_json::json!({ "options": { "typeAware": type_aware } })) - .unwrap(); + fn make_js_config( + path: PathBuf, + type_aware: Option, + type_check: Option, + ) -> JsConfigResult { + let mut config: oxc_linter::Oxlintrc = serde_json::from_value(serde_json::json!({ + "options": { "typeAware": type_aware, "typeCheck": type_check } + })) + .unwrap(); config.path = path.clone(); if let Some(config_dir) = path.parent() { config.set_config_dir(config_dir); @@ -773,7 +794,7 @@ mod test { let js_loader = make_js_loader(move |paths| { Ok(paths .into_iter() - .map(|path| make_js_config(PathBuf::from(path), Some(true))) + .map(|path| make_js_config(PathBuf::from(path), Some(true), None)) .collect()) }); let loader = loader.with_js_config_loader(Some(&js_loader)); @@ -785,6 +806,31 @@ mod test { assert_eq!(config.options.type_aware, Some(true)); } + #[cfg(feature = "napi")] + #[test] + fn test_root_oxlint_config_ts_allows_type_check() { + let root_dir = tempfile::tempdir().unwrap(); + let root_path = root_dir.path().join("oxlint.config.ts"); + std::fs::write(&root_path, "export default {};").unwrap(); + + let mut external_plugin_store = ExternalPluginStore::new(false); + let loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None); + + let js_loader = make_js_loader(move |paths| { + Ok(paths + .into_iter() + .map(|path| make_js_config(PathBuf::from(path), None, Some(true))) + .collect()) + }); + let loader = loader.with_js_config_loader(Some(&js_loader)); + + let config = loader + .load_root_config(root_dir.path(), Some(&PathBuf::from("oxlint.config.ts"))) + .unwrap(); + + assert_eq!(config.options.type_check, Some(true)); + } + #[cfg(feature = "napi")] #[test] fn test_nested_oxlint_config_ts_rejects_type_aware() { @@ -799,7 +845,32 @@ mod test { let js_loader = make_js_loader(move |paths| { Ok(paths .into_iter() - .map(|path| make_js_config(PathBuf::from(path), Some(false))) + .map(|path| make_js_config(PathBuf::from(path), Some(false), None)) + .collect()) + }); + loader = loader.with_js_config_loader(Some(&js_loader)); + + let (_configs, errors) = loader + .load_discovered_with_root_dir(root_dir.path(), [DiscoveredConfig::Js(nested_path)]); + assert_eq!(errors.len(), 1); + assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_))); + } + + #[cfg(feature = "napi")] + #[test] + fn test_nested_oxlint_config_ts_rejects_type_check() { + let root_dir = tempfile::tempdir().unwrap(); + let nested_path = root_dir.path().join("nested/oxlint.config.ts"); + std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap(); + std::fs::write(&nested_path, "export default {};").unwrap(); + + let mut external_plugin_store = ExternalPluginStore::new(false); + let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None); + + let js_loader = make_js_loader(move |paths| { + Ok(paths + .into_iter() + .map(|path| make_js_config(PathBuf::from(path), None, Some(false))) .collect()) }); loader = loader.with_js_config_loader(Some(&js_loader)); @@ -826,7 +897,7 @@ mod test { .into_iter() .map(|path| { let path = PathBuf::from(path); - let mut config = make_js_config(path.clone(), None).config; + let mut config = make_js_config(path.clone(), None, None).config; config.extends_configs = vec![ serde_json::from_value( serde_json::json!({ "options": { "typeAware": true } }), @@ -844,4 +915,39 @@ mod test { assert_eq!(errors.len(), 1); assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_))); } + + #[cfg(feature = "napi")] + #[test] + fn test_nested_oxlint_config_ts_rejects_type_check_from_extends() { + let root_dir = tempfile::tempdir().unwrap(); + let nested_path = root_dir.path().join("nested/oxlint.config.ts"); + std::fs::create_dir_all(nested_path.parent().unwrap()).unwrap(); + std::fs::write(&nested_path, "export default {};").unwrap(); + + let mut external_plugin_store = ExternalPluginStore::new(false); + let mut loader = ConfigLoader::new(None, &mut external_plugin_store, &[], None); + + let js_loader = make_js_loader(move |paths| { + Ok(paths + .into_iter() + .map(|path| { + let path = PathBuf::from(path); + let mut config = make_js_config(path.clone(), None, None).config; + config.extends_configs = vec![ + serde_json::from_value( + serde_json::json!({ "options": { "typeCheck": true } }), + ) + .unwrap(), + ]; + JsConfigResult { path, config } + }) + .collect()) + }); + loader = loader.with_js_config_loader(Some(&js_loader)); + + let (_configs, errors) = loader + .load_discovered_with_root_dir(root_dir.path(), [DiscoveredConfig::Js(nested_path)]); + assert_eq!(errors.len(), 1); + assert!(matches!(errors[0], ConfigLoadError::Diagnostic(_))); + } } diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index af627ad5ded2f..1a0098dd3cae6 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -348,6 +348,7 @@ impl CliRunner { let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store); let type_aware = self.options.type_aware || config_store.type_aware_enabled(); + let type_check = self.options.type_check || config_store.type_check_enabled(); // Send JS plugins config to JS side if let Some(external_linter) = &external_linter { @@ -400,7 +401,7 @@ impl CliRunner { // TODO: Add a warning message if `tsgolint` cannot be found, but type-aware rules are enabled let lint_runner = match LintRunner::builder(options, linter) .with_type_aware(type_aware) - .with_type_check(self.options.type_check) + .with_type_check(type_check) .with_silent(misc_options.silent) .with_fix_kind(fix_options.fix_kind()) .build() @@ -1289,6 +1290,27 @@ mod test { Tester::new().with_cwd("fixtures/tsgolint_type_error".into()).test_and_snapshot(args); } + #[test] + #[cfg(not(target_endian = "big"))] + fn test_tsgolint_type_check_via_config_file() { + let args = &["-c", "config-type-check.json"]; + Tester::new().with_cwd("fixtures/tsgolint_type_error".into()).test_and_snapshot(args); + } + + #[test] + #[cfg(not(target_endian = "big"))] + fn test_tsgolint_type_check_false_via_config_file() { + let args = &["-c", "config-type-check-false.json"]; + Tester::new().with_cwd("fixtures/tsgolint_type_error".into()).test_and_snapshot(args); + } + + #[test] + #[cfg(not(target_endian = "big"))] + fn test_tsgolint_type_check_false_overridden_by_cli_flag() { + let args = &["--type-check", "-c", "config-type-check-false.json"]; + Tester::new().with_cwd("fixtures/tsgolint_type_error".into()).test_and_snapshot(args); + } + #[test] #[cfg(not(target_endian = "big"))] fn test_tsgolint_no_typescript_files() { diff --git a/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_--type-check -c config-type-check-false.json@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_--type-check -c config-type-check-false.json@oxlint.snap new file mode 100644 index 0000000000000..7bcc04440425f --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_--type-check -c config-type-check-false.json@oxlint.snap @@ -0,0 +1,36 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: --type-check -c config-type-check-false.json +working directory: fixtures/tsgolint_type_error +---------- + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.js:2:7] + 1 | "use strict"; + 2 | const foo = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.ts:1:7] + 1 | const foo: number = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + x typescript(TS2322): Type 'string' is not assignable to type 'number'. + ,-[index.ts:1:7] + 1 | const foo: number = "42"; + : ^^^ + `---- + +Found 2 warnings and 1 error. +Finished in ms on 2 files with 107 rules using 1 threads. +---------- +CLI result: LintFoundErrors +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check-false.json@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check-false.json@oxlint.snap new file mode 100644 index 0000000000000..65233b309acd7 --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check-false.json@oxlint.snap @@ -0,0 +1,30 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: -c config-type-check-false.json +working directory: fixtures/tsgolint_type_error +---------- + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.js:2:7] + 1 | "use strict"; + 2 | const foo = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.ts:1:7] + 1 | const foo: number = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + +Found 2 warnings and 0 errors. +Finished in ms on 2 files with 107 rules using 1 threads. +---------- +CLI result: LintSucceeded +---------- diff --git a/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check.json@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check.json@oxlint.snap new file mode 100644 index 0000000000000..450252932b177 --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__tsgolint_type_error_-c config-type-check.json@oxlint.snap @@ -0,0 +1,36 @@ +--- +source: apps/oxlint/src/tester.rs +--- +########## +arguments: -c config-type-check.json +working directory: fixtures/tsgolint_type_error +---------- + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.js:2:7] + 1 | "use strict"; + 2 | const foo = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + ! eslint(no-unused-vars): Variable 'foo' is declared but never used. Unused variables should start with a '_'. + ,-[index.ts:1:7] + 1 | const foo: number = "42"; + : ^|^ + : `-- 'foo' is declared here + `---- + help: Consider removing this declaration. + + x typescript(TS2322): Type 'string' is not assignable to type 'number'. + ,-[index.ts:1:7] + 1 | const foo: number = "42"; + : ^^^ + `---- + +Found 2 warnings and 1 error. +Finished in ms on 2 files with 107 rules using 1 threads. +---------- +CLI result: LintFoundErrors +---------- diff --git a/apps/oxlint/test/fixtures/js_config_dynamic_type_check/files/test.ts b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/files/test.ts new file mode 100644 index 0000000000000..2ccc90c1f434d --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/files/test.ts @@ -0,0 +1,5 @@ +const floating = Promise.resolve("ok"); +floating; + +const value: number = "42"; +void value; diff --git a/apps/oxlint/test/fixtures/js_config_dynamic_type_check/output.snap.md b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/output.snap.md new file mode 100644 index 0000000000000..c4bbd95a1690a --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/output.snap.md @@ -0,0 +1,29 @@ +# Exit code +1 + +# stdout +``` + x typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. + ,-[files/test.ts:2:1] + 1 | const floating = Promise.resolve("ok"); + 2 | floating; + : ^^^^^^^^^ + 3 | + `---- + help: 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. + + x typescript(TS2322): Type 'string' is not assignable to type 'number'. + ,-[files/test.ts:4:7] + 3 | + 4 | const value: number = "42"; + : ^^^^^ + 5 | void value; + `---- + +Found 0 warnings and 2 errors. +Finished in Xms on 1 file with 1 rules using X threads. +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/js_config_dynamic_type_check/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/oxlint.config.ts new file mode 100644 index 0000000000000..4983bf4eff795 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_dynamic_type_check/oxlint.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "#oxlint"; + +const options = { + typeAware: true, + typeCheck: true, +}; + +export default defineConfig({ + categories: { correctness: "off" }, + rules: { + "typescript/no-floating-promises": "error", + }, + options, +}); diff --git a/crates/oxc_linter/fixtures/extends_config/options/type_check_false.json b/crates/oxc_linter/fixtures/extends_config/options/type_check_false.json new file mode 100644 index 0000000000000..f4a2f04dd0ef3 --- /dev/null +++ b/crates/oxc_linter/fixtures/extends_config/options/type_check_false.json @@ -0,0 +1,5 @@ +{ + "options": { + "typeCheck": false + } +} diff --git a/crates/oxc_linter/fixtures/extends_config/options/type_check_true.json b/crates/oxc_linter/fixtures/extends_config/options/type_check_true.json new file mode 100644 index 0000000000000..39499f0085fcf --- /dev/null +++ b/crates/oxc_linter/fixtures/extends_config/options/type_check_true.json @@ -0,0 +1,5 @@ +{ + "options": { + "typeCheck": true + } +} diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index 7d25f580e89c3..ea7574eb29d1e 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -337,6 +337,11 @@ impl ConfigStoreBuilder { self.config.options.type_aware } + #[inline] + pub fn type_check(&self) -> Option { + self.config.options.type_check + } + #[cfg(test)] pub(crate) fn with_rule(mut self, rule: RuleEnum, severity: AllowWarnDeny) -> Self { self.rules.insert(rule, severity); @@ -1390,6 +1395,26 @@ mod test { r#"{ "extends": ["fixtures/extends_config/options/type_aware_false.json"] }"#, ); assert_eq!(config.base.config.options.type_aware, Some(false)); + + let config = config_store_from_str( + r#"{ "extends": ["fixtures/extends_config/options/type_check_true.json"] }"#, + ); + assert_eq!(config.base.config.options.type_check, Some(true)); + + let config = config_store_from_str( + r#" + { + "extends": ["fixtures/extends_config/options/type_check_true.json"], + "options": {"typeCheck": false } + } + "#, + ); + assert_eq!(config.base.config.options.type_check, Some(false)); + + let config = config_store_from_str( + r#"{ "extends": ["fixtures/extends_config/options/type_check_false.json"] }"#, + ); + assert_eq!(config.base.config.options.type_check, Some(false)); } #[test] diff --git a/crates/oxc_linter/src/config/config_store.rs b/crates/oxc_linter/src/config/config_store.rs index cf6af2413b101..9b9a8eb1f392a 100644 --- a/crates/oxc_linter/src/config/config_store.rs +++ b/crates/oxc_linter/src/config/config_store.rs @@ -325,6 +325,11 @@ impl ConfigStore { self.base.base.config.options.type_aware.unwrap_or(false) } + /// Whether type-checking diagnostics are enabled in the root config. + pub fn type_check_enabled(&self) -> bool { + self.base.base.config.options.type_check.unwrap_or(false) + } + pub(crate) fn get_related_config(&self, path: &Path) -> &Config { if self.nested_configs.is_empty() { &self.base @@ -1154,7 +1159,7 @@ mod test { vec![], OxlintCategories::default(), LintConfig { - options: OxlintOptions { type_aware: Some(true) }, + options: OxlintOptions { type_aware: Some(true), ..OxlintOptions::default() }, ..LintConfig::default() }, ResolvedOxlintOverrides::new(vec![]), @@ -1175,4 +1180,33 @@ mod test { let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default()); assert!(!store.type_aware_enabled()); } + + #[test] + fn test_type_check_enabled_from_root_config() { + let base = Config::new( + vec![], + vec![], + OxlintCategories::default(), + LintConfig { + options: OxlintOptions { type_check: Some(true), ..OxlintOptions::default() }, + ..LintConfig::default() + }, + ResolvedOxlintOverrides::new(vec![]), + ); + let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default()); + assert!(store.type_check_enabled()); + } + + #[test] + fn test_type_check_disabled_by_default() { + let base = Config::new( + vec![], + vec![], + OxlintCategories::default(), + LintConfig::default(), + ResolvedOxlintOverrides::new(vec![]), + ); + let store = ConfigStore::new(base, FxHashMap::default(), ExternalPluginStore::default()); + assert!(!store.type_check_enabled()); + } } diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index 726391e2e7500..dcf3ce94241c5 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -31,17 +31,25 @@ pub struct OxlintOptions { /// Equivalent to passing `--type-aware` on the CLI. #[serde(skip_serializing_if = "Option::is_none")] pub type_aware: Option, + /// Enable experimental type checking (includes TypeScript compiler diagnostics). + /// + /// Equivalent to passing `--type-check` on the CLI. + #[serde(skip_serializing_if = "Option::is_none")] + pub type_check: Option, } impl OxlintOptions { #[must_use] pub fn is_empty(&self) -> bool { - self.type_aware.is_none() + self.type_aware.is_none() && self.type_check.is_none() } #[must_use] pub fn merge(&self, other: &OxlintOptions) -> Self { - Self { type_aware: self.type_aware.or(other.type_aware) } + Self { + type_aware: self.type_aware.or(other.type_aware), + type_check: self.type_check.or(other.type_check), + } } } @@ -390,10 +398,11 @@ mod test { assert_eq!(config.path, PathBuf::default()); assert_eq!(config.extends, Vec::::default()); assert_eq!(config.options.type_aware, None); + assert_eq!(config.options.type_check, None); } #[test] - fn test_oxlintrc_type_aware_deserialize() { + fn test_oxlintrc_options_deserialize() { let config: Oxlintrc = serde_json::from_value(json!({ "options": { "typeAware": true } })).unwrap(); assert_eq!(config.options.type_aware, Some(true)); @@ -401,26 +410,40 @@ mod test { let config: Oxlintrc = serde_json::from_value(json!({ "options": { "typeAware": false } })).unwrap(); assert_eq!(config.options.type_aware, Some(false)); + + let config: Oxlintrc = + serde_json::from_value(json!({ "options": { "typeCheck": true } })).unwrap(); + assert_eq!(config.options.type_check, Some(true)); + + let config: Oxlintrc = + serde_json::from_value(json!({ "options": { "typeCheck": false } })).unwrap(); + assert_eq!(config.options.type_check, Some(false)); } #[test] - fn test_oxlintrc_top_level_type_aware_rejected() { + fn test_oxlintrc_top_level_options_rejected() { let config: Result = serde_json::from_value(json!({ "typeAware": true })); assert!(config.is_err()); + + let config: Result = serde_json::from_value(json!({ "typeCheck": true })); + assert!(config.is_err()); } #[test] - fn test_oxlintrc_merge_type_aware() { + fn test_oxlintrc_merge_options() { let mut root: Oxlintrc = - serde_json::from_value(json!({ "options": { "typeAware": true } })).unwrap(); + serde_json::from_value(json!({ "options": { "typeAware": true, "typeCheck": false } })) + .unwrap(); root.path = PathBuf::from("/root/.oxlintrc.json"); let mut base: Oxlintrc = - serde_json::from_value(json!({ "options": { "typeAware": false } })).unwrap(); + serde_json::from_value(json!({ "options": { "typeAware": false, "typeCheck": true } })) + .unwrap(); base.path = PathBuf::from("/root/base.json"); let merged = root.merge(base); assert_eq!(merged.options.type_aware, Some(true)); + assert_eq!(merged.options.type_check, Some(false)); } #[test] diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index 0cd98650f7599..be216055e9cff 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -501,6 +501,14 @@ "null" ], "markdownDescription": "Enable rules that require type information.\n\nEquivalent to passing `--type-aware` on the CLI." + }, + "typeCheck": { + "description": "Enable experimental type checking (includes TypeScript compiler diagnostics).\n\nEquivalent to passing `--type-check` on the CLI.", + "type": [ + "boolean", + "null" + ], + "markdownDescription": "Enable experimental type checking (includes TypeScript compiler diagnostics).\n\nEquivalent to passing `--type-check` on the CLI." } }, "additionalProperties": false, @@ -759,4 +767,4 @@ } }, "markdownDescription": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json --import-plugin`\n\n::: danger NOTE\n\nOnly the `.json` format is supported. You can use comments in configuration files.\n\n:::\n\nExample\n\n`.oxlintrc.json`\n\n```json\n{\n\"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n\"plugins\": [\"import\", \"typescript\", \"unicorn\"],\n\"env\": {\n\"browser\": true\n},\n\"globals\": {\n\"foo\": \"readonly\"\n},\n\"settings\": {\n\"react\": {\n\"version\": \"18.2.0\"\n},\n\"custom\": { \"option\": true }\n},\n\"rules\": {\n\"eqeqeq\": \"warn\",\n\"import/no-cycle\": \"error\",\n\"react/self-closing-comp\": [\"error\", { \"html\": false }]\n},\n\"overrides\": [\n{\n\"files\": [\"*.test.ts\", \"*.spec.ts\"],\n\"rules\": {\n\"@typescript-eslint/no-explicit-any\": \"off\"\n}\n}\n]\n}\n```" -} \ No newline at end of file +} diff --git a/tasks/website_linter/src/snapshots/schema_json.snap b/tasks/website_linter/src/snapshots/schema_json.snap index 401b462cd4ea8..819e22b5023be 100644 --- a/tasks/website_linter/src/snapshots/schema_json.snap +++ b/tasks/website_linter/src/snapshots/schema_json.snap @@ -505,6 +505,14 @@ expression: json "null" ], "markdownDescription": "Enable rules that require type information.\n\nEquivalent to passing `--type-aware` on the CLI." + }, + "typeCheck": { + "description": "Enable experimental type checking (includes TypeScript compiler diagnostics).\n\nEquivalent to passing `--type-check` on the CLI.", + "type": [ + "boolean", + "null" + ], + "markdownDescription": "Enable experimental type checking (includes TypeScript compiler diagnostics).\n\nEquivalent to passing `--type-check` on the CLI." } }, "additionalProperties": false, diff --git a/tasks/website_linter/src/snapshots/schema_markdown.snap b/tasks/website_linter/src/snapshots/schema_markdown.snap index 7e8af13612984..dbcea5c4482d0 100644 --- a/tasks/website_linter/src/snapshots/schema_markdown.snap +++ b/tasks/website_linter/src/snapshots/schema_markdown.snap @@ -314,6 +314,16 @@ Enable rules that require type information. Equivalent to passing `--type-aware` on the CLI. +### options.typeCheck + +type: `boolean` + + +Enable experimental type checking (includes TypeScript compiler diagnostics). + +Equivalent to passing `--type-check` on the CLI. + + ## overrides type: `array`