diff --git a/Cargo.lock b/Cargo.lock index 686e91faf..5efae8e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -385,6 +385,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.1" @@ -1613,6 +1623,7 @@ dependencies = [ "farmfe_testing_helpers", "farmfe_toolkit_plugin_types", "farmfe_utils 0.1.4", + "globset", "lazy_static", "preset_env_base", "sourcemap", @@ -1943,6 +1954,19 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "glow" version = "0.12.1" @@ -2576,12 +2600,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru" diff --git a/crates/toolkit/Cargo.toml b/crates/toolkit/Cargo.toml index d26bd706c..d6d9db341 100644 --- a/crates/toolkit/Cargo.toml +++ b/crates/toolkit/Cargo.toml @@ -51,3 +51,4 @@ swc_ecma_utils = "=0.126.0" lazy_static = "1.4.0" sourcemap = "6.2.3" anyhow = { version = "1.0.40", features = ["backtrace"] } +globset = "0.4.14" diff --git a/crates/toolkit/src/pluginutils/create_filter.rs b/crates/toolkit/src/pluginutils/create_filter.rs new file mode 100644 index 000000000..19795e4e1 --- /dev/null +++ b/crates/toolkit/src/pluginutils/create_filter.rs @@ -0,0 +1,95 @@ +use globset::{GlobBuilder, GlobSet, GlobSetBuilder}; +use std::error::Error; + +pub fn create_filter<'a>( + include_patterns: Option>, + exclude_patterns: Option>, +) -> Result bool + 'a, Box> { + let include_set = patterns_builder(include_patterns.as_deref())?; + let exclude_set = patterns_builder(exclude_patterns.as_deref())?; + + Ok(move |path: &str| { + let match_include = include_set.is_match(path) || include_set.is_empty(); + let match_exclude = exclude_set.is_match(path) && !exclude_set.is_empty(); + + match_include && !match_exclude + }) +} + +fn patterns_builder(patterns: Option<&[&str]>) -> Result> { + let mut builder = GlobSetBuilder::new(); + if let Some(patterns) = patterns { + for &pattern in patterns { + builder.add(GlobBuilder::new(pattern).build()?); + } + } + builder.build().map_err(Into::into) +} +#[cfg(test)] +mod tests { + use super::*; + use crate::pluginutils::normalize_path::normalize_path; + use std::path::PathBuf; + + #[test] + fn includes_by_default() -> Result<(), Box> { + let filter = create_filter(None, None)?; + assert!(filter("index.html")); + Ok(()) + } + + #[test] + fn excludes_items_not_included_if_include_patterns_provided() -> Result<(), Box> { + let filter = create_filter(Some(vec!["*.y"]), None)?; + assert!(!filter("x")); + assert!(filter("a.y")); + Ok(()) + } + + #[test] + fn patterns_with_wildcards() -> Result<(), Box> { + let filter = create_filter(Some(vec!["*.y", "a.?"]), None)?; + assert!(filter("c.y")); + assert!(!filter("c.z")); + assert!(filter("a.x")); + assert!(!filter("b.x")); + Ok(()) + } + + #[test] + fn excludes_items_when_exclude_pattern_provided() -> Result<(), Box> { + let filter = create_filter(None, Some(vec!["*.tmp"]))?; + assert!(filter("a.out")); + assert!(!filter("b.tmp")); + Ok(()) + } + + #[test] + fn properly_handles_inclusion_and_exclusion_patterns() -> Result<(), Box> { + let filter = create_filter(Some(vec!["*.js"]), Some(vec!["*.min.js"]))?; + assert!(filter("app.js")); + assert!(!filter("app.min.js")); + assert!(!filter("app.ts")); + Ok(()) + } + + #[test] + fn handles_relative_paths_correctly() -> Result<(), Box> { + let filter = create_filter(Some(vec!["src/*.js"]), Some(vec!["src/*.test.js"]))?; + assert!(filter(&PathBuf::from("src/main.js").to_string_lossy())); + assert!(!filter( + &PathBuf::from("src/main.test.js").to_string_lossy() + )); + assert!(!filter(&PathBuf::from("lib/main.js").to_string_lossy())); + Ok(()) + } + + #[test] + fn handles_relative_paths() -> Result<(), Box> { + let filter = create_filter(Some(vec!["./index.js"]), Some(vec!["'./foo/../a.js'"]))?; + assert!(!filter(&normalize_path("index.js"))); + assert!(!filter(&normalize_path("a.js"))); + assert!(!filter(&normalize_path("foo/a.js"))); + Ok(()) + } +} diff --git a/crates/toolkit/src/pluginutils/mod.rs b/crates/toolkit/src/pluginutils/mod.rs index babb4b730..bf0e82848 100644 --- a/crates/toolkit/src/pluginutils/mod.rs +++ b/crates/toolkit/src/pluginutils/mod.rs @@ -1,2 +1,3 @@ pub mod add_extension; +pub mod create_filter; pub mod normalize_path; diff --git a/cspell.json b/cspell.json index d95c1563e..f20c554ac 100644 --- a/cspell.json +++ b/cspell.json @@ -159,7 +159,8 @@ "pageerror", "rsdoctor", "deepmerge", - "Mergeable" + "Mergeable", + "globset" ], "ignorePaths": [ "pnpm-lock.yaml",