diff --git a/Cargo.lock b/Cargo.lock index 96d8963077803..2e0dae79ae026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1798,7 +1798,6 @@ dependencies = [ "cow-utils", "fast-glob", "globset", - "ignore", "indexmap", "insta", "itertools", diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index c71f6736d1bda..c82fa5597204c 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -49,7 +49,6 @@ convert_case = { workspace = true } cow-utils = { workspace = true } fast-glob = { workspace = true } globset = { workspace = true } -ignore = { workspace = true } indexmap = { workspace = true, features = ["rayon"] } itertools = { workspace = true } javascript-globals = { workspace = true } diff --git a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs index 6e44fa6118d2d..f7be7eb9c2c57 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -1,4 +1,6 @@ -use ignore::gitignore::GitignoreBuilder; +use std::borrow::Cow; + +use globset::GlobBuilder; use lazy_regex::Regex; use oxc_ast::{ AstKind, @@ -10,7 +12,6 @@ use oxc_span::{CompactStr, Span}; use rustc_hash::FxHashMap; use serde::{Deserialize, Deserializer, de::Error}; use serde_json::Value; -use std::borrow::Cow; use crate::{ ModuleRecord, @@ -856,30 +857,37 @@ impl RestrictedPattern { return GlobResult::None; }; - let mut builder = GitignoreBuilder::new(""); - // returns always OK, will be fixed in the next version - let _ = builder.case_insensitive(!self.case_sensitive.unwrap_or(false)); + let case_insensitive = !self.case_sensitive.unwrap_or(false); - for group in groups { - // returns always OK - let _ = builder.add_line(None, group.as_str()); - } + let mut decision = GlobResult::None; - let Ok(gitignore) = builder.build() else { - return GlobResult::None; - }; + for raw_pat in groups { + let (negated, pat) = match raw_pat.strip_prefix('!') { + Some(rest) => (true, rest), + None => (false, raw_pat.as_str()), + }; - let matched = gitignore.matched(name, false); + // roughly based on https://github.com/BurntSushi/ripgrep/blob/6dfaec03e830892e787686917509c17860456db1/crates/ignore/src/gitignore.rs#L436-L516 + let mut pat = pat.to_string(); - if matched.is_whitelist() { - return GlobResult::Whitelist; - } + if !pat.starts_with('/') && !pat.chars().any(|c| c == '/') && (!pat.starts_with("**")) { + pat = format!("**/{pat}"); + } - if matched.is_none() { - return GlobResult::None; + let Ok(glob) = GlobBuilder::new(&pat) + .case_insensitive(case_insensitive) + .build() + .map(|g| g.compile_matcher()) + else { + continue; + }; + + if glob.is_match(name) { + decision = if negated { GlobResult::Whitelist } else { GlobResult::Found }; + } } - GlobResult::Found + decision } fn get_regex_result(&self, name: &str) -> bool { @@ -1762,6 +1770,12 @@ fn test() { }] }])), ), + ( + r#"import a from "./index.mjs";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["[@a-z]*", "!.*/**"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), ]; let pass_typescript = vec![ @@ -2984,6 +2998,12 @@ fn test() { }] }])), ), + ( + r#"import {x} from "foo"; import {x2} from "./index.mjs"; import {x3} from "index";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["[@a-z]*", "!.*/**","./index.mjs"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), // ( // " // // error diff --git a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap index 87e9035190984..38ca0ca5e6cf0 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -787,6 +787,27 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: foo is forbidden, use bar instead + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used by a pattern. + ╭─[no_restricted_imports.tsx:1:1] + 1 │ import {x} from "foo"; import {x2} from "./index.mjs"; import {x3} from "index"; + · ────────────────────── + ╰──── + help: foo is forbidden, use bar instead + + ⚠ eslint(no-restricted-imports): './index.mjs' import is restricted from being used by a pattern. + ╭─[no_restricted_imports.tsx:1:24] + 1 │ import {x} from "foo"; import {x2} from "./index.mjs"; import {x3} from "index"; + · ─────────────────────────────── + ╰──── + help: foo is forbidden, use bar instead + + ⚠ eslint(no-restricted-imports): 'index' import is restricted from being used by a pattern. + ╭─[no_restricted_imports.tsx:1:56] + 1 │ import {x} from "foo"; import {x2} from "./index.mjs"; import {x3} from "index"; + · ───────────────────────── + ╰──── + help: foo is forbidden, use bar instead + ⚠ eslint(no-restricted-imports): 'import1' import is restricted from being used. ╭─[no_restricted_imports.tsx:1:1] 1 │ import foo from 'import1';