diff options
author | 2021-05-25 01:34:44 -0700 | |
---|---|---|
committer | 2021-05-25 01:34:44 -0700 | |
commit | ea10dacc92ed40fd7729d06a961117d25c78b9a8 (patch) | |
tree | db90d29f04a6a5ea2e13d751d7e70e206426e0e3 | |
parent | f234456bd0ba80696881eb99fd3c64370cc973c5 (diff) | |
download | bun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.tar.gz bun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.tar.zst bun-ea10dacc92ed40fd7729d06a961117d25c78b9a8.zip |
relative path
Former-commit-id: 06fbc24b11f12d6635496a6d251324c021f7af68
-rw-r--r-- | src/bundler.zig | 84 | ||||
-rw-r--r-- | src/fs.zig | 63 | ||||
-rw-r--r-- | src/global.zig | 6 | ||||
-rw-r--r-- | src/http.zig | 140 | ||||
-rw-r--r-- | src/http/mime_type.zig | 24 | ||||
-rw-r--r-- | src/resolver/package_json.zig | 3 | ||||
-rw-r--r-- | src/resolver/resolve_path.zig | 480 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 20 | ||||
-rw-r--r-- | src/test/tester.zig | 8 |
9 files changed, 719 insertions, 109 deletions
diff --git a/src/bundler.zig b/src/bundler.zig index 57909a49e..b089c0f09 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -116,8 +116,7 @@ pub const Bundler = struct { switch (bundler.options.import_path_format) { .relative => { - const relative_path_temp = try std.fs.path.relative(&relative_path_allocator.allocator, source_dir, source_path); - const relative_path = try relative_paths_list.append(relative_path_temp); + const relative_path = try relative_paths_list.append(bundler.fs.relativeTo(source_path)); return Fs.Path.init(relative_path); }, @@ -126,16 +125,26 @@ pub const Bundler = struct { relative_path_allocator_buf_loaded = true; relative_path_allocator = std.heap.FixedBufferAllocator.init(&relative_path_allocator_buf); } - const relative_path_temp = try std.fs.path.relative(&relative_path_allocator.allocator, bundler.fs.top_level_dir, source_path); - const relative_path = try relative_paths_list.append(try std.fmt.allocPrint(&relative_path_allocator.allocator, "{s}{s}", .{ bundler.options.public_url, relative_path_temp })); - return Fs.Path.init(relative_path); + const relative_path = try relative_paths_list.append(bundler.fs.relativeTo(source_path)); + const absolute_url = try relative_paths_list.append( + try std.fmt.allocPrint( + &relative_path_allocator.allocator, + "{s}{s}", + .{ + bundler.options.public_url, + relative_path, + }, + ), + ); + + return Fs.Path.initWithPretty(absolute_url, relative_path); }, } } pub fn processImportRecord(bundler: *Bundler, source_dir: string, import_record: *ImportRecord) !void { - var resolve_result = (bundler.resolver.resolve(source_dir, import_record.path.text, import_record.kind) catch null) orelse return; + var resolve_result = try bundler.resolver.resolve(source_dir, import_record.path.text, import_record.kind); // extremely naive. resolve_result.is_from_node_modules = strings.contains(resolve_result.path_pair.primary.text, "/node_modules"); @@ -169,7 +178,11 @@ pub const Bundler = struct { } // Step 1. Parse & scan - const result = bundler.parse(resolve_result.path_pair.primary, bundler.options.loaders.get(resolve_result.path_pair.primary.text) orelse .file) orelse return null; + const loader = bundler.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file; + var file_path = resolve_result.path_pair.primary; + file_path.pretty = relative_paths_list.append(bundler.fs.relativeTo(file_path.text)) catch unreachable; + + var result = bundler.parse(file_path, loader) orelse return null; switch (result.loader) { .jsx, .js, .ts, .tsx => { @@ -177,9 +190,34 @@ pub const Bundler = struct { for (ast.import_records) |*import_record| { bundler.processImportRecord( - std.fs.path.dirname(resolve_result.path_pair.primary.text) orelse resolve_result.path_pair.primary.text, + std.fs.path.dirname(file_path.text) orelse file_path.text, import_record, - ) catch continue; + ) catch |err| { + switch (err) { + error.ModuleNotFound => { + if (Resolver.Resolver.isPackagePath(import_record.path.text)) { + try bundler.log.addRangeErrorFmt( + &result.source, + import_record.range, + bundler.allocator, + "Could not resolve: \"{s}\". Maybe you need to run \"npm install\" (or yarn/pnpm)?", + .{import_record.path.text}, + ); + } else { + try bundler.log.addRangeErrorFmt( + &result.source, + import_record.range, + bundler.allocator, + "Could not resolve: \"{s}\" relative to \"{s}\"", + .{ import_record.path.text, bundler.fs.relativeTo(result.source.key_path.text) }, + ); + } + }, + else => { + continue; + }, + } + }; } }, else => {}, @@ -195,9 +233,9 @@ pub const Bundler = struct { result: ParseResult, ) !options.OutputFile { var allocator = bundler.allocator; - const relative_path = try std.fs.path.relative(bundler.allocator, bundler.fs.top_level_dir, result.source.path.text); + const relative_path = bundler.fs.relativeTo(result.source.path.text); var out_parts = [_]string{ bundler.options.output_dir, relative_path }; - const out_path = try std.fs.path.join(bundler.allocator, &out_parts); + const out_path = try bundler.fs.joinAlloc(bundler.allocator, &out_parts); const ast = result.ast; @@ -386,11 +424,18 @@ pub const Bundler = struct { } } - const initial_loader = bundler.options.loaders.get(extension); + // We make some things faster in theory by using absolute paths instead of relative paths + const absolute_path = resolve_path.normalizeAndJoinStringBuf( + bundler.fs.top_level_dir, + &tmp_buildfile_buf, + &([_][]const u8{relative_path}), + .auto, + ); - const resolved = (try bundler.resolver.resolve(bundler.fs.top_level_dir, relative_path, .entry_point)) orelse return error.NotFound; + const resolved = (try bundler.resolver.resolve(bundler.fs.top_level_dir, absolute_path, .entry_point)); - const output = switch (bundler.options.loaders.get(resolved.path_pair.primary.text) orelse .file) { + const loader = bundler.options.loaders.get(resolved.path_pair.primary.name.ext) orelse .file; + const output = switch (loader) { .js, .jsx, .ts, .tsx, .json => ServeResult.Value{ .build = (try bundler.buildWithResolveResult(resolved)) orelse return error.BuildFailed, }, @@ -402,7 +447,7 @@ pub const Bundler = struct { return ServeResult{ .value = output, - .mime_type = MimeType.byExtension(extension), + .mime_type = MimeType.byLoader(loader, resolved.path_pair.primary.name.ext), }; } @@ -470,7 +515,7 @@ pub const Bundler = struct { const result = bundler.resolver.resolve(bundler.fs.top_level_dir, entry, .entry_point) catch { continue; - } orelse continue; + }; const key = result.path_pair.primary.text; if (bundler.resolve_results.contains(key)) { continue; @@ -620,10 +665,9 @@ pub const Transformer = struct { var _source = &source; const res = _transform(chosen_alloc, allocator, __log, parser_opts, loader, define, _source) catch continue; - const relative_path = try std.fs.path.relative(chosen_alloc, cwd, absolutePath); - var out_parts = [_]string{ output_dir, relative_path }; - const out_path = try std.fs.path.join(allocator, &out_parts); - try output_files.append(options.OutputFile{ .path = out_path, .contents = res.js }); + const relative_path = resolve_path.relative(cwd, absolutePath); + const out_path = resolve_path.normalizeAndJoin2(cwd, .auto, absolutePath, relative_path); + try output_files.append(options.OutputFile{ .path = allocator.dupe(u8, out_path) catch continue, .contents = res.js }); } return try options.TransformResult.init(output_files.toOwnedSlice(), log, allocator); diff --git a/src/fs.zig b/src/fs.zig index 4828a7cb3..fb0e2c3ce 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -41,7 +41,19 @@ pub const FileSystem = struct { }; pub fn init1(allocator: *std.mem.Allocator, top_level_dir: ?string, enable_watcher: bool) !*FileSystem { - const _top_level_dir = top_level_dir orelse (if (isBrowser) "/project" else try std.process.getCwdAlloc(allocator)); + var _top_level_dir = top_level_dir orelse (if (isBrowser) "/project/" else try std.process.getCwdAlloc(allocator)); + + // Ensure there's a trailing separator in the top level directory + // This makes path resolution more reliable + if (!std.fs.path.isSep(_top_level_dir[_top_level_dir.len - 1])) { + const tld = try allocator.alloc(u8, _top_level_dir.len + 1); + std.mem.copy(u8, tld, _top_level_dir); + tld[tld.len - 1] = std.fs.path.sep; + if (!isBrowser) { + allocator.free(_top_level_dir); + } + _top_level_dir = tld; + } instance = FileSystem{ .allocator = allocator, @@ -245,11 +257,54 @@ pub const FileSystem = struct { }); } + pub fn relative(f: *@This(), from: string, to: string) string { + return @call(.{ .modifier = .always_inline }, path_handler.relative, .{ + from, + to, + }); + } + + pub fn relativeAlloc(f: *@This(), allocator: *std.mem.Allocator, from: string, to: string) string { + return @call(.{ .modifier = .always_inline }, path_handler.relativeAlloc, .{ + alloc, + from, + to, + }); + } + + pub fn relativeTo(f: *@This(), to: string) string { + return @call(.{ .modifier = .always_inline }, path_handler.relative, .{ + f.top_level_dir, + to, + }); + } + + pub fn relativeToAlloc(f: *@This(), allocator: *std.mem.Allocator, to: string) string { + return @call(.{ .modifier = .always_inline }, path_handler.relativeAlloc, .{ + allocator, + f.top_level_dir, + to, + }); + } + pub fn joinAlloc(f: *@This(), allocator: *std.mem.Allocator, parts: anytype) !string { const joined = f.join(parts); return try allocator.dupe(u8, joined); } + threadlocal var realpath_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + pub fn resolveAlloc(f: *@This(), allocator: *std.mem.Allocator, parts: anytype) !string { + const joined = f.join(parts); + + const realpath = try std.fs.realpath(joined, (&realpath_buffer)); + + return try allocator.dupe(u8, realpath); + } + + pub fn resolvePath(f: *@This(), part: string) ![]u8 { + return try std.fs.realpath(part, (&realpath_buffer).ptr); + } + pub const RealFS = struct { entries_mutex: Mutex = Mutex.init(), entries: *EntriesOption.Map, @@ -639,7 +694,7 @@ pub const FileSystem = struct { // doNotCacheEntries bool }; - pub const Implementation = comptime { + pub const Implementation = { switch (build_target) { .wasi, .native => return RealFS, .wasm => return WasmFS, @@ -742,6 +797,10 @@ pub const Path = struct { return Path{ .pretty = text, .text = text, .namespace = "file", .name = PathName.init(text) }; } + pub fn initWithPretty(text: string, pretty: string) Path { + return Path{ .pretty = pretty, .text = text, .namespace = "file", .name = PathName.init(text) }; + } + pub fn initWithNamespace(text: string, namespace: string) Path { return Path{ .pretty = text, .text = text, .namespace = namespace, .name = PathName.init(text) }; } diff --git a/src/global.zig b/src/global.zig index a188d1e4b..fb96bcb3b 100644 --- a/src/global.zig +++ b/src/global.zig @@ -21,6 +21,8 @@ pub const isWindows = std.Target.current.os.tag == .windows; pub const FeatureFlags = struct { pub const strong_etags_for_built_files = true; + + pub const use_std_path_relative = false; }; pub const enableTracing = true; @@ -55,6 +57,10 @@ pub const Output = struct { } }; + pub fn errorWriter() @typeInfo(@TypeOf(Source.StreamType.writer)).Fn.return_type.? { + return source.error_stream.writer(); + } + pub fn printErrorable(comptime fmt: string, args: anytype) !void { if (isWasm) { try source.stream.seekTo(0); diff --git a/src/http.zig b/src/http.zig index f7d1e9fb1..50e4d7dcb 100644 --- a/src/http.zig +++ b/src/http.zig @@ -26,6 +26,13 @@ const SOCKET_FLAGS = os.SOCK_CLOEXEC; threadlocal var req_headers_buf: [100]picohttp.Header = undefined; threadlocal var res_headers_buf: [100]picohttp.Header = undefined; +const ENABLE_LOGGER = false; +pub fn println(comptime fmt: string, args: anytype) void { + // if (ENABLE_LOGGER) { + Output.println(fmt, args); + // } +} + const HTTPStatusCode = u9; pub const URLPath = struct { @@ -157,6 +164,8 @@ pub const RequestContext = struct { keep_alive: bool = true, status: ?HTTPStatusCode = null, has_written_last_header: bool = false, + has_called_done: bool = false, + mime_type: MimeType = MimeType.other, res_headers_count: usize = 0, @@ -226,12 +235,21 @@ pub const RequestContext = struct { } pub fn writeSocket(ctx: *RequestContext, buf: anytype, flags: anytype) !usize { - ctx.conn.client.setWriteBufferSize(@intCast(u32, buf.len)) catch {}; - return ctx.conn.client.write(buf, SOCKET_FLAGS); + // ctx.conn.client.setWriteBufferSize(@intCast(u32, buf.len)) catch {}; + const written = ctx.conn.client.write(buf, SOCKET_FLAGS) catch |err| { + Output.printError("Write error: {s}", .{@errorName(err)}); + return err; + }; + + if (written == 0) { + return error.SocketClosed; + } + + return written; } - pub fn writeBodyBuf(ctx: *RequestContext, body: []const u8) void { - _ = ctx.writeSocket(body, SOCKET_FLAGS) catch 0; + pub fn writeBodyBuf(ctx: *RequestContext, body: []const u8) !void { + _ = try ctx.writeSocket(body, SOCKET_FLAGS); } pub fn writeStatus(ctx: *RequestContext, comptime code: HTTPStatusCode) !void { @@ -255,8 +273,8 @@ pub const RequestContext = struct { return req.writeStatus(404); } - pub fn sendInternalError(ctx: *RequestContext, err: anytype) void { - ctx.writeStatus(500) catch {}; + pub fn sendInternalError(ctx: *RequestContext, err: anytype) !void { + try ctx.writeStatus(500); const printed = std.fmt.bufPrint(&error_buf, "Error: {s}", .{@errorName(err)}) catch |err2| brk: { if (isDebug or isTest) { Global.panic("error while printing error: {s}", .{@errorName(err2)}); @@ -265,21 +283,21 @@ pub const RequestContext = struct { break :brk "Internal error"; }; - ctx.prepareToSendBody(printed.len, false) catch {}; - ctx.writeBodyBuf(printed); + try ctx.prepareToSendBody(printed.len, false); + try ctx.writeBodyBuf(printed); } threadlocal var error_buf: [4096]u8 = undefined; - pub fn sendNotModified(ctx: *RequestContext) void { - ctx.writeStatus(304) catch {}; - ctx.flushHeaders() catch {}; + pub fn sendNotModified(ctx: *RequestContext) !void { + try ctx.writeStatus(304); + try ctx.flushHeaders(); ctx.done(); } - pub fn sendNoContent(ctx: *RequestContext) void { - ctx.writeStatus(204) catch {}; - ctx.flushHeaders() catch {}; + pub fn sendNoContent(ctx: *RequestContext) !void { + try ctx.writeStatus(204); + try ctx.flushHeaders(); ctx.done(); } @@ -301,17 +319,20 @@ pub const RequestContext = struct { threadlocal var weak_etag_tmp_buffer: [100]u8 = undefined; pub fn done(ctx: *RequestContext) void { + std.debug.assert(!ctx.has_called_done); ctx.conn.deinit(); + ctx.has_called_done = true; } - pub fn sendBadRequest(ctx: *RequestContext) void { - ctx.writeStatus(400) catch {}; + pub fn sendBadRequest(ctx: *RequestContext) !void { + try ctx.writeStatus(400); ctx.done(); } pub fn handleGet(ctx: *RequestContext) !void { const result = try ctx.bundler.buildFile(&ctx.log, ctx.allocator, ctx.url.path, ctx.url.extname); + ctx.mime_type = result.mime_type; ctx.appendHeader("Content-Type", result.mime_type.value); if (ctx.keep_alive) { ctx.appendHeader("Connection", "keep-alive"); @@ -342,7 +363,7 @@ pub const RequestContext = struct { .CharacterDevice, => { ctx.log.addErrorFmt(null, logger.Loc.Empty, ctx.allocator, "Bad file type: {s}", .{@tagName(stat.kind)}) catch {}; - ctx.sendBadRequest(); + try ctx.sendBadRequest(); return; }, .SymLink => { @@ -378,7 +399,7 @@ pub const RequestContext = struct { if (ctx.header("If-None-Match")) |etag_header| { if (strings.eql(complete_weak_etag, etag_header.value)) { - ctx.sendNotModified(); + try ctx.sendNotModified(); return; } } @@ -388,7 +409,7 @@ pub const RequestContext = struct { switch (stat.size) { 0 => { - ctx.sendNoContent(); + try ctx.sendNoContent(); return; }, 1...file_chunk_size - 1 => { @@ -401,7 +422,7 @@ pub const RequestContext = struct { } const file_slice = file_chunk_slice[0..file_read]; - ctx.writeStatus(200) catch {}; + try ctx.writeStatus(200); try ctx.prepareToSendBody(file_read, false); if (!send_body) return; _ = try ctx.writeSocket(file_slice, SOCKET_FLAGS); @@ -451,9 +472,9 @@ pub const RequestContext = struct { // full chunk } else { if (pushed_chunk_count == 0) { - ctx.writeStatus(200) catch {}; + try ctx.writeStatus(200); - ctx.prepareToSendBody(0, true) catch {}; + try ctx.prepareToSendBody(0, true); if (!send_body) return; } @@ -469,7 +490,6 @@ pub const RequestContext = struct { } }, .build => |output| { - defer ctx.done(); defer ctx.bundler.allocator.free(output.contents); if (FeatureFlags.strong_etags_for_built_files) { const strong_etag = std.hash.Wyhash.hash(1, output.contents); @@ -481,7 +501,7 @@ pub const RequestContext = struct { if (etag_header.value.len == 8) { const string_etag_as_u64 = std.mem.readIntSliceNative(u64, etag_header.value); if (string_etag_as_u64 == strong_etag) { - ctx.sendNotModified(); + try ctx.sendNotModified(); return; } } @@ -489,10 +509,11 @@ pub const RequestContext = struct { } if (output.contents.len == 0) { - return ctx.sendNoContent(); + return try ctx.sendNoContent(); } - ctx.writeStatus(200) catch {}; + defer ctx.done(); + try ctx.writeStatus(200); try ctx.prepareToSendBody(output.contents.len, false); if (!send_body) return; _ = try ctx.writeSocket(output.contents, SOCKET_FLAGS); @@ -531,34 +552,55 @@ pub const Server = struct { allocator: *std.mem.Allocator, bundler: Bundler, + pub fn adjustUlimit() !void { + var limit = try std.os.getrlimit(.NOFILE); + if (limit.cur < limit.max) { + var new_limit = std.mem.zeroes(std.os.rlimit); + new_limit.cur = limit.max; + new_limit.max = limit.max; + try std.os.setrlimit(.NOFILE, new_limit); + } + } + + pub fn onTCPConnection(server: *Server, conn: tcp.Connection) void { + conn.client.setNoDelay(true) catch {}; + conn.client.setQuickACK(true) catch {}; + conn.client.setLinger(1) catch {}; + + server.handleConnection(&conn); + } + fn run(server: *Server) !void { + adjustUlimit() catch {}; const listener = try tcp.Listener.init(.ip, SOCKET_FLAGS); defer listener.deinit(); listener.setReuseAddress(true) catch {}; listener.setReusePort(true) catch {}; listener.setFastOpen(true) catch {}; + // listener.setNoDelay(true) catch {}; + // listener.setQuickACK(true) catch {}; // try listener.ack(true); try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 9000)); - try listener.listen(128); + try listener.listen(1280); const addr = try listener.getLocalAddress(); Output.println("Started Speedy at http://{s}", .{addr}); + // var listener_handle = try std.os.kqueue(); + // var change_list = std.mem.zeroes([2]os.Kevent); - // try listener.set(true); + // change_list[0].ident = @intCast(usize, listener.socket.fd); + // change_list[1].ident = @intCast(usize, listener.socket.fd); + // var eventlist: [128]os.Kevent = undefined; while (true) { - var conn = try listener.accept(SOCKET_FLAGS); - conn.client.setNoDelay(true) catch {}; - conn.client.setQuickACK(true) catch {}; - // conn.client.setLinger(1) catch {}; + var conn = listener.accept(SOCKET_FLAGS) catch |err| { + continue; + }; server.handleConnection(&conn); - conn.client.getError() catch |err| { - conn.client.deinit(); - }; } } @@ -566,16 +608,24 @@ pub const Server = struct { try server.writeStatus(code, connection); } + threadlocal var req_buf: [32_000]u8 = undefined; + pub fn handleConnection(server: *Server, conn: *tcp.Connection) void { - errdefer conn.deinit(); + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values - var req_buf: [std.mem.page_size]u8 = undefined; var read_size = conn.client.read(&req_buf, SOCKET_FLAGS) catch |err| { + _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {}; return; }; + + if (read_size == 0) { + // Actually, this was not a request. + return; + } + var req = picohttp.Request.parse(req_buf[0..read_size], &req_headers_buf) catch |err| { + _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {}; Output.printErrorln("ERR: {s}", .{@errorName(err)}); - return; }; @@ -584,7 +634,6 @@ pub const Server = struct { var req_ctx = RequestContext.init(req, &request_arena.allocator, conn, &server.bundler) catch |err| { Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - conn.deinit(); return; }; @@ -596,12 +645,11 @@ pub const Server = struct { req_ctx.handleRequest() catch |err| { switch (err) { - error.NotFound => { + error.ModuleNotFound => { req_ctx.sendNotFound() catch {}; }, else => { Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - conn.deinit(); return; }, } @@ -609,7 +657,15 @@ pub const Server = struct { const status = req_ctx.status orelse @intCast(HTTPStatusCode, 500); - Output.println("{d} – {s} {s}", .{ status, @tagName(req_ctx.method), req.path }); + if (req_ctx.log.msgs.items.len == 0) { + println("{d} – {s} {s} as {s}", .{ status, @tagName(req_ctx.method), req.path, req_ctx.mime_type.value }); + } else { + println("{s} {s}", .{ @tagName(req_ctx.method), req.path }); + for (req_ctx.log.msgs.items) |msg| { + msg.writeFormat(Output.errorWriter()) catch continue; + } + req_ctx.log.deinit(); + } } pub fn start(allocator: *std.mem.Allocator, options: Api.TransformOptions) !void { diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig index 09227d47c..455e1561f 100644 --- a/src/http/mime_type.zig +++ b/src/http/mime_type.zig @@ -1,6 +1,7 @@ const std = @import("std"); usingnamespace @import("../global.zig"); +const Loader = @import("../options.zig").Loader; const Two = strings.ExactSizeMatcher(2); const Four = strings.ExactSizeMatcher(4); const Eight = strings.ExactSizeMatcher(8); @@ -25,6 +26,8 @@ pub const Category = enum { }; pub const other = MimeType.init("application/octet-stream", .other); +pub const css = MimeType.init("application/octet-stream", .other); +pub const javascript = MimeType.init("text/javascript;charset=utf-8", .javascript); fn init(comptime str: string, t: Category) MimeType { return MimeType{ @@ -34,23 +37,38 @@ fn init(comptime str: string, t: Category) MimeType { } // TODO: improve this +pub fn byLoader(loader: Loader, ext: string) MimeType { + switch (loader) { + .tsx, .ts, .js, .jsx => { + return javascript; + }, + .css => { + return css; + }, + else => { + return byExtension(ext); + }, + } +} + +// TODO: improve this pub fn byExtension(ext: string) MimeType { return switch (ext.len) { 2 => { return switch (std.mem.readIntNative(u16, ext[0..2])) { - Two.case("js") => MimeType.init("application/javascript;charset=utf-8", .javascript), + Two.case("js") => javascript, else => MimeType.other, }; }, 3 => { const four = [4]u8{ ext[0], ext[1], ext[2], 0 }; return switch (std.mem.readIntNative(u32, &four)) { - Four.case("css") => MimeType.init("text/css;charset=utf-8", .css), + Four.case("css") => css, Four.case("jpg") => MimeType.init("image/jpeg", .image), Four.case("gif") => MimeType.init("image/gif", .image), Four.case("png") => MimeType.init("image/png", .image), Four.case("bmp") => MimeType.init("image/bmp", .image), - Four.case("mjs") => MimeType.init("text/javascript;charset=utf-8", .javascript), + Four.case("jsx"), Four.case("mjs") => MimeType.javascript, Four.case("wav") => MimeType.init("audio/wave", .audio), Four.case("aac") => MimeType.init("audio/aic", .audio), Four.case("mp4") => MimeType.init("video/mp4", .video), diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index ea62c81cf..1594de702 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -46,8 +46,7 @@ pub const PackageJSON = struct { pub fn parse(r: *resolver.Resolver, input_path: string) ?PackageJSON { const parts = [_]string{ input_path, "package.json" }; - const package_json_path = std.fs.path.join(r.allocator, &parts) catch unreachable; - errdefer r.allocator.free(package_json_path); + const package_json_path = r.fs.join(&parts); const entry = r.caches.fs.readFile(r.fs, input_path) catch |err| { if (err != error.IsDir) { diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 9cb3e635c..f56ed55c8 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -1,13 +1,352 @@ const tester = @import("../test/tester.zig"); +const FeatureFlags = @import("../global.zig").FeatureFlags; const std = @import("std"); threadlocal var parser_join_input_buffer: [1024]u8 = undefined; threadlocal var parser_buffer: [1024]u8 = undefined; +inline fn nqlAtIndex(comptime string_count: comptime_int, index: usize, strings: []const []const u8) bool { + comptime var string_index = 1; + + inline while (string_index < string_count) : (string_index += 1) { + if (strings[0][index] != strings[string_index][index]) { + return true; + } + } + + return false; +} + +// TODO: is it faster to determine longest_common_separator in the while loop +// or as an extra step at the end? +// only boether to check if this function appears in benchmarking +pub fn longestCommonPathGeneric(strings: []const []const u8, comptime separator: u8, comptime isPathSeparator: anytype) []const u8 { + var min_length: usize = std.math.maxInt(usize); + for (strings) |str| { + min_length = std.math.min(str.len, min_length); + } + + var last_common_separator_is_at_end = false; + + var index: usize = 0; + var last_common_separator: usize = 0; + + // try to use an unrolled version of this loop + switch (strings.len) { + 0 => { + return ""; + }, + 1 => { + return strings[0]; + }, + 2 => { + while (index < min_length) : (index += 1) { + if (strings[0][index] != strings[1][index]) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 3 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(3, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 4 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(4, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 5 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(5, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 6 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(6, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 7 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(7, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + 8 => { + while (index < min_length) : (index += 1) { + if (nqlAtIndex(8, index, strings)) { + break; + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + else => { + var string_index: usize = 1; + while (index < min_length) : (index += 1) { + while (string_index < strings.len) : (string_index += 1) { + if (strings[0][index] != strings[index][string_index]) { + break; + } + } + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{strings[0][index]})) { + last_common_separator = index; + } + } + }, + } + + if (index == 0) { + return &([_]u8{separator}); + } + + // The above won't work for a case like this: + // /app/public/index.js + // /app/public + // It will return: + // /app/ + // It should return: + // /app/public/ + // To detect /app/public is actually a folder, we do one more loop through the strings + // and say, "do one of you have a path separator after what we thought was the end?" + for (strings) |str| { + if (str.len > index) { + if (@call(.{ .modifier = .always_inline }, isPathSeparator, .{str[index]})) { + return str[0 .. index + 2]; + } + } + } + + return strings[0][0 .. last_common_separator + 1]; +} + +pub fn longestCommonPath(strings: []const []const u8) []const u8 { + return longestCommonPathGeneric(strings, '/', isSepAny); +} + +pub fn longestCommonPathWindows(strings: []const []const u8) []const u8 { + return longestCommonPathGeneric(strings, std.fs.path.sep_windows, isSepWin32); +} + +pub fn longestCommonPathPosix(strings: []const []const u8) []const u8 { + return longestCommonPathGeneric(strings, std.fs.path.sep_posix, isSepPosix); +} + +threadlocal var relative_to_common_path_buf: [4096]u8 = undefined; + +/// Find a relative path from a common path +// Loosely based on Node.js' implementation of path.relative +// https://github.com/nodejs/node/blob/9a7cbe25de88d87429a69050a1a1971234558d97/lib/path.js#L1250-L1259 +pub fn relativeToCommonPath( + _common_path: []const u8, + normalized_from: []const u8, + normalized_to: []const u8, + buf: []u8, + comptime separator: u8, + comptime always_copy: bool, +) []const u8 { + const common_path = if (_common_path.len > 0 and _common_path[0] == separator) _common_path[1..] else _common_path; + + var shortest = std.math.min(normalized_from.len, normalized_to.len); + + var last_common_separator = std.math.max(common_path.len, 1) - 1; + + if (shortest == common_path.len) { + if (normalized_to.len > normalized_from.len) { + if (common_path.len == 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + if (always_copy) { + std.mem.copy(u8, buf, normalized_to); + return buf[0..normalized_to.len]; + } else { + return normalized_to; + } + } + + if (normalized_to[common_path.len - 1] == separator) { + const slice = normalized_to[common_path.len..]; + + if (always_copy) { + + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + std.mem.copy(u8, buf, slice); + return buf[0..slice.len]; + } else { + return slice; + } + } + } + + if (normalized_from.len > normalized_to.len) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + if (normalized_from[common_path.len - 1] == separator) { + last_common_separator = common_path.len - 1; + } else if (common_path.len == 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + last_common_separator = 0; + } + } + } + + // Generate the relative path based on the path difference between `to` + // and `from`. + + var out_slice: []u8 = buf[0..0]; + + if (normalized_from.len > 0) { + var i: usize = last_common_separator; + + while (i <= normalized_from.len) : (i += 1) { + if (i == normalized_from.len or normalized_from[i] == separator) { + if (out_slice.len == 0) { + out_slice = buf[0 .. out_slice.len + 2]; + out_slice[0] = '.'; + out_slice[1] = '.'; + } else { + var old_len = out_slice.len; + out_slice = buf[0 .. out_slice.len + 3]; + out_slice[old_len] = separator; + old_len += 1; + out_slice[old_len] = '.'; + old_len += 1; + out_slice[old_len] = '.'; + } + } + } + } + + if (normalized_to.len > last_common_separator + 1) { + const tail = normalized_to[last_common_separator + 1 ..]; + const insert_leading_slash = tail[0] != separator; + + if (insert_leading_slash) { + buf[out_slice.len] = separator; + out_slice = buf[0 .. out_slice.len + 1]; + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts. + const start = out_slice.len; + out_slice = buf[0 .. out_slice.len + tail.len]; + + std.mem.copy(u8, out_slice[start..], tail); + } + + return buf[0 .. out_slice.len + 1]; +} + +pub fn relativeNormalized(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 { + const two = [_][]const u8{ from, to }; + const common_path = longestCommonPathGeneric(&two, comptime platform.separator(), platform.isSeparator); + return relativeToCommonPath(common_path, from, to, &relative_to_common_path_buf, comptime platform.separator(), always_copy); +} + +threadlocal var relative_from_buf: [4096]u8 = undefined; +threadlocal var relative_to_buf: [4096]u8 = undefined; +pub fn relative(from: []const u8, to: []const u8) []const u8 { + if (FeatureFlags.use_std_path_relative) { + var relative_allocator = std.heap.FixedBufferAllocator.init(&relative_from_buf); + return relativeAlloc(&relative_allocator.allocator, from, to) catch unreachable; + } else { + return relativePlatform(from, to, .auto, false); + } +} + +pub fn relativePlatform(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 { + const normalized_from = normalizeStringBuf(from, &relative_from_buf, true, platform, true); + const normalized_to = normalizeStringBuf(to, &relative_to_buf, true, platform, true); + + return relativeNormalized(normalized_from, normalized_to, platform, always_copy); +} + +pub fn relativeAlloc(allocator: *std.mem.Allocator, from: []const u8, to: []const u8) ![]const u8 { + if (FeatureFlags.use_std_path_relative) { + return try std.fs.path.relative(allocator, from, to); + } else { + const result = relativePlatform(from, to, Platform.current, false); + return try allocator.dupe(u8, result); + } +} + +test "relative" { + var t = tester.Tester.t(std.heap.c_allocator); + defer t.report(@src()); + + const strs = [_][]const u8{ + "/var/boo/foo/", + "/var/boo/foo/baz/", + "/var/boo/foo/beep/", + "/var/boo/foo/beep/bleep", + "/bar/baz", + "/bar/not-related", + "/bar/file.txt", + }; + _ = t.expect("var/foo", try relativeAlloc(std.heap.c_allocator, "/", "/var/foo/"), @src()); + _ = t.expect("index.js", try relativeAlloc(std.heap.c_allocator, "/app/public/", "/app/public/index.js"), @src()); + _ = t.expect("..", try relativeAlloc(std.heap.c_allocator, "/app/public/index.js", "/app/public/"), @src()); + _ = t.expect("../../src/bacon.ts", try relativeAlloc(std.heap.c_allocator, "/app/public/index.html", "/app/src/bacon.ts"), @src()); +} + +test "longestCommonPath" { + var t = tester.Tester.t(std.heap.c_allocator); + defer t.report(@src()); + + const strs = [_][]const u8{ + "/var/boo/foo/", + "/var/boo/foo/baz/", + "/var/boo/foo/beep/", + "/var/boo/foo/beep/bleep", + "/bar/baz", + "/bar/not-related", + "/bar/file.txt", + }; + _ = t.expect("/var/boo/foo/", longestCommonPath(strs[0..2]), @src()); + _ = t.expect("/var/boo/foo/", longestCommonPath(strs[0..4]), @src()); + _ = t.expect("/var/boo/foo/beep/", longestCommonPath(strs[2..3]), @src()); + _ = t.expect("/bar/", longestCommonPath(strs[5..strs.len]), @src()); + _ = t.expect("/", longestCommonPath(&strs), @src()); + + const more = [_][]const u8{ "/app/public/index.html", "/app/public/index.js", "/app/public", "/app/src/bacon.ts" }; + _ = t.expect("/app/", longestCommonPath(&more), @src()); + _ = t.expect("/app/public/", longestCommonPath(more[0..2]), @src()); +} + // This function is based on Node.js' path.normalize function. // https://github.com/nodejs/node/blob/36bb31be5f0b85a0f6cbcb36b64feb3a12c60984/lib/path.js#L66 -pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype) []u8 { +pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype, comptime preserve_trailing_slash: bool) []u8 { var i: usize = 0; var last_segment_length: i32 = 0; var last_slash: i32 = -1; @@ -69,8 +408,9 @@ pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_r written_len += 1; } - const slice = str[@intCast(usize, @intCast(usize, last_slash + 1))..i]; - std.mem.copy(u8, buf[written_len .. written_len + slice.len], slice); + const slice = str[@intCast(usize, last_slash + 1)..i]; + const base = buf[written_len..]; + std.mem.copy(u8, base[0..slice.len], slice); written_len += slice.len; last_segment_length = @intCast(i32, i) - last_slash - 1; } @@ -84,6 +424,14 @@ pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_r } } + if (preserve_trailing_slash) { + // Was there a trailing slash? Let's keep it. + if (stop_len == last_slash + 1 and last_segment_length > 0) { + buf[written_len] = separator; + written_len += 1; + } + } + return buf[0..written_len]; } @@ -93,6 +441,19 @@ pub const Platform = enum { windows, posix, + pub fn separator(comptime platform: Platform) u8 { + return comptime switch (platform) { + .auto => platform.resolve().separator(), + .loose, .posix => std.fs.path.sep_posix, + .windows => std.fs.path.sep_windows, + }; + } + + pub const current = switch (std.Target.current.os.tag) { + .windows => .windows, + else => .posix, + }; + pub fn isSeparator(comptime _platform: Platform, char: u8) bool { const platform = _platform.resolve(); switch (platform) { @@ -170,24 +531,39 @@ pub const Platform = enum { }; pub fn normalizeString(str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 { - return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform); + return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform, false); } -pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 { - comptime const platform = _platform.resolve(); +pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform, comptime preserve_trailing_slash: anytype) []u8 { + const platform = _platform.resolve(); switch (platform) { .auto => unreachable, .windows => { - return normalizeStringWindowsBuf(str, buf, allow_above_root); + return normalizeStringWindowsBuf( + str, + buf, + allow_above_root, + preserve_trailing_slash, + ); }, .posix => { - return normalizeStringPosixBuf(str, buf, allow_above_root); + return normalizeStringPosixBuf( + str, + buf, + allow_above_root, + preserve_trailing_slash, + ); }, .loose => { - return normalizeStringLooseBuf(str, buf, allow_above_root); + return normalizeStringLooseBuf( + str, + buf, + allow_above_root, + preserve_trailing_slash, + ); }, } } @@ -214,7 +590,12 @@ pub fn normalizeAndJoin(_cwd: []const u8, comptime _platform: Platform, part: an // without querying the filesystem // This is the equivalent of pub fn normalizeAndJoinString(_cwd: []const u8, parts: anytype, comptime _platform: Platform) []const u8 { - return normalizeAndJoinStringBuf(_cwd, &parser_join_input_buffer, parts, _platform); + return normalizeAndJoinStringBuf( + _cwd, + &parser_join_input_buffer, + parts, + _platform, + ); } pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, comptime _platform: Platform) []const u8 { @@ -271,7 +652,7 @@ pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, co } // One last normalization, to remove any ../ added - const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform); + const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform, false); std.mem.copy(u8, buf[0..leading_separator.len], leading_separator); std.mem.copy(u8, buf[leading_separator.len .. result.len + leading_separator.len], result); @@ -302,28 +683,81 @@ pub fn lastIndexOfSeparatorLoose(slice: []const u8) ?usize { return std.mem.lastIndexOfAny(u8, slice, "/\\"); } -pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix); +pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 { + return normalizeStringGenericBuf( + str, + &parser_buffer, + allow_above_root, + std.fs.path.sep_posix, + isSepPosix, + lastIndexOfSeparatorPosix, + preserve_trailing_slash, + ); } -pub fn normalizeStringPosixBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix); +pub fn normalizeStringPosixBuf( + str: []const u8, + buf: []u8, + comptime allow_above_root: bool, + comptime preserve_trailing_slash: bool, +) []u8 { + return normalizeStringGeneric( + str, + buf, + allow_above_root, + std.fs.path.sep_posix, + isSepPosix, + lastIndexOfSeparatorPosix, + preserve_trailing_slash, + ); } -pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows); +pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 { + return normalizeStringGenericBuf( + str, + &parser_buffer, + allow_above_root, + std.fs.path.sep_windows, + isSepWin32, + lastIndexOfSeparatorWindows, + preserve_trailing_slash, + ); } -pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows); +pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 { + return normalizeStringGeneric( + str, + buf, + allow_above_root, + std.fs.path.sep_windows, + isSepWin32, + lastIndexOfSeparatorWindows, + preserve_trailing_slash, + ); } -pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose); +pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 { + return normalizeStringGenericBuf( + str, + &parser_buffer, + allow_above_root, + std.fs.path.sep_posix, + isSepAny, + lastIndexOfSeparatorLoose, + preserve_trailing_slash, + ); } -pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 { - return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose); +pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime preserve_trailing_slash: bool) []u8 { + return normalizeStringGeneric( + str, + buf, + allow_above_root, + std.fs.path.sep_posix, + isSepAny, + lastIndexOfSeparatorLoose, + preserve_trailing_slash, + ); } test "normalizeAndJoinStringPosix" { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 41da0d261..ed501c711 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -313,7 +313,7 @@ pub const Resolver = struct { } var tracing_start: i128 = if (enableTracing) 0 else undefined; - pub fn resolve(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !?Result { + pub fn resolve(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !Result { if (enableTracing) { tracing_start = std.time.nanoTimestamp(); } @@ -387,7 +387,7 @@ pub const Resolver = struct { debug.addNote("Cannot resolve this path without a directory") catch {}; } r.flushDebugLogs(.fail) catch {}; - return null; + return error.MissingResolveDir; } r.mutex.lock(); @@ -395,7 +395,7 @@ pub const Resolver = struct { var result = try r.resolveWithoutSymlinks(source_dir, import_path, kind); - return result; + return result orelse error.ModuleNotFound; } pub fn resolveWithoutSymlinks(r: *Resolver, source_dir: string, import_path: string, kind: ast.ImportKind) !?Result { @@ -487,7 +487,7 @@ pub const Resolver = struct { if (import_dir_info.package_json) |pkg| { const pkg_json_dir = std.fs.path.dirname(pkg.source.key_path.text) orelse unreachable; - const rel_path = try std.fs.path.relative(r.allocator, pkg_json_dir, abs_path); + const rel_path = r.fs.relative(pkg_json_dir, abs_path); if (r.checkBrowserMap(pkg, rel_path)) |remap| { // Is the path disabled? if (remap.len == 0) { @@ -602,11 +602,10 @@ pub const Resolver = struct { const base_dir_info = ((r.dirInfoCached(dirname) catch null)) orelse continue; const dir_info = base_dir_info.getEnclosingBrowserScope() orelse continue; const pkg_json = dir_info.package_json orelse continue; - const rel_path = std.fs.path.relative(r.allocator, pkg_json.source.key_path.text, path.text) catch continue; + const rel_path = r.fs.relative(pkg_json.source.key_path.text, path.text); result.module_type = pkg_json.module_type; if (r.checkBrowserMap(pkg_json, rel_path)) |remapped| { if (remapped.len == 0) { - r.allocator.free(rel_path); path.is_disabled = true; } else if (r.resolveWithoutRemapping(dir_info, remapped, kind)) |remapped_result| { result.is_from_node_modules = remapped_result.is_node_module; @@ -618,12 +617,7 @@ pub const Resolver = struct { result.path_pair.secondary = remapped_result.path_pair.primary; }, } - } else { - r.allocator.free(rel_path); - return null; } - } else { - r.allocator.free(rel_path); } } @@ -1272,7 +1266,7 @@ pub const Resolver = struct { pub fn loadAsIndexWithBrowserRemapping(r: *Resolver, dir_info: *DirInfo, path: string, extension_order: []const string) ?MatchResult { if (dir_info.getEnclosingBrowserScope()) |browser_scope| { - comptime const field_rel_path = "index"; + const field_rel_path = comptime "index"; if (browser_scope.package_json) |browser_json| { if (r.checkBrowserMap(browser_json, field_rel_path)) |remap| { // Is the path disabled? @@ -1519,7 +1513,7 @@ pub const Resolver = struct { const segment = base[0..last_dot]; std.mem.copy(u8, &TemporaryBuffer.ExtensionPathBuf, segment); - comptime const exts = [_]string{ ".ts", ".tsx" }; + const exts = comptime [_]string{ ".ts", ".tsx" }; for (exts) |ext_to_replace| { var buffer = TemporaryBuffer.ExtensionPathBuf[0 .. segment.len + ext_to_replace.len]; diff --git a/src/test/tester.zig b/src/test/tester.zig index 81e1c8ea2..ac5ba57e5 100644 --- a/src/test/tester.zig +++ b/src/test/tester.zig @@ -85,7 +85,7 @@ pub const Tester = struct { fail, }; - pub fn expect(tester: *Tester, expected: string, result: string, src: std.builtin.SourceLocation) callconv(.Inline) bool { + pub inline fn expect(tester: *Tester, expected: string, result: string, src: std.builtin.SourceLocation) bool { var expectation = Expectation.init(expected, result, src); switch (expectation.evaluate_outcome()) { .pass => { @@ -137,12 +137,12 @@ pub const Tester = struct { .pass => { std.fmt.format(stderr.writer(), "{s}All {d} expectations passed.{s}\n", .{ GREEN, tester.pass.items.len, GREEN }) catch unreachable; std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable; - std.testing.expect(true); + std.testing.expect(true) catch std.debug.panic("Test failure", .{}); }, .fail => { std.fmt.format(stderr.writer(), "{s}All {d} expectations failed.{s}\n\n", .{ RED, tester.fail.items.len, RED }) catch unreachable; std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable; - std.testing.expect(false); + std.testing.expect(false) catch std.debug.panic("Test failure", .{}); }, .some_fail => { std.fmt.format(stderr.writer(), "{s}{d} failed{s} and {s}{d} passed{s} of {d} expectations{s}\n\n", .{ @@ -156,7 +156,7 @@ pub const Tester = struct { RESET, }) catch unreachable; std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable; - std.testing.expect(false); + std.testing.expect(false) catch std.debug.panic("Test failure", .{}); }, } } |