diff --git a/Cargo.lock b/Cargo.lock index 28ce7efe6cc87..368a63be3e130 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2005,7 +2005,6 @@ dependencies = [ "convert_case", "cow-utils", "fast-glob", - "globset", "icu_segmenter", "indexmap", "insta", diff --git a/Cargo.toml b/Cargo.toml index 3a4134fe3e117..9425cc5441a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -191,7 +191,6 @@ fast-glob = "1.0.0" fixedbitset = "0.5.7" flate2 = "1.1.2" futures = "0.3.31" -globset = "0.4.16" handlebars = "6.3.2" hashbrown = { version = "0.15.4", default-features = false } humansize = "2.1.3" diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index b7b7056d232a0..556cb9f754f37 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -52,7 +52,6 @@ constcat = { workspace = true } convert_case = { workspace = true } cow-utils = { workspace = true } fast-glob = { workspace = true } -globset = { workspace = true } icu_segmenter = { workspace = true } indexmap = { workspace = true, features = ["rayon"] } itertools = { workspace = true } diff --git a/crates/oxc_linter/src/config/config_store.rs b/crates/oxc_linter/src/config/config_store.rs index 0bebdb19cfc14..6d0045bda7700 100644 --- a/crates/oxc_linter/src/config/config_store.rs +++ b/crates/oxc_linter/src/config/config_store.rs @@ -144,8 +144,9 @@ impl Config { }) .unwrap_or(path); + let path = relative_path.to_string_lossy(); let overrides_to_apply = - self.overrides.iter().filter(|config| config.files.is_match(relative_path)); + self.overrides.iter().filter(|config| config.files.is_match(path.as_ref())); let mut overrides_to_apply = overrides_to_apply.peekable(); @@ -373,7 +374,7 @@ mod test { let base_rules = vec![no_explicit_any()]; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.test.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["*.test.{ts,tsx}"]), plugins: None, globals: None, rules: ResolvedOxlintOverrideRules { builtin_rules: vec![], external_rules: vec![] }, @@ -404,7 +405,7 @@ mod test { let base_rules = vec![no_explicit_any()]; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.test.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["*.test.{ts,tsx}"]), plugins: Some(LintPlugins::new( BuiltinLintPlugins::REACT .union(BuiltinLintPlugins::TYPESCRIPT) @@ -441,7 +442,7 @@ mod test { let base_rules = vec![no_explicit_any()]; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.test.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["*.test.{ts,tsx}"]), plugins: None, globals: None, rules: ResolvedOxlintOverrideRules { @@ -478,7 +479,7 @@ mod test { let base_rules = vec![no_explicit_any()]; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["src/**/*.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["src/**/*.{ts,tsx}"]), plugins: None, globals: None, rules: ResolvedOxlintOverrideRules { @@ -515,7 +516,7 @@ mod test { let base_rules = vec![no_explicit_any()]; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["src/**/*.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["src/**/*.{ts,tsx}"]), plugins: None, globals: None, rules: ResolvedOxlintOverrideRules { @@ -561,7 +562,7 @@ mod test { let overrides = ResolvedOxlintOverrides::new(vec![ ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.jsx", "*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.jsx", "*.tsx"]), plugins: Some(LintPlugins::new(BuiltinLintPlugins::REACT, FxHashSet::default())), globals: None, rules: ResolvedOxlintOverrideRules { @@ -571,7 +572,7 @@ mod test { }, ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.ts", "*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.ts", "*.tsx"]), plugins: Some(LintPlugins::new( BuiltinLintPlugins::TYPESCRIPT, FxHashSet::default(), @@ -622,7 +623,7 @@ mod test { }; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: Some(OxlintEnv::from_iter(["es2024".to_string()])), - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), plugins: None, globals: None, rules: ResolvedOxlintOverrideRules { builtin_rules: vec![], external_rules: vec![] }, @@ -649,7 +650,7 @@ mod test { path: None, }; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), env: Some(from_json!({ "es2024": false })), plugins: None, globals: None, @@ -678,7 +679,7 @@ mod test { }; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), env: None, plugins: None, globals: Some(from_json!({ "React": "readonly", "Secret": "writeable" })), @@ -712,7 +713,7 @@ mod test { }; let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), env: None, plugins: None, globals: Some(from_json!({ "React": "off", "Secret": "off" })), @@ -761,7 +762,7 @@ mod test { // First override: typescript plugin for *.{ts,tsx,mts} ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.{ts,tsx,mts}"]).unwrap(), + files: GlobSet::new(vec!["*.{ts,tsx,mts}"]), plugins: Some(LintPlugins::new( BuiltinLintPlugins::TYPESCRIPT, FxHashSet::default(), @@ -775,7 +776,7 @@ mod test { // Second override: react plugin for *.{ts,tsx} with jsx-filename-extension turned off ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.{ts,tsx}"]).unwrap(), + files: GlobSet::new(vec!["*.{ts,tsx}"]), plugins: Some(LintPlugins::new(BuiltinLintPlugins::REACT, FxHashSet::default())), globals: None, rules: ResolvedOxlintOverrideRules { @@ -789,7 +790,7 @@ mod test { // Third override: unicorn plugin for *.{ts,tsx,mts} ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.{ts,tsx,mts}"]).unwrap(), + files: GlobSet::new(vec!["*.{ts,tsx,mts}"]), plugins: Some(LintPlugins::new(BuiltinLintPlugins::UNICORN, FxHashSet::default())), globals: None, rules: ResolvedOxlintOverrideRules { @@ -849,7 +850,7 @@ mod test { // Override adds react plugin (new plugin not in root) let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), plugins: Some(LintPlugins::new(BuiltinLintPlugins::REACT, FxHashSet::default())), globals: None, rules: ResolvedOxlintOverrideRules { builtin_rules: vec![], external_rules: vec![] }, @@ -897,7 +898,7 @@ mod test { // Override adds typescript plugin let overrides = ResolvedOxlintOverrides::new(vec![ResolvedOxlintOverride { env: None, - files: GlobSet::new(vec!["*.tsx"]).unwrap(), + files: GlobSet::new(vec!["*.tsx"]), plugins: Some(LintPlugins::new(BuiltinLintPlugins::TYPESCRIPT, FxHashSet::default())), globals: None, rules: ResolvedOxlintOverrideRules { builtin_rules: vec![], external_rules: vec![] }, diff --git a/crates/oxc_linter/src/config/overrides.rs b/crates/oxc_linter/src/config/overrides.rs index 9ce2ed1fbbda4..4487475ec0e95 100644 --- a/crates/oxc_linter/src/config/overrides.rs +++ b/crates/oxc_linter/src/config/overrides.rs @@ -1,11 +1,10 @@ use std::{ borrow::Cow, ops::{Deref, DerefMut}, - path::Path, }; use schemars::{JsonSchema, r#gen, schema::Schema}; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use serde::{Deserialize, Deserializer, Serialize}; use crate::{LintPlugins, OxlintEnv, OxlintGlobals, config::OxlintRules}; @@ -97,74 +96,38 @@ pub struct OxlintOverride { pub rules: OxlintRules, } -/// A glob pattern. -/// -/// Thin wrapper around [`globset::GlobSet`] because that struct doesn't implement Serialize or schemars -/// traits. -#[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`. - raw: Vec, - globs: globset::GlobSet, -} - -impl GlobSet { - pub fn new, I: IntoIterator>( - patterns: I, - ) -> Result { - 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>(&self, path: P) -> bool { - self.globs.is_match(path) - } -} - -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(&self, serializer: S) -> Result { - self.raw.serialize(serializer) - } -} +/// A set of glob patterns. +#[derive(Debug, Default, Clone, Serialize, JsonSchema)] +pub struct GlobSet(Vec); impl<'de> Deserialize<'de> for GlobSet { fn deserialize>(deserializer: D) -> Result { - let globs = Vec::::deserialize(deserializer)?; - Self::new(globs).map_err(de::Error::custom) + Ok(Self::new(Vec::::deserialize(deserializer)?)) } } -impl JsonSchema for GlobSet { - fn schema_name() -> String { - Self::schema_id().into() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("GlobSet") - } - - fn json_schema(r#gen: &mut r#gen::SchemaGenerator) -> Schema { - r#gen.subschema_for::>() +impl GlobSet { + pub fn new, I: IntoIterator>(patterns: I) -> Self { + Self( + patterns + .into_iter() + .map(|pat| { + let pattern = pat.as_ref(); + if pattern.contains('/') { + pattern.to_owned() + } else { + let mut s = String::with_capacity(pattern.len() + 3); + s.push_str("**/"); + s.push_str(pattern); + s + } + }) + .collect::>(), + ) + } + + pub fn is_match(&self, path: &str) -> bool { + self.0.iter().any(|glob| fast_glob::glob_match(glob, path)) } } @@ -182,15 +145,15 @@ mod test { "files": ["*.tsx",], })) .unwrap(); - assert!(config.files.globs.is_match("/some/path/foo.tsx")); - assert!(!config.files.globs.is_match("/some/path/foo.ts")); + assert!(config.files.is_match("/some/path/foo.tsx")); + assert!(!config.files.is_match("/some/path/foo.ts")); let config: OxlintOverride = from_value(json!({ "files": ["lib/*.ts",], })) .unwrap(); - assert!(config.files.globs.is_match("lib/foo.ts")); - assert!(!config.files.globs.is_match("src/foo.ts")); + assert!(config.files.is_match("lib/foo.ts")); + assert!(!config.files.is_match("src/foo.ts")); } #[test] diff --git a/crates/oxc_linter/src/snapshots/schema_json.snap b/crates/oxc_linter/src/snapshots/schema_json.snap index 5e9b18b531caa..d59374ae961e8 100644 --- a/crates/oxc_linter/src/snapshots/schema_json.snap +++ b/crates/oxc_linter/src/snapshots/schema_json.snap @@ -193,6 +193,7 @@ expression: json } }, "GlobSet": { + "description": "A set of glob patterns.", "type": "array", "items": { "type": "string" diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index fb205bc2cf305..9a18d5798a784 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -189,6 +189,7 @@ } }, "GlobSet": { + "description": "A set of glob patterns.", "type": "array", "items": { "type": "string" diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website/src/linter/snapshots/schema_markdown.snap index 9ba66fc8064e6..6d44ee7298cea 100644 --- a/tasks/website/src/linter/snapshots/schema_markdown.snap +++ b/tasks/website/src/linter/snapshots/schema_markdown.snap @@ -214,7 +214,7 @@ type: `object` type: `string[]` - +A set of glob patterns. #### overrides[n].rules