Files
bun.sh/src/logger.zig
2022-06-22 06:56:47 -07:00

1271 lines
42 KiB
Zig

const std = @import("std");
const Api = @import("./api/schema.zig").Api;
const js = @import("javascript_core");
const ImportKind = @import("./import_record.zig").ImportKind;
const bun = @import("./global.zig");
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const JSC = @import("javascript_core");
const fs = @import("fs.zig");
const unicode = std.unicode;
const expect = std.testing.expect;
const assert = std.debug.assert;
const ArrayList = std.ArrayList;
const StringBuilder = @import("./string_builder.zig");
pub const Kind = enum(i8) {
err,
warn,
note,
debug,
verbose,
pub inline fn shouldPrint(this: Kind, other: Log.Level) bool {
return switch (other) {
.err => switch (this) {
.err, .note => true,
else => false,
},
.warn => switch (this) {
.err, .warn, .note => true,
else => false,
},
.info, .debug => this != .verbose,
.verbose => true,
};
}
pub inline fn string(self: Kind) bun.string {
return switch (self) {
.err => "error",
.warn => "warn",
.note => "note",
.debug => "debug",
.verbose => "verbose",
};
}
pub inline fn toAPI(kind: Kind) Api.MessageLevel {
return switch (kind) {
.err => .err,
.warn => .warn,
.note => .note,
else => .debug,
};
}
};
pub const Loc = packed struct {
start: i32 = -1,
pub inline fn toNullable(loc: *Loc) ?Loc {
return if (loc.start == -1) null else loc.*;
}
pub const toUsize = i;
pub inline fn i(self: *const Loc) usize {
return @intCast(usize, @maximum(self.start, 0));
}
pub const Empty = Loc{ .start = -1 };
pub inline fn eql(loc: Loc, other: Loc) bool {
return loc.start == other.start;
}
pub fn jsonStringify(self: *const Loc, options: anytype, writer: anytype) !void {
return try std.json.stringify(self.start, options, writer);
}
};
pub const Location = struct {
file: string = "",
namespace: string = "file",
line: i32 = 1, // 1-based
column: i32 = 0, // 0-based, in bytes
length: usize = 0, // in bytes
line_text: ?string = null,
suggestion: ?string = null,
offset: usize = 0,
pub fn count(this: Location, builder: *StringBuilder) void {
builder.count(this.file);
builder.count(this.namespace);
if (this.line_text) |text| builder.count(text[0..@minimum(text.len, 690)]);
if (this.suggestion) |text| builder.count(text);
}
pub fn clone(this: Location, allocator: std.mem.Allocator) !Location {
// mostly to catch undefined memory
bun.assertDefined(this.namespace);
bun.assertDefined(this.file);
return Location{
.file = try allocator.dupe(u8, this.file),
.namespace = this.namespace,
.line = this.line,
.column = this.column,
.length = this.length,
.line_text = if (this.line_text != null) try allocator.dupe(u8, this.line_text.?) else null,
.suggestion = if (this.suggestion != null) try allocator.dupe(u8, this.suggestion.?) else null,
.offset = this.offset,
};
}
pub fn cloneWithBuilder(this: Location, string_builder: *StringBuilder) Location {
// mostly to catch undefined memory
bun.assertDefined(this.namespace);
bun.assertDefined(this.file);
return Location{
.file = string_builder.append(this.file),
.namespace = this.namespace,
.line = this.line,
.column = this.column,
.length = this.length,
.line_text = if (this.line_text != null) string_builder.append(this.line_text.?) else null,
.suggestion = if (this.suggestion != null) string_builder.append(this.suggestion.?) else null,
.offset = this.offset,
};
}
pub fn toAPI(this: *const Location) Api.Location {
bun.assertDefined(this.file);
bun.assertDefined(this.namespace);
return Api.Location{
.file = this.file,
.namespace = this.namespace,
.line = this.line,
.column = this.column,
.line_text = this.line_text orelse "",
.suggestion = this.suggestion orelse "",
.offset = @truncate(u32, this.offset),
};
}
// don't really know what's safe to deinit here!
pub fn deinit(_: *Location, _: std.mem.Allocator) void {}
pub fn init(file: string, namespace: string, line: i32, column: i32, length: u32, line_text: ?string, suggestion: ?string) Location {
// mostly to catch undefined memory
bun.assertDefined(file);
bun.assertDefined(namespace);
return Location{
.file = file,
.namespace = namespace,
.line = line,
.column = column,
.length = length,
.line_text = line_text,
.suggestion = suggestion,
.offset = length,
};
}
pub fn init_or_nil(_source: ?*const Source, r: Range) ?Location {
if (_source) |source| {
var data = source.initErrorPosition(r.loc);
var full_line = source.contents[data.line_start..data.line_end];
if (full_line.len > 80 + data.column_count) {
full_line = full_line[std.math.max(data.column_count, 40) - 40 .. std.math.min(data.column_count + 40, full_line.len - 40) + 40];
}
bun.assertDefined(source.path.text);
bun.assertDefined(source.path.namespace);
bun.assertDefined(full_line);
return Location{
.file = source.path.text,
.namespace = source.path.namespace,
.line = usize2Loc(data.line_count).start,
.column = usize2Loc(data.column_count).start,
.length = full_line.len,
.line_text = full_line,
.offset = @intCast(usize, std.math.max(r.loc.start, 0)),
};
} else {
return null;
}
}
};
pub const Data = struct {
text: string,
location: ?Location = null,
pub fn deinit(d: *Data, allocator: std.mem.Allocator) void {
if (d.location) |loc| {
loc.deinit(allocator);
}
allocator.free(d.text);
}
pub fn clone(this: Data, allocator: std.mem.Allocator) !Data {
return Data{
.text = if (this.text.len > 0) try allocator.dupe(u8, this.text) else "",
.location = if (this.location != null) try this.location.?.clone(allocator) else null,
};
}
pub fn cloneWithBuilder(this: Data, builder: *StringBuilder) Data {
return Data{
.text = if (this.text.len > 0) builder.append(this.text) else "",
.location = if (this.location != null) this.location.?.cloneWithBuilder(builder) else null,
};
}
pub fn count(this: Data, builder: *StringBuilder) void {
builder.count(this.text);
if (this.location) |loc| loc.count(builder);
}
pub fn toAPI(this: *const Data) Api.MessageData {
return Api.MessageData{
.text = this.text,
.location = if (this.location != null) this.location.?.toAPI() else null,
};
}
pub fn writeFormat(
this: *const Data,
to: anytype,
kind: Kind,
comptime enable_ansi_colors: bool,
comptime is_note: bool,
) !void {
if (this.text.len == 0) return;
const message_color = switch (kind) {
.err => comptime Output.color_map.get("b").?,
.note => comptime Output.color_map.get("cyan").? ++ Output.color_map.get("d").?,
else => comptime Output.color_map.get("d").? ++ Output.color_map.get("b").?,
};
const color_name: string = switch (kind) {
.err => comptime Output.color_map.get("red").?,
.note => comptime Output.color_map.get("cyan").?,
else => comptime Output.color_map.get("d").?,
};
try to.writeAll("\n\n");
if (comptime enable_ansi_colors) {
try to.writeAll(color_name);
}
try to.writeAll(kind.string());
try std.fmt.format(to, comptime Output.prettyFmt("<r><d>: <r>", enable_ansi_colors), .{});
if (comptime enable_ansi_colors) {
try to.writeAll(message_color);
}
try std.fmt.format(to, comptime Output.prettyFmt("{s}<r>\n", enable_ansi_colors), .{this.text});
if (this.location) |location| {
if (location.line_text) |line_text_| {
const line_text = std.mem.trimRight(u8, line_text_, "\r\n\t");
const location_in_line_text = @intCast(u32, std.math.max(location.column, 1) - 1);
const has_position = location.column > -1 and line_text.len > 0 and location_in_line_text < line_text.len;
if (has_position) {
if (comptime enable_ansi_colors) {
const is_colored = message_color.len > 0;
const before_segment = line_text[0..location_in_line_text];
try to.writeAll(before_segment);
if (is_colored) {
try to.writeAll(color_name);
}
const rest_of_line = line_text[location_in_line_text..];
if (rest_of_line.len > 0) {
var end_of_segment: usize = 1;
var iter = strings.CodepointIterator.initOffset(rest_of_line, 1);
// extremely naive: we should really use IsIdentifierContinue || isIdentifierStart here
// highlight until we reach the next matching
switch (line_text[location_in_line_text]) {
'\'' => {
end_of_segment = iter.scanUntilQuotedValueOrEOF('\'');
},
'"' => {
end_of_segment = iter.scanUntilQuotedValueOrEOF('"');
},
'<' => {
end_of_segment = iter.scanUntilQuotedValueOrEOF('>');
},
'`' => {
end_of_segment = iter.scanUntilQuotedValueOrEOF('`');
},
else => {},
}
try to.writeAll(rest_of_line[0..end_of_segment]);
if (is_colored) {
try to.writeAll("\x1b[0m");
}
try to.writeAll(rest_of_line[end_of_segment..]);
} else if (is_colored) {
try to.writeAll("\x1b[0m");
}
} else {
try to.writeAll(line_text);
}
try to.writeAll("\n");
try to.writeByteNTimes(' ', location_in_line_text);
if (comptime enable_ansi_colors) {
const is_colored = message_color.len > 0;
if (is_colored) {
try to.writeAll(message_color);
try to.writeAll(color_name);
// always bold the ^
try to.writeAll(comptime Output.color_map.get("b").?);
}
try to.writeByte('^');
if (is_colored) {
try to.writeAll("\x1b[0m\n");
}
} else {
try to.writeAll("^\n");
}
}
}
if (location.file.len > 0) {
if (comptime enable_ansi_colors) {
if (!is_note and kind == .err) {
try to.writeAll(comptime Output.color_map.get("b").?);
} else {}
}
try std.fmt.format(to, comptime Output.prettyFmt("{s}<r>", enable_ansi_colors), .{
location.file,
});
if (location.line > -1 and location.column > -1) {
try std.fmt.format(to, comptime Output.prettyFmt("<d>:<r><yellow>{d}<r><d>:<r><yellow>{d}<r> <d>{d}<r>", enable_ansi_colors), .{
location.line,
location.column,
location.offset,
});
} else if (location.line > -1) {
try std.fmt.format(to, comptime Output.prettyFmt("<d>:<r><yellow>{d}<r> <d>{d}<r>", enable_ansi_colors), .{
location.line,
location.offset,
});
}
}
}
}
};
pub const BabyString = packed struct {
offset: u16,
len: u16,
pub fn in(parent: string, text: string) BabyString {
return BabyString{
.offset = @truncate(u16, std.mem.indexOf(u8, parent, text) orelse unreachable),
.len = @truncate(u16, text.len),
};
}
pub fn slice(this: BabyString, container: string) string {
return container[this.offset..][0..this.len];
}
};
pub const Msg = struct {
kind: Kind = Kind.err,
data: Data,
metadata: Metadata = .{ .build = 0 },
notes: ?[]Data = null,
pub fn count(this: *const Msg, builder: *StringBuilder) void {
this.data.count(builder);
if (this.notes) |notes| {
for (notes) |note| {
note.count(builder);
}
}
}
pub fn clone(this: *const Msg, allocator: std.mem.Allocator) !Msg {
return Msg{
.kind = this.kind,
.data = try this.data.clone(allocator),
.metadata = this.metadata,
.notes = if (this.notes != null and this.notes.?.len > 0)
try bun.clone(this.notes.?, allocator)
else
null,
};
}
pub fn cloneWithBuilder(this: *const Msg, notes: []Data, builder: *StringBuilder) Msg {
return Msg{
.kind = this.kind,
.data = this.data.cloneWithBuilder(builder),
.metadata = this.metadata,
.notes = if (this.notes != null and this.notes.?.len > 0) brk: {
for (this.notes.?) |note, i| {
notes[i] = note.cloneWithBuilder(builder);
}
break :brk notes[0..this.notes.?.len];
} else null,
};
}
pub const Metadata = union(Tag) {
build: u0,
resolve: Resolve,
pub const Tag = enum(u8) {
build = 1,
resolve = 2,
};
pub const Resolve = struct {
specifier: BabyString,
import_kind: ImportKind,
};
};
pub fn toAPI(this: *const Msg, allocator: std.mem.Allocator) !Api.Message {
const notes_len = if (this.notes != null) this.notes.?.len else 0;
var _notes = try allocator.alloc(
Api.MessageData,
notes_len,
);
var msg = Api.Message{
.level = this.kind.toAPI(),
.data = this.data.toAPI(),
.notes = _notes,
.on = Api.MessageMeta{
.resolve = if (this.metadata == .resolve) this.metadata.resolve.specifier.slice(this.data.text) else "",
.build = this.metadata == .build,
},
};
if (this.notes) |notes| {
if (notes.len > 0) {
for (notes) |note, i| {
_notes[i] = note.toAPI();
}
}
}
return msg;
}
pub fn toAPIFromList(comptime ListType: type, list: ListType, allocator: std.mem.Allocator) ![]Api.Message {
var out_list = try allocator.alloc(Api.Message, list.items.len);
for (list.items) |item, i| {
out_list[i] = try item.toAPI(allocator);
}
return out_list;
}
pub fn deinit(msg: *Msg, allocator: std.mem.Allocator) void {
msg.data.deinit(allocator);
if (msg.notes) |notes| {
for (notes) |note| {
note.deinit(allocator);
}
}
msg.notes = null;
}
pub fn writeFormat(
msg: *const Msg,
to: anytype,
comptime enable_ansi_colors: bool,
) !void {
try msg.data.writeFormat(to, msg.kind, enable_ansi_colors, false);
if (msg.notes) |notes| {
for (notes) |note| {
try note.writeFormat(to, .note, enable_ansi_colors, true);
}
}
}
pub fn formatWriter(
msg: *const Msg,
comptime Writer: type,
writer: Writer,
comptime _: bool,
) !void {
if (msg.data.location) |location| {
try writer.print("{s}: {s}\n{s}\n{s}:{}:{} ({d})", .{
msg.kind.string(),
msg.data.text,
location.line_text,
location.file,
location.line,
location.column,
location.offset,
});
} else {
try writer.print("{s}: {s}", .{
msg.kind.string(),
msg.data.text,
});
}
}
pub fn formatNoWriter(msg: *const Msg, comptime formatterFunc: @TypeOf(Global.panic)) void {
formatterFunc("\n\n{s}: {s}\n{s}\n{s}:{}:{} ({d})", .{
msg.kind.string(),
msg.data.text,
msg.data.location.?.line_text,
msg.data.location.?.file,
msg.data.location.?.line,
msg.data.location.?.column,
msg.data.location.?.offset,
});
}
};
pub const Range = packed struct {
loc: Loc = Loc.Empty,
len: i32 = 0,
pub const None = Range{ .loc = Loc.Empty, .len = 0 };
pub fn in(this: Range, buf: []const u8) []const u8 {
if (this.loc.start < 0 or this.len <= 0) return "";
const slice = buf[@intCast(usize, this.loc.start)..];
return slice[0..@minimum(@intCast(usize, this.len), buf.len)];
}
pub fn isEmpty(r: *const Range) bool {
return r.len == 0 and r.loc.start == Loc.Empty.start;
}
pub fn end(self: *const Range) Loc {
return Loc{ .start = self.loc.start + self.len };
}
pub fn endI(self: *const Range) usize {
return std.math.lossyCast(usize, self.loc.start + self.len);
}
pub fn jsonStringify(self: *const Range, options: anytype, writer: anytype) !void {
return try std.json.stringify([2]i32{ self.loc.start, self.len + self.loc.start }, options, writer);
}
};
pub const Log = struct {
debug: bool = false,
warnings: usize = 0,
errors: usize = 0,
msgs: ArrayList(Msg),
level: Level = if (Environment.isDebug) Level.info else Level.warn,
pub fn reset(this: *Log) void {
this.msgs.clearRetainingCapacity();
this.warnings = 0;
this.errors = 0;
}
pub var default_log_level = if (Environment.isDebug) Level.info else Level.warn;
pub fn hasAny(this: *const Log) bool {
return (this.warnings + this.errors) > 0;
}
pub fn toAPI(this: *const Log, allocator: std.mem.Allocator) !Api.Log {
var warnings: u32 = 0;
var errors: u32 = 0;
for (this.msgs.items) |msg| {
errors += @intCast(u32, @boolToInt(msg.kind == .err));
warnings += @intCast(u32, @boolToInt(msg.kind == .warn));
}
return Api.Log{
.warnings = warnings,
.errors = errors,
.msgs = try Msg.toAPIFromList(@TypeOf(this.msgs), this.msgs, allocator),
};
}
pub const Level = enum(i8) {
verbose,
debug,
info,
warn,
err,
pub fn atLeast(this: Level, other: Level) bool {
return @enumToInt(this) >= @enumToInt(other);
}
pub const label: std.EnumArray(Level, string) = brk: {
var map = std.EnumArray(Level, string).initFill("");
map.set(Level.verbose, "verbose");
map.set(Level.debug, "debug");
map.set(Level.info, "info");
map.set(Level.warn, "warn");
map.set(Level.err, "error");
break :brk map;
};
};
pub fn init(allocator: std.mem.Allocator) Log {
return Log{
.msgs = ArrayList(Msg).init(allocator),
.level = default_log_level,
};
}
pub fn initComptime(allocator: std.mem.Allocator) Log {
return Log{
.msgs = ArrayList(Msg).init(allocator),
};
}
pub fn addVerbose(log: *Log, source: ?*const Source, loc: Loc, text: string) !void {
@setCold(true);
try log.addMsg(Msg{
.kind = .verbose,
.data = rangeData(source, Range{ .loc = loc }, text),
});
}
pub fn toJS(this: Log, global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, fmt: string) JSC.JSValue {
const msgs: []const Msg = this.msgs.items;
var errors_stack: [256]*anyopaque = undefined;
const count = @intCast(u16, @minimum(msgs.len, errors_stack.len));
switch (count) {
0 => return JSC.JSValue.jsUndefined(),
1 => {
const msg = msgs[0];
return JSC.JSValue.fromRef(JSC.BuildError.create(global, allocator, msg));
},
else => {
for (msgs[0..count]) |msg, i| {
switch (msg.metadata) {
.build => {
errors_stack[i] = JSC.BuildError.create(global, allocator, msg).?;
},
.resolve => {
errors_stack[i] = JSC.ResolveError.create(global, allocator, msg, "").?;
},
}
}
const out = JSC.ZigString.init(fmt);
const agg = global.createAggregateError(errors_stack[0..count].ptr, count, &out);
return agg;
},
}
}
pub fn appendTo(self: *Log, other: *Log) !void {
var notes_count: usize = 0;
for (self.msgs.items) |msg_| {
const msg: Msg = msg_;
if (msg.notes) |notes| {
for (notes) |note| {
notes_count += @intCast(usize, @boolToInt(note.text.len > 0));
}
}
}
if (notes_count > 0) {
var notes = try other.msgs.allocator.alloc(Data, notes_count);
var note_i: usize = 0;
for (self.msgs.items) |*msg| {
if (msg.notes) |current_notes| {
var start_note_i: usize = note_i;
for (current_notes) |note| {
notes[note_i] = note;
note_i += 1;
}
msg.notes = notes[start_note_i..note_i];
}
}
}
try other.msgs.appendSlice(self.msgs.items);
other.warnings += self.warnings;
other.errors += self.errors;
self.msgs.deinit();
}
pub fn appendToMaybeRecycled(self: *Log, other: *Log, source: *const Source) !void {
try other.msgs.appendSlice(self.msgs.items);
other.warnings += self.warnings;
other.errors += self.errors;
if (source.contents_is_recycled) {
var string_builder = StringBuilder{};
var notes_count: usize = 0;
{
var i: usize = 0;
var j: usize = other.msgs.items.len - self.msgs.items.len;
while (i < self.msgs.items.len) : ({
i += 1;
j += 1;
}) {
const msg: Msg = self.msgs.items[i];
msg.count(&string_builder);
if (msg.notes) |notes| {
notes_count += notes.len;
}
}
}
try string_builder.allocate(other.msgs.allocator);
var notes_buf = try other.msgs.allocator.alloc(Data, notes_count);
var note_i: usize = 0;
{
var i: usize = 0;
var j: usize = other.msgs.items.len - self.msgs.items.len;
while (i < self.msgs.items.len) : ({
i += 1;
j += 1;
}) {
const msg: Msg = self.msgs.items[i];
other.msgs.items[j] = msg.cloneWithBuilder(notes_buf[note_i..], &string_builder);
note_i += (msg.notes orelse &[_]Data{}).len;
}
}
}
self.msgs.deinit();
}
pub fn deinit(self: *Log) void {
self.msgs.deinit();
}
pub fn addVerboseWithNotes(log: *Log, source: ?*const Source, loc: Loc, text: string, notes: []Data) !void {
@setCold(true);
if (!Kind.shouldPrint(.verbose, log.level)) return;
try log.addMsg(Msg{
.kind = .verbose,
.data = rangeData(source, Range{ .loc = loc }, text),
.notes = notes,
});
}
inline fn _addResolveErrorWithLevel(log: *Log, source: *const Source, r: Range, allocator: std.mem.Allocator, comptime fmt: string, args: anytype, import_kind: ImportKind, comptime dupe_text: bool, comptime is_error: bool) !void {
const text = try std.fmt.allocPrint(allocator, fmt, args);
// TODO: fix this. this is stupid, it should be returned in allocPrint.
const specifier = BabyString.in(text, args.@"0");
if (comptime is_error) {
log.errors += 1;
} else {
log.warnings += 1;
}
const data = if (comptime dupe_text) brk: {
var _data = rangeData(
source,
r,
text,
);
if (_data.location != null) {
if (_data.location.?.line_text) |line| {
_data.location.?.line_text = allocator.dupe(u8, line) catch unreachable;
}
}
break :brk _data;
} else rangeData(
source,
r,
text,
);
const msg = Msg{
.kind = if (comptime is_error) Kind.err else Kind.warn,
.data = data,
.metadata = .{ .resolve = Msg.Metadata.Resolve{ .specifier = specifier, .import_kind = import_kind } },
};
try log.addMsg(msg);
}
inline fn _addResolveError(log: *Log, source: *const Source, r: Range, allocator: std.mem.Allocator, comptime fmt: string, args: anytype, import_kind: ImportKind, comptime dupe_text: bool) !void {
return _addResolveErrorWithLevel(log, source, r, allocator, fmt, args, import_kind, dupe_text, true);
}
inline fn _addResolveWarn(log: *Log, source: *const Source, r: Range, allocator: std.mem.Allocator, comptime fmt: string, args: anytype, import_kind: ImportKind, comptime dupe_text: bool) !void {
return _addResolveErrorWithLevel(log, source, r, allocator, fmt, args, import_kind, dupe_text, false);
}
pub fn addResolveError(
log: *Log,
source: *const Source,
r: Range,
allocator: std.mem.Allocator,
comptime fmt: string,
args: anytype,
import_kind: ImportKind,
) !void {
@setCold(true);
return try _addResolveError(log, source, r, allocator, fmt, args, import_kind, false);
}
pub fn addResolveErrorWithTextDupe(
log: *Log,
source: *const Source,
r: Range,
allocator: std.mem.Allocator,
comptime fmt: string,
args: anytype,
import_kind: ImportKind,
) !void {
@setCold(true);
return try _addResolveError(log, source, r, allocator, fmt, args, import_kind, true);
}
pub fn addResolveErrorWithTextDupeMaybeWarn(
log: *Log,
source: *const Source,
r: Range,
allocator: std.mem.Allocator,
comptime fmt: string,
args: anytype,
import_kind: ImportKind,
warn: bool,
) !void {
@setCold(true);
if (warn) {
return try _addResolveError(log, source, r, allocator, fmt, args, import_kind, true);
} else {
return try _addResolveWarn(log, source, r, allocator, fmt, args, import_kind, true);
}
}
pub fn addRangeError(log: *Log, source: ?*const Source, r: Range, text: string) !void {
@setCold(true);
log.errors += 1;
try log.addMsg(Msg{
.kind = .err,
.data = rangeData(source, r, text),
});
}
pub fn addRangeErrorFmt(log: *Log, source: ?*const Source, r: Range, allocator: std.mem.Allocator, comptime text: string, args: anytype) !void {
@setCold(true);
log.errors += 1;
try log.addMsg(Msg{
.kind = .err,
.data = rangeData(source, r, std.fmt.allocPrint(allocator, text, args) catch unreachable),
});
}
pub fn addRangeErrorFmtWithNotes(log: *Log, source: ?*const Source, r: Range, allocator: std.mem.Allocator, notes: []Data, comptime text: string, args: anytype) !void {
@setCold(true);
log.errors += 1;
try log.addMsg(Msg{
.kind = .err,
.data = rangeData(source, r, std.fmt.allocPrint(allocator, text, args) catch unreachable),
.notes = notes,
});
}
pub fn addErrorFmt(log: *Log, source: ?*const Source, l: Loc, allocator: std.mem.Allocator, comptime text: string, args: anytype) !void {
@setCold(true);
log.errors += 1;
try log.addMsg(Msg{
.kind = .err,
.data = rangeData(source, Range{ .loc = l }, std.fmt.allocPrint(allocator, text, args) catch unreachable),
});
}
pub fn addRangeWarning(log: *Log, source: ?*const Source, r: Range, text: string) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
try log.addMsg(Msg{
.kind = .warn,
.data = rangeData(source, r, text),
});
}
pub fn addWarningFmt(log: *Log, source: ?*const Source, l: Loc, allocator: std.mem.Allocator, comptime text: string, args: anytype) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
try log.addMsg(Msg{
.kind = .warn,
.data = rangeData(source, Range{ .loc = l }, std.fmt.allocPrint(allocator, text, args) catch unreachable),
});
}
pub fn addRangeWarningFmt(log: *Log, source: ?*const Source, r: Range, allocator: std.mem.Allocator, comptime text: string, args: anytype) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
try log.addMsg(Msg{
.kind = .warn,
.data = rangeData(source, r, std.fmt.allocPrint(allocator, text, args) catch unreachable),
});
}
pub fn addRangeWarningFmtWithNote(
log: *Log,
source: ?*const Source,
r: Range,
allocator: std.mem.Allocator,
comptime fmt: string,
args: anytype,
comptime note_fmt: string,
note_args: anytype,
note_range: Range,
) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
var notes = try allocator.alloc(Data, 1);
notes[0] = rangeData(source, note_range, std.fmt.allocPrint(allocator, note_fmt, note_args) catch unreachable);
try log.addMsg(Msg{
.kind = .warn,
.data = rangeData(source, r, std.fmt.allocPrint(allocator, fmt, args) catch unreachable),
.notes = notes,
});
}
pub fn addWarning(log: *Log, source: ?*const Source, l: Loc, text: string) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
try log.addMsg(Msg{
.kind = .warn,
.data = rangeData(source, Range{ .loc = l }, text),
});
}
pub fn addRangeDebug(log: *Log, source: ?*const Source, r: Range, text: string) !void {
@setCold(true);
if (!Kind.shouldPrint(.debug, log.level)) return;
try log.addMsg(Msg{
.kind = .debug,
.data = rangeData(source, r, text),
});
}
pub fn addRangeDebugWithNotes(log: *Log, source: ?*const Source, r: Range, text: string, notes: []Data) !void {
@setCold(true);
if (!Kind.shouldPrint(.debug, log.level)) return;
// log.de += 1;
try log.addMsg(Msg{
.kind = Kind.debug,
.data = rangeData(source, r, text),
.notes = notes,
});
}
pub fn addRangeErrorWithNotes(log: *Log, source: ?*const Source, r: Range, text: string, notes: []Data) !void {
@setCold(true);
log.errors += 1;
try log.addMsg(Msg{
.kind = Kind.err,
.data = rangeData(source, r, text),
.notes = notes,
});
}
pub fn addRangeWarningWithNotes(log: *Log, source: ?*const Source, r: Range, text: string, notes: []Data) !void {
@setCold(true);
if (!Kind.shouldPrint(.warn, log.level)) return;
log.warnings += 1;
try log.addMsg(Msg{
.kind = .warning,
.data = rangeData(source, r, text),
.notes = notes,
});
}
pub inline fn addMsg(self: *Log, msg: Msg) !void {
if (comptime Environment.allow_assert) {
if (msg.notes) |notes| {
bun.assertDefined(notes);
for (notes) |note| {
bun.assertDefined(note.text);
if (note.location) |loc| {
bun.assertDefined(loc);
}
}
}
}
try self.msgs.append(msg);
}
pub fn addError(self: *Log, _source: ?*const Source, loc: Loc, text: string) !void {
@setCold(true);
self.errors += 1;
try self.addMsg(Msg{ .kind = .err, .data = rangeData(_source, Range{ .loc = loc }, text) });
}
pub fn printForLogLevel(self: *Log, to: anytype) !void {
if (Output.enable_ansi_colors) {
return self.printForLogLevelWithEnableAnsiColors(to, true);
} else {
return self.printForLogLevelWithEnableAnsiColors(to, false);
}
}
pub fn printForLogLevelWithEnableAnsiColors(self: *Log, to: anytype, comptime enable_ansi_colors: bool) !void {
var printed = false;
if (self.warnings > 0 and self.errors > 0) {
// Print warnings at the top
// errors at the bottom
// This is so if you're reading from a terminal
// and there are a bunch of warnings
// You can more easily see where the errors are
for (self.msgs.items) |msg| {
if (msg.kind != .err) {
if (msg.kind.shouldPrint(self.level)) {
try msg.writeFormat(to, enable_ansi_colors);
printed = true;
}
}
}
for (self.msgs.items) |msg| {
if (msg.kind == .err) {
if (msg.kind.shouldPrint(self.level)) {
try msg.writeFormat(to, enable_ansi_colors);
printed = true;
}
}
}
} else {
for (self.msgs.items) |msg| {
if (msg.kind.shouldPrint(self.level)) {
try msg.writeFormat(to, enable_ansi_colors);
printed = true;
}
}
}
if (printed) _ = try to.write("\n");
}
pub fn toZigException(this: *const Log, allocator: std.mem.Allocator) *js.ZigException.Holder {
var holder = try allocator.create(js.ZigException.Holder);
holder.* = js.ZigException.Holder.init();
var zig_exception: *js.ZigException = holder.zigException();
zig_exception.exception = this;
zig_exception.code = js.JSErrorCode.BundlerError;
return holder;
}
};
pub inline fn usize2Loc(loc: usize) Loc {
return Loc{ .start = @intCast(i32, loc) };
}
pub const Source = struct {
path: fs.Path,
key_path: fs.Path,
index: u32 = 0,
contents: string,
contents_is_recycled: bool = false,
pub const ErrorPosition = struct {
line_start: usize,
line_end: usize,
column_count: usize,
line_count: usize,
};
pub fn initEmptyFile(filepath: string) Source {
const path = fs.Path.init(filepath);
return Source{ .path = path, .key_path = path, .index = 0, .contents = "" };
}
pub fn initFile(file: fs.File, _: std.mem.Allocator) !Source {
var source = Source{
.path = file.path,
.key_path = fs.Path.init(file.path.text),
.contents = file.contents,
};
source.path.namespace = "file";
return source;
}
pub fn initRecycledFile(file: fs.File, _: std.mem.Allocator) !Source {
var source = Source{
.path = file.path,
.key_path = fs.Path.init(file.path.text),
.contents = file.contents,
.contents_is_recycled = true,
};
source.path.namespace = "file";
return source;
}
pub fn initPathString(pathString: string, contents: string) Source {
var path = fs.Path.init(pathString);
return Source{ .key_path = path, .path = path, .contents = contents };
}
pub fn textForRange(self: *const Source, r: Range) string {
return self.contents[r.loc.i()..r.endI()];
}
pub fn rangeOfOperatorBefore(self: *const Source, loc: Loc, op: string) Range {
const text = self.contents[0..loc.i()];
const index = strings.index(text, op);
if (index >= 0) {
return Range{ .loc = Loc{
.start = loc.start + index,
}, .len = @intCast(i32, op.len) };
}
return Range{ .loc = loc };
}
pub fn rangeOfString(self: *const Source, loc: Loc) Range {
if (loc.start < 0) return Range.None;
const text = self.contents[loc.i()..];
if (text.len == 0) {
return Range.None;
}
const quote = text[0];
if (quote == '"' or quote == '\'') {
var i: usize = 1;
var c: u8 = undefined;
while (i < text.len) {
c = text[i];
if (c == quote) {
return Range{ .loc = loc, .len = @intCast(i32, i + 1) };
} else if (c == '\\') {
i += 1;
}
i += 1;
}
}
return Range{ .loc = loc, .len = 0 };
}
pub fn rangeOfOperatorAfter(self: *const Source, loc: Loc, op: string) Range {
const text = self.contents[loc.i()..];
const index = strings.index(text, op);
if (index >= 0) {
return Range{ .loc = Loc{
.start = loc.start + index,
}, .len = op.len };
}
return Range{ .loc = loc };
}
pub fn initErrorPosition(self: *const Source, _offset: Loc) ErrorPosition {
var prev_code_point: i32 = 0;
var offset: usize = std.math.min(if (_offset.start < 0) 0 else @intCast(usize, _offset.start), @maximum(self.contents.len, 1) - 1);
const contents = self.contents;
var iter_ = strings.CodepointIterator{
.bytes = self.contents[0..offset],
.i = 0,
};
var iter = strings.CodepointIterator.Cursor{};
var line_start: usize = 0;
var line_count: usize = 1;
var column_number: usize = 1;
while (iter_.next(&iter)) {
switch (iter.c) {
'\n' => {
column_number = 1;
line_start = iter.width + iter.i;
if (prev_code_point != '\r') {
line_count += 1;
}
},
'\r' => {
column_number = 0;
line_start = iter.width + iter.i;
line_count += 1;
},
0x2028, 0x2029 => {
line_start = iter.width + iter.i; // These take three bytes to encode in UTF-8
line_count += 1;
column_number = 1;
},
else => {
column_number += 1;
},
}
prev_code_point = iter.c;
}
iter_ = strings.CodepointIterator{
.bytes = self.contents[offset..],
.i = 0,
};
iter = strings.CodepointIterator.Cursor{};
// Scan to the end of the line (or end of file if this is the last line)
var line_end: usize = contents.len;
loop: while (iter_.next(&iter)) {
switch (iter.c) {
'\r', '\n', 0x2028, 0x2029 => {
line_end = offset + iter.i;
break :loop;
},
else => {},
}
}
return ErrorPosition{
.line_start = if (line_start > 0) line_start - 1 else line_start,
.line_end = line_end,
.line_count = line_count,
.column_count = column_number,
};
}
};
pub fn rangeData(source: ?*const Source, r: Range, text: string) Data {
return Data{ .text = text, .location = Location.init_or_nil(source, r) };
}