pub const std = @import("std");
pub const logger = @import("root").bun.logger;
pub const js_lexer = bun.js_lexer;
pub const importRecord = @import("./import_record.zig");
pub const js_ast = bun.JSAst;
pub const options = @import("./options.zig");
pub const js_printer = bun.js_printer;
pub const renamer = @import("./renamer.zig");
const _runtime = @import("./runtime.zig");
pub const RuntimeImports = _runtime.Runtime.Imports;
pub const RuntimeFeatures = _runtime.Runtime.Features;
pub const RuntimeNames = _runtime.Runtime.Names;
pub const fs = @import("./fs.zig");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = @import("./string_mutable.zig").MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const G = js_ast.G;
const Define = @import("./defines.zig").Define;
const DefineData = @import("./defines.zig").DefineData;
const FeatureFlags = @import("./feature_flags.zig");
pub const isPackagePath = @import("./resolver/resolver.zig").isPackagePath;
pub const ImportKind = importRecord.ImportKind;
pub const BindingNodeIndex = js_ast.BindingNodeIndex;
const Decl = G.Decl;
const Property = G.Property;
const Arg = G.Arg;
const Allocator = std.mem.Allocator;
pub const StmtNodeIndex = js_ast.StmtNodeIndex;
pub const ExprNodeIndex = js_ast.ExprNodeIndex;
pub const ExprNodeList = js_ast.ExprNodeList;
pub const StmtNodeList = js_ast.StmtNodeList;
pub const BindingNodeList = js_ast.BindingNodeList;
const DeclaredSymbol = js_ast.DeclaredSymbol;
const ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStringMap;
const JSC = @import("root").bun.JSC;
fn _disabledAssert(_: bool) void {
if (!Environment.allow_assert) @compileLog("assert is missing an if (Environment.allow_assert)");
unreachable;
}
const assert = if (Environment.allow_assert) std.debug.assert else _disabledAssert;
const debug = Output.scoped(.JSParser, false);
const ExprListLoc = struct {
list: ExprNodeList,
loc: logger.Loc,
};
pub const LocRef = js_ast.LocRef;
pub const S = js_ast.S;
pub const B = js_ast.B;
pub const T = js_lexer.T;
pub const E = js_ast.E;
pub const Stmt = js_ast.Stmt;
pub const Expr = js_ast.Expr;
pub const Binding = js_ast.Binding;
pub const Symbol = js_ast.Symbol;
pub const Level = js_ast.Op.Level;
pub const Op = js_ast.Op;
pub const Scope = js_ast.Scope;
pub const locModuleScope = logger.Loc{ .start = -100 };
const Ref = @import("./ast/base.zig").Ref;
const RefHashCtx = @import("./ast/base.zig").RefHashCtx;
pub const StringHashMap = bun.StringHashMap;
pub const AutoHashMap = std.AutoHashMap;
const StringHashMapUnmanaged = bun.StringHashMapUnmanaged;
const ObjectPool = @import("./pool.zig").ObjectPool;
const NodeFallbackModules = @import("./node_fallbacks.zig");
const DeferredImportNamespace = struct {
namespace: LocRef,
import_record_id: u32,
};
const SkipTypeParameterResult = enum {
did_not_skip_anything,
could_be_type_cast,
definitely_type_parameters,
};
const TypeParameterFlag = packed struct {
/// TypeScript 4.7
allow_in_out_variance_annoatations: bool = false,
/// TypeScript 5.0
allow_const_modifier: bool = false,
pub const all = TypeParameterFlag{
.allow_in_out_variance_annoatations = true,
.allow_const_modifier = true,
};
};
const JSXImport = enum {
jsx,
jsxDEV,
jsxs,
Fragment,
createElement,
pub const Symbols = struct {
jsx: ?LocRef = null,
jsxDEV: ?LocRef = null,
jsxs: ?LocRef = null,
Fragment: ?LocRef = null,
createElement: ?LocRef = null,
factory_name: []const u8 = "React.createElement",
fragment_name: []const u8 = "Fragment",
pub fn get(this: *const Symbols, name: []const u8) ?Ref {
if (strings.eqlComptime(name, "jsx")) return if (this.jsx) |jsx| jsx.ref.? else null;
if (strings.eqlComptime(name, "jsxDEV")) return if (this.jsxDEV) |jsx| jsx.ref.? else null;
if (strings.eqlComptime(name, "jsxs")) return if (this.jsxs) |jsxs| jsxs.ref.? else null;
if (strings.eql(name, this.fragment_name)) return if (this.Fragment) |Fragment| Fragment.ref.? else null;
if (strings.eql(name, this.factory_name)) return if (this.createElement) |createElement| createElement.ref.? else null;
return null;
}
pub fn getWithTag(this: *const Symbols, tag: JSXImport) ?Ref {
return switch (tag) {
.jsx => if (this.jsx) |jsx| jsx.ref.? else null,
.jsxDEV => if (this.jsxDEV) |jsx| jsx.ref.? else null,
.jsxs => if (this.jsxs) |jsxs| jsxs.ref.? else null,
.Fragment => if (this.Fragment) |Fragment| Fragment.ref.? else null,
.createElement => if (this.createElement) |createElement| createElement.ref.? else null,
};
}
pub fn runtimeImportNames(this: *const Symbols, buf: *[3]string) []const string {
var i: usize = 0;
if (this.jsxDEV != null) {
std.debug.assert(this.jsx == null); // we should never end up with this in the same file
buf[0] = "jsxDEV";
i += 1;
}
if (this.jsx != null) {
std.debug.assert(this.jsxDEV == null); // we should never end up with this in the same file
buf[0] = "jsx";
i += 1;
}
if (this.jsxs != null) {
buf[i] = "jsxs";
i += 1;
}
if (this.Fragment != null) {
buf[i] = this.fragment_name;
i += 1;
}
return buf[0..i];
}
};
};
const arguments_str: string = "arguments";
// Dear reader,
// There are some things you should know about this file to make it easier for humans to read
// "P" is the internal parts of the parser
// "p.e" allocates a new Expr
// "p.b" allocates a new Binding
// "p.s" allocates a new Stmt
// We do it this way so if we want to refactor how these are allocated in the future, we only have to modify one function to change it everywhere
// Everything in JavaScript is either an Expression, a Binding, or a Statement.
// Expression: foo(1)
// Statement: let a = 1;
// Binding: a
// While the names for Expr, Binding, and Stmt are directly copied from esbuild, those were likely inspired by Go's parser.
// which is another example of a very fast parser.
const ScopeOrderList = std.ArrayListUnmanaged(?ScopeOrder);
const JSXFactoryName = "JSX";
const JSXAutomaticName = "jsx_module";
// kept as a static reference
const exports_string_name: string = "exports";
const MacroRefs = std.AutoArrayHashMap(Ref, u32);
pub const AllocatedNamesPool = ObjectPool(
std.ArrayList(string),
struct {
pub fn init(allocator: std.mem.Allocator) anyerror!std.ArrayList(string) {
return std.ArrayList(string).init(allocator);
}
}.init,
true,
4,
);
const Substitution = union(enum) {
success: Expr,
failure: Expr,
continue_: Expr,
};
fn foldStringAddition(lhs: Expr, rhs: Expr) ?Expr {
switch (lhs.data) {
.e_string => |left| {
if (rhs.data == .e_string and left.isUTF8() and rhs.data.e_string.isUTF8()) {
var orig = lhs.data.e_string.*;
const rhs_clone = Expr.init(E.String, rhs.data.e_string.*, rhs.loc);
orig.push(
rhs_clone.data.e_string,
);
orig.prefer_template = orig.prefer_template or rhs_clone.data.e_string.prefer_template;
return Expr.init(E.String, orig, lhs.loc);
}
},
.e_binary => |bin| {
// 123 + "bar" + "baz"
if (bin.op == .bin_add) {
if (foldStringAddition(bin.right, rhs)) |out| {
return Expr.init(E.Binary, E.Binary{ .op = bin.op, .left = bin.left, .right = out }, lhs.loc);
}
}
},
else => {},
}
return null;
}
// If we are currently in a hoisted child of the module scope, relocate these
// declarations to the top level and return an equivalent assignment statement.
// Make sure to check that the declaration kind is "var" before calling this.
// And make sure to check that the returned statement is not the zero value.
//
// This is done to make some transformations non-destructive
// Without relocating vars to the top level, simplifying this:
// if (false) var foo = 1;
// to nothing is unsafe
// Because "foo" was defined. And now it's not.
pub const RelocateVars = struct {
pub const Mode = enum { normal, for_in_or_for_of };
stmt: ?Stmt = null,
ok: bool = false,
};
const VisitArgsOpts = struct {
body: []Stmt = &([_]Stmt{}),
has_rest_arg: bool = false,
// This is true if the function is an arrow function or a method
is_unique_formal_parameters: bool = false,
};
const BunJSX = struct {
pub threadlocal var bun_jsx_identifier: E.Identifier = undefined;
};
pub fn ExpressionTransposer(
comptime Kontext: type,
comptime visitor: fn (ptr: *Kontext, arg: Expr, state: anytype) Expr,
) type {
return struct {
pub const Context = Kontext;
pub const This = @This();
context: *Context,
pub fn init(c: *Context) This {
return This{
.context = c,
};
}
pub fn maybeTransposeIf(self: *This, arg: Expr, state: anytype) Expr {
switch (arg.data) {
.e_if => |ex| {
return Expr.init(
E.If,
E.If{
.yes = self.maybeTransposeIf(ex.yes, state),
.no = self.maybeTransposeIf(ex.no, state),
.test_ = ex.test_,
},
arg.loc,
);
},
else => {
return visitor(self.context, arg, state);
},
}
}
};
}
pub fn locAfterOp(e: E.Binary) logger.Loc {
if (e.left.loc.start < e.right.loc.start) {
return e.right.loc;
} else {
// handle the case when we have transposed the operands
return e.left.loc;
}
}
const ExportsStringName = "exports";
const TransposeState = struct {
is_await_target: bool = false,
is_then_catch_target: bool = false,
is_require_immediately_assigned_to_decl: bool = false,
loc: logger.Loc = logger.Loc.Empty,
};
var true_args = &[_]Expr{
.{
.data = .{ .e_boolean = .{ .value = true } },
.loc = logger.Loc.Empty,
},
};
const JSXTag = struct {
pub const TagType = enum { fragment, tag };
pub const Data = union(TagType) {
fragment: u8,
tag: Expr,
pub fn asExpr(d: *const Data) ?ExprNodeIndex {
switch (d.*) {
.tag => |tag| {
return tag;
},
else => {
return null;
},
}
}
};
data: Data,
range: logger.Range,
name: string = "",
pub fn parse(comptime P: type, p: *P) anyerror!JSXTag {
const loc = p.lexer.loc();
// A missing tag is a fragment
if (p.lexer.token == .t_greater_than) {
return JSXTag{
.range = logger.Range{ .loc = loc, .len = 0 },
.data = Data{ .fragment = 1 },
.name = "",
};
}
// The tag is an identifier
var name = p.lexer.identifier;
var tag_range = p.lexer.range();
try p.lexer.expectInsideJSXElement(.t_identifier);
// Certain identifiers are strings
//
= 'a' and name[0] <= 'z')) {
return JSXTag{
.data = Data{ .tag = p.newExpr(E.String{
.data = name,
}, loc) },
.range = tag_range,
};
}
// Otherwise, this is an identifier
//