mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 03:48:56 +00:00
771 lines
28 KiB
Zig
771 lines
28 KiB
Zig
const std = @import("std");
|
|
const logger = @import("logger.zig");
|
|
const js_lexer = @import("js_lexer.zig");
|
|
const importRecord = @import("import_record.zig");
|
|
const js_ast = @import("js_ast.zig");
|
|
const options = @import("options.zig");
|
|
const alloc = @import("alloc.zig");
|
|
const rename = @import("renamer.zig");
|
|
|
|
const fs = @import("fs.zig");
|
|
usingnamespace @import("strings.zig");
|
|
usingnamespace @import("ast/base.zig");
|
|
usingnamespace js_ast.G;
|
|
|
|
const expect = std.testing.expect;
|
|
const ImportKind = importRecord.ImportKind;
|
|
const BindingNodeIndex = js_ast.BindingNodeIndex;
|
|
|
|
const Ref = js_ast.Ref;
|
|
const LocRef = js_ast.LocRef;
|
|
const S = js_ast.S;
|
|
const B = js_ast.B;
|
|
const G = js_ast.G;
|
|
const T = js_lexer.T;
|
|
const E = js_ast.E;
|
|
const Stmt = js_ast.Stmt;
|
|
const Expr = js_ast.Expr;
|
|
const Binding = js_ast.Binding;
|
|
const Symbol = js_ast.Symbol;
|
|
const Level = js_ast.Op.Level;
|
|
const Op = js_ast.Op;
|
|
const Scope = js_ast.Scope;
|
|
const locModuleScope = logger.Loc.Empty;
|
|
const Ast = js_ast.Ast;
|
|
|
|
const hex_chars = "0123456789ABCDEF";
|
|
const first_ascii = 0x20;
|
|
const last_ascii = 0x7E;
|
|
const first_high_surrogate: u21 = 0xD800;
|
|
const last_high_surrogate: u21 = 0xDBFF;
|
|
const first_low_surrogate: u21 = 0xDC00;
|
|
const last_low_surrogate: u21 = 0xDFFF;
|
|
|
|
fn notimpl() void {
|
|
std.debug.panic("Not implemented yet!", .{});
|
|
}
|
|
|
|
pub const SourceMapChunk = struct {
|
|
buffer: MutableString,
|
|
end_state: State = State{},
|
|
final_generated_column: usize = 0,
|
|
should_ignore: bool = false,
|
|
|
|
// Coordinates in source maps are stored using relative offsets for size
|
|
// reasons. When joining together chunks of a source map that were emitted
|
|
// in parallel for different parts of a file, we need to fix up the first
|
|
// segment of each chunk to be relative to the end of the previous chunk.
|
|
pub const State = struct {
|
|
// This isn't stored in the source map. It's only used by the bundler to join
|
|
// source map chunks together correctly.
|
|
generated_line: i32 = 0,
|
|
|
|
// These are stored in the source map in VLQ format.
|
|
generated_column: i32 = 0,
|
|
source_index: i32 = 0,
|
|
original_line: i32 = 0,
|
|
original_column: i32 = 0,
|
|
};
|
|
};
|
|
|
|
pub const Options = struct {
|
|
to_module_ref: js_ast.Ref,
|
|
indent: usize = 0,
|
|
// If we're writing out a source map, this table of line start indices lets
|
|
// us do binary search on to figure out what line a given AST node came from
|
|
// line_offset_tables: []LineOffsetTable
|
|
};
|
|
|
|
pub const PrintResult = struct { js: string, source_map: ?SourceMapChunk = null };
|
|
|
|
const ExprFlag = enum {
|
|
forbid_call,
|
|
forbid_in,
|
|
has_non_optional_chain_parent,
|
|
expr_result_is_unused,
|
|
};
|
|
|
|
pub fn NewPrinter(comptime ascii_only: bool) type {
|
|
// comptime const comptime_buf_len = 64;
|
|
// comptime var comptime_buf = [comptime_buf_len]u8{};
|
|
// comptime var comptime_buf_i: usize = 0;
|
|
|
|
return struct {
|
|
symbols: Symbol.Map,
|
|
import_records: []importRecord.ImportRecord,
|
|
|
|
js: MutableString,
|
|
|
|
needs_semicolon: bool = false,
|
|
stmt_start: i32 = -1,
|
|
options: Options,
|
|
export_default_start: i32 = -1,
|
|
arrow_expr_start: i32 = -1,
|
|
for_of_init_start: i32 = -1,
|
|
prev_op: Op.Code = Op.Code.bin_add,
|
|
prev_op_end: i32 = -1,
|
|
prev_num_end: i32 = -1,
|
|
prev_reg_exp_end: i32 = -1,
|
|
call_target: ?Expr.Data = null,
|
|
int_to_bytes_buffer: [64]u8 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
writer: MutableString.Writer,
|
|
allocator: *std.mem.Allocator,
|
|
|
|
const Printer = @This();
|
|
pub fn comptime_flush(p: *Printer) void {}
|
|
// pub fn comptime_flush(p: *Printer) callconv(.Inline) void {
|
|
// const result = comptime {
|
|
// if (comptime_buf_i > 0) {
|
|
// return comptime_buf[0..comptime_buf_i];
|
|
// } else {
|
|
// return "";
|
|
// }
|
|
// };
|
|
|
|
// if (result.len) {
|
|
// p.print(result);
|
|
// comptime {
|
|
// if (comptime_buf_i > 0) {
|
|
// comptime_buf_i = 0;
|
|
// while (comptime_buf_i < comptime_buf_i) {
|
|
// comptime_buf[comptime_buf_i] = 0;
|
|
// comptime_buf_i += 1;
|
|
// }
|
|
// comptime_buf_i = 0;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// pub fn comptime_print(p: *Printer, str: comptime []const u8) callconv(.Inline) void {
|
|
// comptime const needsFlush = (str.len + comptime_buf_i >= comptime_buf_len - 1);
|
|
// if (needsFlush) {
|
|
// p.comptime_flush();
|
|
// }
|
|
|
|
// comptime {
|
|
// if (str.len > 63) {
|
|
// @compileError("comptime_print buffer overflow");
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
// comptime {
|
|
// comptime str_i = 0;
|
|
// while (str_i < str.len) {
|
|
// comptime_buf[comptime_buf_i] = str[str_i];
|
|
// comptime_buf_i += 1;
|
|
// str_i += 1;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
pub fn print(p: *Printer, str: anytype) void {
|
|
switch (@TypeOf(str)) {
|
|
comptime_int => {
|
|
p.js.appendChar(str) catch unreachable;
|
|
},
|
|
string => {
|
|
p.js.append(str) catch unreachable;
|
|
},
|
|
u8 => {
|
|
p.js.appendChar(str) catch unreachable;
|
|
},
|
|
u16 => {
|
|
p.js.appendChar(@intCast(u8, str)) catch unreachable;
|
|
},
|
|
u21 => {
|
|
p.js.appendChar(@intCast(u8, str)) catch unreachable;
|
|
},
|
|
else => {
|
|
p.js.append(@as(string, str)) catch unreachable;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn unsafePrint(p: *Printer, str: string) void {
|
|
p.js.appendAssumeCapacity(str);
|
|
}
|
|
|
|
pub fn printIndent(p: *Printer) void {
|
|
comptime_flush(p);
|
|
|
|
if (p.options.indent == 0) {
|
|
return;
|
|
}
|
|
|
|
p.js.growBy(p.options.indent * " ".len) catch unreachable;
|
|
while (p.options.indent > 0) {
|
|
p.unsafePrint(" ");
|
|
p.options.indent -= 1;
|
|
}
|
|
}
|
|
|
|
pub fn printSpace(p: *Printer) void {
|
|
p.print(" ");
|
|
}
|
|
pub fn printNewline(p: *Printer) void {
|
|
notimpl();
|
|
}
|
|
pub fn printSemicolonAfterStatement(p: *Printer) void {
|
|
p.print(";\n");
|
|
}
|
|
pub fn printSemicolonIfNeeded(p: *Printer) void {
|
|
notimpl();
|
|
}
|
|
pub fn printSpaceBeforeIdentifier(
|
|
p: *Printer,
|
|
) void {
|
|
const n = p.js.len();
|
|
if (n > 0 and (js_lexer.isIdentifierContinue(p.js.list.items[n - 1]) or n == p.prev_reg_exp_end)) {
|
|
p.print(" ");
|
|
}
|
|
}
|
|
pub fn printDotThenPrefix(p: *Printer) Level {
|
|
return .lowest;
|
|
}
|
|
|
|
pub fn printUndefined(level: Level) void {
|
|
notimpl();
|
|
}
|
|
|
|
pub fn printBody(stmt: Stmt) void {
|
|
notimpl();
|
|
}
|
|
pub fn printBlock(loc: logger.Loc, stmts: []Stmt) void {
|
|
notimpl();
|
|
}
|
|
pub fn printDecls(keyword: string, decls: []G.Decl, flags: ExprFlag) void {
|
|
notimpl();
|
|
}
|
|
|
|
// noop for now
|
|
pub fn addSourceMapping(p: *Printer, loc: logger.Loc) void {}
|
|
|
|
pub fn printSymbol(p: *Printer, ref: Ref) void {
|
|
notimpl();
|
|
}
|
|
pub fn printClauseAlias(p: *Printer, alias: string) void {
|
|
notimpl();
|
|
}
|
|
pub fn printFunc(p: *Printer, func: G.Fn) void {
|
|
notimpl();
|
|
}
|
|
pub fn printClass(p: *Printer, class: G.Class) void {
|
|
notimpl();
|
|
}
|
|
|
|
pub fn bestQuoteCharForString(p: *Printer, str: JavascriptString, allow_backtick: bool) u8 {
|
|
var single_cost: usize = 0;
|
|
var double_cost: usize = 0;
|
|
var backtick_cost: usize = 0;
|
|
var char: u8 = 0;
|
|
var i: usize = 0;
|
|
while (i < str.len) {
|
|
switch (str[i]) {
|
|
'\'' => {
|
|
single_cost += 1;
|
|
},
|
|
'"' => {
|
|
double_cost += 1;
|
|
},
|
|
'`' => {
|
|
backtick_cost += 1;
|
|
},
|
|
'$' => {
|
|
if (i + 1 < str.len and str[i + 1] == '{') {
|
|
backtick_cost += 1;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
i += 1;
|
|
}
|
|
|
|
char = '"';
|
|
if (double_cost > single_cost) {
|
|
char = '\'';
|
|
|
|
if (single_cost > backtick_cost and allow_backtick) {
|
|
char = '`';
|
|
}
|
|
} else if (double_cost > backtick_cost and allow_backtick) {
|
|
char = '`';
|
|
}
|
|
|
|
return char;
|
|
}
|
|
|
|
pub fn printNonNegativeFloat(p: *Printer, float: f64) void {
|
|
// cool thing about languages like this
|
|
// i know this is going to be in the stack and not the heap
|
|
var parts = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
// normally, you pay the cost of parsing a string formatter at runtime
|
|
// not in zig! CI pays for it instead
|
|
// its probably still doing some unnecessary integer conversion somewhere though
|
|
var slice = std.fmt.bufPrint(&parts, "{d}", .{float}) catch unreachable;
|
|
p.js.list.appendSlice(p.allocator, slice) catch unreachable;
|
|
}
|
|
|
|
pub fn printQuotedUTF16(e: *Printer, text: JavascriptString, quote: u8) void {
|
|
// utf-8 is a max of 4 bytes
|
|
// we leave two extra chars for "\" and "u"
|
|
var temp = [6]u8{ 0, 0, 0, 0, 0, 0 };
|
|
var i: usize = 0;
|
|
const n: usize = text.len;
|
|
var r: u21 = 0;
|
|
var c: u21 = 0;
|
|
var width: u3 = 0;
|
|
|
|
e.js.growIfNeeded(text.len) catch unreachable;
|
|
|
|
while (i < n) {
|
|
c = text[i];
|
|
i += 1;
|
|
|
|
// TODO: here
|
|
switch (c) {
|
|
// Special-case the null character since it may mess with code written in C
|
|
// that treats null characters as the end of the string.
|
|
0x00 => {
|
|
// We don't want "\x001" to be written as "\01"
|
|
if (i < n and text[i] >= '0' and text[i] <= '9') {
|
|
e.print("\\x00");
|
|
} else {
|
|
e.print("\\0");
|
|
}
|
|
},
|
|
|
|
// Special-case the bell character since it may cause dumping this file to
|
|
// the terminal to make a sound, which is undesirable. Note that we can't
|
|
// use an octal literal to print this shorter since octal literals are not
|
|
// allowed in strict mode (or in template strings).
|
|
0x07 => {
|
|
e.print("\\x07");
|
|
},
|
|
0x08 => {
|
|
e.print("\\b");
|
|
},
|
|
0x0C => {
|
|
e.print("\\f");
|
|
},
|
|
'\n' => {
|
|
if (quote == '`') {
|
|
e.print("\n");
|
|
} else {
|
|
e.print("\\n");
|
|
}
|
|
},
|
|
std.ascii.control_code.CR => {
|
|
e.print("\\r");
|
|
},
|
|
// \v
|
|
std.ascii.control_code.VT => {
|
|
e.print("\\v");
|
|
},
|
|
// "\\"
|
|
92 => {
|
|
e.print("\\");
|
|
},
|
|
'\'' => {
|
|
if (quote == '\'') {
|
|
e.print("\\");
|
|
}
|
|
e.print("'");
|
|
},
|
|
'"' => {
|
|
if (quote == '"') {
|
|
e.print("\\");
|
|
}
|
|
|
|
e.print("\"");
|
|
},
|
|
'`' => {
|
|
if (quote == '`') {
|
|
e.print("\\");
|
|
}
|
|
|
|
e.print('`');
|
|
},
|
|
'$' => {
|
|
if (quote == '`' and i < n and text[i] == '{') {
|
|
e.print("\\");
|
|
}
|
|
|
|
e.print('$');
|
|
},
|
|
0x2028 => {
|
|
e.print("\\u2028");
|
|
},
|
|
0x2029 => {
|
|
e.print("\\u2029");
|
|
},
|
|
0xFEFF => {
|
|
e.print("\\uFEFF");
|
|
},
|
|
else => {
|
|
switch (c) {
|
|
// Common case: just append a single byte
|
|
// we know it's not 0 since we already checked
|
|
1...last_ascii => {
|
|
e.print(@intCast(u8, c));
|
|
},
|
|
first_high_surrogate...last_high_surrogate => {
|
|
|
|
// Is there a next character?
|
|
|
|
if (i < n) {
|
|
const c2 = text[i];
|
|
|
|
if (c2 >= first_high_surrogate and c2 <= last_low_surrogate) {
|
|
// this is some magic to me
|
|
r = (c << 10) + c2 + (0x10000 - (first_high_surrogate << 10) - first_low_surrogate);
|
|
i += 1;
|
|
// Escape this character if UTF-8 isn't allowed
|
|
if (ascii_only) {
|
|
// this is more magic!!
|
|
const bytes = [_]u8{
|
|
'\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15],
|
|
'\\', 'u', hex_chars[c2 >> 12], hex_chars[(c2 >> 8) & 15], hex_chars[(c2 >> 4) & 15], hex_chars[c2 & 15],
|
|
};
|
|
e.print(&bytes);
|
|
|
|
continue;
|
|
// Otherwise, encode to UTF-8
|
|
} else {
|
|
width = std.unicode.utf8Encode(r, &temp) catch unreachable;
|
|
e.print(temp[0..width]);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write an unpaired high surrogate
|
|
temp = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] };
|
|
e.print(&temp);
|
|
},
|
|
// Is this an unpaired low surrogate or four-digit hex escape?
|
|
first_low_surrogate...last_low_surrogate => {
|
|
// Write an unpaired high surrogate
|
|
temp = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] };
|
|
e.print(&temp);
|
|
},
|
|
else => {
|
|
// this extra branch should get compiled
|
|
if (ascii_only) {
|
|
if (c > 0xFF) {
|
|
// Write an unpaired high surrogate
|
|
temp = [_]u8{ '\\', 'u', hex_chars[c >> 12], hex_chars[(c >> 8) & 15], hex_chars[(c >> 4) & 15], hex_chars[c & 15] };
|
|
e.print(&temp);
|
|
} else {
|
|
// Can this be a two-digit hex escape?
|
|
const quad = [_]u8{ '\\', 'x', hex_chars[c >> 4], hex_chars[c & 15] };
|
|
e.print(&quad);
|
|
}
|
|
} else {
|
|
width = std.unicode.utf8Encode(c, &temp) catch unreachable;
|
|
e.print(temp[0..width]);
|
|
}
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn printExpr(p: *Printer, expr: Expr, level: Level, flags: ExprFlag) void {
|
|
p.addSourceMapping(expr.loc);
|
|
|
|
switch (expr.data) {
|
|
.e_missing => |e| {
|
|
notimpl();
|
|
},
|
|
.e_undefined => |e| {
|
|
notimpl();
|
|
},
|
|
.e_super => |e| {
|
|
notimpl();
|
|
},
|
|
.e_null => |e| {
|
|
notimpl();
|
|
},
|
|
.e_this => |e| {
|
|
notimpl();
|
|
},
|
|
.e_spread => |e| {
|
|
notimpl();
|
|
},
|
|
.e_new_target => |e| {
|
|
notimpl();
|
|
},
|
|
.e_import_meta => |e| {
|
|
notimpl();
|
|
},
|
|
.e_new => |e| {
|
|
notimpl();
|
|
},
|
|
.e_call => |e| {
|
|
notimpl();
|
|
},
|
|
.e_require => |e| {
|
|
notimpl();
|
|
},
|
|
.e_require_or_require_resolve => |e| {
|
|
notimpl();
|
|
},
|
|
.e_import => |e| {
|
|
notimpl();
|
|
},
|
|
.e_dot => |e| {
|
|
notimpl();
|
|
},
|
|
.e_index => |e| {
|
|
notimpl();
|
|
},
|
|
.e_if => |e| {
|
|
notimpl();
|
|
},
|
|
.e_arrow => |e| {
|
|
notimpl();
|
|
},
|
|
.e_function => |e| {
|
|
notimpl();
|
|
},
|
|
.e_class => |e| {
|
|
notimpl();
|
|
},
|
|
.e_array => |e| {
|
|
notimpl();
|
|
},
|
|
.e_object => |e| {
|
|
notimpl();
|
|
},
|
|
.e_boolean => |e| {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print(if (e.value) "true" else "false");
|
|
},
|
|
.e_string => |e| {
|
|
// If this was originally a template literal, print it as one as long as we're not minifying
|
|
if (e.prefer_template) {
|
|
p.print("`");
|
|
p.printQuotedUTF16(e.value, '`');
|
|
p.print("`");
|
|
return;
|
|
}
|
|
|
|
const c = p.bestQuoteCharForString(e.value, true);
|
|
p.print(c);
|
|
p.printQuotedUTF16(e.value, c);
|
|
p.print(c);
|
|
},
|
|
.e_template => |e| {
|
|
notimpl();
|
|
},
|
|
.e_reg_exp => |e| {
|
|
notimpl();
|
|
},
|
|
.e_big_int => |e| {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print(e.value);
|
|
p.print('n');
|
|
},
|
|
.e_number => |e| {
|
|
const value = e.value;
|
|
const absValue = std.math.fabs(value);
|
|
|
|
if (std.math.isNan(value)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print("NaN");
|
|
} else if (std.math.isPositiveInf(value)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print("Infinity");
|
|
} else if (std.math.isNegativeInf(value)) {
|
|
if (level.gte(.prefix)) {
|
|
p.print("(-Infinity)");
|
|
} else {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.print("(-Infinity)");
|
|
}
|
|
} else if (!std.math.signbit(value)) {
|
|
p.printSpaceBeforeIdentifier();
|
|
p.printNonNegativeFloat(absValue);
|
|
|
|
// Remember the end of the latest number
|
|
p.prev_num_end = p.js.lenI();
|
|
} else if (level.gte(.prefix)) {
|
|
// Expressions such as "(-1).toString" need to wrap negative numbers.
|
|
// Instead of testing for "value < 0" we test for "signbit(value)" and
|
|
// "!isNaN(value)" because we need this to be true for "-0" and "-0 < 0"
|
|
// is false.
|
|
p.print("(-");
|
|
p.printNonNegativeFloat(absValue);
|
|
p.print(")");
|
|
} else {
|
|
p.printSpaceBeforeOperator(Op.Code.un_neg);
|
|
p.print("-");
|
|
p.printNonNegativeFloat(absValue);
|
|
|
|
// Remember the end of the latest number
|
|
p.prev_num_end = p.js.lenI();
|
|
}
|
|
},
|
|
.e_identifier => |e| {
|
|
notimpl();
|
|
},
|
|
.e_import_identifier => |e| {
|
|
notimpl();
|
|
},
|
|
.e_await => |e| {
|
|
notimpl();
|
|
},
|
|
.e_yield => |e| {
|
|
notimpl();
|
|
},
|
|
.e_unary => |e| {
|
|
notimpl();
|
|
},
|
|
.e_binary => |e| {
|
|
notimpl();
|
|
},
|
|
else => {
|
|
std.debug.panic("Unexpected expression of type {s}", .{expr.data});
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn printSpaceBeforeOperator(p: *Printer, next: Op.Code) void {
|
|
if (p.prev_op_end == p.js.lenI()) {
|
|
const prev = p.prev_op;
|
|
// "+ + y" => "+ +y"
|
|
// "+ ++ y" => "+ ++y"
|
|
// "x + + y" => "x+ +y"
|
|
// "x ++ + y" => "x+++y"
|
|
// "x + ++ y" => "x+ ++y"
|
|
// "-- >" => "-- >"
|
|
// "< ! --" => "<! --"
|
|
if (((prev == Op.Code.bin_add or prev == Op.Code.un_pos) and (next == Op.Code.bin_add or next == Op.Code.un_pos or next == Op.Code.un_pre_inc)) or
|
|
((prev == Op.Code.bin_sub or prev == Op.Code.un_neg) and (next == Op.Code.bin_sub or next == Op.Code.un_neg or next == Op.Code.un_pre_dec)) or
|
|
(prev == Op.Code.un_post_dec and next == Op.Code.bin_gt) or
|
|
(prev == Op.Code.un_not and next == Op.Code.un_pre_dec and p.js.len() > 1 and p.js.list.items[p.js.list.items.len - 2] == '<'))
|
|
{
|
|
p.print(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn printProperty(p: *Printer, prop: G.Property) void {
|
|
notimpl();
|
|
}
|
|
pub fn printBinding(p: *Printer, binding: Binding) void {
|
|
notimpl();
|
|
}
|
|
pub fn printStmt(p: *Printer, stmt: Stmt) !void {
|
|
p.comptime_flush();
|
|
|
|
p.addSourceMapping(stmt.loc);
|
|
|
|
switch (stmt.data) {
|
|
.s_comment => |s| {
|
|
p.printIndentedComment(s.text);
|
|
},
|
|
.s_function => |s| {},
|
|
.s_class => |s| {},
|
|
.s_empty => |s| {},
|
|
.s_export_default => |s| {},
|
|
.s_export_star => |s| {},
|
|
.s_export_clause => |s| {},
|
|
.s_export_from => |s| {},
|
|
.s_local => |s| {},
|
|
.s_if => |s| {},
|
|
.s_do_while => |s| {},
|
|
.s_for_in => |s| {},
|
|
.s_for_of => |s| {},
|
|
.s_while => |s| {},
|
|
.s_with => |s| {},
|
|
.s_label => |s| {},
|
|
.s_try => |s| {},
|
|
.s_for => |s| {},
|
|
.s_switch => |s| {},
|
|
.s_import => |s| {},
|
|
.s_block => |s| {},
|
|
.s_debugger => |s| {},
|
|
.s_directive => |s| {},
|
|
.s_break => |s| {},
|
|
.s_continue => |s| {},
|
|
.s_return => |s| {},
|
|
.s_throw => |s| {},
|
|
.s_expr => |s| {
|
|
p.printIndent();
|
|
p.stmt_start = p.js.lenI();
|
|
p.printExpr(s.value, .lowest, .expr_result_is_unused);
|
|
p.printSemicolonAfterStatement();
|
|
},
|
|
else => {
|
|
std.debug.panic("Unexpected statement of type {s}", .{@TypeOf(stmt)});
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn printIndentedComment(p: *Printer, _text: string) void {
|
|
var text = _text;
|
|
if (strings.startsWith(text, "/*")) {
|
|
// Re-indent multi-line comments
|
|
while (strings.indexOfChar(text, '\n')) |newline_index| {
|
|
p.printIndent();
|
|
p.print(text[0 .. newline_index + 1]);
|
|
text = text[newline_index + 1 ..];
|
|
}
|
|
p.printIndent();
|
|
p.print(text);
|
|
p.printNewline();
|
|
} else {
|
|
// Print a mandatory newline after single-line comments
|
|
p.printIndent();
|
|
p.print(text);
|
|
p.print("\n");
|
|
}
|
|
}
|
|
|
|
pub fn init(allocator: *std.mem.Allocator, tree: Ast, symbols: Symbol.Map, opts: Options) !Printer {
|
|
var js = try MutableString.init(allocator, 1024);
|
|
return Printer{
|
|
.allocator = allocator,
|
|
.import_records = tree.import_records,
|
|
.options = opts,
|
|
.symbols = symbols,
|
|
.js = js,
|
|
.writer = js.writer(),
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
const UnicodePrinter = NewPrinter(false);
|
|
const AsciiPrinter = NewPrinter(true);
|
|
|
|
pub fn printAst(allocator: *std.mem.Allocator, tree: Ast, symbols: js_ast.Symbol.Map, ascii_only: bool, opts: Options) !PrintResult {
|
|
if (ascii_only) {
|
|
var printer = try AsciiPrinter.init(allocator, tree, symbols, opts);
|
|
for (tree.parts) |part| {
|
|
for (part.stmts) |stmt| {
|
|
try printer.printStmt(stmt);
|
|
}
|
|
}
|
|
|
|
return PrintResult{
|
|
.js = printer.js.toOwnedSlice(),
|
|
};
|
|
} else {
|
|
var printer = try UnicodePrinter.init(allocator, tree, symbols, opts);
|
|
for (tree.parts) |part| {
|
|
for (part.stmts) |stmt| {
|
|
try printer.printStmt(stmt);
|
|
}
|
|
}
|
|
|
|
return PrintResult{
|
|
.js = printer.js.toOwnedSlice(),
|
|
};
|
|
}
|
|
}
|