//! ** IMPORTANT ** //! ** When making changes to the JavaScript Parser that impact runtime behavior or fix bugs ** //! ** you must also increment the `expected_version` in RuntimeTranspilerCache.zig ** //! ** IMPORTANT ** pub const ExprListLoc = struct { list: ExprNodeList, loc: logger.Loc, }; pub const locModuleScope = logger.Loc{ .start = -100 }; pub const DeferredImportNamespace = struct { namespace: LocRef, import_record_id: u32, }; pub const SkipTypeParameterResult = enum { did_not_skip_anything, could_be_type_cast, definitely_type_parameters, }; pub const TypeParameterFlag = packed struct(u8) { /// TypeScript 4.7 allow_in_out_variance_annotations: bool = false, /// TypeScript 5.0 allow_const_modifier: bool = false, /// Allow "<>" without any type parameters allow_empty_type_parameters: bool = false, _: u5 = 0, }; pub 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, pub fn get(noalias 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.eqlComptime(name, "Fragment")) return if (this.Fragment) |Fragment| Fragment.ref.? else null; if (strings.eqlComptime(name, "createElement")) return if (this.createElement) |createElement| createElement.ref.? else null; return null; } pub fn getWithTag(noalias 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(noalias this: *const Symbols, buf: *[3]string) []const string { var i: usize = 0; if (this.jsxDEV != null) { bun.assert(this.jsx == null); // we should never end up with this in the same file buf[0] = "jsxDEV"; i += 1; } if (this.jsx != null) { bun.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] = "Fragment"; i += 1; } return buf[0..i]; } pub fn sourceImportNames(noalias this: *const Symbols) []const string { return if (this.createElement != null) &[_]string{"createElement"} else &[_]string{}; } }; }; pub 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. pub const ScopeOrderList = std.ArrayListUnmanaged(?ScopeOrder); // kept as a static reference pub const exports_string_name: string = "exports"; const MacroRefData = struct { import_record_id: u32, // if name is null the macro is imported as a namespace import // import * as macros from "./macros.js" with {type: "macro"}; name: ?string = null, }; const MacroRefs = std.AutoArrayHashMap(Ref, MacroRefData); pub const Substitution = union(enum) { success: Expr, failure: Expr, continue_: Expr, }; // 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, }; pub 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, }; pub fn ExpressionTransposer( comptime ContextType: type, comptime StateType: type, comptime visitor: fn (noalias ptr: *ContextType, arg: Expr, state: StateType) Expr, ) type { return struct { pub const Context = ContextType; pub const This = @This(); context: *Context, pub fn init(c: *Context) This { return .{ .context = c }; } pub fn maybeTransposeIf(self: *This, arg: Expr, state: StateType) Expr { switch (arg.data) { .e_if => |ex| { return Expr.init(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 transposeKnownToBeIf(self: *This, arg: Expr, state: StateType) Expr { return Expr.init(E.If, .{ .yes = self.maybeTransposeIf(arg.data.e_if.yes, state), .no = self.maybeTransposeIf(arg.data.e_if.no, state), .test_ = arg.data.e_if.test_, }, arg.loc); } }; } 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; } } pub 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, import_record_tag: ?ImportRecord.Tag = null, import_loader: ?bun.options.Loader = null, import_options: Expr = Expr.empty, }; pub 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, /// Empty string for fragments. 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.expectInsideJSXElementWithName(.t_identifier, "JSX element name"); // Certain identifiers are strings //
= 'a' and name[0] <= 'z')) { return JSXTag{ .data = Data{ .tag = p.newExpr(E.String{ .data = name, }, loc) }, .range = tag_range, .name = name, }; } // Otherwise, this is an identifier //