Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ impl Runner for LintRunner {
FxHashMap::default()
};

enable_plugins.apply_overrides(&mut oxlintrc.plugins);
{
let mut plugins = oxlintrc.plugins.unwrap_or_default();
enable_plugins.apply_overrides(&mut plugins);
oxlintrc.plugins = Some(plugins);
}

let oxlintrc_for_print = if misc_options.print_config || basic_options.init {
Some(oxlintrc.clone())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ working directory: fixtures/extends_config
help: Delete this console statement.

Found 0 warnings and 1 error.
Finished in <variable>ms on 1 file with 103 rules using 1 threads.
Finished in <variable>ms on 1 file with 102 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: apps/oxlint/src/tester.rs
snapshot_kind: text
---
##########
arguments: overrides
Expand All @@ -22,6 +21,15 @@ working directory: fixtures/extends_config
`----
help: Use `unknown` instead, this will force you to explicitly, and safely, assert the type is correct.

! ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-is-valid.html\eslint-plugin-jsx-a11y(anchor-is-valid)]8;;\: Missing `href` attribute for the `a` element.
,-[overrides/test.tsx:2:11]
1 | function component(): any {
2 | return <a>click here</a>;
: ^
3 | }
`----
help: Provide an `href` for the `a` element.

x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-ambiguous-text.html\eslint-plugin-jsx-a11y(anchor-ambiguous-text)]8;;\: Ambiguous text within anchor, screen reader users rely on link text for context.
,-[overrides/test.tsx:2:10]
1 | function component(): any {
Expand All @@ -31,7 +39,7 @@ working directory: fixtures/extends_config
`----
help: Avoid using ambiguous text like "click here", replace it with more descriptive text that provides context.

Found 0 warnings and 3 errors.
Found 1 warning and 3 errors.
Finished in <variable>ms on 2 files using 1 threads.
----------
CLI result: LintFoundErrors
Expand Down
11 changes: 10 additions & 1 deletion crates/oxc_linter/src/config/categories.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{borrow::Cow, ops::Deref};
use std::{
borrow::Cow,
ops::{Deref, DerefMut},
};

use rustc_hash::FxHashMap;
use schemars::JsonSchema;
Expand All @@ -18,6 +21,12 @@ impl Deref for OxlintCategories {
}
}

impl DerefMut for OxlintCategories {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl OxlintCategories {
pub fn filters(&self) -> impl Iterator<Item = LintFilter> + '_ {
self.iter().map(|(category, severity)| LintFilter::new(*severity, *category).unwrap())
Expand Down
149 changes: 56 additions & 93 deletions crates/oxc_linter/src/config/config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,99 +78,70 @@ impl ConfigStoreBuilder {
start_empty: bool,
oxlintrc: Oxlintrc,
) -> Result<Self, ConfigBuilderError> {
// TODO(refactor); can we make this function infallible, and move all the error handling to
// the `build` method?
let Oxlintrc {
plugins,
settings,
env,
globals,
categories,
rules: oxlintrc_rules,
overrides,
path,
ignore_patterns: _,
extends,
} = oxlintrc;

let config = LintConfig { plugins, settings, env, globals, path: Some(path) };
let rules =
if start_empty { FxHashMap::default() } else { Self::warn_correctness(plugins) };
let cache = RulesCache::new(config.plugins);
let mut builder = Self { rules, config, overrides, cache };
// TODO: this can be cached to avoid re-computing the same oxlintrc
fn resolve_oxlintrc_config(config: Oxlintrc) -> Result<Oxlintrc, ConfigBuilderError> {
let path = config.path.clone();
let root_path = path.parent();
let extends = config.extends.clone();

let mut oxlintrc = config;

for path in extends.iter().rev() {
if path.starts_with("eslint:") || path.starts_with("plugin:") {
// `eslint:` and `plugin:` named configs are not supported
continue;
}
// if path does not include a ".", then we will heuristically skip it since it
// kind of looks like it might be a named config
if !path.to_string_lossy().contains('.') {
continue;
}

for filter in categories.filters() {
builder = builder.with_filter(&filter);
}
let path = match root_path {
Some(p) => &p.join(path),
None => path,
};

{
if !extends.is_empty() {
let config_path = builder.config.path.clone();
let config_path_parent = config_path.as_ref().and_then(|p| p.parent());

for path in &extends {
if path.starts_with("eslint:") || path.starts_with("plugin:") {
// eslint: and plugin: named configs are not supported
continue;
}
// if path does not include a ".", then we will heuristically skip it since it
// kind of looks like it might be a named config
if !path.to_string_lossy().contains('.') {
continue;
let extends_oxlintrc = Oxlintrc::from_file(path).map_err(|e| {
ConfigBuilderError::InvalidConfigFile {
file: path.display().to_string(),
reason: e.to_string(),
}
})?;

// resolve path relative to config path
let path = match config_path_parent {
Some(config_file_path) => &config_file_path.join(path),
None => path,
};
// TODO: throw an error if this is a self-referential extend
// TODO(perf): use a global config cache to avoid re-parsing the same file multiple times
match Oxlintrc::from_file(path) {
Ok(extended_config) => {
// TODO(refactor): can we merge this together? seems redundant to use `override_rules` and then
// use `ConfigStoreBuilder`, but we don't have a better way of loading rules from config files other than that.
// Use `override_rules` to apply rule configurations and add/remove rules as needed
extended_config
.rules
.override_rules(&mut builder.rules, &builder.cache.borrow());
// Use `ConfigStoreBuilder` to load extended config files and then apply rules from those
let mut extended_config_store =
ConfigStoreBuilder::from_oxlintrc(true, extended_config)?;
let rules = std::mem::take(&mut extended_config_store.rules);
builder = builder.with_rules(rules);

// Handle plugin inheritance
let parent_plugins = extended_config_store.plugins();
let child_plugins = builder.plugins();

if child_plugins == LintPlugins::default() {
// If child has default plugins, inherit from parent
builder = builder.with_plugins(parent_plugins);
} else if child_plugins != LintPlugins::empty() {
// If child specifies plugins, combine with parent's plugins
builder = builder.with_plugins(child_plugins.union(parent_plugins));
}

if !extended_config_store.overrides.is_empty() {
let overrides =
std::mem::take(&mut extended_config_store.overrides);
builder = builder.with_overrides(overrides);
}
}
Err(err) => {
return Err(ConfigBuilderError::InvalidConfigFile {
file: path.display().to_string(),
reason: err.to_string(),
});
}
}
}
let extends = resolve_oxlintrc_config(extends_oxlintrc)?;

oxlintrc = oxlintrc.merge(extends);
}
Ok(oxlintrc)
}

let oxlintrc = resolve_oxlintrc_config(oxlintrc)?;

let rules = if start_empty {
FxHashMap::default()
} else {
Self::warn_correctness(oxlintrc.plugins.unwrap_or_default())
};

let config = LintConfig {
plugins: oxlintrc.plugins.unwrap_or_default(),
settings: oxlintrc.settings,
env: oxlintrc.env,
globals: oxlintrc.globals,
path: Some(oxlintrc.path),
};
let cache = RulesCache::new(config.plugins);
let mut builder = Self { rules, config, overrides: oxlintrc.overrides, cache };

for filter in oxlintrc.categories.filters() {
builder = builder.with_filter(&filter);
}

{
let all_rules = builder.cache.borrow();

oxlintrc_rules.override_rules(&mut builder.rules, all_rules.as_slice());
oxlintrc.rules.override_rules(&mut builder.rules, all_rules.as_slice());
}

Ok(builder)
Expand Down Expand Up @@ -218,14 +189,6 @@ impl ConfigStoreBuilder {
self
}

pub(crate) fn with_rules<R: IntoIterator<Item = (RuleEnum, AllowWarnDeny)>>(
mut self,
rules: R,
) -> Self {
self.rules.extend(rules);
self
}

/// Appends an override to the end of the current list of overrides.
pub fn with_overrides<O: IntoIterator<Item = OxlintOverride>>(mut self, overrides: O) -> Self {
self.overrides.extend(overrides);
Expand Down
7 changes: 5 additions & 2 deletions crates/oxc_linter/src/config/config_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,14 @@ impl Config {
}
}

/// Resolves a lint configuration for a given file, by applying overrides based on the file's path.
/// Stores the configuration state for the linter including:
/// 1. the root configuration (base)
/// 2. any nested configurations (`nested_configs`)
///
/// If an explicit config has been provided `-c config.json`, then `nested_configs` will be empty
#[derive(Debug, Clone)]
pub struct ConfigStore {
base: Config,

nested_configs: FxHashMap<PathBuf, Config>,
}

Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct LintConfig {
impl From<Oxlintrc> for LintConfig {
fn from(config: Oxlintrc) -> Self {
Self {
plugins: config.plugins,
plugins: config.plugins.unwrap_or_default(),
settings: config.settings,
env: config.env,
globals: config.globals,
Expand Down
8 changes: 7 additions & 1 deletion crates/oxc_linter/src/config/overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub struct OxlintOverride {
///
/// Thin wrapper around [`globset::GlobSet`] because that struct doesn't implement Serialize or schemars
/// traits.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Default)]
pub struct GlobSet {
/// Raw patterns from the config. Inefficient, but required for [serialization](Serialize),
/// which in turn is required for `--print-config`.
Expand Down Expand Up @@ -126,6 +126,12 @@ impl GlobSet {
}
}

impl std::fmt::Debug for GlobSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("GlobSet").field(&self.raw).finish()
}
}

impl Serialize for GlobSet {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.raw.serialize(serializer)
Expand Down
Loading
Loading