@@ -17,49 +17,58 @@ use vite_path::{AbsolutePath, RelativePathBuf};
1717use vite_str:: Str ;
1818use wax:: { Glob , Program as _} ;
1919
20- /// A negative glob pattern resolved to an absolute base directory and optional variant .
20+ /// A glob pattern resolved to an absolute base directory.
2121///
22- /// For example, `../shared/dist/**` relative to `/ws/packages/app` resolves to:
23- /// - `resolved_base`: `/ws/packages/shared/dist`
22+ /// Uses [`wax::Glob::partition`] to separate the invariant prefix from the
23+ /// wildcard suffix, then resolves the prefix to an absolute path via
24+ /// [`path_clean`] (normalizing components like `..`).
25+ ///
26+ /// For example, `../shared/src/**` relative to `/ws/packages/app` resolves to:
27+ /// - `resolved_base`: `/ws/packages/shared/src`
2428/// - `variant`: `Some(Glob("**"))`
2529#[ expect( clippy:: disallowed_types, reason = "path_clean returns std::path::PathBuf" ) ]
26- pub struct ResolvedNegativeGlob {
30+ pub struct ResolvedGlob {
2731 resolved_base : std:: path:: PathBuf ,
2832 variant : Option < Glob < ' static > > ,
2933}
3034
31- /// Resolve negative globs relative to `base_dir`, handling `..` components.
32- ///
33- /// Uses [`Glob::partition`] to split each pattern into an invariant base and an
34- /// optional variant, then resolves the base with [`path_clean`] to normalize `..`.
35- pub fn resolve_negative_globs (
36- patterns : & std:: collections:: BTreeSet < Str > ,
37- base_dir : & AbsolutePath ,
38- ) -> anyhow:: Result < Vec < ResolvedNegativeGlob > > {
39- patterns
40- . iter ( )
41- . map ( |pattern| {
42- let glob = Glob :: new ( pattern. as_str ( ) ) ?. into_owned ( ) ;
43- let ( base_pathbuf, variant) = glob. partition ( ) ;
44- let base_str = base_pathbuf. to_str ( ) . unwrap_or ( "." ) ;
45- let resolved_base = if base_str. is_empty ( ) {
46- base_dir. as_path ( ) . to_path_buf ( )
47- } else {
48- base_dir. join ( base_str) . as_path ( ) . clean ( )
49- } ;
50- Ok ( ResolvedNegativeGlob { resolved_base, variant : variant. map ( Glob :: into_owned) } )
51- } )
52- . collect ( )
53- }
35+ impl ResolvedGlob {
36+ /// Resolve a glob pattern relative to `base_dir`.
37+ pub fn new ( pattern : & str , base_dir : & AbsolutePath ) -> anyhow:: Result < Self > {
38+ let glob = Glob :: new ( pattern) ?. into_owned ( ) ;
39+ let ( base_pathbuf, variant) = glob. partition ( ) ;
40+ let base_str = base_pathbuf. to_str ( ) . unwrap_or ( "." ) ;
41+ let resolved_base = if base_str. is_empty ( ) {
42+ base_dir. as_path ( ) . to_path_buf ( )
43+ } else {
44+ base_dir. join ( base_str) . as_path ( ) . clean ( )
45+ } ;
46+ Ok ( Self { resolved_base, variant : variant. map ( Glob :: into_owned) } )
47+ }
48+
49+ /// Walk the filesystem and yield matching file paths.
50+ #[ expect( clippy:: disallowed_types, reason = "yields std::path::PathBuf from wax walker" ) ]
51+ pub fn walk ( & self ) -> Box < dyn Iterator < Item = std:: path:: PathBuf > + ' _ > {
52+ match & self . variant {
53+ Some ( variant_glob) => Box :: new (
54+ variant_glob
55+ . walk ( & self . resolved_base )
56+ . filter_map ( Result :: ok)
57+ . map ( wax:: walk:: Entry :: into_path) ,
58+ ) ,
59+ None => Box :: new ( std:: iter:: once ( self . resolved_base . clone ( ) ) ) ,
60+ }
61+ }
5462
55- /// Check if an absolute path is excluded by any of the resolved negative globs.
56- #[ expect( clippy:: disallowed_types, reason = "matching against std::path::Path from wax walker" ) ]
57- pub fn is_excluded ( path : & std:: path:: Path , negatives : & [ ResolvedNegativeGlob ] ) -> bool {
58- negatives. iter ( ) . any ( |neg| {
59- path. strip_prefix ( & neg. resolved_base ) . ok ( ) . is_some_and ( |remainder| {
60- neg. variant . as_ref ( ) . map_or ( remainder. as_os_str ( ) . is_empty ( ) , |v| v. is_match ( remainder) )
63+ /// Check if an absolute path matches this resolved glob.
64+ #[ expect( clippy:: disallowed_types, reason = "matching against std::path::Path" ) ]
65+ pub fn matches ( & self , path : & std:: path:: Path ) -> bool {
66+ path. strip_prefix ( & self . resolved_base ) . ok ( ) . is_some_and ( |remainder| {
67+ self . variant
68+ . as_ref ( )
69+ . map_or ( remainder. as_os_str ( ) . is_empty ( ) , |v| v. is_match ( remainder) )
6170 } )
62- } )
71+ }
6372}
6473
6574/// Compute globbed inputs by walking positive glob patterns and filtering with negative patterns.
@@ -88,7 +97,6 @@ pub fn is_excluded(path: &std::path::Path, negatives: &[ResolvedNegativeGlob]) -
8897/// )?;
8998/// // Returns: { "packages/foo/src/index.ts" => 0x1234..., ... }
9099/// ```
91- #[ expect( clippy:: disallowed_types, reason = "path_clean and wax walker return std::path types" ) ]
92100pub fn compute_globbed_inputs (
93101 base_dir : & AbsolutePath ,
94102 workspace_root : & AbsolutePath ,
@@ -100,43 +108,24 @@ pub fn compute_globbed_inputs(
100108 return Ok ( BTreeMap :: new ( ) ) ;
101109 }
102110
103- // Resolve negative globs, normalizing `..` components
104- let resolved_negatives = resolve_negative_globs ( negative_globs, base_dir) ?;
111+ let negatives: Vec < ResolvedGlob > = negative_globs
112+ . iter ( )
113+ . map ( |p| ResolvedGlob :: new ( p. as_str ( ) , base_dir) )
114+ . collect :: < anyhow:: Result < _ > > ( ) ?;
105115
106116 let mut result = BTreeMap :: new ( ) ;
107117
108- // Walk each positive glob pattern, resolving `..` via partition + path_clean
109118 for pattern in positive_globs {
110- let glob = Glob :: new ( pattern. as_str ( ) ) ?. into_owned ( ) ;
111- let ( base_pathbuf, variant) = glob. partition ( ) ;
112- let base_str = base_pathbuf. to_str ( ) . unwrap_or ( "." ) ;
113- let resolved_base = if base_str. is_empty ( ) {
114- base_dir. as_path ( ) . to_path_buf ( )
115- } else {
116- base_dir. join ( base_str) . as_path ( ) . clean ( )
117- } ;
118-
119- let entries: Box < dyn Iterator < Item = std:: path:: PathBuf > > = match variant {
120- Some ( variant_glob) => Box :: new (
121- variant_glob
122- . walk ( & resolved_base)
123- . filter_map ( Result :: ok)
124- . map ( wax:: walk:: Entry :: into_path) ,
125- ) ,
126- None => {
127- // No wildcard: exact file path
128- Box :: new ( std:: iter:: once ( resolved_base) )
129- }
130- } ;
119+ let resolved = ResolvedGlob :: new ( pattern. as_str ( ) , base_dir) ?;
131120
132- for absolute_path in entries {
121+ for absolute_path in resolved . walk ( ) {
133122 // Skip non-files
134123 if !absolute_path. is_file ( ) {
135124 continue ;
136125 }
137126
138127 // Apply negative patterns
139- if is_excluded ( & absolute_path , & resolved_negatives ) {
128+ if negatives . iter ( ) . any ( |neg| neg . matches ( & absolute_path ) ) {
140129 continue ;
141130 }
142131
0 commit comments