Files
bun.sh/src/fmt.zig
Jarred Sumner 3ea6133c46 CI: Remove unused top-level decls in formatter in zig (#19879)
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-23 22:49:48 -07:00

1892 lines
66 KiB
Zig

const std = @import("std");
const bun = @import("bun");
const Output = bun.Output;
const strings = bun.strings;
const string = bun.string;
const js_lexer = bun.js_lexer;
const fmt = std.fmt;
const Environment = bun.Environment;
const sha = bun.sha;
pub const TableSymbols = struct {
enable_ansi_colors: bool,
pub const unicode = TableSymbols{ .enable_ansi_colors = true };
pub const ascii = TableSymbols{ .enable_ansi_colors = false };
pub fn topLeftSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn topRightSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn topColumnSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "-";
}
pub fn bottomLeftSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn bottomRightSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn bottomColumnSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "-";
}
pub fn middleLeftSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn middleRightSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn middleColumnSep(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
pub fn horizontalEdge(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "-";
}
pub fn verticalEdge(comptime s: TableSymbols) []const u8 {
return if (s.enable_ansi_colors) "" else "|";
}
};
pub fn Table(
comptime column_color: []const u8,
comptime column_left_pad: usize,
comptime column_right_pad: usize,
comptime enable_ansi_colors: bool,
) type {
const symbols = TableSymbols{ .enable_ansi_colors = enable_ansi_colors };
return struct {
column_names: []const []const u8,
column_inside_lengths: []const usize,
comptime symbols: TableSymbols = symbols,
pub fn init(column_names_: []const []const u8, column_inside_lengths_: []const usize) @This() {
return .{
.column_names = column_names_,
.column_inside_lengths = column_inside_lengths_,
};
}
pub fn printTopLineSeparator(this: *const @This()) void {
this.printLine(symbols.topLeftSep(), symbols.topRightSep(), symbols.topColumnSep());
}
pub fn printBottomLineSeparator(this: *const @This()) void {
this.printLine(symbols.bottomLeftSep(), symbols.bottomRightSep(), symbols.bottomColumnSep());
}
pub fn printLineSeparator(this: *const @This()) void {
this.printLine(symbols.middleLeftSep(), symbols.middleRightSep(), symbols.middleColumnSep());
}
pub fn printLine(this: *const @This(), left_edge_separator: string, right_edge_separator: string, column_separator: string) void {
for (this.column_inside_lengths, 0..) |column_inside_length, i| {
if (i == 0) {
Output.pretty("{s}", .{left_edge_separator});
} else {
Output.pretty("{s}", .{column_separator});
}
for (0..column_left_pad + column_inside_length + column_right_pad) |_| Output.pretty("{s}", .{symbols.horizontalEdge()});
if (i == this.column_inside_lengths.len - 1) {
Output.pretty("{s}\n", .{right_edge_separator});
}
}
}
pub fn printColumnNames(this: *const @This()) void {
for (this.column_inside_lengths, 0..) |column_inside_length, i| {
Output.pretty("{s}", .{symbols.verticalEdge()});
for (0..column_left_pad) |_| Output.pretty(" ", .{});
Output.pretty("<b><" ++ column_color ++ ">{s}<r>", .{this.column_names[i]});
for (this.column_names[i].len..column_inside_length + column_right_pad) |_| Output.pretty(" ", .{});
if (i == this.column_inside_lengths.len - 1) {
Output.pretty("{s}\n", .{symbols.verticalEdge()});
}
}
}
};
}
pub const RedactedNpmUrlFormatter = struct {
url: string,
pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void {
var i: usize = 0;
while (i < this.url.len) {
if (strings.startsWithUUID(this.url[i..])) {
try writer.writeAll("***");
i += 36;
continue;
}
const npm_secret_len = strings.startsWithNpmSecret(this.url[i..]);
if (npm_secret_len > 0) {
try writer.writeAll("***");
i += npm_secret_len;
continue;
}
// TODO: redact password from `https://username:password@registry.com/`
try writer.writeByte(this.url[i]);
i += 1;
}
}
};
pub fn redactedNpmUrl(str: string) RedactedNpmUrlFormatter {
return .{
.url = str,
};
}
pub const RedactedSourceFormatter = struct {
text: string,
pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void {
var i: usize = 0;
while (i < this.text.len) {
if (strings.startsWithSecret(this.text[i..])) |secret| {
const offset, const len = secret;
try writer.writeAll(this.text[i..][0..offset]);
try writer.writeByteNTimes('*', len);
i += offset + len;
continue;
}
try writer.writeByte(this.text[i]);
i += 1;
}
}
};
pub fn redactedSource(str: string) RedactedSourceFormatter {
return .{
.text = str,
};
}
// https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/node_modules/npm-package-arg/lib/npa.js#L163
pub const DependencyUrlFormatter = struct {
url: string,
pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void {
var remain = this.url;
while (strings.indexOfChar(remain, '/')) |slash| {
try writer.writeAll(remain[0..slash]);
try writer.writeAll("%2f");
remain = remain[slash + 1 ..];
}
try writer.writeAll(remain);
}
};
pub fn dependencyUrl(url: string) DependencyUrlFormatter {
return .{
.url = url,
};
}
const IntegrityFormatStyle = enum {
short,
full,
};
pub fn IntegrityFormatter(comptime style: IntegrityFormatStyle) type {
return struct {
bytes: [sha.SHA512.digest]u8,
pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void {
var buf: [std.base64.standard.Encoder.calcSize(sha.SHA512.digest)]u8 = undefined;
const count = bun.simdutf.base64.encode(this.bytes[0..sha.SHA512.digest], &buf, false);
const encoded = buf[0..count];
if (comptime style == .short)
try writer.print("sha512-{s}[...]{s}", .{ encoded[0..13], encoded[encoded.len - 15 ..] })
else
try writer.print("sha512-{s}", .{encoded});
}
};
}
pub fn integrity(bytes: [sha.SHA512.digest]u8, comptime style: IntegrityFormatStyle) IntegrityFormatter(style) {
return .{ .bytes = bytes };
}
const JSONFormatter = struct {
input: []const u8,
pub fn format(self: JSONFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try bun.js_printer.writeJSONString(self.input, @TypeOf(writer), writer, .latin1);
}
};
const JSONFormatterUTF8 = struct {
input: []const u8,
opts: Options,
pub const Options = struct {
quote: bool = true,
};
pub fn format(self: JSONFormatterUTF8, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (self.opts.quote) {
try bun.js_printer.writeJSONString(self.input, @TypeOf(writer), writer, .utf8);
} else {
try bun.js_printer.writePreQuotedString(self.input, @TypeOf(writer), writer, '"', false, true, .utf8);
}
}
};
/// Expects latin1
pub fn formatJSONStringLatin1(text: []const u8) JSONFormatter {
return .{ .input = text };
}
pub fn formatJSONStringUTF8(text: []const u8, opts: JSONFormatterUTF8.Options) JSONFormatterUTF8 {
return .{ .input = text, .opts = opts };
}
const SharedTempBuffer = [32 * 1024]u8;
fn getSharedBuffer() []u8 {
return std.mem.asBytes(shared_temp_buffer_ptr orelse brk: {
shared_temp_buffer_ptr = bun.default_allocator.create(SharedTempBuffer) catch unreachable;
break :brk shared_temp_buffer_ptr.?;
});
}
threadlocal var shared_temp_buffer_ptr: ?*SharedTempBuffer = null;
pub fn formatUTF16Type(comptime Slice: type, slice_: Slice, writer: anytype) !void {
var chunk = getSharedBuffer();
// Defensively ensure recursion doesn't cause the buffer to be overwritten in-place
shared_temp_buffer_ptr = null;
defer {
if (shared_temp_buffer_ptr) |existing| {
if (existing != chunk.ptr) {
bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr)));
}
} else {
shared_temp_buffer_ptr = @ptrCast(chunk.ptr);
}
}
var slice = slice_;
while (slice.len > 0) {
const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true);
if (result.read == 0 or result.written == 0)
break;
try writer.writeAll(chunk[0..result.written]);
slice = slice[result.read..];
}
}
pub fn formatUTF16TypeWithPathOptions(comptime Slice: type, slice_: Slice, writer: anytype, opts: PathFormatOptions) !void {
var chunk = getSharedBuffer();
// Defensively ensure recursion doesn't cause the buffer to be overwritten in-place
shared_temp_buffer_ptr = null;
defer {
if (shared_temp_buffer_ptr) |existing| {
if (existing != chunk.ptr) {
bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr)));
}
} else {
shared_temp_buffer_ptr = @ptrCast(chunk.ptr);
}
}
var slice = slice_;
while (slice.len > 0) {
const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true);
if (result.read == 0 or result.written == 0)
break;
const to_write = chunk[0..result.written];
if (!opts.escape_backslashes and opts.path_sep == .any) {
try writer.writeAll(to_write);
} else {
var ptr = to_write;
while (strings.indexOfAny(ptr, "\\/")) |i| {
const sep = switch (opts.path_sep) {
.windows => '\\',
.posix => '/',
.auto => std.fs.path.sep,
.any => ptr[i],
};
try writer.writeAll(ptr[0..i]);
try writer.writeByte(sep);
if (opts.escape_backslashes and sep == '\\') {
try writer.writeByte(sep);
}
ptr = ptr[i + 1 ..];
}
try writer.writeAll(ptr);
}
slice = slice[result.read..];
}
}
pub inline fn utf16(slice_: []const u16) FormatUTF16 {
return FormatUTF16{ .buf = slice_ };
}
/// Debug, this does not handle invalid utf32
pub inline fn debugUtf32PathFormatter(path: []const u32) DebugUTF32PathFormatter {
return DebugUTF32PathFormatter{ .path = path };
}
pub const DebugUTF32PathFormatter = struct {
path: []const u32,
pub fn format(this: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void {
var path_buf: bun.PathBuffer = undefined;
const result = bun.simdutf.convert.utf32.to.utf8.with_errors.le(this.path, &path_buf);
const converted = if (result.isSuccessful())
path_buf[0..result.count]
else
"Invalid UTF32!";
try writer.writeAll(converted);
}
};
pub const FormatUTF16 = struct {
buf: []const u16,
path_fmt_opts: ?PathFormatOptions = null,
pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void {
if (self.path_fmt_opts) |opts| {
try formatUTF16TypeWithPathOptions([]const u16, self.buf, writer, opts);
} else {
try formatUTF16Type([]const u16, self.buf, writer);
}
}
};
pub const FormatUTF8 = struct {
buf: []const u8,
path_fmt_opts: ?PathFormatOptions = null,
pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void {
if (self.path_fmt_opts) |opts| {
if (opts.path_sep == .any and opts.escape_backslashes == false) {
try writer.writeAll(self.buf);
return;
}
var ptr = self.buf;
while (strings.indexOfAny(ptr, "\\/")) |i| {
const sep = switch (opts.path_sep) {
.windows => '\\',
.posix => '/',
.auto => std.fs.path.sep,
.any => ptr[i],
};
try writer.writeAll(ptr[0..i]);
try writer.writeByte(sep);
if (opts.escape_backslashes and sep == '\\') {
try writer.writeByte(sep);
}
ptr = ptr[i + 1 ..];
}
try writer.writeAll(ptr);
return;
}
try writer.writeAll(self.buf);
}
};
pub const PathFormatOptions = struct {
// The path separator used when formatting the path.
path_sep: Sep = .any,
/// Any backslashes are escaped, including backslashes
/// added through `path_sep`.
escape_backslashes: bool = false,
pub const Sep = enum {
/// Keep paths separators as is.
any,
/// Replace all path separators with the current platform path separator.
auto,
/// Replace all path separators with `/`.
posix,
/// Replace all path separators with `\`.
windows,
};
};
pub const FormatOSPath = if (Environment.isWindows) FormatUTF16 else FormatUTF8;
pub fn fmtOSPath(buf: bun.OSPathSlice, options: PathFormatOptions) FormatOSPath {
return FormatOSPath{
.buf = buf,
.path_fmt_opts = options,
};
}
pub fn fmtPath(
comptime T: type,
path: []const T,
options: PathFormatOptions,
) if (T == u8) FormatUTF8 else FormatUTF16 {
if (T == u8) {
return FormatUTF8{
.buf = path,
.path_fmt_opts = options,
};
}
return FormatUTF16{
.buf = path,
.path_fmt_opts = options,
};
}
pub fn formatLatin1(slice_: []const u8, writer: anytype) !void {
var chunk = getSharedBuffer();
var slice = slice_;
// Defensively ensure recursion doesn't cause the buffer to be overwritten in-place
shared_temp_buffer_ptr = null;
defer {
if (shared_temp_buffer_ptr) |existing| {
if (existing != chunk.ptr) {
bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr)));
}
} else {
shared_temp_buffer_ptr = @ptrCast(chunk.ptr);
}
}
while (strings.firstNonASCII(slice)) |i| {
if (i > 0) {
try writer.writeAll(slice[0..i]);
slice = slice[i..];
}
const result = strings.copyLatin1IntoUTF8(chunk, @TypeOf(slice), slice[0..@min(chunk.len, slice.len)]);
if (result.read == 0 or result.written == 0)
break;
try writer.writeAll(chunk[0..result.written]);
slice = slice[result.read..];
}
if (slice.len > 0)
try writer.writeAll(slice); // write the remaining bytes
}
pub const URLFormatter = struct {
proto: Proto = .http,
hostname: ?string = null,
port: ?u16 = null,
const Proto = enum {
http,
https,
unix,
abstract,
};
pub fn format(this: URLFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("{s}://", .{switch (this.proto) {
.http => "http",
.https => "https",
.unix => "unix",
.abstract => "abstract",
}});
if (this.hostname) |hostname| {
const needs_brackets = hostname[0] != '[' and strings.isIPV6Address(hostname);
if (needs_brackets) {
try writer.print("[{s}]", .{hostname});
} else {
try writer.writeAll(hostname);
}
} else {
try writer.writeAll("localhost");
}
if (this.proto == .unix) {
return;
}
const is_port_optional = this.port == null or (this.proto == .https and this.port == 443) or
(this.proto == .http and this.port == 80);
if (is_port_optional) {
try writer.writeAll("/");
} else {
try writer.print(":{d}/", .{this.port.?});
}
}
};
pub const HostFormatter = struct {
host: string,
port: ?u16 = null,
is_https: bool = false,
pub fn format(formatter: HostFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (strings.indexOfChar(formatter.host, ':') != null) {
try writer.writeAll(formatter.host);
return;
}
try writer.writeAll(formatter.host);
const is_port_optional = formatter.port == null or (formatter.is_https and formatter.port == 443) or
(!formatter.is_https and formatter.port == 80);
if (!is_port_optional) {
try writer.print(":{d}", .{formatter.port.?});
return;
}
}
};
/// Format a string to an ECMAScript identifier.
/// Unlike the string_mutable.zig version, this always allocate/copy
pub fn fmtIdentifier(name: string) FormatValidIdentifier {
return FormatValidIdentifier{ .name = name };
}
/// Format a string to an ECMAScript identifier.
/// Different implementation than string_mutable because string_mutable may avoid allocating
/// This will always allocate
pub const FormatValidIdentifier = struct {
name: string,
pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
var iterator = strings.CodepointIterator.init(self.name);
var cursor = strings.CodepointIterator.Cursor{};
var has_needed_gap = false;
var needs_gap = false;
var start_i: usize = 0;
if (!iterator.next(&cursor)) {
try writer.writeAll("_");
return;
}
// Common case: no gap necessary. No allocation necessary.
needs_gap = !js_lexer.isIdentifierStart(cursor.c);
if (!needs_gap) {
// Are there any non-alphanumeric chars at all?
while (iterator.next(&cursor)) {
if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) {
needs_gap = true;
start_i = cursor.i;
break;
}
}
}
if (needs_gap) {
needs_gap = false;
if (start_i > 0) try writer.writeAll(self.name[0..start_i]);
var slice = self.name[start_i..];
iterator = strings.CodepointIterator.init(slice);
cursor = strings.CodepointIterator.Cursor{};
while (iterator.next(&cursor)) {
if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) {
if (needs_gap) {
try writer.writeAll("_");
needs_gap = false;
has_needed_gap = true;
}
try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]);
} else if (!needs_gap) {
needs_gap = true;
// skip the code point, replace it with a single _
}
}
// If it ends with an emoji
if (needs_gap) {
try writer.writeAll("_");
needs_gap = false;
has_needed_gap = true;
}
return;
}
try writer.writeAll(self.name);
}
};
// Formats a string to be safe to output in a Github action.
// - Encodes "\n" as "%0A" to support multi-line strings.
// https://github.com/actions/toolkit/issues/193#issuecomment-605394935
// - Strips ANSI output as it will appear malformed.
pub fn githubActionWriter(writer: anytype, self: string) !void {
var offset: usize = 0;
const end = @as(u32, @truncate(self.len));
while (offset < end) {
if (strings.indexOfNewlineOrNonASCIIOrANSI(self, @as(u32, @truncate(offset)))) |i| {
const byte = self[i];
if (byte > 0x7F) {
offset += @max(strings.wtf8ByteSequenceLength(byte), 1);
continue;
}
if (i > 0) {
try writer.writeAll(self[offset..i]);
}
var n: usize = 1;
if (byte == '\n') {
try writer.writeAll("%0A");
} else if (i + 1 < end) {
const next = self[i + 1];
if (byte == '\r' and next == '\n') {
n += 1;
try writer.writeAll("%0A");
} else if (byte == '\x1b' and next == '[') {
n += 1;
if (i + 2 < end) {
const remain = self[(i + 2)..@min(i + 5, end)];
if (strings.indexOfChar(remain, 'm')) |j| {
n += j + 1;
}
}
}
}
offset = i + n;
} else {
try writer.writeAll(self[offset..end]);
break;
}
}
}
pub const GithubActionFormatter = struct {
text: string,
pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try githubActionWriter(writer, this.text);
}
};
pub fn githubAction(self: string) strings.GithubActionFormatter {
return GithubActionFormatter{
.text = self,
};
}
pub fn quotedWriter(writer: anytype, self: string) !void {
const remain = self;
if (strings.containsNewlineOrNonASCIIOrQuote(remain)) {
try bun.js_printer.writeJSONString(self, @TypeOf(writer), writer, strings.Encoding.utf8);
} else {
try writer.writeAll("\"");
try writer.writeAll(self);
try writer.writeAll("\"");
}
}
pub const QuotedFormatter = struct {
text: []const u8,
pub fn format(this: QuotedFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try quotedWriter(writer, this.text);
}
};
pub fn fmtJavaScript(text: []const u8, opts: QuickAndDirtyJavaScriptSyntaxHighlighter.Options) QuickAndDirtyJavaScriptSyntaxHighlighter {
return QuickAndDirtyJavaScriptSyntaxHighlighter{
.text = text,
.opts = opts,
};
}
pub const QuickAndDirtyJavaScriptSyntaxHighlighter = struct {
text: []const u8,
opts: Options,
pub const Options = struct {
enable_colors: bool,
check_for_unhighlighted_write: bool = true,
redact_sensitive_information: bool = false,
pub const default: Options = .{
.enable_colors = Output.enable_ansi_colors,
.check_for_no_highlighting = true,
.redact_sensitive_information = false,
};
};
const ColorCode = enum {
magenta,
blue,
orange,
red,
pink,
pub fn color(this: ColorCode) []const u8 {
return switch (this) {
.magenta => "\x1b[35m",
.blue => "\x1b[34m",
.orange => "\x1b[33m",
.red => "\x1b[31m",
// light pink
.pink => "\x1b[38;5;206m",
};
}
};
pub const Keyword = enum {
abstract,
as,
@"async",
@"await",
case,
@"catch",
class,
@"const",
@"continue",
debugger,
default,
delete,
do,
@"else",
@"enum",
@"export",
extends,
false,
finally,
@"for",
function,
@"if",
implements,
import,
in,
instanceof,
interface,
let,
new,
null,
package,
private,
protected,
public,
@"return",
static,
super,
@"switch",
this,
throw,
@"break",
true,
@"try",
type,
typeof,
@"var",
void,
@"while",
with,
yield,
string,
number,
boolean,
symbol,
any,
object,
unknown,
never,
namespace,
declare,
readonly,
undefined,
pub fn colorCode(this: Keyword) ColorCode {
return switch (this) {
.abstract => .blue,
.as => .blue,
.@"async" => .magenta,
.@"await" => .magenta,
.case => .magenta,
.@"catch" => .magenta,
.class => .magenta,
.@"const" => .magenta,
.@"continue" => .magenta,
.debugger => .magenta,
.default => .magenta,
.delete => .red,
.do => .magenta,
.@"else" => .magenta,
.@"break" => .magenta,
.undefined => .orange,
.@"enum" => .blue,
.@"export" => .magenta,
.extends => .magenta,
.false => .orange,
.finally => .magenta,
.@"for" => .magenta,
.function => .magenta,
.@"if" => .magenta,
.implements => .blue,
.import => .magenta,
.in => .magenta,
.instanceof => .magenta,
.interface => .blue,
.let => .magenta,
.new => .magenta,
.null => .orange,
.package => .magenta,
.private => .blue,
.protected => .blue,
.public => .blue,
.@"return" => .magenta,
.static => .magenta,
.super => .magenta,
.@"switch" => .magenta,
.this => .orange,
.throw => .magenta,
.true => .orange,
.@"try" => .magenta,
.type => .blue,
.typeof => .magenta,
.@"var" => .magenta,
.void => .magenta,
.@"while" => .magenta,
.with => .magenta,
.yield => .magenta,
.string => .blue,
.number => .blue,
.boolean => .blue,
.symbol => .blue,
.any => .blue,
.object => .blue,
.unknown => .blue,
.never => .blue,
.namespace => .blue,
.declare => .blue,
.readonly => .blue,
};
}
};
pub const Keywords = bun.ComptimeEnumMap(Keyword);
pub const RedactedKeyword = enum {
_auth,
_authToken,
token,
_password,
email,
};
pub const RedactedKeywords = bun.ComptimeEnumMap(RedactedKeyword);
pub fn format(this: @This(), comptime unused_fmt: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
comptime bun.assert(unused_fmt.len == 0);
var text = this.text;
if (this.opts.check_for_unhighlighted_write) {
if (!this.opts.enable_colors or text.len > 2048 or text.len == 0 or !strings.isAllASCII(text)) {
if (this.opts.redact_sensitive_information) {
try writer.print("{}", .{redactedSource(text)});
} else {
try writer.writeAll(text);
}
return;
}
}
var prev_keyword: ?Keyword = null;
var should_redact_value = false;
outer: while (text.len > 0) {
if (js_lexer.isIdentifierStart(text[0])) {
var i: usize = 1;
while (i < text.len and js_lexer.isIdentifierContinue(text[i])) {
i += 1;
}
if (Keywords.get(text[0..i])) |keyword| {
should_redact_value = false;
if (keyword != .as)
prev_keyword = keyword;
const code = keyword.colorCode();
try writer.print(Output.prettyFmt("<r>{s}{s}<r>", true), .{ code.color(), text[0..i] });
} else {
should_redact_value = this.opts.redact_sensitive_information and RedactedKeywords.has(text[0..i]);
write: {
if (prev_keyword) |prev| {
switch (prev) {
.new => {
prev_keyword = null;
if (i < text.len and text[i] == '(') {
try writer.print(Output.prettyFmt("<r><b>{s}<r>", true), .{text[0..i]});
break :write;
}
},
.abstract, .namespace, .declare, .type, .interface => {
try writer.print(Output.prettyFmt("<r><b><blue>{s}<r>", true), .{text[0..i]});
prev_keyword = null;
break :write;
},
.import => {
if (strings.eqlComptime(text[0..i], "from")) {
const code = ColorCode.magenta;
try writer.print(Output.prettyFmt("<r>{s}{s}<r>", true), .{ code.color(), text[0..i] });
prev_keyword = null;
break :write;
}
},
else => {},
}
}
try writer.writeAll(text[0..i]);
}
}
text = text[i..];
} else {
if (this.opts.redact_sensitive_information and should_redact_value) {
while (text.len > 0 and std.ascii.isWhitespace(text[0])) {
try writer.writeByte(text[0]);
text = text[1..];
}
if (text.len > 0 and (text[0] == '=' or text[0] == ':')) {
try writer.writeByte(text[0]);
text = text[1..];
while (text.len > 0 and std.ascii.isWhitespace(text[0])) {
try writer.writeByte(text[0]);
text = text[1..];
}
if (text.len == 0) return;
}
}
switch (text[0]) {
'0'...'9' => |num| {
if (this.opts.redact_sensitive_information) {
if (should_redact_value) {
should_redact_value = false;
const end = strings.indexOfChar(text, '\n') orelse text.len;
text = text[end..];
try writer.writeAll(Output.prettyFmt("<r><yellow>***<r>", true));
continue;
}
if (strings.startsWithUUID(text)) {
text = text[36..];
try writer.writeAll(Output.prettyFmt("<r><yellow>***<r>", true));
continue;
}
}
prev_keyword = null;
var i: usize = 1;
if (text.len > 1 and num == '0' and text[1] == 'x') {
i += 1;
while (i < text.len and switch (text[i]) {
'0'...'9', 'a'...'f', 'A'...'F' => true,
else => false,
}) {
i += 1;
}
} else {
while (i < text.len and switch (text[i]) {
'0'...'9', '.', 'e', 'E', 'x', 'X', 'b', 'B', 'o', 'O' => true,
else => false,
}) {
i += 1;
}
}
try writer.print(Output.prettyFmt("<r><yellow>{s}<r>", true), .{text[0..i]});
text = text[i..];
},
inline '`', '"', '\'' => |char| {
prev_keyword = null;
var i: usize = 1;
while (i < text.len and text[i] != char) {
// if we're redacting, no need to syntax highlight contents
if (!should_redact_value and char == '`') {
if (text[i] == '$' and i + 1 < text.len and text[i + 1] == '{') {
const curly_start = i;
i += 2;
while (i < text.len and text[i] != '}') {
if (text[i] == '\\') {
i += 1;
}
i += 1;
}
try writer.print(Output.prettyFmt("<r><green>{s}<r>", true), .{text[0..curly_start]});
try writer.writeAll("${");
var opts = this.opts;
opts.check_for_unhighlighted_write = false;
const curly_remain = QuickAndDirtyJavaScriptSyntaxHighlighter{
.text = text[curly_start + 2 .. i],
.opts = opts,
};
if (curly_remain.text.len > 0) {
try curly_remain.format("", .{}, writer);
}
if (i < text.len and text[i] == '}') {
i += 1;
}
try writer.writeAll("}");
text = text[i..];
i = 0;
if (text.len > 0 and text[0] == char) {
try writer.writeAll(Output.prettyFmt("<r><green>`<r>", true));
text = text[1..];
continue :outer;
}
continue;
}
}
if (i + 1 < text.len and text[i] == '\\') {
i += 1;
}
i += 1;
}
// Include the trailing quote, if any
i += @intFromBool(i < text.len);
if (should_redact_value) {
should_redact_value = false;
if (i > 2 and text[i - 1] == char) {
const len = text[0..i].len - 2;
try writer.print(Output.prettyFmt("<r><green>{c}", true), .{char});
try writer.writeByteNTimes('*', len);
try writer.print(Output.prettyFmt("{c}<r>", true), .{char});
} else {
try writer.writeByteNTimes('*', text[0..i].len);
}
text = text[i..];
continue;
} else if (this.opts.redact_sensitive_information) {
try_redact: {
var inner = text[1..i];
if (inner.len > 0 and inner[inner.len - 1] == char) {
inner = inner[0 .. inner.len - 1];
}
if (inner.len == 0) {
break :try_redact;
}
if (inner.len == 36 and strings.isUUID(inner)) {
try writer.print(Output.prettyFmt("<r><green>{c}", true), .{char});
try writer.writeByteNTimes('*', 36);
try writer.print(Output.prettyFmt("{c}<r>", true), .{char});
text = text[i..];
continue;
}
const npm_secret_len = strings.startsWithNpmSecret(inner);
if (npm_secret_len != 0) {
try writer.print(Output.prettyFmt("<r><green>{c}", true), .{char});
try writer.writeByteNTimes('*', npm_secret_len);
try writer.print(Output.prettyFmt("{c}<r>", true), .{char});
text = text[i..];
continue;
}
if (strings.findUrlPassword(inner)) |url_pass| {
const offset, const len = url_pass;
try writer.print(Output.prettyFmt("<r><green>{c}{s}", true), .{
char,
inner[0..offset],
});
try writer.writeByteNTimes('*', len);
try writer.print(Output.prettyFmt("{s}{c}<r>", true), .{
inner[offset + len ..],
char,
});
text = text[i..];
continue;
}
}
try writer.print(Output.prettyFmt("<r><green>{s}<r>", true), .{text[0..i]});
text = text[i..];
continue;
}
try writer.print(Output.prettyFmt("<r><green>{s}<r>", true), .{text[0..i]});
text = text[i..];
},
'/' => {
prev_keyword = null;
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
var i: usize = 1;
// the start of a line comment
if (i < text.len and text[i] == '/') {
while (i < text.len and text[i] != '\n') {
i += 1;
}
const remain_to_print = text[0..i];
if (i < text.len and text[i] == '\n') {
i += 1;
}
if (i < text.len and text[i] == '\r') {
i += 1;
}
if (this.opts.redact_sensitive_information) {
try writer.print(Output.prettyFmt("<r><d>{}<r>", true), .{redactedSource(remain_to_print)});
} else {
try writer.print(Output.prettyFmt("<r><d>{s}<r>", true), .{remain_to_print});
}
text = text[i..];
continue;
}
as_multiline_comment: {
if (i < text.len and text[i] == '*') {
i += 1;
while (i + 2 < text.len and !strings.eqlComptime(text[i..][0..2], "*/")) {
i += 1;
}
if (i + 2 < text.len and strings.eqlComptime(text[i..][0..2], "*/")) {
i += 2;
} else {
i = 1;
break :as_multiline_comment;
}
if (this.opts.redact_sensitive_information) {
try writer.print(Output.prettyFmt("<r><d>{}<r>", true), .{redactedSource(text[0..i])});
} else {
try writer.print(Output.prettyFmt("<r><d>{s}<r>", true), .{text[0..i]});
}
text = text[i..];
continue;
}
}
try writer.writeAll(text[0..i]);
text = text[i..];
},
'}', '{' => |brace| {
// support potentially highlighting "from" in an import statement
if ((prev_keyword orelse Keyword.@"continue") != .import) {
prev_keyword = null;
}
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
try writer.writeByte(brace);
text = text[1..];
},
'[', ']' => |bracket| {
prev_keyword = null;
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
try writer.writeByte(bracket);
text = text[1..];
},
';' => {
prev_keyword = null;
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
try writer.print(Output.prettyFmt("<r><d>;<r>", true), .{});
text = text[1..];
},
'.' => {
prev_keyword = null;
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
var i: usize = 1;
if (text.len > 1 and (js_lexer.isIdentifierStart(text[1]) or text[1] == '#')) {
i = 2;
while (i < text.len and js_lexer.isIdentifierContinue(text[i])) {
i += 1;
}
if (i < text.len and (text[i] == '(')) {
try writer.print(Output.prettyFmt("<r><i><b>{s}<r>", true), .{text[0..i]});
text = text[i..];
continue;
}
i = 1;
}
try writer.writeByte(text[0]);
text = text[1..];
},
'<' => {
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
var i: usize = 1;
// JSX
jsx: {
if (text.len > 1 and text[0] == '/') {
i = 2;
}
prev_keyword = null;
while (i < text.len and js_lexer.isIdentifierContinue(text[i])) {
i += 1;
} else {
i = 1;
break :jsx;
}
while (i < text.len and text[i] != '>') {
i += 1;
if (i < text.len and text[i] == '<') {
i = 1;
break :jsx;
}
}
if (i < text.len and text[i] == '>') {
i += 1;
try writer.print(Output.prettyFmt("<r><cyan>{s}<r>", true), .{text[0..i]});
text = text[i..];
continue;
}
i = 1;
}
try writer.print(Output.prettyFmt("<r>{s}<r>", true), .{text[0..i]});
text = text[i..];
},
else => |c| {
if (should_redact_value) {
should_redact_value = false;
const len = strings.indexOfChar(text, '\n') orelse text.len;
try writer.writeByteNTimes('*', len);
text = text[len..];
continue;
}
try writer.writeByte(c);
text = text[1..];
},
}
}
}
}
};
pub fn quote(self: string) bun.fmt.QuotedFormatter {
return bun.fmt.QuotedFormatter{
.text = self,
};
}
pub fn EnumTagListFormatter(comptime Enum: type, comptime Separator: @Type(.enum_literal)) type {
return struct {
pretty: bool = true,
const output = brk: {
var text: []const u8 = "";
const names = std.meta.fieldNames(Enum);
for (names, 0..) |name, i| {
if (Separator == .list) {
if (i > 0) {
if (i + 1 == names.len) {
text = text ++ ", or ";
} else {
text = text ++ ", ";
}
}
text = text ++ "\"" ++ name ++ "\"";
} else if (Separator == .dash) {
text = text ++ "\n- " ++ name;
} else {
@compileError("Unknown separator type: must be .dash or .list");
}
}
break :brk text;
};
pub fn format(_: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
try writer.writeAll(output);
}
};
}
pub fn enumTagList(comptime Enum: type, comptime separator: @Type(.enum_literal)) EnumTagListFormatter(Enum, separator) {
return EnumTagListFormatter(Enum, separator){};
}
pub fn formatIp(address: std.net.Address, into: []u8) ![]u8 {
// std.net.Address.format includes `:<port>` and square brackets (IPv6)
// while Node does neither. This uses format then strips these to bring
// the result into conformance with Node.
var result = try std.fmt.bufPrint(into, "{}", .{address});
// Strip `:<port>`
if (std.mem.lastIndexOfScalar(u8, result, ':')) |colon| {
result = result[0..colon];
}
// Strip brackets
if (result[0] == '[' and result[result.len - 1] == ']') {
result = result[1 .. result.len - 1];
}
return result;
}
// https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/
pub fn fastDigitCount(x: u64) u64 {
if (x == 0) {
return 1;
}
const table = [_]u64{
4294967296,
8589934582,
8589934582,
8589934582,
12884901788,
12884901788,
12884901788,
17179868184,
17179868184,
17179868184,
21474826480,
21474826480,
21474826480,
21474826480,
25769703776,
25769703776,
25769703776,
30063771072,
30063771072,
30063771072,
34349738368,
34349738368,
34349738368,
34349738368,
38554705664,
38554705664,
38554705664,
41949672960,
41949672960,
41949672960,
42949672960,
42949672960,
};
return x + table[std.math.log2(x)] >> 32;
}
pub const SizeFormatter = struct {
value: usize = 0,
opts: Options,
pub const Options = struct {
space_between_number_and_unit: bool = true,
};
pub fn format(self: SizeFormatter, comptime _: []const u8, opts: fmt.FormatOptions, writer: anytype) !void {
const math = std.math;
const value = self.value;
if (value == 0) {
if (self.opts.space_between_number_and_unit) {
return writer.writeAll("0 KB");
} else {
return writer.writeAll("0KB");
}
}
if (value < 512) {
try fmt.formatInt(self.value, 10, .lower, opts, writer);
if (self.opts.space_between_number_and_unit) {
return writer.writeAll(" bytes");
} else {
return writer.writeByte('B');
}
}
const mags_si = " KMGTPEZY";
const log2 = math.log2(value);
const magnitude = @min(log2 / comptime math.log2(1000), mags_si.len - 1);
const new_value = math.lossyCast(f64, value) / math.pow(f64, 1000, math.lossyCast(f64, magnitude));
const suffix = mags_si[magnitude];
if (suffix == ' ') {
if (self.opts.space_between_number_and_unit) {
try writer.print("{d:.2} KB", .{new_value / 1000.0});
} else {
try writer.print("{d:.2}KB", .{new_value / 1000.0});
}
return;
}
const precision: usize = if (std.math.approxEqAbs(f64, new_value, @trunc(new_value), 0.100)) 1 else 2;
try fmt.formatType(new_value, "d", .{ .precision = precision }, writer, 0);
if (self.opts.space_between_number_and_unit) {
try writer.writeAll(&.{ ' ', suffix, 'B' });
} else {
try writer.writeAll(&.{ suffix, 'B' });
}
}
};
pub fn size(bytes: anytype, opts: SizeFormatter.Options) SizeFormatter {
return .{
.value = switch (@TypeOf(bytes)) {
f64, f32, f128 => @intFromFloat(bytes),
i64, isize => @intCast(bytes),
else => bytes,
},
.opts = opts,
};
}
const lower_hex_table = [_]u8{
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f',
};
const upper_hex_table = [_]u8{
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
};
pub fn HexIntFormatter(comptime Int: type, comptime lower: bool) type {
return struct {
value: Int,
const table = if (lower) lower_hex_table else upper_hex_table;
const BufType = [@bitSizeOf(Int) / 4]u8;
fn getOutBuf(value: Int) BufType {
var buf: BufType = undefined;
inline for (&buf, 0..) |*c, i| {
// value relative to the current nibble
c.* = table[@as(u8, @as(u4, @truncate(value >> comptime ((buf.len - i - 1) * 4)))) & 0xF];
}
return buf;
}
pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
const value = self.value;
try writer.writeAll(&getOutBuf(value));
}
};
}
pub fn HexInt(comptime Int: type, comptime lower: std.fmt.Case, value: Int) HexIntFormatter(Int, lower == .lower) {
const Formatter = HexIntFormatter(Int, lower == .lower);
return Formatter{ .value = value };
}
pub fn hexIntLower(value: anytype) HexIntFormatter(@TypeOf(value), true) {
const Formatter = HexIntFormatter(@TypeOf(value), true);
return Formatter{ .value = value };
}
pub fn hexIntUpper(value: anytype) HexIntFormatter(@TypeOf(value), false) {
const Formatter = HexIntFormatter(@TypeOf(value), false);
return Formatter{ .value = value };
}
/// Equivalent to `{d:.<precision>}` but trims trailing zeros
/// if decimal part is less than `precision` digits.
fn TrimmedPrecisionFormatter(comptime precision: usize) type {
return struct {
num: f64,
precision: usize,
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
const whole = @trunc(self.num);
try writer.print("{d}", .{whole});
const rem = self.num - whole;
if (rem != 0) {
var buf: [2 + precision]u8 = undefined;
var formatted = std.fmt.bufPrint(&buf, "{d:." ++ std.fmt.comptimePrint("{d}", .{precision}) ++ "}", .{rem}) catch unreachable;
formatted = formatted[2..];
const trimmed = std.mem.trimRight(u8, formatted, "0");
try writer.print(".{s}", .{trimmed});
}
}
};
}
pub fn trimmedPrecision(value: f64, comptime precision: usize) TrimmedPrecisionFormatter(precision) {
const Formatter = TrimmedPrecisionFormatter(precision);
return Formatter{ .num = value, .precision = precision };
}
const FormatDurationData = struct {
ns: u64,
negative: bool = false,
};
/// This is copied from std.fmt.formatDuration, except it will only print one decimal instead of three
fn formatDurationOneDecimal(data: FormatDurationData, comptime _: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
// worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
var buf: [24]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
var buf_writer = fbs.writer();
if (data.negative) {
buf_writer.writeByte('-') catch unreachable;
}
var ns_remaining = data.ns;
inline for (.{
.{ .ns = 365 * std.time.ns_per_day, .sep = 'y' },
.{ .ns = std.time.ns_per_week, .sep = 'w' },
.{ .ns = std.time.ns_per_day, .sep = 'd' },
.{ .ns = std.time.ns_per_hour, .sep = 'h' },
.{ .ns = std.time.ns_per_min, .sep = 'm' },
}) |unit| {
if (ns_remaining >= unit.ns) {
const units = ns_remaining / unit.ns;
std.fmt.formatInt(units, 10, .lower, .{}, buf_writer) catch unreachable;
buf_writer.writeByte(unit.sep) catch unreachable;
ns_remaining -= units * unit.ns;
if (ns_remaining == 0)
return std.fmt.formatBuf(fbs.getWritten(), opts, writer);
}
}
inline for (.{
.{ .ns = std.time.ns_per_s, .sep = "s" },
.{ .ns = std.time.ns_per_ms, .sep = "ms" },
.{ .ns = std.time.ns_per_us, .sep = "us" },
}) |unit| {
const kunits = ns_remaining * 1000 / unit.ns;
if (kunits >= 1000) {
std.fmt.formatInt(kunits / 1000, 10, .lower, .{}, buf_writer) catch unreachable;
const frac = @divFloor(kunits % 1000, 100);
if (frac > 0) {
var decimal_buf = [_]u8{ '.', 0 };
_ = std.fmt.formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 1 });
buf_writer.writeAll(&decimal_buf) catch unreachable;
}
buf_writer.writeAll(unit.sep) catch unreachable;
return std.fmt.formatBuf(fbs.getWritten(), opts, writer);
}
}
std.fmt.formatInt(ns_remaining, 10, .lower, .{}, buf_writer) catch unreachable;
buf_writer.writeAll("ns") catch unreachable;
return std.fmt.formatBuf(fbs.getWritten(), opts, writer);
}
/// Return a Formatter for number of nanoseconds according to its magnitude:
/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s
pub fn fmtDurationOneDecimal(ns: u64) std.fmt.Formatter(formatDurationOneDecimal) {
return .{ .data = FormatDurationData{ .ns = ns } };
}
pub fn fmtSlice(data: anytype, comptime delim: []const u8) FormatSlice(@TypeOf(data), delim) {
return .{ .slice = data };
}
fn FormatSlice(comptime T: type, comptime delim: []const u8) type {
return struct {
slice: T,
pub fn format(self: @This(), comptime format_str: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
if (self.slice.len == 0) return;
const f = "{" ++ format_str ++ "}";
try writer.print(f, .{self.slice[0]});
for (self.slice[1..]) |item| {
if (delim.len > 0) try writer.writeAll(delim);
try writer.print(f, .{item});
}
}
};
}
/// Uses WebKit's double formatter
pub fn double(number: f64) FormatDouble {
return .{ .number = number };
}
pub const FormatDouble = struct {
number: f64,
extern fn WTF__dtoa(buf_124_bytes: *[124]u8, number: f64) usize;
pub fn dtoa(buf: *[124]u8, number: f64) []const u8 {
const len = WTF__dtoa(buf, number);
return buf[0..len];
}
pub fn dtoaWithNegativeZero(buf: *[124]u8, number: f64) []const u8 {
if (std.math.isNegativeZero(number)) {
return "-0";
}
const len = WTF__dtoa(buf, number);
return buf[0..len];
}
pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
var buf: [124]u8 = undefined;
const slice = dtoa(&buf, self.number);
try writer.writeAll(slice);
}
};
pub fn nullableFallback(value: anytype, null_fallback: []const u8) NullableFallback(@TypeOf(value)) {
return .{ .value = value, .null_fallback = null_fallback };
}
pub fn NullableFallback(comptime T: type) type {
return struct {
value: T,
null_fallback: []const u8,
pub fn format(self: @This(), comptime template: []const u8, opts: fmt.FormatOptions, writer: anytype) !void {
if (self.value) |value| {
try std.fmt.formatType(value, template, opts, writer, 4);
} else {
try writer.writeAll(self.null_fallback);
}
}
};
}
pub fn escapePowershell(str: []const u8) std.fmt.Formatter(escapePowershellImpl) {
return .{ .data = str };
}
fn escapePowershellImpl(str: []const u8, comptime f: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
comptime bun.assert(f.len == 0);
var remain = str;
while (bun.strings.indexOfAny(remain, "\"`")) |i| {
try writer.writeAll(remain[0..i]);
try writer.writeAll("`");
try writer.writeByte(remain[i]);
remain = remain[i + 1 ..];
}
try writer.writeAll(remain);
}
pub const js_bindings = struct {
const gen = bun.gen.fmt;
/// Internal function for testing in highlighter.test.ts
pub fn fmtString(global: *bun.JSC.JSGlobalObject, code: []const u8, formatter_id: gen.Formatter) bun.JSError!bun.String {
var buffer = bun.MutableString.initEmpty(bun.default_allocator);
defer buffer.deinit();
var writer = buffer.bufferedWriter();
switch (formatter_id) {
.highlight_javascript => {
const formatter = bun.fmt.fmtJavaScript(code, .{
.enable_colors = true,
.check_for_unhighlighted_write = false,
});
std.fmt.format(writer.writer(), "{}", .{formatter}) catch |err| {
return global.throwError(err, "while formatting");
};
},
.escape_powershell => {
std.fmt.format(writer.writer(), "{}", .{escapePowershell(code)}) catch |err| {
return global.throwError(err, "while formatting");
};
},
}
writer.flush() catch |err| {
return global.throwError(err, "while formatting");
};
return bun.String.createUTF8(buffer.list.items);
}
};
// Equivalent to ERR_OUT_OF_RANGE from
fn NewOutOfRangeFormatter(comptime T: type) type {
return struct {
value: T,
min: i64 = std.math.maxInt(i64),
max: i64 = std.math.maxInt(i64),
field_name: []const u8,
msg: []const u8 = "",
pub fn format(self: @This(), comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void {
if (self.field_name.len > 0) {
try writer.writeAll("The value of \"");
try writer.writeAll(self.field_name);
try writer.writeAll("\" is out of range. It must be ");
} else {
if (comptime Environment.isDebug) {
@panic("Set field_name plz");
}
try writer.writeAll("The value is out of range. It must be ");
}
const min = self.min;
const max = self.max;
const msg = self.msg;
if (min != std.math.maxInt(i64) and max != std.math.maxInt(i64)) {
try std.fmt.format(writer, ">= {d} and <= {d}.", .{ min, max });
} else if (min != std.math.maxInt(i64)) {
try std.fmt.format(writer, ">= {d}.", .{min});
} else if (max != std.math.maxInt(i64)) {
try std.fmt.format(writer, "<= {d}.", .{max});
} else if (msg.len > 0) {
try writer.writeAll(msg);
try writer.writeByte('.');
} else {
try writer.writeAll("within the range of values for type ");
try writer.writeAll(comptime @typeName(T));
try writer.writeAll(".");
}
if (comptime T == f64 or T == f32) {
try std.fmt.format(writer, " Received {}", .{double(self.value)});
} else if (comptime T == []const u8) {
try std.fmt.format(writer, " Received {s}", .{self.value});
} else {
try std.fmt.format(writer, " Received {d}", .{self.value});
}
}
};
}
const DoubleOutOfRangeFormatter = NewOutOfRangeFormatter(f64);
const IntOutOfRangeFormatter = NewOutOfRangeFormatter(i64);
const StringOutOfRangeFormatter = NewOutOfRangeFormatter([]const u8);
const BunStringOutOfRangeFormatter = NewOutOfRangeFormatter(bun.String);
fn OutOfRangeFormatter(comptime T: type) type {
if (T == f64 or T == f32) {
return DoubleOutOfRangeFormatter;
} else if (T == []const u8) {
return StringOutOfRangeFormatter;
} else if (T == bun.String) {
return BunStringOutOfRangeFormatter;
}
return IntOutOfRangeFormatter;
}
pub const OutOfRangeOptions = struct {
min: i64 = std.math.maxInt(i64),
max: i64 = std.math.maxInt(i64),
field_name: []const u8,
msg: []const u8 = "",
};
pub fn outOfRange(value: anytype, options: OutOfRangeOptions) OutOfRangeFormatter(@TypeOf(value)) {
return .{ .value = value, .min = options.min, .max = options.max, .field_name = options.field_name, .msg = options.msg };
}
/// esbuild has an 8 character truncation of a base32 encoded bytes. this
/// is not exactly that, but it will appear as such. the character list
/// chosen omits similar characters in the unlikely case someone is
/// trying to memorize a hash.
///
/// this hash is used primarily for the hashes in bundler chunk file names. the
/// output is all lowercase to avoid issues with case-insensitive filesystems.
pub fn truncatedHash32(int: u64) std.fmt.Formatter(truncatedHash32Impl) {
return .{ .data = int };
}
fn truncatedHash32Impl(int: u64, comptime fmt_str: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
comptime bun.assert(fmt_str.len == 0);
const in_bytes = std.mem.asBytes(&int);
const chars = "0123456789abcdefghjkmnpqrstvwxyz";
try writer.writeAll(&.{
chars[in_bytes[0] & 31],
chars[in_bytes[1] & 31],
chars[in_bytes[2] & 31],
chars[in_bytes[3] & 31],
chars[in_bytes[4] & 31],
chars[in_bytes[5] & 31],
chars[in_bytes[6] & 31],
chars[in_bytes[7] & 31],
});
}