diff --git a/src/glob.zig b/src/glob.zig index 8fa778bfb1b53c..7280a7f488daee 100644 --- a/src/glob.zig +++ b/src/glob.zig @@ -309,6 +309,9 @@ pub fn GlobWalker_( /// If the pattern contains "./" or "../" has_relative_components: bool = false, + end_byte_of_basename_excluding_special_syntax: u32 = 0, + basename_excluding_special_syntax_component_idx: u32 = 0, + patternComponents: ArrayList(Component) = .{}, matchedPaths: ArrayList(BunString) = .{}, i: u32 = 0, @@ -332,6 +335,9 @@ pub fn GlobWalker_( get_next, /// Currently iterating over a directory directory: Directory, + /// A pattern with no special glob syntax was supplied, for example: `/Users/zackradisic/foo/bar` + /// In that case, we stat the file on initialization + root_matched: ?[:0]const u8, const Directory = struct { fd: Accessor.Handle, @@ -361,8 +367,54 @@ pub fn GlobWalker_( pub fn init(this: *Iterator) !Maybe(void) { log("Iterator init pattern={s}", .{this.walker.pattern}); + var was_absolute = false; + const root_work_item = brk: { + var use_posix = bun.Environment.isPosix; + const is_absolute = if (bun.Environment.isPosix) std.fs.path.isAbsolute(this.walker.pattern) else std.fs.path.isAbsolute(this.walker.pattern) or is_absolute: { + use_posix = true; + break :is_absolute std.fs.path.isAbsolutePosix(this.walker.pattern); + }; + + if (!is_absolute) break :brk WorkItem.new(this.walker.cwd, 0, .directory); + + was_absolute = true; + + const path_without_special_syntax = this.walker.pattern[0..this.walker.end_byte_of_basename_excluding_special_syntax]; + const component_idx = this.walker.basename_excluding_special_syntax_component_idx + 1; + + // This means we got a pattern without any special glob syntax, for example: + // `/Users/zackradisic/foo/bar` + // In that case we don't need to do any walking and can just open up the FS entry + if (component_idx >= this.walker.patternComponents.items.len) { + const path = try this.walker.arena.allocator().dupeZ(u8, path_without_special_syntax); + const fd = switch (try Accessor.open(path)) { + .err => |e| { + if (e.getErrno() == bun.C.E.NOTDIR) { + // TODO check symlink + this.iter_state = .{ .root_matched = path }; + return Maybe(void).success; + } + const errpath = try this.walker.arena.allocator().dupeZ(u8, path); + return .{ .err = e.withPath(errpath) }; + }, + .result => |fd| fd, + }; + _ = Accessor.close(fd); + this.iter_state = .{ .root_matched = path }; + return Maybe(void).success; + } + + bun.assert(this.walker.end_byte_of_basename_excluding_special_syntax < this.walker.pattern.len); + + break :brk WorkItem.new( + path_without_special_syntax, + component_idx, + .directory, + ); + }; + var path_buf: *[bun.MAX_PATH_BYTES]u8 = &this.walker.pathBuf; - const root_path = this.walker.cwd; + const root_path = root_work_item.path; @memcpy(path_buf[0..root_path.len], root_path[0..root_path.len]); path_buf[root_path.len] = 0; // const root_path_z = path_buf[0..root_path.len :0]; @@ -377,8 +429,13 @@ pub fn GlobWalker_( this.cwd_fd = cwd_fd; - const root_work_item = WorkItem.new(this.walker.cwd, 0, .directory); - switch (try this.transitionToDirIterState(root_work_item, true)) { + switch (if (was_absolute) try this.transitionToDirIterState( + root_work_item, + false, + ) else try this.transitionToDirIterState( + root_work_item, + true, + )) { .err => |err| return .{ .err = err }, else => {}, } @@ -514,6 +571,11 @@ pub fn GlobWalker_( pub fn next(this: *Iterator) !Maybe(?MatchedPath) { while (true) { switch (this.iter_state) { + .root_matched => { + const maybe_matched = this.iter_state.root_matched; + this.iter_state.root_matched = null; + return .{ .result = maybe_matched }; + }, .get_next => { // Done if (this.walker.workbuf.items.len == 0) return .{ .result = null }; @@ -750,6 +812,7 @@ pub fn GlobWalker_( len: u32, syntax_hint: SyntaxHint = .None, + trailing_sep: bool = false, is_ascii: bool = false, /// Only used when component is not ascii @@ -757,6 +820,14 @@ pub fn GlobWalker_( start_cp: u32 = 0, end_cp: u32 = 0, + pub fn patternSlice(this: *const Component, pattern: []const u8) []const u8 { + return pattern[this.start .. this.start + this.len - @as(u1, @bitCast(this.trailing_sep))]; + } + + pub fn patternSliceCp(this: *const Component, pattern: []u32) []u32 { + return pattern[this.start_cp .. this.end_cp - @as(u1, @bitCast(this.trailing_sep))]; + } + const SyntaxHint = enum { None, Single, @@ -771,6 +842,13 @@ pub fn GlobWalker_( Dot, /// ../ DotBack, + + fn isSpecialSyntax(this: SyntaxHint) bool { + return switch (this) { + .Literal => false, + else => true, + }; + } }; }; @@ -807,15 +885,12 @@ pub fn GlobWalker_( const ptr = @intFromPtr(this); log("GlobWalker(0x{x}) components:", .{ptr}); for (components.items) |cmp| { - if (cmp.syntax_hint == .None) { - continue; - } switch (cmp.syntax_hint) { .Single => log(" *", .{}), .Double => log(" **", .{}), .Dot => log(" .", .{}), .DotBack => log(" ../", .{}), - .Literal, .WildcardFilepath, .None => log(" hint={s} component_str={s}", .{ @tagName(cmp.syntax_hint), pattern[cmp.start .. cmp.start + cmp.len] }), + .Literal, .WildcardFilepath, .None => log(" hint={s} component_str={s}", .{ @tagName(cmp.syntax_hint), cmp.patternSlice(pattern) }), } } } @@ -841,6 +916,8 @@ pub fn GlobWalker_( .follow_symlinks = follow_symlinks, .error_on_broken_symlinks = error_on_broken_symlinks, .only_files = only_files, + .basename_excluding_special_syntax_component_idx = 0, + .end_byte_of_basename_excluding_special_syntax = @intCast(pattern.len), }; try GlobWalker.buildPatternComponents( @@ -850,6 +927,8 @@ pub fn GlobWalker_( &this.cp_len, &this.pattern_codepoints, &this.has_relative_components, + &this.end_byte_of_basename_excluding_special_syntax, + &this.basename_excluding_special_syntax_component_idx, ); // copy arena after all allocations are successful @@ -1073,6 +1152,8 @@ pub fn GlobWalker_( pattern: *Component, next_pattern: ?*Component, ) bool { + if (pattern.trailing_sep) return false; + // Handle case b) if (!is_last) return pattern.syntax_hint == .Double and component_idx + 1 == this.patternComponents.items.len -| 1 and @@ -1095,11 +1176,11 @@ pub fn GlobWalker_( return switch (pattern_component.syntax_hint) { .Double, .Single => true, .WildcardFilepath => if (comptime !isWindows) - matchWildcardFilepath(this.pattern[pattern_component.start .. pattern_component.start + pattern_component.len], filepath) + matchWildcardFilepath(pattern_component.patternSlice(this.pattern), filepath) else this.matchPatternSlow(pattern_component, filepath), .Literal => if (comptime !isWindows) - matchWildcardLiteral(this.pattern[pattern_component.start .. pattern_component.start + pattern_component.len], filepath) + matchWildcardLiteral(pattern_component.patternSlice(this.pattern), filepath) else this.matchPatternSlow(pattern_component, filepath), else => this.matchPatternSlow(pattern_component, filepath), @@ -1111,7 +1192,7 @@ pub fn GlobWalker_( if (comptime !isWindows) { if (pattern_component.is_ascii and isAllAscii(filepath)) return GlobAscii.match( - this.pattern[pattern_component.start .. pattern_component.start + pattern_component.len], + pattern_component.patternSlice(this.pattern), filepath, ); } @@ -1131,16 +1212,16 @@ pub fn GlobWalker_( } fn componentStringUnicodeWindows(this: *GlobWalker, pattern_component: *Component) []const u32 { - return this.pattern_codepoints[pattern_component.start_cp..pattern_component.end_cp]; + return pattern_component.patternSliceCp(this.pattern_codepoints); } fn componentStringUnicodePosix(this: *GlobWalker, pattern_component: *Component) []const u32 { - if (pattern_component.unicode_set) return this.pattern_codepoints[pattern_component.start_cp..pattern_component.end_cp]; + if (pattern_component.unicode_set) return pattern_component.patternSliceCp(this.pattern_codepoints); - const codepoints = this.pattern_codepoints[pattern_component.start_cp..pattern_component.end_cp]; + const codepoints = pattern_component.patternSliceCp(this.pattern_codepoints); GlobWalker.convertUtf8ToCodepoints( codepoints, - this.pattern[pattern_component.start .. pattern_component.start + pattern_component.len], + pattern_component.patternSlice(this.pattern), ); pattern_component.unicode_set = true; return codepoints; @@ -1161,19 +1242,6 @@ pub fn GlobWalker_( return name; } - fn appendMatchedPath( - this: *GlobWalker, - entry_name: []const u8, - dir_name: [:0]const u8, - ) !void { - const subdir_parts: []const []const u8 = &[_][]const u8{ - dir_name[0..dir_name.len], - entry_name, - }; - const name = try this.join(subdir_parts); - try this.matchedPaths.append(this.arena.allocator(), BunString.fromBytes(name)); - } - fn appendMatchedPathSymlink(this: *GlobWalker, symlink_full_path: []const u8) !void { const name = try this.arena.allocator().dupe(u8, symlink_full_path); try this.matchedPaths.append(this.arena.allocator(), BunString.fromBytes(name)); @@ -1235,23 +1303,21 @@ pub fn GlobWalker_( return false; } - fn addComponent( - allocator: Allocator, + fn makeComponent( pattern: []const u8, - patternComponents: *ArrayList(Component), start_cp: u32, end_cp: u32, start_byte: u32, end_byte: u32, has_relative_patterns: *bool, - ) !void { + ) ?Component { var component: Component = .{ .start = start_byte, .len = end_byte - start_byte, .start_cp = start_cp, .end_cp = end_cp, }; - if (component.len == 0) return; + if (component.len == 0) return null; out: { if (component.len == 1 and pattern[component.start] == '.') { @@ -1325,7 +1391,13 @@ pub fn GlobWalker_( component.is_ascii = true; } - try patternComponents.append(allocator, component); + if (pattern[component.start + component.len -| 1] == '/') { + component.trailing_sep = true; + } else if (comptime bun.Environment.isWindows) { + component.trailing_sep = pattern[component.start + component.len -| 1] == '\\'; + } + + return component; } fn buildPatternComponents( @@ -1335,6 +1407,8 @@ pub fn GlobWalker_( out_cp_len: *u32, out_pattern_cp: *[]u32, has_relative_patterns: *bool, + end_byte_of_basename_excluding_special_syntax: *u32, + basename_excluding_special_syntax_component_idx: *u32, ) !void { var start_cp: u32 = 0; var start_byte: u32 = 0; @@ -1344,23 +1418,35 @@ pub fn GlobWalker_( var cp_len: u32 = 0; var prevIsBackslash = false; + var saw_special = false; while (iter.next(&cursor)) : (cp_len += 1) { const c = cursor.c; switch (c) { '\\' => { if (comptime isWindows) { - const end_cp = cp_len; - try addComponent( - arena.allocator(), + var end_cp = cp_len; + var end_byte = cursor.i; + // is last char + if (cursor.i + cursor.width == pattern.len) { + end_cp += 1; + end_byte += cursor.width; + } + if (makeComponent( pattern, - patternComponents, start_cp, end_cp, start_byte, - cursor.i, + end_byte, has_relative_patterns, - ); + )) |component| { + saw_special = saw_special or component.syntax_hint.isSpecialSyntax(); + if (!saw_special) { + basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len); + end_byte_of_basename_excluding_special_syntax.* = cursor.i + cursor.width; + } + try patternComponents.append(arena.allocator(), component); + } start_cp = cp_len + 1; start_byte = cursor.i + cursor.width; continue; @@ -1381,16 +1467,21 @@ pub fn GlobWalker_( end_cp += 1; end_byte += cursor.width; } - try addComponent( - arena.allocator(), + if (makeComponent( pattern, - patternComponents, start_cp, end_cp, start_byte, end_byte, has_relative_patterns, - ); + )) |component| { + saw_special = saw_special or component.syntax_hint.isSpecialSyntax(); + if (!saw_special) { + basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len); + end_byte_of_basename_excluding_special_syntax.* = cursor.i + cursor.width; + } + try patternComponents.append(arena.allocator(), component); + } start_cp = cp_len + 1; start_byte = cursor.i + cursor.width; }, @@ -1409,16 +1500,24 @@ pub fn GlobWalker_( out_pattern_cp.* = codepoints; const end_cp = cp_len; - try addComponent( - arena.allocator(), + if (makeComponent( pattern, - patternComponents, start_cp, end_cp, start_byte, @intCast(pattern.len), has_relative_patterns, - ); + )) |component| { + saw_special = saw_special or component.syntax_hint.isSpecialSyntax(); + if (!saw_special) { + basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len); + end_byte_of_basename_excluding_special_syntax.* = cursor.i + cursor.width; + } + try patternComponents.append(arena.allocator(), component); + } else if (!saw_special) { + basename_excluding_special_syntax_component_idx.* = @intCast(patternComponents.items.len); + end_byte_of_basename_excluding_special_syntax.* = cursor.i + cursor.width; + } } }; } diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index 71dad1ba7ded4c..d56693e6b9ca98 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -2401,7 +2401,7 @@ pub const Interpreter = struct { var iter = GlobWalker.Iterator{ .walker = this.walker }; defer iter.deinit(); - switch (try iter.init()) { + switch (iter.init() catch |e| OOM(e)) { .err => |err| return .{ .err = err }, else => {}, } diff --git a/test/js/bun/glob/__snapshots__/scan.test.ts.snap b/test/js/bun/glob/__snapshots__/scan.test.ts.snap index 61d0048c425c0a..e756e12afb8a23 100644 --- a/test/js/bun/glob/__snapshots__/scan.test.ts.snap +++ b/test/js/bun/glob/__snapshots__/scan.test.ts.snap @@ -578,3 +578,528 @@ exports[`fast-glob e2e tests only files (cwd) **/*: **/* 1`] = ` "third/library/b/book.md", ] `; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*: absolute: fixtures/* 1`] = ` +[ + "/fixtures/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**: absolute: fixtures/** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/*: absolute: fixtures/**/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/nested: absolute: fixtures/*/nested 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/nested/*: absolute: fixtures/*/nested/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/nested/**: absolute: fixtures/*/nested/** 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/nested/**/*: absolute: fixtures/*/nested/**/* 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/nested/*: absolute: fixtures/**/nested/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/nested/**: absolute: fixtures/**/nested/** 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/nested/**/*: absolute: fixtures/**/nested/**/* 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}: absolute: fixtures/{first,second} 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/*: absolute: fixtures/{first,second}/* 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/second/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/**: absolute: fixtures/{first,second}/** 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/**/*: absolute: fixtures/{first,second}/**/* 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/{first,second}/*: absolute: fixtures/*/{first,second}/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/{first,second}/*/{nested,file.md}: absolute: fixtures/*/{first,second}/*/{nested,file.md} 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/{first,second}/**: absolute: fixtures/**/{first,second}/** 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/{first,second}/{nested,file.md}: absolute: fixtures/**/{first,second}/{nested,file.md} 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/second/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/{first,second}/**/{nested,file.md}: absolute: fixtures/**/{first,second}/**/{nested,file.md} 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/{nested,file.md}: absolute: fixtures/{first,second}/{nested,file.md} 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/second/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/*/nested/*: absolute: fixtures/{first,second}/*/nested/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/{first,second}/**/nested/**: absolute: fixtures/{first,second}/**/nested/** 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/*/{nested,file.md}/*: absolute: fixtures/*/{nested,file.md}/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular fixtures/**/{nested,file.md}/*: absolute: fixtures/**/{nested,file.md}/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular ./fixtures/*: absolute: ./fixtures/* 1`] = ` +[ + "/fixtures/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd *: absolute: * 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **: absolute: ** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/*: absolute: **/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */nested: absolute: */nested 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */nested/*: absolute: */nested/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */nested/**: absolute: */nested/** 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */nested/**/*: absolute: */nested/**/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/nested/*: absolute: **/nested/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/nested/**: absolute: **/nested/** 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/nested/**/*: absolute: **/nested/**/* 1`] = ` +[ + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}: absolute: {first,second} 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/*: absolute: {first,second}/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/**: absolute: {first,second}/** 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/**/*: absolute: {first,second}/**/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */{first,second}/*: absolute: */{first,second}/* 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/second/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */{first,second}/*/{nested,file.md}: absolute: */{first,second}/*/{nested,file.md} 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/{first,second}/**: absolute: **/{first,second}/** 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/{first,second}/{nested,file.md}: absolute: **/{first,second}/{nested,file.md} 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/second/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/{first,second}/**/{nested,file.md}: absolute: **/{first,second}/**/{nested,file.md} 1`] = ` +[ + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/{nested,file.md}: absolute: {first,second}/{nested,file.md} 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/*/nested/*: absolute: {first,second}/*/nested/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd {first,second}/**/nested/**: absolute: {first,second}/**/nested/** 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd */{nested,file.md}/*: absolute: */{nested,file.md}/* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular cwd **/{nested,file.md}/*: absolute: **/{nested,file.md}/* 1`] = ` +[ + "/fixtures/first/nested/file.md", + "/fixtures/second/nested/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ./*: absolute: ./* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ./*: absolute: ./* 2`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ./**: absolute: ./** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ./**/*: absolute: ./**/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ../*: absolute: ../* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ../**: absolute: ../** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ../../*: absolute: ../../* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ../{first,second}: absolute: ../{first,second} 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns regular relative cwd ./../*: absolute: ./../* 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) patterns absolute cwd *: absolute: * 1`] = `[]`; + +exports[`fast-glob e2e tests patterns absolute cwd *: absolute: * 1`] = ` +[ + "", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns absolute cwd **: absolute: ** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests patterns absolute cwd **: absolute: ** 1`] = ` +[ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`fast-glob e2e tests (absolute) patterns absolute cwd **/*: absolute: **/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests patterns absolute cwd **/*: absolute: **/* 1`] = ` +[ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +] +`; + +exports[`fast-glob e2e tests (absolute) only files fixtures/*: absolute: fixtures/* 1`] = ` +[ + "/fixtures/file.md", +] +`; + +exports[`fast-glob e2e tests (absolute) only files fixtures/**: absolute: fixtures/** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) only files fixtures/**/*: absolute: fixtures/**/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) only files (cwd) *: absolute: * 1`] = `[]`; + +exports[`fast-glob e2e tests (absolute) only files (cwd) **: absolute: ** 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; + +exports[`fast-glob e2e tests (absolute) only files (cwd) **/*: absolute: **/* 1`] = ` +[ + "/fixtures/file.md", + "/fixtures/first/file.md", + "/fixtures/first/nested/directory/file.json", + "/fixtures/first/nested/directory/file.md", + "/fixtures/first/nested/file.md", + "/fixtures/second/file.md", + "/fixtures/second/nested/directory/file.md", + "/fixtures/second/nested/file.md", + "/fixtures/third/library/a/book.md", + "/fixtures/third/library/b/book.md", +] +`; diff --git a/test/js/bun/glob/scan.test.ts b/test/js/bun/glob/scan.test.ts index 33784723f737e1..c7bce2126f303a 100644 --- a/test/js/bun/glob/scan.test.ts +++ b/test/js/bun/glob/scan.test.ts @@ -26,6 +26,8 @@ import fg from "fast-glob"; import * as path from "path"; import { tempFixturesDir, createTempDirectoryWithBrokenSymlinks, prepareEntries } from "./util"; import { tempDirWithFiles } from "harness"; +import * as os from "node:os"; +import * as fs from "node:fs"; let origAggressiveGC = Bun.unsafe.gcAggressionLevel(); let tempBrokenSymlinksDir: string; @@ -299,10 +301,6 @@ const onlyFilesPatterns = { ], }; -beforeAll(() => { - tempFixturesDir(); -}); - /** * These are the e2e tests from fast-glob, with some omitted because we don't support features like ignored patterns * The snapshots are generated by running fast-glob on them first @@ -312,67 +310,160 @@ beforeAll(() => { * In practice this discrepancy makes no difference, so the snapshots were changed accordingly to match Bun.Glob / Unix bash shell style. */ describe("fast-glob e2e tests", async () => { + let absolute_pattern_dir: string = ""; + // beforeAll(() => { + tempFixturesDir(); + const tmp = os.tmpdir(); + absolute_pattern_dir = fs.mkdtempSync(path.join(tmp, "absolute_patterns")); + // add some more directories so patterns like ../**/* don't break + absolute_pattern_dir = path.join(absolute_pattern_dir, "ooga/booga"); + fs.mkdirSync(absolute_pattern_dir, { recursive: true })!; + tempFixturesDir(absolute_pattern_dir); + // }); + + let buildsnapshot = false; const absoluteCwd = process.cwd(); const cwd = import.meta.dir; console.log("CWD IS", cwd); + const stripAbsoluteDir = (path: string): string => path.slice(absolute_pattern_dir.length); + // const stripAbsoluteDir = (path: string): string => path; + + regular.regular.forEach(pattern => { + // console.log("ABSOLUTE PATTERN DIR", absolute_pattern_dir); + const absolutePattern = path.join(absolute_pattern_dir, pattern); + test(`(absolute) patterns regular ${pattern}`, () => { + let entries = buildsnapshot + ? prepareEntries(fg.globSync(absolutePattern, { cwd })) + : prepareEntries(Array.from(new Glob(absolutePattern).scanSync({ cwd, followSymlinks: true }))); + + // console.log("PATTERN", absolutePattern, entries); + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - regular.regular.forEach(pattern => test(`patterns regular ${pattern}`, () => { - // let entries = fg.globSync(pattern, { cwd }); - const entries = prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd, followSymlinks: true }))); + let entries = buildsnapshot + ? prepareEntries(fg.globSync(pattern, { cwd })) + : prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd, followSymlinks: true }))); + expect(entries).toMatchSnapshot(pattern); - }), - ); + }); + }); + + regular.cwd.forEach(({ pattern, cwd: secondHalf }) => { + const absolutePattern = path.join(absolute_pattern_dir, pattern); + test(`(absolute) patterns regular cwd ${pattern}`, () => { + const testCwd = path.join(cwd, secondHalf); + let entries = buildsnapshot + ? prepareEntries(fg.globSync(absolutePattern, { cwd: testCwd })) + : prepareEntries(Array.from(new Glob(absolutePattern).scanSync({ cwd: testCwd, followSymlinks: true }))); + + // let entries = ; + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - regular.cwd.forEach(({ pattern, cwd: secondHalf }) => test(`patterns regular cwd ${pattern}`, () => { const testCwd = path.join(cwd, secondHalf); - // let entries = fg.globSync(pattern, { cwd: testCwd }); - let entries = prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true }))); + let entries = buildsnapshot + ? prepareEntries(fg.globSync(pattern, { cwd: testCwd })) + : prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true }))); expect(entries).toMatchSnapshot(pattern); - }), - ); + }); + }); + + regular.relativeCwd.forEach(({ pattern, cwd: secondHalf }) => { + const absolutePattern = path.join(absolute_pattern_dir, pattern); + test(`(absolute) patterns regular relative cwd ${pattern}`, () => { + const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; + let entries = buildsnapshot + ? prepareEntries(fg.globSync(absolutePattern, { cwd: testCwd })) + : prepareEntries(Array.from(new Glob(absolutePattern).scanSync({ cwd: testCwd, followSymlinks: true }))); + + // let entries = + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - regular.relativeCwd.forEach(({ pattern, cwd: secondHalf }) => test(`patterns regular relative cwd ${pattern}`, () => { const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; - // let entries = fg.globSync(pattern, { cwd: testCwd }); - let entries = prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true }))); + let entries = buildsnapshot + ? prepareEntries(fg.globSync(pattern, { cwd: testCwd })) + : prepareEntries(Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true }))); + + // let entries = expect(entries).toMatchSnapshot(pattern); - }), - ); + }); + }); + + absolutePatterns.cwd.forEach(({ pattern, cwd: secondHalf }) => { + const absolutePattern = path.join(absolute_pattern_dir, pattern); + test(`(absolute) patterns absolute cwd ${pattern}`, () => { + const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; + let entries = buildsnapshot + ? fg.globSync(absolutePattern, { cwd: testCwd, absolute: true }) + : Array.from(new Glob(absolutePattern).scanSync({ cwd: testCwd, followSymlinks: true, absolute: true })); + // entries = entries.sort().map(entry => entry.slice(absoluteCwd.length + 1)); + entries = prepareEntries(entries); + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - absolutePatterns.cwd.forEach(({ pattern, cwd: secondHalf }) => test(`patterns absolute cwd ${pattern}`, () => { const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; - // let entries = fg.globSync(pattern, { cwd: testCwd, absolute: true }); - let entries = Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true, absolute: true })); + let entries = buildsnapshot + ? fg.globSync(pattern, { cwd: testCwd, absolute: true }) + : Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true, absolute: true })); + entries = entries.sort().map(entry => entry.slice(absoluteCwd.length + 1)); entries = prepareEntries(entries); - expect(entries).toMatchSnapshot(pattern); - }), - ); + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); + }); + + onlyFilesPatterns.regular.forEach(pattern => { + const absolutePattern = path.join(absolute_pattern_dir, pattern); + + test(`(absolute) only files ${pattern}`, () => { + let entries = buildsnapshot + ? prepareEntries(fg.globSync(absolutePattern, { cwd, absolute: false, onlyFiles: true })) + : prepareEntries( + Array.from(new Glob(absolutePattern).scanSync({ cwd, followSymlinks: true, onlyFiles: true })), + ); + + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - onlyFilesPatterns.regular.forEach(pattern => test(`only files ${pattern}`, () => { - // let entries = fg.globSync(pattern, { cwd, absolute: false, onlyFiles: true }); - let entries = prepareEntries( - Array.from(new Glob(pattern).scanSync({ cwd, followSymlinks: true, onlyFiles: true })), - ); + let entries = prepareEntries(fg.globSync(pattern, { cwd, absolute: false, onlyFiles: true })); + + // let entries = prepareEntries( + // Array.from(new Glob(pattern).scanSync({ cwd, followSymlinks: true, onlyFiles: true })), + // ); expect(entries).toMatchSnapshot(pattern); - }), - ); + }); + }); + + onlyFilesPatterns.cwd.forEach(({ pattern, cwd: secondHalf }) => { + const absolutePattern = path.join(absolute_pattern_dir, pattern); + test(`(absolute) only files (cwd) ${pattern}`, () => { + const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; + let entries = buildsnapshot + ? prepareEntries(fg.globSync(absolutePattern, { cwd: testCwd, absolute: false, onlyFiles: true })) + : prepareEntries( + Array.from(new Glob(absolutePattern).scanSync({ cwd: testCwd, followSymlinks: true, onlyFiles: true })), + ); + + expect(entries.map(stripAbsoluteDir)).toMatchSnapshot(`absolute: ${pattern}`); + }); - onlyFilesPatterns.cwd.forEach(({ pattern, cwd: secondHalf }) => test(`only files (cwd) ${pattern}`, () => { const testCwd = secondHalf ? path.join(cwd, secondHalf) : cwd; - // let entries = fg.globSync(pattern, { cwd: testCwd, absolute: false, onlyFiles: true }); - let entries = prepareEntries( - Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true, onlyFiles: true })), - ); + let entries = buildsnapshot + ? prepareEntries(fg.globSync(pattern, { cwd: testCwd, absolute: false, onlyFiles: true })) + : prepareEntries( + Array.from(new Glob(pattern).scanSync({ cwd: testCwd, followSymlinks: true, onlyFiles: true })), + ); + expect(entries).toMatchSnapshot(pattern); - }), - ); + }); + }); }); test("broken symlinks", async () => { @@ -445,6 +536,58 @@ test("glob.scan('.')", async () => { expect(entries).toContain("README.md"); }); +function makeTmpdir(): string { + const tmp = os.tmpdir(); + return fs.mkdtempSync(path.join(tmp, "test_builder")); +} + +describe("trailing directory separator", async () => { + test("matches directories absolute", async () => { + const tmpdir = makeTmpdir(); + const files = [`${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, `${tmpdir}${path.sep}bunx-baz`]; + await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`; + const glob = new Glob(`${path.join(tmpdir, "bunx-*")}${path.sep}`); + const entries = await Array.fromAsync(glob.scan({ onlyFiles: false })); + expect(entries.sort()).toEqual(files.slice(2, 3).sort()); + }); + + test("matches directories relative", async () => { + const tmpdir = makeTmpdir(); + const files = [`bunx-foo`, `bunx-bar`, `bunx-baz`]; + await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`.cwd(tmpdir); + const glob = new Glob(`bunx-*/`); + const entries = await Array.fromAsync(glob.scan({ onlyFiles: false, cwd: tmpdir })); + expect(entries.sort()).toEqual(files.slice(2, 3).sort()); + }); +}); + +describe("absolute path pattern", async () => { + test("works *", async () => { + const tmpdir = makeTmpdir(); + const files = [`${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, `${tmpdir}${path.sep}bunx-baz`]; + await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`; + const glob = new Glob(`${path.join(tmpdir, "bunx-*")}`); + const entries = await Array.fromAsync(glob.scan({ onlyFiles: false })); + expect(entries.sort()).toEqual(files.sort()); + }); + + test("works **", async () => { + const tmpdir = makeTmpdir(); + const files = [ + `${tmpdir}${path.sep}bunx-foo`, + `${tmpdir}${path.sep}bunx-bar`, + `${tmpdir}${path.sep}bunx-baz`, + `${tmpdir}${path.sep}foo`, + `${tmpdir}${path.sep}bar`, + `${tmpdir}${path.sep}bar`, + ]; + await Bun.$`mkdir -p ${files.slice(0, 3)}; touch ${files.slice(3)}`; + const glob = new Glob(`${path.join(tmpdir, "**")}${path.sep}`); + const entries = await Array.fromAsync(glob.scan({ onlyFiles: false })); + expect(entries.sort()).toEqual(files.slice(0, 3).sort()); + }); +}); + describe("glob.scan wildcard fast path", async () => { test("works", async () => { const tempdir = tempDirWithFiles("glob-scan-wildcard-fast-path", { diff --git a/test/js/bun/glob/util.ts b/test/js/bun/glob/util.ts index c29c6ba8af6fda..47b65c0e1bf7db 100644 --- a/test/js/bun/glob/util.ts +++ b/test/js/bun/glob/util.ts @@ -19,7 +19,7 @@ export function createTempDirectoryWithBrokenSymlinks() { return tempDir; } -export function tempFixturesDir() { +export function tempFixturesDir(baseDir: string = import.meta.dir) { const files: Record> = { ".directory": { "file.md": "", @@ -61,7 +61,7 @@ export function tempFixturesDir() { return dir; } - const dir = path.join(import.meta.dir, "fixtures"); + const dir = path.join(baseDir, "fixtures"); fs.mkdirSync(dir, { recursive: true }); impl(dir, files);