From 2704ead1509b7f2d8791fc950628e3dff866019f Mon Sep 17 00:00:00 2001 From: Jonathan Cammisuli Date: Tue, 7 Nov 2023 07:29:08 -0500 Subject: [PATCH] feat(core): extglob to standard glob parser --- Cargo.lock | 1 + packages/nx/Cargo.toml | 1 + .../nx/src/native/cache/expand_outputs.rs | 2 +- packages/nx/src/native/{utils => }/glob.rs | 210 +++++++---------- packages/nx/src/native/glob/glob_group.rs | 46 ++++ packages/nx/src/native/glob/glob_parser.rs | 215 ++++++++++++++++++ packages/nx/src/native/glob/glob_transform.rs | 187 +++++++++++++++ packages/nx/src/native/mod.rs | 1 + .../native/plugins/js/ts_import_locators.rs | 2 +- .../native/utils/find_matching_projects.rs | 4 +- packages/nx/src/native/utils/mod.rs | 1 - packages/nx/src/native/walker.rs | 2 +- .../nx/src/native/workspace/config_files.rs | 7 +- 13 files changed, 546 insertions(+), 133 deletions(-) rename packages/nx/src/native/{utils => }/glob.rs (61%) create mode 100644 packages/nx/src/native/glob/glob_group.rs create mode 100644 packages/nx/src/native/glob/glob_parser.rs create mode 100644 packages/nx/src/native/glob/glob_transform.rs diff --git a/Cargo.lock b/Cargo.lock index c65ed25ea88b3e..c50e3f6f9ae87f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1352,6 +1352,7 @@ dependencies = [ "napi", "napi-build", "napi-derive", + "nom", "once_cell", "parking_lot", "rayon", diff --git a/packages/nx/Cargo.toml b/packages/nx/Cargo.toml index 9b8d54cd9ca456..a7417048fe37bd 100644 --- a/packages/nx/Cargo.toml +++ b/packages/nx/Cargo.toml @@ -21,6 +21,7 @@ napi = { version = '2.12.6', default-features = false, features = [ 'tokio_rt', ] } napi-derive = '2.9.3' +nom = '7.1.3' regex = "1.9.1" rayon = "1.7.0" thiserror = "1.0.40" diff --git a/packages/nx/src/native/cache/expand_outputs.rs b/packages/nx/src/native/cache/expand_outputs.rs index dda9e75b81473a..6f86898693ed81 100644 --- a/packages/nx/src/native/cache/expand_outputs.rs +++ b/packages/nx/src/native/cache/expand_outputs.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::native::utils::glob::build_glob_set; +use crate::native::glob::build_glob_set; use crate::native::utils::path::Normalize; use crate::native::walker::nx_walker_sync; diff --git a/packages/nx/src/native/utils/glob.rs b/packages/nx/src/native/glob.rs similarity index 61% rename from packages/nx/src/native/utils/glob.rs rename to packages/nx/src/native/glob.rs index cb6edcf48bae5f..982d984da4af33 100644 --- a/packages/nx/src/native/utils/glob.rs +++ b/packages/nx/src/native/glob.rs @@ -1,6 +1,9 @@ +mod glob_group; +mod glob_parser; +mod glob_transform; + +use crate::native::glob::glob_transform::convert_glob; use globset::{GlobBuilder, GlobSet, GlobSetBuilder}; -use once_cell::sync::Lazy; -use regex::Regex; use std::fmt::Debug; use std::path::Path; use tracing::trace; @@ -69,136 +72,28 @@ impl NxGlobSet { pub(crate) fn build_glob_set + Debug>(globs: &[S]) -> anyhow::Result { let result = globs .iter() - .map(|s| convert_glob(s.as_ref())) + .map(|s| { + let glob = s.as_ref(); + // handle glob patterns that come in with `!` in the front without any groups + if glob.starts_with('!') && !(glob.contains('|') || glob.contains('(')) { + Ok(vec![glob.to_string()]) + } else if glob.contains('!') || glob.contains('|') || glob.contains('(') { + convert_glob(glob) + } else { + Ok(vec![glob.to_string()]) + } + }) .collect::>>()? - .into_iter() - .flatten() - .collect::>(); + .concat(); - trace!(?globs, ?result, "converted globs to result"); + trace!(?globs, ?result, "converted globs"); NxGlobSetBuilder::new(&result)?.build() } -// path/!{cache}/** -static NEGATIVE_DIR_REGEX: Lazy = Lazy::new(|| Regex::new(r"!\{(.*?)}").unwrap()); -// path/**/(subdir1|subdir2)/*.(js|ts) -static GROUP_PATTERNS_REGEX: Lazy = Lazy::new(|| Regex::new(r"\((.*?)\)").unwrap()); -// path/{cache}* -static SINGLE_PATTERN_REGEX: Lazy = Lazy::new(|| Regex::new(r"\{(.*)}\*").unwrap()); - -/// Converts a glob string to a list of globs -/// e.g. `path/!(cache)/**` -> `path/**`, `!path/cache/**` -fn convert_glob(glob: &str) -> anyhow::Result> { - // If there are no negations or multiple patterns, return the glob as is - if !glob.contains('!') && !glob.contains('|') && !glob.contains('(') { - return Ok(vec![glob.to_string()]); - } - - let glob = GROUP_PATTERNS_REGEX.replace_all(glob, |caps: ®ex::Captures| { - format!("{{{}}}", &caps[1].replace('|', ",")) - }); - - let mut globs: Vec = Vec::new(); - - // push a glob directory glob that is either "path/*" or "path/**" - globs.push( - NEGATIVE_DIR_REGEX - .replace_all(&glob, |caps: ®ex::Captures| { - let capture = caps.get(0); - match capture { - Some(capture) => { - let char = glob.as_bytes()[capture.end()] as char; - if char == '*' { - "".to_string() - } else { - "*".to_string() - } - } - None => "".to_string(), - } - }) - .into(), - ); - - let matches: Vec<_> = NEGATIVE_DIR_REGEX.find_iter(&glob).collect(); - - // convert negative captures to globs (e.g. "path/!{cache,dir}/**" -> "!path/{cache,dir}/**") - if matches.len() == 1 { - globs.push(format!( - "!{}", - SINGLE_PATTERN_REGEX - .replace(&glob, |caps: ®ex::Captures| { format!("{}*", &caps[1]) }) - .replace('!', "") - )); - } else { - // if there is more than one negative capture, convert each capture to a *, and negate the whole glob - for matched in matches { - let a = glob.replace(matched.as_str(), "*"); - globs.push(format!("!{}", a.replace('!', ""))); - } - } - - Ok(globs) -} - #[cfg(test)] mod test { use super::*; - use std::assert_eq; - - #[test] - fn convert_globs_full_convert() { - let full_convert = - convert_glob("dist/!(cache|cache2)/**/!(README|LICENSE).(js|ts)").unwrap(); - assert_eq!( - full_convert, - [ - "dist/*/**/*.{js,ts}", - "!dist/*/**/{README,LICENSE}.{js,ts}", - "!dist/{cache,cache2}/**/*.{js,ts}", - ] - ); - } - - #[test] - fn convert_globs_no_dirs() { - let no_dirs = convert_glob("dist/**/!(README|LICENSE).(js|ts)").unwrap(); - assert_eq!( - no_dirs, - ["dist/**/*.{js,ts}", "!dist/**/{README,LICENSE}.{js,ts}"] - ); - } - - #[test] - fn convert_globs_no_files() { - let no_files = convert_glob("dist/!(cache|cache2)/**/*.(js|ts)").unwrap(); - assert_eq!( - no_files, - ["dist/*/**/*.{js,ts}", "!dist/{cache,cache2}/**/*.{js,ts}"] - ); - } - - #[test] - fn convert_globs_no_extensions() { - let no_extensions = convert_glob("dist/!(cache|cache2)/**/*.js").unwrap(); - assert_eq!( - no_extensions, - ["dist/*/**/*.js", "!dist/{cache,cache2}/**/*.js"] - ); - } - - #[test] - fn convert_globs_no_patterns() { - let no_patterns = convert_glob("dist/**/*.js").unwrap(); - assert_eq!(no_patterns, ["dist/**/*.js",]); - } - - #[test] - fn convert_globs_single_negative() { - let negative_single_dir = convert_glob("packages/!(package-a)*").unwrap(); - assert_eq!(negative_single_dir, ["packages/*", "!packages/package-a*"]); - } #[test] fn should_work_with_simple_globs() { @@ -275,6 +170,7 @@ mod test { // matches assert!(glob_set.is_match("dist/nested/file.txt")); assert!(glob_set.is_match("dist/nested/file.md")); + assert!(glob_set.is_match("dist/nested/doublenested/triplenested/file.txt")); // no matches assert!(!glob_set.is_match("dist/file.txt")); assert!(!glob_set.is_match("dist/cache/nested/README.txt")); @@ -322,4 +218,72 @@ mod test { assert!(!glob_set.is_match("packages/package-a-b/nested")); assert!(!glob_set.is_match("packages/package-b/nested")); } + + #[test] + fn should_handle_complex_extglob_patterns() { + let glob_set = build_glob_set(&["**/?(*.)+(spec|test).[jt]s?(x)?(.snap)"]).unwrap(); + // matches + assert!(glob_set.is_match("packages/package-a/spec.jsx.snap")); + assert!(glob_set.is_match("packages/package-a/spec.js.snap")); + assert!(glob_set.is_match("packages/package-a/spec.jsx")); + assert!(glob_set.is_match("packages/package-a/spec.js")); + assert!(glob_set.is_match("packages/package-a/spec.tsx.snap")); + assert!(glob_set.is_match("packages/package-a/spec.ts.snap")); + assert!(glob_set.is_match("packages/package-a/spec.tsx")); + assert!(glob_set.is_match("packages/package-a/spec.ts")); + assert!(glob_set.is_match("packages/package-a/file.spec.jsx.snap")); + assert!(glob_set.is_match("packages/package-a/file.spec.js.snap")); + assert!(glob_set.is_match("packages/package-a/file.spec.jsx")); + assert!(glob_set.is_match("packages/package-a/file.spec.js")); + assert!(glob_set.is_match("packages/package-a/file.spec.tsx.snap")); + assert!(glob_set.is_match("packages/package-a/file.spec.ts.snap")); + assert!(glob_set.is_match("packages/package-a/file.spec.tsx")); + assert!(glob_set.is_match("packages/package-a/file.spec.ts")); + assert!(glob_set.is_match("spec.jsx.snap")); + assert!(glob_set.is_match("spec.js.snap")); + assert!(glob_set.is_match("spec.jsx")); + assert!(glob_set.is_match("spec.js")); + assert!(glob_set.is_match("spec.tsx.snap")); + assert!(glob_set.is_match("spec.ts.snap")); + assert!(glob_set.is_match("spec.tsx")); + assert!(glob_set.is_match("spec.ts")); + assert!(glob_set.is_match("file.spec.jsx.snap")); + assert!(glob_set.is_match("file.spec.js.snap")); + assert!(glob_set.is_match("file.spec.jsx")); + assert!(glob_set.is_match("file.spec.js")); + assert!(glob_set.is_match("file.spec.tsx.snap")); + assert!(glob_set.is_match("file.spec.ts.snap")); + assert!(glob_set.is_match("file.spec.tsx")); + assert!(glob_set.is_match("file.spec.ts")); + + // no matches + assert!(!glob_set.is_match("packages/package-a/spec.jsx.snapx")); + assert!(!glob_set.is_match("packages/package-a/spec.js.snapx")); + assert!(!glob_set.is_match("packages/package-a/file.ts")); + + let glob_set = build_glob_set(&["**/!(*.module).ts"]).unwrap(); + //matches + assert!(glob_set.is_match("test.ts")); + assert!(glob_set.is_match("nested/comp.test.ts")); + //no matches + assert!(!glob_set.is_match("test.module.ts")); + + let glob_set = build_glob_set(&["**/*.*(component,module).ts?(x)"]).unwrap(); + //matches + assert!(glob_set.is_match("test.component.ts")); + assert!(glob_set.is_match("test.module.ts")); + assert!(glob_set.is_match("test.component.tsx")); + assert!(glob_set.is_match("test.module.tsx")); + assert!(glob_set.is_match("nested/comp.test.component.ts")); + assert!(glob_set.is_match("nested/comp.test.module.ts")); + assert!(glob_set.is_match("nested/comp.test.component.tsx")); + assert!(glob_set.is_match("nested/comp.test.module.tsx")); + //no matches + assert!(!glob_set.is_match("test.ts")); + assert!(!glob_set.is_match("test.component.spec.ts")); + assert!(!glob_set.is_match("test.module.spec.ts")); + assert!(!glob_set.is_match("test.component.spec.tsx")); + assert!(!glob_set.is_match("test.module.spec.tsx")); + assert!(!glob_set.is_match("nested/comp.test.component.spec.ts")); + } } diff --git a/packages/nx/src/native/glob/glob_group.rs b/packages/nx/src/native/glob/glob_group.rs new file mode 100644 index 00000000000000..bfee8b76d5a0d4 --- /dev/null +++ b/packages/nx/src/native/glob/glob_group.rs @@ -0,0 +1,46 @@ +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, PartialEq)] +pub enum GlobGroup<'a> { + // *(a|b|c) + ZeroOrMore(Cow<'a, str>), + // ?(a|b|c) + ZeroOrOne(Cow<'a, str>), + // +(a|b|c) + OneOrMore(Cow<'a, str>), + // @(a|b|c) + ExactOne(Cow<'a, str>), + // !(a|b|c) + Negated(Cow<'a, str>), + NegatedFileName(Cow<'a, str>), + NonSpecialGroup(Cow<'a, str>), + NonSpecial(Cow<'a, str>), +} + +impl<'a> Display for GlobGroup<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + GlobGroup::ZeroOrMore(s) + | GlobGroup::ZeroOrOne(s) + | GlobGroup::OneOrMore(s) + | GlobGroup::ExactOne(s) + | GlobGroup::NonSpecialGroup(s) + | GlobGroup::Negated(s) => { + if s.contains(',') { + write!(f, "{{{}}}", s) + } else { + write!(f, "{}", s) + } + } + GlobGroup::NegatedFileName(s) => { + if s.contains(',') { + write!(f, "{{{}}}.", s) + } else { + write!(f, "{}.", s) + } + } + GlobGroup::NonSpecial(s) => write!(f, "{}", s), + } + } +} diff --git a/packages/nx/src/native/glob/glob_parser.rs b/packages/nx/src/native/glob/glob_parser.rs new file mode 100644 index 00000000000000..2cbeacccfa8df6 --- /dev/null +++ b/packages/nx/src/native/glob/glob_parser.rs @@ -0,0 +1,215 @@ +use crate::native::glob::glob_group::GlobGroup; +use nom::branch::alt; +use nom::bytes::complete::{is_not, tag, take_till, take_until, take_while}; +use nom::combinator::{cut, eof, map, map_parser}; +use nom::error::{context, convert_error, VerboseError}; +use nom::multi::{many_till, separated_list0}; +use nom::sequence::{preceded, terminated}; +use nom::{Finish, IResult}; +use std::borrow::Cow; + +fn simple_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "simple_group", + map(preceded(tag("("), group), GlobGroup::NonSpecialGroup), + )(input) +} + +fn zero_or_more_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "zero_or_more_group", + map(preceded(tag("*("), group), GlobGroup::ZeroOrMore), + )(input) +} + +fn zero_or_one_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "zero_or_one_group", + map(preceded(tag("?("), group), GlobGroup::ZeroOrOne), + )(input) +} + +fn one_or_more_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "one_or_more_group", + map(preceded(tag("+("), group), GlobGroup::OneOrMore), + )(input) +} + +fn exact_one_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "exact_one_group", + map(preceded(tag("@("), group), GlobGroup::ExactOne), + )(input) +} + +fn negated_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "negated_group", + map(preceded(tag("!("), group), GlobGroup::Negated), + )(input) +} + +fn negated_file_group(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context("negated_file_group", |input| { + let (input, result) = preceded(tag("!("), group)(input)?; + let (input, _) = tag(".")(input)?; + Ok((input, GlobGroup::NegatedFileName(result))) + })(input) +} + +fn non_special_character(input: &str) -> IResult<&str, GlobGroup, VerboseError<&str>> { + context( + "non_special_character", + map( + alt(( + take_while(|c| c != '?' && c != '+' && c != '@' && c != '!' && c != '('), + is_not("*("), + )), + |i: &str| GlobGroup::NonSpecial(i.into()), + ), + )(input) +} + +fn group(input: &str) -> IResult<&str, Cow, VerboseError<&str>> { + context( + "group", + map_parser(terminated(take_until(")"), tag(")")), separated_group_items), + )(input) +} + +fn separated_group_items(input: &str) -> IResult<&str, Cow, VerboseError<&str>> { + map( + separated_list0( + alt((tag("|"), tag(","))), + take_while(|c| c != '|' && c != ','), + ), + |items: Vec<&str>| { + if items.len() == 1 { + Cow::from(items[0]) + } else { + Cow::from(items.join(",")) + } + }, + )(input) +} + +fn parse_segment(input: &str) -> IResult<&str, Vec, VerboseError<&str>> { + context( + "parse_segment", + many_till( + context( + "glob_group", + alt(( + simple_group, + zero_or_more_group, + zero_or_one_group, + one_or_more_group, + exact_one_group, + negated_file_group, + negated_group, + non_special_character, + )), + ), + eof, + ), + )(input) + .map(|(i, (groups, _))| (i, groups)) +} + +fn separated_segments(input: &str) -> IResult<&str, Vec>, VerboseError<&str>> { + separated_list0( + tag("/"), + cut(map_parser(take_till(|c| c == '/'), parse_segment)), + )(input) +} + +pub fn parse_glob(input: &str) -> anyhow::Result>> { + let result = separated_segments(input).finish(); + if let Ok((_, result)) = result { + Ok(result) + } else { + Err(anyhow::anyhow!( + "{}", + convert_error(input, result.err().unwrap()) + )) + } +} + +#[cfg(test)] +mod test { + use crate::native::glob::glob_group::GlobGroup; + use crate::native::glob::glob_parser::parse_glob; + + #[test] + fn should_parse_globs() { + let result = parse_glob("a/b/c").unwrap(); + assert_eq!( + result, + vec![ + vec![GlobGroup::NonSpecial("a".into())], + vec![GlobGroup::NonSpecial("b".into())], + vec![GlobGroup::NonSpecial("c".into())] + ] + ); + + let result = parse_glob("a/*.ts").unwrap(); + assert_eq!( + result, + vec![ + vec![GlobGroup::NonSpecial("a".into())], + vec![GlobGroup::NonSpecial("*.ts".into())] + ] + ); + + let result = parse_glob("a/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)").unwrap(); + assert_eq!( + result, + vec![ + vec![GlobGroup::NonSpecial("a".into())], + vec![GlobGroup::NonSpecial("**".into()),], + vec![ + GlobGroup::ZeroOrOne("*.".into()), + GlobGroup::OneOrMore("spec,test".into()), + GlobGroup::NonSpecial(".[jt]s".into()), + GlobGroup::ZeroOrOne("x".into()), + GlobGroup::ZeroOrOne(".snap".into()) + ] + ] + ); + + let result = parse_glob("!(e2e|test)/*.ts").unwrap(); + assert_eq!( + result, + [ + [GlobGroup::Negated("e2e,test".into())], + [GlobGroup::NonSpecial("*.ts".into())] + ] + ); + + let result = parse_glob("**/*.(js|ts)").unwrap(); + assert_eq!( + result, + vec![ + vec![GlobGroup::NonSpecial("**".into())], + vec![ + GlobGroup::NonSpecial("*.".into()), + GlobGroup::NonSpecialGroup("js,ts".into()) + ] + ] + ); + + let result = parse_glob("**/!(README).[jt]s!(x)").unwrap(); + assert_eq!( + result, + vec![ + vec![GlobGroup::NonSpecial("**".into())], + vec![ + GlobGroup::NegatedFileName("README".into()), + GlobGroup::NonSpecial("[jt]s".into()), + GlobGroup::Negated("x".into()) + ] + ] + ); + } +} diff --git a/packages/nx/src/native/glob/glob_transform.rs b/packages/nx/src/native/glob/glob_transform.rs new file mode 100644 index 00000000000000..1f04728e82d907 --- /dev/null +++ b/packages/nx/src/native/glob/glob_transform.rs @@ -0,0 +1,187 @@ +use crate::native::glob::glob_group::GlobGroup; +use crate::native::glob::glob_parser::parse_glob; +use itertools::Itertools; +use std::collections::HashSet; + +#[derive(Debug)] +enum GlobType { + Negative(String), + Positive(String), +} + +pub fn convert_glob(glob: &str) -> anyhow::Result> { + let parsed = parse_glob(glob)?; + let mut built_segments: Vec> = Vec::new(); + for (index, glob_segment) in parsed.iter().enumerate() { + let is_last = index == parsed.len() - 1; + built_segments.push(build_segment("", glob_segment, is_last, false)); + } + + let mut globs = built_segments + .iter() + .multi_cartesian_product() + .map(|product| { + let mut negative = false; + let mut full_path = false; + let mut path = String::from(""); + for (index, glob) in product.iter().enumerate() { + full_path = index == product.len() - 1; + match glob { + GlobType::Negative(s) if index != product.len() - 1 => { + path.push_str(&format!("{}/", s)); + negative = true; + break; + } + GlobType::Negative(s) => { + path.push_str(&format!("{}/", s)); + negative = true; + } + GlobType::Positive(s) => { + path.push_str(&format!("{}/", s)); + } + } + } + + let modified_path = if full_path { + &path[..path.len() - 1] + } else { + &path + }; + + if negative { + format!("!{}", modified_path) + } else { + modified_path.to_owned() + } + }) + .collect::>() + .into_iter() + .collect::>(); + globs.sort(); + Ok(globs) +} + +fn build_segment( + existing: &str, + group: &[GlobGroup], + is_last_segment: bool, + is_negative: bool, +) -> Vec { + if let Some(glob_part) = group.iter().next() { + let built_glob = format!("{}{}", existing, glob_part); + match glob_part { + GlobGroup::ZeroOrMore(_) | GlobGroup::ZeroOrOne(_) => { + let existing = if !is_last_segment { "*" } else { existing }; + let off_group = build_segment(existing, &group[1..], is_last_segment, is_negative); + let on_group = + build_segment(&built_glob, &group[1..], is_last_segment, is_negative); + off_group.into_iter().chain(on_group).collect::>() + } + GlobGroup::Negated(_) => { + let existing = if !is_last_segment { "*" } else { existing }; + let off_group = build_segment(existing, &group[1..], is_last_segment, is_negative); + let on_group = build_segment(&built_glob, &group[1..], is_last_segment, true); + off_group.into_iter().chain(on_group).collect::>() + } + GlobGroup::NegatedFileName(_) => { + let off_group = build_segment("*.", &group[1..], is_last_segment, is_negative); + let on_group = build_segment(&built_glob, &group[1..], is_last_segment, true); + off_group.into_iter().chain(on_group).collect::>() + } + GlobGroup::OneOrMore(_) + | GlobGroup::ExactOne(_) + | GlobGroup::NonSpecial(_) + | GlobGroup::NonSpecialGroup(_) => { + build_segment(&built_glob, &group[1..], is_last_segment, is_negative) + } + } + } else if is_negative { + vec![GlobType::Negative(existing.to_string())] + } else { + vec![GlobType::Positive(existing.to_string())] + } +} + +#[cfg(test)] +mod test { + use super::convert_glob; + + #[test] + fn convert_globs_full_convert() { + let full_convert = + convert_glob("dist/!(cache|cache2)/**/!(README|LICENSE).(js|ts)").unwrap(); + assert_eq!( + full_convert, + [ + "!dist/*/**/{README,LICENSE}.{js,ts}", + "!dist/{cache,cache2}/", + "dist/*/**/*.{js,ts}", + ] + ); + } + + #[test] + fn convert_globs_no_dirs() { + let no_dirs = convert_glob("dist/**/!(README|LICENSE).(js|ts)").unwrap(); + assert_eq!( + no_dirs, + ["!dist/**/{README,LICENSE}.{js,ts}", "dist/**/*.{js,ts}",] + ); + } + + #[test] + fn convert_globs_no_files() { + let no_files = convert_glob("dist/!(cache|cache2)/**/*.(js|ts)").unwrap(); + assert_eq!(no_files, ["!dist/{cache,cache2}/", "dist/*/**/*.{js,ts}",]); + } + + #[test] + fn convert_globs_no_extensions() { + let no_extensions = convert_glob("dist/!(cache|cache2)/**/*.js").unwrap(); + assert_eq!(no_extensions, ["!dist/{cache,cache2}/", "dist/*/**/*.js",]); + } + + #[test] + fn convert_globs_no_patterns() { + let no_patterns = convert_glob("dist/**/*.js").unwrap(); + assert_eq!(no_patterns, ["dist/**/*.js",]); + } + + #[test] + fn convert_globs_single_negative() { + let negative_single_dir = convert_glob("packages/!(package-a)*").unwrap(); + assert_eq!(negative_single_dir, ["!packages/package-a*", "packages/*"]); + } + + #[test] + fn test_transforming_globs() { + let globs = convert_glob("!(test|e2e)/?(*.)+(spec|test).[jt]s!(x)?(.snap)").unwrap(); + assert_eq!( + globs, + vec![ + "!*/*.{spec,test}.[jt]sx", + "!*/*.{spec,test}.[jt]sx.snap", + "!*/{spec,test}.[jt]sx", + "!*/{spec,test}.[jt]sx.snap", + "!{test,e2e}/", + "*/*.{spec,test}.[jt]s", + "*/*.{spec,test}.[jt]s.snap", + "*/{spec,test}.[jt]s", + "*/{spec,test}.[jt]s.snap" + ] + ); + + let globs = convert_glob("**/!(package-a)*").unwrap(); + assert_eq!(globs, vec!["!**/package-a*", "**/*"]); + + let globs = convert_glob("dist/!(cache|cache2)/**/!(README|LICENSE).(js|ts)").unwrap(); + assert_eq!( + globs, + [ + "!dist/*/**/{README,LICENSE}.{js,ts}", + "!dist/{cache,cache2}/", + "dist/*/**/*.{js,ts}" + ] + ); + } +} diff --git a/packages/nx/src/native/mod.rs b/packages/nx/src/native/mod.rs index 78f6ae572f2d20..685119bfb3b08a 100644 --- a/packages/nx/src/native/mod.rs +++ b/packages/nx/src/native/mod.rs @@ -1,4 +1,5 @@ pub mod cache; +pub mod glob; pub mod hasher; mod logger; pub mod plugins; diff --git a/packages/nx/src/native/plugins/js/ts_import_locators.rs b/packages/nx/src/native/plugins/js/ts_import_locators.rs index d05daa52019c87..ae62aeaf28d1ca 100644 --- a/packages/nx/src/native/plugins/js/ts_import_locators.rs +++ b/packages/nx/src/native/plugins/js/ts_import_locators.rs @@ -665,7 +665,7 @@ fn find_imports( #[cfg(test)] mod find_imports { use super::*; - use crate::native::utils::glob::build_glob_set; + use crate::native::glob::build_glob_set; use crate::native::utils::path::Normalize; use crate::native::walker::nx_walker; use assert_fs::prelude::*; diff --git a/packages/nx/src/native/utils/find_matching_projects.rs b/packages/nx/src/native/utils/find_matching_projects.rs index c0a21850c9884e..4dde0e6fea8a48 100644 --- a/packages/nx/src/native/utils/find_matching_projects.rs +++ b/packages/nx/src/native/utils/find_matching_projects.rs @@ -1,5 +1,5 @@ +use crate::native::glob::{build_glob_set, NxGlobSet}; use crate::native::project_graph::types::{Project, ProjectGraph}; -use crate::native::utils::glob::{build_glob_set, NxGlobSet}; use hashbrown::HashSet; use std::collections::HashMap; @@ -212,7 +212,7 @@ fn add_matching_projects_by_tag<'a>( .get(*project_name) .and_then(|p| p.tags.as_ref()) .map(|tags| tags.iter().map(|tag| tag.as_str()).collect::>()); - let Some(tags) = project_tags else { + let Some(tags) = project_tags else { continue; }; diff --git a/packages/nx/src/native/utils/mod.rs b/packages/nx/src/native/utils/mod.rs index 9791a4fcbf33d4..99e058f6e13005 100644 --- a/packages/nx/src/native/utils/mod.rs +++ b/packages/nx/src/native/utils/mod.rs @@ -1,5 +1,4 @@ mod find_matching_projects; -pub mod glob; pub mod path; pub use find_matching_projects::*; diff --git a/packages/nx/src/native/walker.rs b/packages/nx/src/native/walker.rs index 0f79f292c9f6b3..4a3b3cff8475b4 100644 --- a/packages/nx/src/native/walker.rs +++ b/packages/nx/src/native/walker.rs @@ -5,7 +5,7 @@ use std::thread::available_parallelism; use crossbeam_channel::{unbounded, Receiver}; use ignore::WalkBuilder; -use crate::native::utils::glob::build_glob_set; +use crate::native::glob::build_glob_set; use walkdir::WalkDir; diff --git a/packages/nx/src/native/workspace/config_files.rs b/packages/nx/src/native/workspace/config_files.rs index 230498d67abe84..dbc406da708537 100644 --- a/packages/nx/src/native/workspace/config_files.rs +++ b/packages/nx/src/native/workspace/config_files.rs @@ -1,4 +1,4 @@ -use crate::native::utils::glob::build_glob_set; +use crate::native::glob::build_glob_set; use crate::native::utils::path::Normalize; use crate::native::workspace::types::ConfigurationParserResult; @@ -12,7 +12,7 @@ pub(super) fn glob_files( files: Option<&[(PathBuf, String)]>, ) -> napi::Result, WorkspaceErrors> { let Some(files) = files else { - return Ok(Default::default()) + return Ok(Default::default()); }; let globs = @@ -33,8 +33,7 @@ pub(super) fn get_project_configurations( where ConfigurationParser: Fn(Vec) -> napi::Result, { - let config_paths = - glob_files(globs, files).map_err(anyhow::Error::from)?; + let config_paths = glob_files(globs, files).map_err(anyhow::Error::from)?; parse_configurations(config_paths) }