mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): Add support for ignorePatterns property within config file (#7092)
This could probably use some tests, but I'm not really sure what exactly should be tested. Will leave a review with a few comments on things that might need a different approach. Closes #7032.
This commit is contained in:
parent
cc078d6e77
commit
32f860d238
16 changed files with 116 additions and 27 deletions
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ignorePatterns": [
|
||||||
|
"tests/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ignorePatterns": [
|
||||||
|
"*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -40,5 +40,6 @@
|
||||||
"env": {
|
"env": {
|
||||||
"builtin": true
|
"builtin": true
|
||||||
},
|
},
|
||||||
"globals": {}
|
"globals": {},
|
||||||
|
"ignorePatterns": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,6 @@
|
||||||
"env": {
|
"env": {
|
||||||
"builtin": true
|
"builtin": true
|
||||||
},
|
},
|
||||||
"globals": {}
|
"globals": {},
|
||||||
|
"ignorePatterns": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,11 +102,6 @@ impl Runner for LintRunner {
|
||||||
.copied()
|
.copied()
|
||||||
.collect::<Vec<&'static str>>();
|
.collect::<Vec<&'static str>>();
|
||||||
|
|
||||||
let paths =
|
|
||||||
Walk::new(&paths, &ignore_options).with_extensions(Extensions(extensions)).paths();
|
|
||||||
|
|
||||||
let number_of_files = paths.len();
|
|
||||||
|
|
||||||
let config_search_result = Self::find_oxlint_config(&self.cwd, &basic_options.config);
|
let config_search_result = Self::find_oxlint_config(&self.cwd, &basic_options.config);
|
||||||
|
|
||||||
if let Err(err) = config_search_result {
|
if let Err(err) = config_search_result {
|
||||||
|
|
@ -115,6 +110,17 @@ impl Runner for LintRunner {
|
||||||
|
|
||||||
let mut oxlintrc = config_search_result.unwrap();
|
let mut oxlintrc = config_search_result.unwrap();
|
||||||
|
|
||||||
|
let ignore_paths = oxlintrc
|
||||||
|
.ignore_patterns
|
||||||
|
.iter()
|
||||||
|
.map(|value| oxlintrc.path.parent().unwrap().join(value))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let paths = Walk::new(&paths, &ignore_options, &ignore_paths)
|
||||||
|
.with_extensions(Extensions(extensions))
|
||||||
|
.paths();
|
||||||
|
|
||||||
|
let number_of_files = paths.len();
|
||||||
|
|
||||||
enable_plugins.apply_overrides(&mut oxlintrc.plugins);
|
enable_plugins.apply_overrides(&mut oxlintrc.plugins);
|
||||||
|
|
||||||
let oxlintrc_for_print =
|
let oxlintrc_for_print =
|
||||||
|
|
@ -750,4 +756,24 @@ mod test {
|
||||||
assert_eq!(result.number_of_warnings, 2);
|
assert_eq!(result.number_of_warnings, 2);
|
||||||
assert_eq!(result.number_of_errors, 2);
|
assert_eq!(result.number_of_errors, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_ignore_patterns_extension() {
|
||||||
|
let result = test(&[
|
||||||
|
"-c",
|
||||||
|
"fixtures/config_ignore_patterns/ignore_extension/eslintrc.json",
|
||||||
|
"fixtures/config_ignore_patterns/ignore_extension",
|
||||||
|
]);
|
||||||
|
assert_eq!(result.number_of_files, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_ignore_patterns_directory() {
|
||||||
|
let result = test(&[
|
||||||
|
"-c",
|
||||||
|
"fixtures/config_ignore_patterns/ignore_directory/eslintrc.json",
|
||||||
|
"fixtures/config_ignore_patterns/ignore_directory",
|
||||||
|
]);
|
||||||
|
assert_eq!(result.number_of_files, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,11 @@ impl ignore::ParallelVisitor for WalkCollector {
|
||||||
impl Walk {
|
impl Walk {
|
||||||
/// Will not canonicalize paths.
|
/// Will not canonicalize paths.
|
||||||
/// # Panics
|
/// # Panics
|
||||||
pub fn new(paths: &[PathBuf], options: &IgnoreOptions) -> Self {
|
pub fn new(
|
||||||
|
paths: &[PathBuf],
|
||||||
|
options: &IgnoreOptions,
|
||||||
|
config_ignore_patterns: &[PathBuf],
|
||||||
|
) -> Self {
|
||||||
assert!(!paths.is_empty(), "At least one path must be provided to Walk::new");
|
assert!(!paths.is_empty(), "At least one path must be provided to Walk::new");
|
||||||
|
|
||||||
let mut inner = ignore::WalkBuilder::new(
|
let mut inner = ignore::WalkBuilder::new(
|
||||||
|
|
@ -89,17 +93,24 @@ impl Walk {
|
||||||
if !options.no_ignore {
|
if !options.no_ignore {
|
||||||
inner.add_custom_ignore_filename(&options.ignore_path);
|
inner.add_custom_ignore_filename(&options.ignore_path);
|
||||||
|
|
||||||
|
let mut override_builder = OverrideBuilder::new(Path::new("/"));
|
||||||
if !options.ignore_pattern.is_empty() {
|
if !options.ignore_pattern.is_empty() {
|
||||||
let mut override_builder = OverrideBuilder::new(Path::new("/"));
|
|
||||||
for pattern in &options.ignore_pattern {
|
for pattern in &options.ignore_pattern {
|
||||||
// Meaning of ignore pattern is reversed
|
// Meaning of ignore pattern is reversed
|
||||||
// <https://docs.rs/ignore/latest/ignore/overrides/struct.OverrideBuilder.html#method.add>
|
// <https://docs.rs/ignore/latest/ignore/overrides/struct.OverrideBuilder.html#method.add>
|
||||||
let pattern = format!("!{pattern}");
|
let pattern = format!("!{pattern}");
|
||||||
override_builder.add(&pattern).unwrap();
|
override_builder.add(&pattern).unwrap();
|
||||||
}
|
}
|
||||||
let overrides = override_builder.build().unwrap();
|
|
||||||
inner.overrides(overrides);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !config_ignore_patterns.is_empty() {
|
||||||
|
for pattern in config_ignore_patterns {
|
||||||
|
let pattern = format!("!{}", pattern.to_str().unwrap());
|
||||||
|
override_builder.add(&pattern).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.overrides(override_builder.build().unwrap());
|
||||||
}
|
}
|
||||||
// Turning off `follow_links` because:
|
// Turning off `follow_links` because:
|
||||||
// * following symlinks is a really slow syscall
|
// * following symlinks is a really slow syscall
|
||||||
|
|
@ -155,7 +166,7 @@ mod test {
|
||||||
symlinks: false,
|
symlinks: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut paths = Walk::new(&fixtures, &ignore_options)
|
let mut paths = Walk::new(&fixtures, &ignore_options, &[])
|
||||||
.with_extensions(Extensions(["js", "vue"].to_vec()))
|
.with_extensions(Extensions(["js", "vue"].to_vec()))
|
||||||
.paths()
|
.paths()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,6 @@ enum SyntheticRunLevel {
|
||||||
impl LanguageServer for Backend {
|
impl LanguageServer for Backend {
|
||||||
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
||||||
self.init(params.root_uri)?;
|
self.init(params.root_uri)?;
|
||||||
self.init_ignore_glob().await;
|
|
||||||
let options = params.initialization_options.and_then(|mut value| {
|
let options = params.initialization_options.and_then(|mut value| {
|
||||||
let settings = value.get_mut("settings")?.take();
|
let settings = value.get_mut("settings")?.take();
|
||||||
serde_json::from_value::<Options>(settings).ok()
|
serde_json::from_value::<Options>(settings).ok()
|
||||||
|
|
@ -117,7 +116,8 @@ impl LanguageServer for Backend {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
self.init_linter_config().await;
|
let oxlintrc = self.init_linter_config().await;
|
||||||
|
self.init_ignore_glob(oxlintrc).await;
|
||||||
Ok(InitializeResult {
|
Ok(InitializeResult {
|
||||||
server_info: Some(ServerInfo { name: "oxc".into(), version: None }),
|
server_info: Some(ServerInfo { name: "oxc".into(), version: None }),
|
||||||
offset_encoding: None,
|
offset_encoding: None,
|
||||||
|
|
@ -411,7 +411,7 @@ impl Backend {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_ignore_glob(&self) {
|
async fn init_ignore_glob(&self, oxlintrc: Option<Oxlintrc>) {
|
||||||
let uri = self
|
let uri = self
|
||||||
.root_uri
|
.root_uri
|
||||||
.get()
|
.get()
|
||||||
|
|
@ -447,6 +447,17 @@ impl Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(oxlintrc) = oxlintrc {
|
||||||
|
if !oxlintrc.ignore_patterns.is_empty() {
|
||||||
|
let mut builder =
|
||||||
|
ignore::gitignore::GitignoreBuilder::new(oxlintrc.path.parent().unwrap());
|
||||||
|
for entry in &oxlintrc.ignore_patterns {
|
||||||
|
builder.add_line(None, entry).expect("Failed to add ignore line");
|
||||||
|
}
|
||||||
|
gitignore_globs.push(builder.build().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::ptr_arg)]
|
#[allow(clippy::ptr_arg)]
|
||||||
|
|
@ -470,12 +481,12 @@ impl Backend {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_linter_config(&self) {
|
async fn init_linter_config(&self) -> Option<Oxlintrc> {
|
||||||
let Some(Some(uri)) = self.root_uri.get() else {
|
let Some(Some(uri)) = self.root_uri.get() else {
|
||||||
return;
|
return None;
|
||||||
};
|
};
|
||||||
let Ok(root_path) = uri.to_file_path() else {
|
let Ok(root_path) = uri.to_file_path() else {
|
||||||
return;
|
return None;
|
||||||
};
|
};
|
||||||
let mut config_path = None;
|
let mut config_path = None;
|
||||||
let config = root_path.join(self.options.lock().await.get_config_path().unwrap());
|
let config = root_path.join(self.options.lock().await.get_config_path().unwrap());
|
||||||
|
|
@ -484,16 +495,17 @@ impl Backend {
|
||||||
}
|
}
|
||||||
if let Some(config_path) = config_path {
|
if let Some(config_path) = config_path {
|
||||||
let mut linter = self.server_linter.write().await;
|
let mut linter = self.server_linter.write().await;
|
||||||
|
let config = Oxlintrc::from_file(&config_path)
|
||||||
|
.expect("should have initialized linter with new options");
|
||||||
*linter = ServerLinter::new_with_linter(
|
*linter = ServerLinter::new_with_linter(
|
||||||
LinterBuilder::from_oxlintrc(
|
LinterBuilder::from_oxlintrc(true, config.clone())
|
||||||
true,
|
.with_fix(FixKind::SafeFix)
|
||||||
Oxlintrc::from_file(&config_path)
|
.build(),
|
||||||
.expect("should have initialized linter with new options"),
|
|
||||||
)
|
|
||||||
.with_fix(FixKind::SafeFix)
|
|
||||||
.build(),
|
|
||||||
);
|
);
|
||||||
|
return Some(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_file_update(&self, uri: Url, content: Option<String>, version: Option<i32>) {
|
async fn handle_file_update(&self, uri: Url, content: Option<String>, version: Option<i32>) {
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ impl LinterBuilder {
|
||||||
rules: oxlintrc_rules,
|
rules: oxlintrc_rules,
|
||||||
overrides,
|
overrides,
|
||||||
path,
|
path,
|
||||||
|
ignore_patterns: _,
|
||||||
} = oxlintrc;
|
} = oxlintrc;
|
||||||
|
|
||||||
let config = LintConfig { plugins, settings, env, globals, path: Some(path) };
|
let config = LintConfig { plugins, settings, env, globals, path: Some(path) };
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ pub struct Oxlintrc {
|
||||||
/// Absolute path to the configuration file.
|
/// Absolute path to the configuration file.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// Globs to ignore during linting. These are resolved from the configuration file path.
|
||||||
|
#[serde(rename = "ignorePatterns")]
|
||||||
|
pub ignore_patterns: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Oxlintrc {
|
impl Oxlintrc {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/oxc_linter/src/lib.rs
|
source: crates/oxc_linter/src/lib.rs
|
||||||
expression: json
|
expression: json
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
|
@ -37,6 +36,14 @@ snapshot_kind: text
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignorePatterns": {
|
||||||
|
"description": "Globs to ignore during linting. These are resolved from the configuration file path.",
|
||||||
|
"default": [],
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignorePatterns": {
|
||||||
|
"description": "Globs to ignore during linting. These are resolved from the configuration file path.",
|
||||||
|
"default": [],
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
"description": "Add, remove, or otherwise reconfigure rules for specific files or groups of files.",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: tasks/website/src/linter/json_schema.rs
|
source: tasks/website/src/linter/json_schema.rs
|
||||||
expression: snapshot
|
expression: snapshot
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
# Oxlint Configuration File
|
# Oxlint Configuration File
|
||||||
|
|
||||||
|
|
@ -161,6 +160,15 @@ 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"`.
|
You may also use `"readable"` or `false` to represent `"readonly"`, and `"writeable"` or `true` to represent `"writable"`.
|
||||||
|
|
||||||
|
|
||||||
|
## ignorePatterns
|
||||||
|
|
||||||
|
type: `string[]`
|
||||||
|
|
||||||
|
default: `[]`
|
||||||
|
|
||||||
|
Globs to ignore during linting. These are resolved from the configuration file path.
|
||||||
|
|
||||||
|
|
||||||
## overrides
|
## overrides
|
||||||
|
|
||||||
type: `array`
|
type: `array`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue