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 StringHashMapUnamanged = 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 = "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, }; } const Runtime = struct { pub const full: []const string = &[_]string{ "jsx", "jsxs" }; pub const jsxs_: []const string = &[_]string{"jsxs"}; pub const jsx_: []const string = &[_]string{"jsx"}; }; const DevRuntime = struct { pub const full: []const string = &[_]string{ "jsxDEV", "jsxs" }; pub const jsxs_: []const string = &[_]string{"jsxs"}; pub const jsx_: []const string = &[_]string{"jsxDEV"}; }; pub fn runtimeImportNames(this: *const Symbols) []const string { if (this.jsxDEV != null) { std.debug.assert(this.jsx == null); // we should never end up with this in the same file if (this.jsxs != null) return DevRuntime.full; return DevRuntime.jsx_; } if (this.jsx != null and this.jsxs != null) return Runtime.full; if (this.jsxs != null) return Runtime.jsxs_; if (this.jsx != null) return Runtime.jsx_; return &[_]string{}; } const Legacy = struct { pub const full: []const string = &[_]string{ "createElement", "Fragment" }; pub const createElement_: []const string = &[_]string{"createElement"}; pub const Fragment_: []const string = &[_]string{"Fragment"}; }; pub fn legacyImportNames(this: *const Symbols, jsx: *const options.JSX.Pragma, buf: *[2]string) []const string { _ = jsx; if (this.Fragment != null and this.createElement != null) { buf[0..2].* = .{ this.factory_name, this.fragment_name, }; return buf[0..2]; } if (this.createElement != null) { buf[0] = this.factory_name; return buf[0..1]; } if (this.Fragment != null) { buf[0] = this.fragment_name; return buf[0..1]; } return &[_]string{}; } }; }; 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, ); 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, loc: logger.Loc, }; 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 //