@@ -981,15 +981,21 @@ impl Glob {
981981 if matches {
982982 // For withFileTypes, we return the relative path (no dotRelative/mark modifications)
983983 // The JavaScript wrapper handles path formatting via PathScurry
984- let normalized_string = normalized. into_owned ( ) ;
985- if seen. insert ( normalized_string. clone ( ) ) {
984+ // Convert separators for output: use backslashes on Windows without posix
985+ let output_path = if self . should_normalize_backslashes ( ) {
986+ normalized. into_owned ( )
987+ } else {
988+ normalized. replace ( '/' , "\\ " )
989+ } ;
990+ if seen. insert ( output_path. clone ( ) ) {
986991 // When includeChildMatches is false, track this path to exclude its children
992+ // (use the normalized path with forward slashes for internal tracking)
987993 if !self . include_child_matches {
988- matched_parents. insert ( normalized_string . clone ( ) ) ;
994+ matched_parents. insert ( output_path . replace ( '\\' , "/" ) ) ;
989995 }
990996
991997 results. push ( PathData {
992- path : normalized_string ,
998+ path : output_path ,
993999 is_directory : is_dir,
9941000 is_file : entry. is_file ( ) ,
9951001 is_symlink : entry. is_symlink ( ) ,
@@ -1140,60 +1146,50 @@ impl Glob {
11401146
11411147 /// Build a normalized path from walk entry, minimizing allocations.
11421148 /// Returns Cow::Borrowed when no transformation is needed, Cow::Owned otherwise.
1149+ ///
1150+ /// IMPORTANT: This function ALWAYS normalizes to forward slashes because it's used
1151+ /// for internal pattern matching. Pattern matching (`matches()`, `could_match_in_dir()`)
1152+ /// always expects forward slashes regardless of platform.
1153+ ///
1154+ /// Output formatting (backslashes on Windows without posix) is handled separately
1155+ /// in `build_result_path`.
11431156 #[ inline]
11441157 fn normalize_path < ' a > (
11451158 & self ,
11461159 rel_str_from_walk_root : & ' a str ,
11471160 prefix_to_strip : & Option < String > ,
11481161 is_walk_root : bool ,
11491162 ) -> Cow < ' a , str > {
1150- let use_forward_slashes = self . should_normalize_backslashes ( ) ;
11511163 let has_backslash = rel_str_from_walk_root. contains ( '\\' ) ;
1152- let has_forward_slash = rel_str_from_walk_root. contains ( '/' ) ;
11531164
1154- // Determine what conversions are needed
1155- let needs_to_forward = use_forward_slashes && has_backslash;
1156- let needs_to_backslash = !use_forward_slashes && has_forward_slash;
1157- let needs_conversion = needs_to_forward || needs_to_backslash;
1158-
1159- // Fast path: no prefix and no conversion needed
1160- if prefix_to_strip. is_none ( ) && !needs_conversion {
1165+ // Fast path: no prefix and no backslashes to convert
1166+ if prefix_to_strip. is_none ( ) && !has_backslash {
11611167 return Cow :: Borrowed ( rel_str_from_walk_root) ;
11621168 }
11631169
1164- // Determine the separator to use when building paths
1165- let sep = if use_forward_slashes { "/" } else { "\\ " } ;
1166-
1167- // Helper to convert path separators
1168- let convert_path = |path : & str | -> String {
1169- if needs_to_forward {
1170+ // Helper to convert backslashes to forward slashes
1171+ let convert_to_forward = |path : & str | -> String {
1172+ if path. contains ( '\\' ) {
11701173 path. replace ( '\\' , "/" )
1171- } else if needs_to_backslash {
1172- path. replace ( '/' , "\\ " )
11731174 } else {
11741175 path. to_string ( )
11751176 }
11761177 } ;
11771178
1178- // Need to construct the path
1179+ // Need to construct the path with forward slashes
11791180 match prefix_to_strip {
11801181 Some ( prefix) => {
1182+ let prefix_converted = convert_to_forward ( prefix) ;
11811183 if is_walk_root {
1182- Cow :: Owned ( convert_path ( prefix) )
1183- } else if needs_conversion {
1184- Cow :: Owned ( format ! (
1185- "{}{}{}" ,
1186- convert_path( prefix) ,
1187- sep,
1188- convert_path( rel_str_from_walk_root)
1189- ) )
1184+ Cow :: Owned ( prefix_converted)
11901185 } else {
1191- Cow :: Owned ( format ! ( "{prefix}{sep}{rel_str_from_walk_root}" ) )
1186+ let rel_converted = convert_to_forward ( rel_str_from_walk_root) ;
1187+ Cow :: Owned ( format ! ( "{prefix_converted}/{rel_converted}" ) )
11921188 }
11931189 }
11941190 None => {
1195- // Needs separator conversion
1196- Cow :: Owned ( convert_path ( rel_str_from_walk_root) )
1191+ // Just convert backslashes to forward slashes
1192+ Cow :: Owned ( convert_to_forward ( rel_str_from_walk_root) )
11971193 }
11981194 }
11991195 }
@@ -1296,6 +1292,9 @@ impl Glob {
12961292
12971293 /// Build the final result path from the normalized path.
12981294 /// Uses the provided buffer to minimize allocations.
1295+ ///
1296+ /// The `normalized` path always uses forward slashes (for internal pattern matching).
1297+ /// This function converts to backslashes for output on Windows when `posix: false`.
12991298 #[ inline]
13001299 fn build_result_path (
13011300 & self ,
@@ -1307,16 +1306,9 @@ impl Glob {
13071306 ) -> String {
13081307 // When mark:true, add trailing slash to directories but NOT to symlinks
13091308 let should_mark_as_dir = is_dir && !is_symlink && self . mark ;
1310- let sep = if self . should_normalize_backslashes ( ) {
1311- '/'
1312- } else {
1313- '\\'
1314- } ;
1315- let dot_prefix = if self . should_normalize_backslashes ( ) {
1316- "./"
1317- } else {
1318- ".\\ "
1319- } ;
1309+ let use_forward = self . should_normalize_backslashes ( ) ;
1310+ let sep = if use_forward { '/' } else { '\\' } ;
1311+ let dot_prefix = if use_forward { "./" } else { ".\\ " } ;
13201312
13211313 if self . absolute {
13221314 // Build absolute path
@@ -1333,24 +1325,31 @@ impl Glob {
13331325 }
13341326 } else {
13351327 // Build relative path
1328+ // First, convert separators if needed (normalized always uses forward slashes)
1329+ let output_normalized = if use_forward {
1330+ normalized. to_string ( )
1331+ } else {
1332+ normalized. replace ( '/' , "\\ " )
1333+ } ;
1334+
13361335 let base = if self . dot_relative
1337- && !normalized . starts_with ( "../" )
1338- && !normalized . starts_with ( "..\\ " )
1336+ && !output_normalized . starts_with ( "../" )
1337+ && !output_normalized . starts_with ( "..\\ " )
13391338 {
13401339 result_buffer. clear ( ) ;
13411340 result_buffer. push_str ( dot_prefix) ;
1342- result_buffer. push_str ( normalized ) ;
1343- result_buffer. as_str ( )
1341+ result_buffer. push_str ( & output_normalized ) ;
1342+ result_buffer. clone ( )
13441343 } else {
1345- normalized
1344+ output_normalized
13461345 } ;
13471346
13481347 if should_mark_as_dir && !base. ends_with ( '/' ) && !base. ends_with ( '\\' ) {
1349- let mut result = base. to_string ( ) ;
1348+ let mut result = base;
13501349 result. push ( sep) ;
13511350 result
13521351 } else {
1353- base. to_string ( )
1352+ base
13541353 }
13551354 }
13561355 }
@@ -2187,18 +2186,21 @@ impl Glob {
21872186 formatted
21882187 }
21892188 } else {
2190- let sep = if self . should_normalize_backslashes ( ) {
2191- '/'
2189+ let use_forward = self . should_normalize_backslashes ( ) ;
2190+ let sep = if use_forward { '/' } else { '\\' } ;
2191+ // Convert separators for output (static_path uses forward slashes internally)
2192+ let output_base = if use_forward {
2193+ base_path. to_string ( )
21922194 } else {
2193- '\\'
2195+ base_path . replace ( '/' , " \\ " )
21942196 } ;
21952197 let base = if self . dot_relative
2196- && !base_path . starts_with ( "../" )
2197- && !base_path . starts_with ( "..\\ " )
2198+ && !output_base . starts_with ( "../" )
2199+ && !output_base . starts_with ( "..\\ " )
21982200 {
2199- format ! ( ".{sep}{base_path }" )
2201+ format ! ( ".{sep}{output_base }" )
22002202 } else {
2201- base_path . to_string ( )
2203+ output_base
22022204 } ;
22032205 if self . mark
22042206 && is_dir
@@ -2623,14 +2625,21 @@ impl Glob {
26232625 } ;
26242626
26252627 if matches {
2626- let normalized_string = normalized. into_owned ( ) ;
2627- if seen. insert ( normalized_string. clone ( ) ) {
2628+ // Convert separators for output: use backslashes on Windows without posix
2629+ let output_path = if self . should_normalize_backslashes ( ) {
2630+ normalized. into_owned ( )
2631+ } else {
2632+ normalized. replace ( '/' , "\\ " )
2633+ } ;
2634+ if seen. insert ( output_path. clone ( ) ) {
2635+ // When includeChildMatches is false, track this path to exclude its children
2636+ // (use the normalized path with forward slashes for internal tracking)
26282637 if !self . include_child_matches {
2629- matched_parents. insert ( normalized_string . clone ( ) ) ;
2638+ matched_parents. insert ( output_path . replace ( '\\' , "/" ) ) ;
26302639 }
26312640
26322641 callback ( PathData {
2633- path : normalized_string ,
2642+ path : output_path ,
26342643 is_directory : is_dir,
26352644 is_file : entry. is_file ( ) ,
26362645 is_symlink : entry. is_symlink ( ) ,
0 commit comments