mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
parent
c6a48684c3
commit
2268a0ef90
20 changed files with 757 additions and 44 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -710,6 +710,7 @@ dependencies = [
|
|||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1709,6 +1710,7 @@ dependencies = [
|
|||
"markdown",
|
||||
"memchr",
|
||||
"mime_guess",
|
||||
"nonmax",
|
||||
"once_cell",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
|
|
|
|||
26
apps/oxlint/fixtures/overrides/.oxlintrc.json
Normal file
26
apps/oxlint/fixtures/overrides/.oxlintrc.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"rules": {
|
||||
"no-var": "error",
|
||||
"no-console": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.js"],
|
||||
"rules": {
|
||||
"no-console": "warn"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.{js,jsx}"],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
"no-console": "warn"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
2
apps/oxlint/fixtures/overrides/other.jsx
Normal file
2
apps/oxlint/fixtures/overrides/other.jsx
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
var msg = "hello";
|
||||
console.log(msg);
|
||||
2
apps/oxlint/fixtures/overrides/test.js
Normal file
2
apps/oxlint/fixtures/overrides/test.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
var msg = "hello";
|
||||
console.log(msg);
|
||||
2
apps/oxlint/fixtures/overrides/test.ts
Normal file
2
apps/oxlint/fixtures/overrides/test.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
var msg = "hello";
|
||||
console.log(msg);
|
||||
|
|
@ -629,4 +629,25 @@ mod test {
|
|||
std::fs::read_to_string("fixtures/print_config/ban_rules/expect.json").unwrap();
|
||||
assert_eq!(config, expect_json.trim());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overrides() {
|
||||
let result =
|
||||
test(&["-c", "fixtures/overrides/.oxlintrc.json", "fixtures/overrides/test.js"]);
|
||||
assert_eq!(result.number_of_files, 1);
|
||||
assert_eq!(result.number_of_warnings, 0);
|
||||
assert_eq!(result.number_of_errors, 1);
|
||||
|
||||
let result =
|
||||
test(&["-c", "fixtures/overrides/.oxlintrc.json", "fixtures/overrides/test.ts"]);
|
||||
assert_eq!(result.number_of_files, 1);
|
||||
assert_eq!(result.number_of_warnings, 1);
|
||||
assert_eq!(result.number_of_errors, 1);
|
||||
|
||||
let result =
|
||||
test(&["-c", "fixtures/overrides/.oxlintrc.json", "fixtures/overrides/other.jsx"]);
|
||||
assert_eq!(result.number_of_files, 1);
|
||||
assert_eq!(result.number_of_warnings, 0);
|
||||
assert_eq!(result.number_of_errors, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ pub struct OxcCode {
|
|||
pub scope: Option<Cow<'static, str>>,
|
||||
pub number: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl OxcCode {
|
||||
pub fn is_some(&self) -> bool {
|
||||
self.scope.is_some() || self.number.is_some()
|
||||
|
|
|
|||
|
|
@ -26,27 +26,28 @@ oxc_cfg = { workspace = true }
|
|||
oxc_codegen = { workspace = true }
|
||||
oxc_diagnostics = { workspace = true }
|
||||
oxc_ecmascript = { workspace = true }
|
||||
oxc_index = { workspace = true }
|
||||
oxc_index = { workspace = true, features = ["serialize"] }
|
||||
oxc_macros = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_regular_expression = { workspace = true }
|
||||
oxc_resolver = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_span = { workspace = true, features = ["schemars", "serialize"] }
|
||||
oxc_syntax = { workspace = true }
|
||||
oxc_syntax = { workspace = true, features = ["serialize"] }
|
||||
|
||||
aho-corasick = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
convert_case = { workspace = true }
|
||||
cow-utils = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
globset = { workspace = true, features = ["serde1"] }
|
||||
itertools = { workspace = true }
|
||||
json-strip-comments = { workspace = true }
|
||||
language-tags = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
mime_guess = { workspace = true }
|
||||
nonmax = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
phf = { workspace = true, features = ["macros"] }
|
||||
rayon = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use oxc_span::CompactStr;
|
|||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{
|
||||
config::{ESLintRule, LintPlugins, OxlintRules},
|
||||
config::{ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules},
|
||||
rules::RULES,
|
||||
AllowWarnDeny, FixKind, FrameworkFlags, LintConfig, LintFilter, LintFilterKind, LintOptions,
|
||||
Linter, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity,
|
||||
|
|
@ -18,6 +18,7 @@ pub struct LinterBuilder {
|
|||
pub(super) rules: FxHashSet<RuleWithSeverity>,
|
||||
options: LintOptions,
|
||||
config: LintConfig,
|
||||
overrides: OxlintOverrides,
|
||||
cache: RulesCache,
|
||||
}
|
||||
|
||||
|
|
@ -36,9 +37,10 @@ impl LinterBuilder {
|
|||
let options = LintOptions::default();
|
||||
let config = LintConfig::default();
|
||||
let rules = FxHashSet::default();
|
||||
let overrides = OxlintOverrides::default();
|
||||
let cache = RulesCache::new(config.plugins);
|
||||
|
||||
Self { rules, options, config, cache }
|
||||
Self { rules, options, config, overrides, cache }
|
||||
}
|
||||
|
||||
/// Warn on all rules in all plugins and categories, including those in `nursery`.
|
||||
|
|
@ -48,6 +50,7 @@ impl LinterBuilder {
|
|||
pub fn all() -> Self {
|
||||
let options = LintOptions::default();
|
||||
let config = LintConfig { plugins: LintPlugins::all(), ..LintConfig::default() };
|
||||
let overrides = OxlintOverrides::default();
|
||||
let cache = RulesCache::new(config.plugins);
|
||||
Self {
|
||||
rules: RULES
|
||||
|
|
@ -56,6 +59,7 @@ impl LinterBuilder {
|
|||
.collect(),
|
||||
options,
|
||||
config,
|
||||
overrides,
|
||||
cache,
|
||||
}
|
||||
}
|
||||
|
|
@ -82,15 +86,22 @@ impl LinterBuilder {
|
|||
/// match any recognized rules.
|
||||
pub fn from_oxlintrc(start_empty: bool, oxlintrc: Oxlintrc) -> Self {
|
||||
// TODO: monorepo config merging, plugin-based extends, etc.
|
||||
let Oxlintrc { plugins, settings, env, globals, categories, rules: oxlintrc_rules } =
|
||||
oxlintrc;
|
||||
let Oxlintrc {
|
||||
plugins,
|
||||
settings,
|
||||
env,
|
||||
globals,
|
||||
categories,
|
||||
rules: oxlintrc_rules,
|
||||
overrides,
|
||||
} = oxlintrc;
|
||||
|
||||
let config = LintConfig { plugins, settings, env, globals };
|
||||
let options = LintOptions::default();
|
||||
let rules =
|
||||
if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) };
|
||||
let cache = RulesCache::new(config.plugins);
|
||||
let mut builder = Self { rules, options, config, cache };
|
||||
let mut builder = Self { rules, options, config, overrides, cache };
|
||||
|
||||
if !categories.is_empty() {
|
||||
builder = builder.with_filters(categories.filters());
|
||||
|
|
@ -240,7 +251,8 @@ impl LinterBuilder {
|
|||
self.rules.into_iter().collect::<Vec<_>>()
|
||||
};
|
||||
rules.sort_unstable_by_key(|r| r.id());
|
||||
Linter::new(rules, self.options, self.config)
|
||||
let config = ConfigStore::new(rules, self.config, self.overrides);
|
||||
Linter::new(self.options, config)
|
||||
}
|
||||
|
||||
/// Warn for all correctness rules in the given set of plugins.
|
||||
|
|
@ -564,7 +576,7 @@ mod test {
|
|||
desired_plugins.set(LintPlugins::TYPESCRIPT, false);
|
||||
|
||||
let linter = LinterBuilder::default().with_plugins(desired_plugins).build();
|
||||
for rule in linter.rules() {
|
||||
for rule in linter.rules().iter() {
|
||||
let name = rule.name();
|
||||
let plugin = rule.plugin_name();
|
||||
assert_ne!(
|
||||
|
|
|
|||
303
crates/oxc_linter/src/config/flat.rs
Normal file
303
crates/oxc_linter/src/config/flat.rs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
use crate::LintPlugins;
|
||||
use crate::{rules::RULES, RuleWithSeverity};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::{
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::{
|
||||
overrides::{OverrideId, OxlintOverrides},
|
||||
LintConfig,
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
type AppliedOverrideHash = u64;
|
||||
|
||||
// TODO: support `categories` et. al. in overrides.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ResolvedLinterState {
|
||||
// TODO: Arc + Vec -> SyncVec? It would save a pointer dereference.
|
||||
pub rules: Arc<[RuleWithSeverity]>,
|
||||
pub config: Arc<LintConfig>,
|
||||
}
|
||||
|
||||
impl Clone for ResolvedLinterState {
|
||||
fn clone(&self) -> Self {
|
||||
Self { rules: Arc::clone(&self.rules), config: Arc::clone(&self.config) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of a list of config deltas, lazily applying them to a base config as requested by
|
||||
/// [`ConfigStore::resolve`]. This struct is [`Sync`] + [`Send`] since the linter runs on each file
|
||||
/// in parallel.
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigStore {
|
||||
// TODO: flatten base config + overrides into a single "flat" config. Similar idea to ESLint's
|
||||
// flat configs, but we would still support v8 configs. Doing this could open the door to
|
||||
// supporting flat configs (e.g. eslint.config.js). Still need to figure out how this plays
|
||||
// with nested configs.
|
||||
/// Resolved override cache. The key is a hash of each override's ID that matched the list of
|
||||
/// file globs in order to avoid re-allocating the same set of rules multiple times.
|
||||
cache: DashMap<AppliedOverrideHash, ResolvedLinterState, FxBuildHasher>,
|
||||
/// "root" level configuration. In the future this may just be the first entry in `overrides`.
|
||||
base: ResolvedLinterState,
|
||||
/// Config deltas applied to `base`.
|
||||
overrides: OxlintOverrides,
|
||||
}
|
||||
|
||||
impl ConfigStore {
|
||||
pub fn new(
|
||||
base_rules: Vec<RuleWithSeverity>,
|
||||
base_config: LintConfig,
|
||||
overrides: OxlintOverrides,
|
||||
) -> Self {
|
||||
let base = ResolvedLinterState {
|
||||
rules: Arc::from(base_rules.into_boxed_slice()),
|
||||
config: Arc::new(base_config),
|
||||
};
|
||||
// best-best case: no overrides are provided & config is initialized with 0 capacity best
|
||||
// case: each file matches only a single override, so we only need `overrides.len()`
|
||||
// capacity worst case: files match more than one override. In the most ridiculous case, we
|
||||
// could end up needing (overrides.len() ** 2) capacity. I don't really want to
|
||||
// pre-allocate that much space unconditionally. Better to re-alloc if we end up needing
|
||||
// it.
|
||||
let cache = DashMap::with_capacity_and_hasher(overrides.len(), FxBuildHasher);
|
||||
|
||||
Self { cache, base, overrides }
|
||||
}
|
||||
|
||||
/// Set the base rules, replacing all existing rules.
|
||||
#[cfg(test)]
|
||||
#[inline]
|
||||
pub fn set_rules(&mut self, new_rules: Vec<RuleWithSeverity>) {
|
||||
self.base.rules = Arc::from(new_rules.into_boxed_slice());
|
||||
}
|
||||
|
||||
pub fn number_of_rules(&self) -> usize {
|
||||
self.base.rules.len()
|
||||
}
|
||||
|
||||
pub fn rules(&self) -> &Arc<[RuleWithSeverity]> {
|
||||
&self.base.rules
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(&self, path: &Path) -> ResolvedLinterState {
|
||||
if self.overrides.is_empty() {
|
||||
return self.base.clone();
|
||||
}
|
||||
|
||||
let mut overrides_to_apply: Vec<OverrideId> = Vec::new();
|
||||
let mut hasher = FxBuildHasher.build_hasher();
|
||||
|
||||
for (id, override_config) in self.overrides.iter_enumerated() {
|
||||
if override_config.files.is_match(path) {
|
||||
overrides_to_apply.push(id);
|
||||
id.hash(&mut hasher);
|
||||
}
|
||||
}
|
||||
|
||||
if overrides_to_apply.is_empty() {
|
||||
return self.base.clone();
|
||||
}
|
||||
|
||||
let key = hasher.finish();
|
||||
self.cache
|
||||
.entry(key)
|
||||
.or_insert_with(|| self.apply_overrides(&overrides_to_apply))
|
||||
.value()
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// NOTE: this function must not borrow any entries from `self.cache` or DashMap will deadlock.
|
||||
fn apply_overrides(&self, override_ids: &[OverrideId]) -> ResolvedLinterState {
|
||||
let plugins = self
|
||||
.overrides
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|cfg| cfg.plugins)
|
||||
.unwrap_or(self.base.config.plugins);
|
||||
|
||||
let all_rules = RULES
|
||||
.iter()
|
||||
.filter(|rule| plugins.contains(LintPlugins::from(rule.plugin_name())))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut rules = self
|
||||
.base
|
||||
.rules
|
||||
.iter()
|
||||
.filter(|rule| plugins.contains(LintPlugins::from(rule.plugin_name())))
|
||||
.cloned()
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let overrides = override_ids.iter().map(|id| &self.overrides[*id]);
|
||||
for override_config in overrides {
|
||||
if override_config.rules.is_empty() {
|
||||
continue;
|
||||
}
|
||||
override_config.rules.override_rules(&mut rules, &all_rules);
|
||||
}
|
||||
|
||||
let rules = rules.into_iter().collect::<Vec<_>>();
|
||||
let config = if plugins == self.base.config.plugins {
|
||||
Arc::clone(&self.base.config)
|
||||
} else {
|
||||
let mut config = (*self.base.config.as_ref()).clone();
|
||||
|
||||
config.plugins = plugins;
|
||||
Arc::new(config)
|
||||
};
|
||||
|
||||
ResolvedLinterState { rules: Arc::from(rules.into_boxed_slice()), config }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{ConfigStore, OxlintOverrides};
|
||||
use crate::{config::LintConfig, AllowWarnDeny, LintPlugins, RuleEnum, RuleWithSeverity};
|
||||
|
||||
macro_rules! from_json {
|
||||
($json:tt) => {
|
||||
serde_json::from_value(serde_json::json!($json)).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(clippy::default_trait_access)]
|
||||
fn no_explicit_any() -> RuleWithSeverity {
|
||||
RuleWithSeverity::new(RuleEnum::NoExplicitAny(Default::default()), AllowWarnDeny::Warn)
|
||||
}
|
||||
|
||||
#[allow(clippy::default_trait_access)]
|
||||
fn no_cycle() -> RuleWithSeverity {
|
||||
RuleWithSeverity::new(RuleEnum::NoCycle(Default::default()), AllowWarnDeny::Warn)
|
||||
}
|
||||
|
||||
/// an empty ruleset is a no-op
|
||||
#[test]
|
||||
fn test_no_rules() {
|
||||
let base_rules = vec![no_explicit_any()];
|
||||
let overrides: OxlintOverrides = from_json!([{
|
||||
"files": ["*.test.{ts,tsx}"],
|
||||
"rules": {}
|
||||
}]);
|
||||
let store = ConfigStore::new(base_rules, LintConfig::default(), overrides);
|
||||
|
||||
let rules_for_source_file = store.resolve("App.tsx".as_ref());
|
||||
let rules_for_test_file = store.resolve("App.test.tsx".as_ref());
|
||||
|
||||
assert_eq!(rules_for_source_file.rules.len(), 1);
|
||||
assert_eq!(rules_for_test_file.rules.len(), 1);
|
||||
assert_eq!(
|
||||
rules_for_test_file.rules[0].rule.id(),
|
||||
rules_for_source_file.rules[0].rule.id()
|
||||
);
|
||||
}
|
||||
|
||||
/// adding plugins but no rules is a no-op
|
||||
#[test]
|
||||
fn test_no_rules_and_new_plugins() {
|
||||
let base_rules = vec![no_explicit_any()];
|
||||
let overrides: OxlintOverrides = from_json!([{
|
||||
"files": ["*.test.{ts,tsx}"],
|
||||
"plugins": ["react", "typescript", "unicorn", "oxc", "jsx-a11y"],
|
||||
"rules": {}
|
||||
}]);
|
||||
let store = ConfigStore::new(base_rules, LintConfig::default(), overrides);
|
||||
|
||||
let rules_for_source_file = store.resolve("App.tsx".as_ref());
|
||||
let rules_for_test_file = store.resolve("App.test.tsx".as_ref());
|
||||
|
||||
assert_eq!(rules_for_source_file.rules.len(), 1);
|
||||
assert_eq!(rules_for_test_file.rules.len(), 1);
|
||||
assert_eq!(
|
||||
rules_for_test_file.rules[0].rule.id(),
|
||||
rules_for_source_file.rules[0].rule.id()
|
||||
);
|
||||
}
|
||||
|
||||
/// removing plugins strips rules from those plugins, even if no rules are
|
||||
/// added/removed explicitly
|
||||
#[test]
|
||||
fn test_no_rules_and_remove_plugins() {
|
||||
let base_rules = vec![no_cycle()];
|
||||
let overrides = from_json!([{
|
||||
"files": ["*.test.{ts,tsx}"],
|
||||
"plugins": ["jest"],
|
||||
"rules": {}
|
||||
}]);
|
||||
let config = LintConfig {
|
||||
plugins: LintPlugins::default() | LintPlugins::IMPORT,
|
||||
..LintConfig::default()
|
||||
};
|
||||
let store = ConfigStore::new(base_rules, config, overrides);
|
||||
|
||||
assert_eq!(store.resolve("App.tsx".as_ref()).rules.len(), 1);
|
||||
assert_eq!(store.resolve("App.test.tsx".as_ref()).rules.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_rule() {
|
||||
let base_rules = vec![no_explicit_any()];
|
||||
let overrides: OxlintOverrides = from_json!([{
|
||||
"files": ["*.test.{ts,tsx}"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
}
|
||||
}]);
|
||||
|
||||
let store = ConfigStore::new(base_rules, LintConfig::default(), overrides);
|
||||
assert_eq!(store.number_of_rules(), 1);
|
||||
|
||||
let rules_for_source_file = store.resolve("App.tsx".as_ref());
|
||||
assert_eq!(rules_for_source_file.rules.len(), 1);
|
||||
|
||||
assert!(store.resolve("App.test.tsx".as_ref()).rules.is_empty());
|
||||
assert!(store.resolve("App.test.ts".as_ref()).rules.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_rule() {
|
||||
let base_rules = vec![no_explicit_any()];
|
||||
let overrides = from_json!([{
|
||||
"files": ["src/**/*.{ts,tsx}"],
|
||||
"rules": {
|
||||
"no-unused-vars": "warn"
|
||||
}
|
||||
}]);
|
||||
|
||||
let store = ConfigStore::new(base_rules, LintConfig::default(), overrides);
|
||||
assert_eq!(store.number_of_rules(), 1);
|
||||
|
||||
assert_eq!(store.resolve("App.tsx".as_ref()).rules.len(), 1);
|
||||
assert_eq!(store.resolve("src/App.tsx".as_ref()).rules.len(), 2);
|
||||
assert_eq!(store.resolve("src/App.ts".as_ref()).rules.len(), 2);
|
||||
assert_eq!(store.resolve("src/foo/bar/baz/App.tsx".as_ref()).rules.len(), 2);
|
||||
assert_eq!(store.resolve("src/foo/bar/baz/App.spec.tsx".as_ref()).rules.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_change_rule_severity() {
|
||||
let base_rules = vec![no_explicit_any()];
|
||||
let overrides = from_json!([{
|
||||
"files": ["src/**/*.{ts,tsx}"],
|
||||
"rules": {
|
||||
"no-explicit-any": "error"
|
||||
}
|
||||
}]);
|
||||
|
||||
let store = ConfigStore::new(base_rules, LintConfig::default(), overrides);
|
||||
assert_eq!(store.number_of_rules(), 1);
|
||||
|
||||
let app = store.resolve("App.tsx".as_ref()).rules;
|
||||
assert_eq!(app.len(), 1);
|
||||
assert_eq!(app[0].severity, AllowWarnDeny::Warn);
|
||||
|
||||
let src_app = store.resolve("src/App.tsx".as_ref()).rules;
|
||||
assert_eq!(src_app.len(), 1);
|
||||
assert_eq!(src_app[0].severity, AllowWarnDeny::Deny);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
mod categories;
|
||||
mod env;
|
||||
mod flat;
|
||||
mod globals;
|
||||
mod overrides;
|
||||
mod oxlintrc;
|
||||
mod plugins;
|
||||
mod rules;
|
||||
mod settings;
|
||||
|
||||
pub(crate) use self::flat::ResolvedLinterState;
|
||||
pub use self::{
|
||||
env::OxlintEnv,
|
||||
flat::ConfigStore,
|
||||
globals::OxlintGlobals,
|
||||
overrides::OxlintOverrides,
|
||||
oxlintrc::Oxlintrc,
|
||||
plugins::LintPlugins,
|
||||
rules::ESLintRule,
|
||||
|
|
@ -16,7 +21,7 @@ pub use self::{
|
|||
settings::{jsdoc::JSDocPluginSettings, OxlintSettings},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct LintConfig {
|
||||
pub(crate) plugins: LintPlugins,
|
||||
pub(crate) settings: OxlintSettings,
|
||||
|
|
|
|||
153
crates/oxc_linter/src/config/overrides.rs
Normal file
153
crates/oxc_linter/src/config/overrides.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
use std::{borrow::Cow, ops::Deref, path::Path};
|
||||
|
||||
use nonmax::NonMaxU32;
|
||||
use schemars::{gen, schema::Schema, JsonSchema};
|
||||
use serde::{de, ser, Deserialize, Serialize};
|
||||
|
||||
use oxc_index::{Idx, IndexVec};
|
||||
|
||||
use crate::{config::OxlintRules, LintPlugins};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct OverrideId(NonMaxU32);
|
||||
|
||||
impl Idx for OverrideId {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn from_usize(idx: usize) -> Self {
|
||||
assert!(idx < u32::MAX as usize);
|
||||
// SAFETY: We just checked `idx` is a legal value for `NonMaxU32`
|
||||
Self(unsafe { NonMaxU32::new_unchecked(idx as u32) })
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0.get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
// nominal wrapper required to add JsonSchema impl
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
pub struct OxlintOverrides(IndexVec<OverrideId, OxlintOverride>);
|
||||
|
||||
impl Deref for OxlintOverrides {
|
||||
type Target = IndexVec<OverrideId, OxlintOverride>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl OxlintOverrides {
|
||||
#[inline]
|
||||
pub fn empty() -> Self {
|
||||
Self(IndexVec::new())
|
||||
}
|
||||
|
||||
// must be explicitly defined to make serde happy
|
||||
/// Returns `true` if the overrides list has no elements.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for OxlintOverrides {
|
||||
fn schema_name() -> String {
|
||||
"OxlintOverrides".to_owned()
|
||||
}
|
||||
|
||||
fn schema_id() -> Cow<'static, str> {
|
||||
Cow::Borrowed("OxlintOverrides")
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema {
|
||||
gen.subschema_for::<Vec<OxlintOverride>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
#[non_exhaustive]
|
||||
pub struct OxlintOverride {
|
||||
/// A list of glob patterns to override.
|
||||
///
|
||||
/// ## Example
|
||||
/// `[ "*.test.ts", "*.spec.ts" ]`
|
||||
pub files: GlobSet,
|
||||
|
||||
/// Optionally change what plugins are enabled for this override. When
|
||||
/// omitted, the base config's plugins are used.
|
||||
#[serde(default)]
|
||||
pub plugins: Option<LintPlugins>,
|
||||
|
||||
#[serde(default)]
|
||||
pub rules: OxlintRules,
|
||||
}
|
||||
|
||||
/// A glob pattern.
|
||||
///
|
||||
/// Thin wrapper around [`globset::GlobSet`] because that struct doesn't implement Serialize or schemars
|
||||
/// traits.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GlobSet {
|
||||
/// Raw patterns from the config. Inefficient, but required for [serialization](Serialize),
|
||||
/// which in turn is required for `--print-config`.
|
||||
raw: Vec<String>,
|
||||
globs: globset::GlobSet,
|
||||
}
|
||||
|
||||
impl GlobSet {
|
||||
pub fn new<S: AsRef<str>, I: IntoIterator<Item = S>>(
|
||||
patterns: I,
|
||||
) -> Result<Self, globset::Error> {
|
||||
let patterns = patterns.into_iter();
|
||||
let size_hint = patterns.size_hint();
|
||||
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
let mut raw = Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0));
|
||||
|
||||
for pattern in patterns {
|
||||
let pattern = pattern.as_ref();
|
||||
let glob = globset::Glob::new(pattern)?;
|
||||
builder.add(glob);
|
||||
raw.push(pattern.to_string());
|
||||
}
|
||||
|
||||
let globs = builder.build()?;
|
||||
Ok(Self { raw, globs })
|
||||
}
|
||||
|
||||
pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
self.globs.is_match(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Serialize for GlobSet {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
self.raw.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for GlobSet {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let globs = Vec::<String>::deserialize(deserializer)?;
|
||||
Self::new(globs).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for GlobSet {
|
||||
fn schema_name() -> String {
|
||||
Self::schema_id().into()
|
||||
}
|
||||
|
||||
fn schema_id() -> Cow<'static, str> {
|
||||
Cow::Borrowed("GlobSet")
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema {
|
||||
gen.subschema_for::<Vec<String>>()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
categories::OxlintCategories, env::OxlintEnv, globals::OxlintGlobals, plugins::LintPlugins,
|
||||
rules::OxlintRules, settings::OxlintSettings,
|
||||
categories::OxlintCategories, env::OxlintEnv, globals::OxlintGlobals,
|
||||
overrides::OxlintOverrides, plugins::LintPlugins, rules::OxlintRules, settings::OxlintSettings,
|
||||
};
|
||||
|
||||
use crate::utils::read_to_string;
|
||||
|
|
@ -30,7 +30,7 @@ use crate::utils::read_to_string;
|
|||
/// ```json
|
||||
/// {
|
||||
/// "$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
/// "plugins": ["import", "unicorn"],
|
||||
/// "plugins": ["import", "typescript", "unicorn"],
|
||||
/// "env": {
|
||||
/// "browser": true
|
||||
/// },
|
||||
|
|
@ -42,7 +42,15 @@ use crate::utils::read_to_string;
|
|||
/// "rules": {
|
||||
/// "eqeqeq": "warn",
|
||||
/// "import/no-cycle": "error"
|
||||
/// }
|
||||
/// },
|
||||
/// "overrides": [
|
||||
/// {
|
||||
/// "files": ["*.test.ts", "*.spec.ts"],
|
||||
/// "rules": {
|
||||
/// "@typescript-eslint/no-explicit-any": "off"
|
||||
/// }
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]
|
||||
|
|
@ -74,6 +82,9 @@ pub struct Oxlintrc {
|
|||
pub env: OxlintEnv,
|
||||
/// Enabled or disabled specific global variables.
|
||||
pub globals: OxlintGlobals,
|
||||
/// Add, remove, or otherwise reconfigure rules for specific files or groups of files.
|
||||
#[serde(skip_serializing_if = "OxlintOverrides::is_empty")]
|
||||
pub overrides: OxlintOverrides,
|
||||
}
|
||||
|
||||
impl Oxlintrc {
|
||||
|
|
|
|||
|
|
@ -179,9 +179,26 @@ impl<'de> Deserialize<'de> for LintPlugins {
|
|||
A: de::SeqAccess<'de>,
|
||||
{
|
||||
let mut plugins = LintPlugins::default();
|
||||
while let Some(plugin) = seq.next_element::<&str>()? {
|
||||
plugins |= plugin.into();
|
||||
loop {
|
||||
// serde_json::from_str will provide an &str, while
|
||||
// serde_json::from_value provides a String. The former is
|
||||
// used in almost all cases, but the latter is more
|
||||
// convenient for test cases.
|
||||
match seq.next_element::<&str>() {
|
||||
Ok(Some(next)) => {
|
||||
plugins |= next.into();
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(_) => {
|
||||
if let Some(next) = seq.next_element::<String>()? {
|
||||
plugins |= next.as_str().into();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(plugins)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,11 @@ impl<'a> ContextHost<'a> {
|
|||
self.semantic.source_type()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugins(&self) -> LintPlugins {
|
||||
self.plugins
|
||||
}
|
||||
|
||||
/// Add a diagnostic message to the end of the list of diagnostics. Can be used
|
||||
/// by any rule to report issues.
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ mod utils;
|
|||
pub mod loader;
|
||||
pub mod table;
|
||||
|
||||
use crate::config::ResolvedLinterState;
|
||||
use std::{io::Write, path::Path, rc::Rc, sync::Arc};
|
||||
|
||||
use config::LintConfig;
|
||||
use config::{ConfigStore, LintConfig};
|
||||
use context::ContextHost;
|
||||
use options::LintOptions;
|
||||
use oxc_semantic::{AstNode, Semantic};
|
||||
|
|
@ -57,9 +58,10 @@ fn size_asserts() {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Linter {
|
||||
rules: Vec<RuleWithSeverity>,
|
||||
// rules: Vec<RuleWithSeverity>,
|
||||
options: LintOptions,
|
||||
config: Arc<LintConfig>,
|
||||
// config: Arc<LintConfig>,
|
||||
config: ConfigStore,
|
||||
}
|
||||
|
||||
impl Default for Linter {
|
||||
|
|
@ -69,18 +71,14 @@ impl Default for Linter {
|
|||
}
|
||||
|
||||
impl Linter {
|
||||
pub(crate) fn new(
|
||||
rules: Vec<RuleWithSeverity>,
|
||||
options: LintOptions,
|
||||
config: LintConfig,
|
||||
) -> Self {
|
||||
Self { rules, options, config: Arc::new(config) }
|
||||
pub(crate) fn new(options: LintOptions, config: ConfigStore) -> Self {
|
||||
Self { options, config }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
pub fn with_rules(mut self, rules: Vec<RuleWithSeverity>) -> Self {
|
||||
self.rules = rules;
|
||||
self.config.set_rules(rules);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -105,20 +103,19 @@ impl Linter {
|
|||
}
|
||||
|
||||
pub fn number_of_rules(&self) -> usize {
|
||||
self.rules.len()
|
||||
self.config.number_of_rules()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn rules(&self) -> &Vec<RuleWithSeverity> {
|
||||
&self.rules
|
||||
pub(crate) fn rules(&self) -> &Arc<[RuleWithSeverity]> {
|
||||
self.config.rules()
|
||||
}
|
||||
|
||||
pub fn run<'a>(&self, path: &Path, semantic: Rc<Semantic<'a>>) -> Vec<Message<'a>> {
|
||||
let ctx_host =
|
||||
Rc::new(ContextHost::new(path, semantic, self.options, Arc::clone(&self.config)));
|
||||
// Get config + rules for this file. Takes base rules and applies glob-based overrides.
|
||||
let ResolvedLinterState { rules, config } = self.config.resolve(path);
|
||||
let ctx_host = Rc::new(ContextHost::new(path, semantic, self.options, config));
|
||||
|
||||
let rules = self
|
||||
.rules
|
||||
let rules = rules
|
||||
.iter()
|
||||
.filter(|rule| rule.should_run(&ctx_host))
|
||||
.map(|rule| (rule, Rc::clone(&ctx_host).spawn(rule)));
|
||||
|
|
@ -126,7 +123,7 @@ impl Linter {
|
|||
let semantic = ctx_host.semantic();
|
||||
|
||||
let should_run_on_jest_node =
|
||||
self.config.plugins.has_test() && ctx_host.frameworks().is_test();
|
||||
ctx_host.plugins().has_test() && ctx_host.frameworks().is_test();
|
||||
|
||||
// IMPORTANT: We have two branches here for performance reasons:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ expression: json
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Oxlintrc",
|
||||
"description": "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 { \"$schema\": \"./node_modules/oxlint/configuration_schema.json\", \"plugins\": [\"import\", \"unicorn\"], \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" } } ```",
|
||||
"description": "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 { \"$schema\": \"./node_modules/oxlint/configuration_schema.json\", \"plugins\": [\"import\", \"typescript\", \"unicorn\"], \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" }, \"overrides\": [ { \"files\": [\"*.test.ts\", \"*.spec.ts\"], \"rules\": { \"@typescript-eslint/no-explicit-any\": \"off\" } } ] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
|
|
@ -36,6 +36,14 @@ expression: json
|
|||
}
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OxlintOverrides"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"default": [
|
||||
"react",
|
||||
|
|
@ -170,6 +178,12 @@ expression: json
|
|||
"$ref": "#/definitions/DummyRule"
|
||||
}
|
||||
},
|
||||
"GlobSet": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"GlobalValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -322,6 +336,48 @@ expression: json
|
|||
"$ref": "#/definitions/GlobalValue"
|
||||
}
|
||||
},
|
||||
"OxlintOverride": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"files"
|
||||
],
|
||||
"properties": {
|
||||
"files": {
|
||||
"description": "A list of glob patterns to override.\n\n## Example `[ \"*.test.ts\", \"*.spec.ts\" ]`",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GlobSet"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"description": "Optionally change what plugins are enabled for this override. When omitted, the base config's plugins are used.",
|
||||
"default": null,
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LintPlugins"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"default": {},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OxlintRules"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"OxlintOverrides": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/OxlintOverride"
|
||||
}
|
||||
},
|
||||
"OxlintRules": {
|
||||
"$ref": "#/definitions/DummyRuleMap"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,11 +34,8 @@ impl Default for RuleTable {
|
|||
|
||||
impl RuleTable {
|
||||
pub fn new() -> Self {
|
||||
let default_rules = Linter::default()
|
||||
.rules
|
||||
.into_iter()
|
||||
.map(|rule| rule.name())
|
||||
.collect::<FxHashSet<&str>>();
|
||||
let default_rules =
|
||||
Linter::default().rules().iter().map(|rule| rule.name()).collect::<FxHashSet<&str>>();
|
||||
|
||||
let mut rows = RULES
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Oxlintrc",
|
||||
"description": "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 { \"$schema\": \"./node_modules/oxlint/configuration_schema.json\", \"plugins\": [\"import\", \"unicorn\"], \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" } } ```",
|
||||
"description": "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 { \"$schema\": \"./node_modules/oxlint/configuration_schema.json\", \"plugins\": [\"import\", \"typescript\", \"unicorn\"], \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" }, \"overrides\": [ { \"files\": [\"*.test.ts\", \"*.spec.ts\"], \"rules\": { \"@typescript-eslint/no-explicit-any\": \"off\" } } ] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
|
|
@ -32,6 +32,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OxlintOverrides"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"default": [
|
||||
"react",
|
||||
|
|
@ -166,6 +174,12 @@
|
|||
"$ref": "#/definitions/DummyRule"
|
||||
}
|
||||
},
|
||||
"GlobSet": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"GlobalValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -318,6 +332,48 @@
|
|||
"$ref": "#/definitions/GlobalValue"
|
||||
}
|
||||
},
|
||||
"OxlintOverride": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"files"
|
||||
],
|
||||
"properties": {
|
||||
"files": {
|
||||
"description": "A list of glob patterns to override.\n\n## Example `[ \"*.test.ts\", \"*.spec.ts\" ]`",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GlobSet"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"description": "Optionally change what plugins are enabled for this override. When omitted, the base config's plugins are used.",
|
||||
"default": null,
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LintPlugins"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"default": {},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OxlintRules"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"OxlintOverrides": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/OxlintOverride"
|
||||
}
|
||||
},
|
||||
"OxlintRules": {
|
||||
"$ref": "#/definitions/DummyRuleMap"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Example
|
|||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": [
|
||||
"import",
|
||||
"typescript",
|
||||
"unicorn"
|
||||
],
|
||||
"env": {
|
||||
|
|
@ -35,7 +36,18 @@ Example
|
|||
"rules": {
|
||||
"eqeqeq": "warn",
|
||||
"import/no-cycle": "error"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.test.ts",
|
||||
"*.spec.ts"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -148,6 +160,38 @@ Globals can be disabled by setting their value to `"off"`. For example, in an en
|
|||
You may also use `"readable"` or `false` to represent `"readonly"`, and `"writeable"` or `true` to represent `"writable"`.
|
||||
|
||||
|
||||
## overrides
|
||||
|
||||
type: `array`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### overrides[n]
|
||||
|
||||
type: `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### overrides[n].files
|
||||
|
||||
type: `string[]`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### overrides[n].rules
|
||||
|
||||
type: `object`
|
||||
|
||||
|
||||
See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html)
|
||||
|
||||
|
||||
## plugins
|
||||
|
||||
type: `string[]`
|
||||
|
|
|
|||
Loading…
Reference in a new issue