feat(core): extglob to standard glob parser (#20089)

This commit is contained in:
Jonathan Cammisuli 2023-11-10 12:09:40 -05:00 committed by GitHub
parent a73e9fd562
commit dc7c3dbfe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 602 additions and 130 deletions

1
Cargo.lock generated
View File

@ -1352,6 +1352,7 @@ dependencies = [
"napi", "napi",
"napi-build", "napi-build",
"napi-derive", "napi-derive",
"nom",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"rayon", "rayon",

View File

@ -21,6 +21,7 @@ napi = { version = '2.12.6', default-features = false, features = [
'tokio_rt', 'tokio_rt',
] } ] }
napi-derive = '2.9.3' napi-derive = '2.9.3'
nom = '7.1.3'
regex = "1.9.1" regex = "1.9.1"
rayon = "1.7.0" rayon = "1.7.0"
thiserror = "1.0.40" thiserror = "1.0.40"

View File

@ -1,6 +1,6 @@
use std::path::PathBuf; 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::utils::path::Normalize;
use crate::native::walker::nx_walker_sync; use crate::native::walker::nx_walker_sync;

View File

@ -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 globset::{GlobBuilder, GlobSet, GlobSetBuilder};
use once_cell::sync::Lazy;
use regex::Regex;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::Path; use std::path::Path;
use tracing::trace; use tracing::trace;
@ -69,136 +72,25 @@ impl NxGlobSet {
pub(crate) fn build_glob_set<S: AsRef<str> + Debug>(globs: &[S]) -> anyhow::Result<NxGlobSet> { pub(crate) fn build_glob_set<S: AsRef<str> + Debug>(globs: &[S]) -> anyhow::Result<NxGlobSet> {
let result = globs let result = globs
.iter() .iter()
.map(|s| convert_glob(s.as_ref())) .map(|s| {
let glob = s.as_ref();
if glob.contains('!') || glob.contains('|') || glob.contains('(') {
convert_glob(glob)
} else {
Ok(vec![glob.to_string()])
}
})
.collect::<anyhow::Result<Vec<_>>>()? .collect::<anyhow::Result<Vec<_>>>()?
.into_iter() .concat();
.flatten()
.collect::<Vec<_>>();
trace!(?globs, ?result, "converted globs to result"); trace!(?globs, ?result, "converted globs");
NxGlobSetBuilder::new(&result)?.build() NxGlobSetBuilder::new(&result)?.build()
} }
// path/!{cache}/**
static NEGATIVE_DIR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"!\{(.*?)}").unwrap());
// path/**/(subdir1|subdir2)/*.(js|ts)
static GROUP_PATTERNS_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\((.*?)\)").unwrap());
// path/{cache}*
static SINGLE_PATTERN_REGEX: Lazy<Regex> = 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<Vec<String>> {
// 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: &regex::Captures| {
format!("{{{}}}", &caps[1].replace('|', ","))
});
let mut globs: Vec<String> = Vec::new();
// push a glob directory glob that is either "path/*" or "path/**"
globs.push(
NEGATIVE_DIR_REGEX
.replace_all(&glob, |caps: &regex::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: &regex::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)] #[cfg(test)]
mod test { mod test {
use super::*; 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] #[test]
fn should_work_with_simple_globs() { fn should_work_with_simple_globs() {
@ -275,6 +167,7 @@ mod test {
// matches // matches
assert!(glob_set.is_match("dist/nested/file.txt")); 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/file.md"));
assert!(glob_set.is_match("dist/nested/doublenested/triplenested/file.txt"));
// no matches // no matches
assert!(!glob_set.is_match("dist/file.txt")); assert!(!glob_set.is_match("dist/file.txt"));
assert!(!glob_set.is_match("dist/cache/nested/README.txt")); assert!(!glob_set.is_match("dist/cache/nested/README.txt"));
@ -322,4 +215,72 @@ mod test {
assert!(!glob_set.is_match("packages/package-a-b/nested")); assert!(!glob_set.is_match("packages/package-a-b/nested"));
assert!(!glob_set.is_match("packages/package-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"));
}
} }

View File

@ -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),
}
}
}

View File

@ -0,0 +1,276 @@
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::{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<str>, VerboseError<&str>> {
context(
"group",
map_parser(terminated(take_until(")"), tag(")")), separated_group_items),
)(input)
}
fn separated_group_items(input: &str) -> IResult<&str, Cow<str>, 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<GlobGroup>, 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<Vec<GlobGroup>>, VerboseError<&str>> {
separated_list0(tag("/"), map_parser(take_till(|c| c == '/'), parse_segment))(input)
}
// match on !test/, but not !(test)/
fn negated_glob(input: &str) -> (&str, bool) {
let (tagged_input, _) = match tag::<_, _, VerboseError<&str>>("!")(input) {
Ok(result) => result,
Err(_) => return (input, false),
};
match tag::<_, _, VerboseError<&str>>("(")(tagged_input) {
Ok(_) => (input, false),
Err(_) => (tagged_input, true),
}
}
pub fn parse_glob(input: &str) -> anyhow::Result<(bool, Vec<Vec<GlobGroup>>)> {
let (input, negated) = negated_glob(input);
let result = separated_segments(input).finish();
if let Ok((_, result)) = result {
Ok((negated, 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,
(
false,
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,
(
false,
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,
(
false,
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,
(
false,
vec![
vec![GlobGroup::Negated("e2e,test".into())],
vec![GlobGroup::NonSpecial("*.ts".into())]
]
)
);
let result = parse_glob("**/*.(js|ts)").unwrap();
assert_eq!(
result,
(
false,
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,
(
false,
vec![
vec![GlobGroup::NonSpecial("**".into())],
vec![
GlobGroup::NegatedFileName("README".into()),
GlobGroup::NonSpecial("[jt]s".into()),
GlobGroup::Negated("x".into())
]
]
)
);
let result = parse_glob("!test/!(README).[jt]s!(x)").unwrap();
assert_eq!(
result,
(
true,
vec![
vec![GlobGroup::NonSpecial("test".into())],
vec![
GlobGroup::NegatedFileName("README".into()),
GlobGroup::NonSpecial("[jt]s".into()),
GlobGroup::Negated("x".into())
]
]
)
);
let result = parse_glob("!(test)/!(README).[jt]s!(x)").unwrap();
assert_eq!(
result,
(
false,
vec![
vec![GlobGroup::Negated("test".into())],
vec![
GlobGroup::NegatedFileName("README".into()),
GlobGroup::NonSpecial("[jt]s".into()),
GlobGroup::Negated("x".into())
]
]
)
);
}
}

View File

@ -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<Vec<String>> {
let (negated, parsed) = parse_glob(glob)?;
let mut built_segments: Vec<Vec<GlobType>> = 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 || negated {
format!("!{}", modified_path)
} else {
modified_path.to_owned()
}
})
.collect::<HashSet<_>>()
.into_iter()
.collect::<Vec<_>>();
globs.sort();
Ok(globs)
}
fn build_segment(
existing: &str,
group: &[GlobGroup],
is_last_segment: bool,
is_negative: bool,
) -> Vec<GlobType> {
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::<Vec<_>>()
}
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::<Vec<_>>()
}
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::<Vec<_>>()
}
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}"
]
);
}
}

View File

@ -1,4 +1,5 @@
pub mod cache; pub mod cache;
pub mod glob;
pub mod hasher; pub mod hasher;
mod logger; mod logger;
pub mod plugins; pub mod plugins;

View File

@ -665,7 +665,7 @@ fn find_imports(
#[cfg(test)] #[cfg(test)]
mod find_imports { mod find_imports {
use super::*; 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::utils::path::Normalize;
use crate::native::walker::nx_walker; use crate::native::walker::nx_walker;
use assert_fs::prelude::*; use assert_fs::prelude::*;

View File

@ -1,5 +1,5 @@
use crate::native::glob::{build_glob_set, NxGlobSet};
use crate::native::project_graph::types::{Project, ProjectGraph}; use crate::native::project_graph::types::{Project, ProjectGraph};
use crate::native::utils::glob::{build_glob_set, NxGlobSet};
use hashbrown::HashSet; use hashbrown::HashSet;
use std::collections::HashMap; use std::collections::HashMap;
@ -212,7 +212,7 @@ fn add_matching_projects_by_tag<'a>(
.get(*project_name) .get(*project_name)
.and_then(|p| p.tags.as_ref()) .and_then(|p| p.tags.as_ref())
.map(|tags| tags.iter().map(|tag| tag.as_str()).collect::<Vec<_>>()); .map(|tags| tags.iter().map(|tag| tag.as_str()).collect::<Vec<_>>());
let Some(tags) = project_tags else { let Some(tags) = project_tags else {
continue; continue;
}; };

View File

@ -1,5 +1,4 @@
mod find_matching_projects; mod find_matching_projects;
pub mod glob;
pub mod path; pub mod path;
pub use find_matching_projects::*; pub use find_matching_projects::*;

View File

@ -5,7 +5,7 @@ use std::thread::available_parallelism;
use crossbeam_channel::{unbounded, Receiver}; use crossbeam_channel::{unbounded, Receiver};
use ignore::WalkBuilder; use ignore::WalkBuilder;
use crate::native::utils::glob::build_glob_set; use crate::native::glob::build_glob_set;
use walkdir::WalkDir; use walkdir::WalkDir;

View File

@ -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::utils::path::Normalize;
use std::collections::HashMap; use std::collections::HashMap;