Skip to content

Commit ea25591

Browse files
committed
fix(linter): parse ignorePatterns with gitignore syntax
1 parent 93eaf5f commit ea25591

File tree

10 files changed

+101
-17
lines changed

10 files changed

+101
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"ignorePatterns": [
3+
"ignored_dir"
4+
]
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
debugger;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"ignorePatterns": [
3+
"ignored_dir"
4+
]
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
debugger;

apps/oxlint/src/lint.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ impl LintRunner {
205205
} else {
206206
None
207207
};
208-
let config_builder = match ConfigStoreBuilder::from_oxlintrc(
208+
let config_builder = match ConfigStoreBuilder::from_base_oxlintrc(
209+
&self.cwd,
209210
false,
210211
oxlintrc,
211212
external_linter,
@@ -667,6 +668,16 @@ mod test {
667668
.test_and_snapshot_multiple(&[args1, args2]);
668669
}
669670

671+
#[test]
672+
// https://github.com/oxc-project/oxc/issues/13204
673+
fn ignore_pattern_non_glob_syntax() {
674+
let args1 = &[];
675+
let args2 = &["."];
676+
Tester::new()
677+
.with_cwd("fixtures/ignore_pattern_non_glob_syntax".into())
678+
.test_and_snapshot_multiple(&[args1, args2]);
679+
}
680+
670681
#[test]
671682
fn filter_allow_all() {
672683
let args = &["-A", "all", "fixtures/linter"];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: apps/oxlint/src/tester.rs
3+
---
4+
##########
5+
arguments:
6+
working directory: fixtures/ignore_pattern_non_glob_syntax
7+
----------
8+
Found 0 warnings and 0 errors.
9+
Finished in <variable>ms on 0 files using 1 threads.
10+
----------
11+
CLI result: LintSucceeded
12+
----------
13+
14+
##########
15+
arguments: .
16+
working directory: fixtures/ignore_pattern_non_glob_syntax
17+
----------
18+
Found 0 warnings and 0 errors.
19+
Finished in <variable>ms on 0 files using 1 threads.
20+
----------
21+
CLI result: LintSucceeded
22+
----------

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ impl ServerLinter {
4949
Oxlintrc::default()
5050
};
5151

52-
let config_builder = ConfigStoreBuilder::from_oxlintrc(
52+
let config_builder = ConfigStoreBuilder::from_base_oxlintrc(
53+
&root_path,
5354
false,
5455
oxlintrc,
5556
None,

crates/oxc_linter/src/config/config_builder.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,49 @@ impl ConfigStoreBuilder {
100100
oxlintrc: Oxlintrc,
101101
external_linter: Option<&ExternalLinter>,
102102
external_plugin_store: &mut ExternalPluginStore,
103+
) -> Result<Self, ConfigBuilderError> {
104+
let parent_path =
105+
oxlintrc.path.parent().map_or_else(|| PathBuf::from("."), std::path::Path::to_path_buf);
106+
107+
Self::from_oxlintrc_with_ignore_root(
108+
start_empty,
109+
oxlintrc,
110+
external_linter,
111+
external_plugin_store,
112+
parent_path.as_path(),
113+
)
114+
}
115+
116+
/// Similar to the [`ConfigStoreBuilder::from_oxlintrc`] method, but
117+
/// applies the config on top of a default [`Oxlintrc`].
118+
/// The ignore root of this file, should be the current working directory.
119+
/// Even if the file is not located at the current working directory.
120+
///
121+
/// # Errors
122+
///
123+
/// Returns [`ConfigBuilderError::InvalidConfigFile`] if a referenced config file is not valid.
124+
pub fn from_base_oxlintrc(
125+
cwd: &Path,
126+
start_empty: bool,
127+
oxlintrc: Oxlintrc,
128+
external_linter: Option<&ExternalLinter>,
129+
external_plugin_store: &mut ExternalPluginStore,
130+
) -> Result<Self, ConfigBuilderError> {
131+
Self::from_oxlintrc_with_ignore_root(
132+
start_empty,
133+
oxlintrc,
134+
external_linter,
135+
external_plugin_store,
136+
cwd,
137+
)
138+
}
139+
140+
fn from_oxlintrc_with_ignore_root(
141+
start_empty: bool,
142+
oxlintrc: Oxlintrc,
143+
external_linter: Option<&ExternalLinter>,
144+
external_plugin_store: &mut ExternalPluginStore,
145+
ignore_root: &Path,
103146
) -> Result<Self, ConfigBuilderError> {
104147
// TODO: this can be cached to avoid re-computing the same oxlintrc
105148
fn resolve_oxlintrc_config(
@@ -167,7 +210,6 @@ impl ConfigStoreBuilder {
167210

168211
let resolver = Resolver::default();
169212

170-
#[expect(clippy::missing_panics_doc, reason = "oxlintrc.path is always a file path")]
171213
let oxlintrc_dir = oxlintrc.path.parent().unwrap();
172214

173215
for plugin_specifier in &external_plugins {
@@ -201,7 +243,7 @@ impl ConfigStoreBuilder {
201243
globals: oxlintrc.globals,
202244
ignore_patterns: LintConfig::resolve_oxlintrc_ignore_patterns(
203245
&oxlintrc.ignore_patterns,
204-
&oxlintrc.path,
246+
ignore_root,
205247
),
206248
path: Some(oxlintrc.path),
207249
};

crates/oxc_linter/src/config/config_store.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,9 @@ impl ConfigStore {
330330
}
331331

332332
pub fn should_ignore(&self, path: &Path) -> bool {
333-
self.get_related_config(path)
334-
.ignore_patterns()
335-
.is_some_and(|ignore_patterns| ignore_patterns.matched(path, false).is_ignore())
333+
self.get_related_config(path).ignore_patterns().is_some_and(|ignore_patterns| {
334+
ignore_patterns.matched_path_or_any_parents(path, false).is_ignore()
335+
})
336336
}
337337

338338
// NOTE: This function is not crate visible because it is used in `oxlint` as well to resolve configs

crates/oxc_linter/src/config/mod.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ pub use config_builder::{ConfigBuilderError, ConfigStoreBuilder};
1414
pub use config_store::{Config, ConfigStore, ResolvedLinterState};
1515
pub use env::OxlintEnv;
1616
pub use globals::{GlobalValue, OxlintGlobals};
17-
use ignore::overrides::OverrideBuilder;
17+
use ignore::gitignore::{Gitignore, GitignoreBuilder};
1818
pub use overrides::OxlintOverrides;
1919
pub use oxlintrc::Oxlintrc;
2020
pub use plugins::{BuiltinLintPlugins, LintPlugins};
2121
pub use rules::{ESLintRule, OxlintRules};
2222
pub use settings::{OxlintSettings, jsdoc::JSDocPluginSettings};
2323

24-
pub type ResolvedIgnorePatterns = ignore::overrides::Override;
24+
pub type ResolvedIgnorePatterns = Gitignore;
2525

2626
#[derive(Debug, Default, Clone)]
2727
pub struct LintConfig {
@@ -53,23 +53,19 @@ impl From<Oxlintrc> for LintConfig {
5353
impl LintConfig {
5454
pub(crate) fn resolve_oxlintrc_ignore_patterns(
5555
ignore_patterns: &[String],
56-
config_path: &Path,
56+
ignore_root: &Path,
5757
) -> Option<ResolvedIgnorePatterns> {
5858
if ignore_patterns.is_empty() {
5959
return None;
6060
}
61-
// expect that every oxlint config file with "ignorePatterns" provides its config path with parent.
62-
// for the default config the path is empty, but there should be no ignore patterns
63-
let oxlint_wd = config_path.parent()?.to_path_buf();
6461

65-
let mut builder = OverrideBuilder::new(&oxlint_wd);
62+
let mut gitignore_builder = GitignoreBuilder::new(ignore_root);
6663

6764
for pattern in ignore_patterns {
68-
let pattern = format!("!{pattern}");
69-
builder.add(&pattern).unwrap();
65+
gitignore_builder.add_line(None, pattern).unwrap();
7066
}
7167

72-
builder.build().ok()
68+
gitignore_builder.build().ok()
7369
}
7470
}
7571

0 commit comments

Comments
 (0)