diff options
author | 2022-05-19 17:52:40 -0700 | |
---|---|---|
committer | 2022-05-19 17:52:40 -0700 | |
commit | 44d1b217ac15ed1b729efd88899123f1851cfa02 (patch) | |
tree | c2a063a7710902d99899890e9bc7371f9d3284ae | |
parent | 3672bb85eb9891f30295f31673038e1d9ed7dc6d (diff) | |
download | bun-44d1b217ac15ed1b729efd88899123f1851cfa02.tar.gz bun-44d1b217ac15ed1b729efd88899123f1851cfa02.tar.zst bun-44d1b217ac15ed1b729efd88899123f1851cfa02.zip |
move js_parser to src/
Diffstat (limited to '')
-rw-r--r-- | src/js_parser.zig | 18212 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 18211 |
2 files changed, 18211 insertions, 18212 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index d202fcc54..9cd910795 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1 +1,18211 @@ -usingnamespace @import("js_parser/js_parser.zig"); +pub const std = @import("std"); +pub const logger = @import("./logger.zig"); +pub const js_lexer = @import("./js_lexer.zig"); +pub const importRecord = @import("./import_record.zig"); +pub const js_ast = @import("./js_ast.zig"); +pub const options = @import("./options.zig"); +pub const js_printer = @import("./js_printer.zig"); +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 _hash_map = @import("./hash_map.zig"); +const bun = @import("./global.zig"); +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 ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStringMap; + +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 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; + +pub const StringHashMap = _hash_map.StringHashMap; +pub const AutoHashMap = _hash_map.AutoHashMap; +const StringHashMapUnamanged = _hash_map.StringHashMapUnamanged; +const ObjectPool = @import("./pool.zig").ObjectPool; +const NodeFallbackModules = @import("./node_fallbacks.zig"); +// 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: 1 +// 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, +); + +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()) { + lhs.data.e_string.push(rhs.data.e_string); + return lhs; + } + }, + .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, + 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| { + ex.yes = self.maybeTransposeIf(ex.yes, state); + ex.no = self.maybeTransposeIf(ex.no, state); + return arg; + }, + 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 + // <div + // <button + // <Hello-:Button + if (strings.containsComptime(name, "-:") or (p.lexer.token != .t_dot and name[0] >= 'a' and name[0] <= 'z')) { + return JSXTag{ + .data = Data{ .tag = p.e(E.String{ + .data = name, + }, loc) }, + .range = tag_range, + }; + } + + // Otherwise, this is an identifier + // <Button> + var tag = p.e(E.Identifier{ .ref = try p.storeNameInRef(name) }, loc); + + // Parse a member expression chain + // <Button.Red> + while (p.lexer.token == .t_dot) { + try p.lexer.nextInsideJSXElement(); + const member_range = p.lexer.range(); + const member = p.lexer.identifier; + try p.lexer.expectInsideJSXElement(.t_identifier); + + if (strings.indexOfChar(member, '-')) |index| { + try p.log.addError(p.source, logger.Loc{ .start = member_range.loc.start + @intCast(i32, index) }, "Unexpected \"-\""); + return error.SyntaxError; + } + + var _name = try p.allocator.alloc(u8, name.len + 1 + member.len); + std.mem.copy(u8, _name, name); + _name[name.len] = '.'; + std.mem.copy(u8, _name[name.len + 1 .. _name.len], member); + name = _name; + tag_range.len = member_range.loc.start + member_range.len - tag_range.loc.start; + tag = p.e(E.Dot{ .target = tag, .name = member, .name_loc = member_range.loc }, loc); + } + + return JSXTag{ .data = Data{ .tag = tag }, .range = tag_range, .name = name }; + } +}; + +pub const TypeScript = struct { + // This function is taken from the official TypeScript compiler source code: + // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts + pub fn canFollowTypeArgumentsInExpression(token: js_lexer.T) bool { + switch (token) { + // These are the only tokens can legally follow a type argument list. So we + // definitely want to treat them as type arg lists. + .t_open_paren, // foo<x>( + .t_no_substitution_template_literal, // foo<T> `...` + // foo<T> `...${100}...` + .t_template_head, + => { + return true; + }, + // These cases can't legally follow a type arg list. However, they're not + // legal expressions either. The user is probably in the middle of a + // generic type. So treat it as such. + .t_dot, // foo<x>. + .t_close_paren, // foo<x>) + .t_close_bracket, // foo<x>] + .t_colon, // foo<x>: + .t_semicolon, // foo<x>; + .t_question, // foo<x>? + .t_equals_equals, // foo<x> == + .t_equals_equals_equals, // foo<x> === + .t_exclamation_equals, // foo<x> != + .t_exclamation_equals_equals, // foo<x> !== + .t_ampersand_ampersand, // foo<x> && + .t_bar_bar, // foo<x> || + .t_question_question, // foo<x> ?? + .t_caret, // foo<x> ^ + .t_ampersand, // foo<x> & + .t_bar, // foo<x> | + .t_close_brace, // foo<x> } + .t_end_of_file, // foo<x> + => { + return true; + }, + + // We don't want to treat these as type arguments. Otherwise we'll parse + // this as an invocation expression. Instead, we want to parse out the + // expression in isolation from the type arguments. + .t_comma, // foo<x>, + .t_open_brace, // foo<x> { + => { + return false; + }, + else => { + // Anything else treat as an expression + return false; + }, + } + } + pub const Identifier = struct { + pub const StmtIdentifier = enum { + s_type, + + s_namespace, + + s_abstract, + + s_module, + + s_interface, + + s_declare, + }; + pub fn forStr(str: string) ?StmtIdentifier { + switch (str.len) { + "type".len => return if (strings.eqlComptimeIgnoreLen(str, "type")) + .s_type + else + null, + "interface".len => { + if (strings.eqlComptime(str, "interface")) { + return .s_interface; + } else if (strings.eqlComptime(str, "namespace")) { + return .s_namespace; + } else { + return null; + } + }, + "abstract".len => { + if (strings.eqlComptime(str, "abstract")) { + return .s_abstract; + } else { + return null; + } + }, + "declare".len => { + if (strings.eqlComptime(str, "declare")) { + return .s_declare; + } else { + return null; + } + }, + "module".len => { + if (strings.eqlComptime(str, "module")) { + return .s_module; + } else { + return null; + } + }, + else => return null, + } + } + pub const IMap = ComptimeStringMap(Kind, .{ + .{ "unique", .unique }, + .{ "abstract", .abstract }, + .{ "asserts", .asserts }, + .{ "keyof", .prefix }, + .{ "readonly", .prefix }, + .{ "infer", .prefix }, + .{ "any", .primitive }, + .{ "never", .primitive }, + .{ "unknown", .primitive }, + .{ "undefined", .primitive }, + .{ "object", .primitive }, + .{ "number", .primitive }, + .{ "string", .primitive }, + .{ "boolean", .primitive }, + .{ "bigint", .primitive }, + .{ "symbol", .primitive }, + }); + pub const Kind = enum { + normal, + unique, + abstract, + asserts, + prefix, + primitive, + }; + }; + + pub const SkipTypeOptions = struct { + is_return_type: bool = false, + }; +}; + +// We must prevent collisions from generated names. +// We want to avoid adding a pass over all the symbols in the file. +// To do that: +// For every generated symbol, we reserve two backup symbol names +// If any usages of the preferred ref, we swap original_name with the backup +// If any usages of the backup ref, we swap original_name with the internal +// We *assume* the internal name is never used. +// In practice, it is possible. But, the internal names are so crazy long you'd have to be deliberately trying to use them. +const GeneratedSymbol = @import("./runtime.zig").Runtime.GeneratedSymbol; + +pub const ImportScanner = struct { + stmts: []Stmt = &([_]Stmt{}), + + kept_import_equals: bool = false, + removed_import_equals: bool = false, + pub fn scan(comptime P: type, p: *P, stmts: []Stmt, will_transform_to_common_js: bool) !ImportScanner { + var scanner = ImportScanner{}; + var stmts_end: usize = 0; + const allocator = p.allocator; + const is_typescript_enabled: bool = comptime P.parser_features.typescript; + + for (stmts) |_stmt| { + // zls needs the hint, it seems. + var stmt: Stmt = _stmt; + switch (stmt.data) { + .s_import => |st__| { + var st = st__.*; + defer { + st__.* = st; + } + + var record: *ImportRecord = &p.import_records.items[st.import_record_index]; + + if (record.path.isMacro()) { + record.is_unused = true; + record.path.is_disabled = true; + continue; + } + + // The official TypeScript compiler always removes unused imported + // symbols. However, we deliberately deviate from the official + // TypeScript compiler's behavior doing this in a specific scenario: + // we are not bundling, symbol renaming is off, and the tsconfig.json + // "importsNotUsedAsValues" setting is present and is not set to + // "remove". + // + // This exists to support the use case of compiling partial modules for + // compile-to-JavaScript languages such as Svelte. These languages try + // to reference imports in ways that are impossible for esbuild to know + // about when esbuild is only given a partial module to compile. Here + // is an example of some Svelte code that might use esbuild to convert + // TypeScript to JavaScript: + // + // <script lang="ts"> + // import Counter from './Counter.svelte'; + // export let name: string = 'world'; + // </script> + // <main> + // <h1>Hello {name}!</h1> + // <Counter /> + // </main> + // + // Tools that use esbuild to compile TypeScript code inside a Svelte + // file like this only give esbuild the contents of the <script> tag. + // These tools work around this missing import problem when using the + // official TypeScript compiler by hacking the TypeScript AST to + // remove the "unused import" flags. This isn't possible in esbuild + // because esbuild deliberately does not expose an AST manipulation + // API for performance reasons. + // + // We deviate from the TypeScript compiler's behavior in this specific + // case because doing so is useful for these compile-to-JavaScript + // languages and is benign in other cases. The rationale is as follows: + // + // * If "importsNotUsedAsValues" is absent or set to "remove", then + // we don't know if these imports are values or types. It's not + // safe to keep them because if they are types, the missing imports + // will cause run-time failures because there will be no matching + // exports. It's only safe keep imports if "importsNotUsedAsValues" + // is set to "preserve" or "error" because then we can assume that + // none of the imports are types (since the TypeScript compiler + // would generate an error in that case). + // + // * If we're bundling, then we know we aren't being used to compile + // a partial module. The parser is seeing the entire code for the + // module so it's safe to remove unused imports. And also we don't + // want the linker to generate errors about missing imports if the + // imported file is also in the bundle. + // + // * If identifier minification is enabled, then using esbuild as a + // partial-module transform library wouldn't work anyway because + // the names wouldn't match. And that means we're minifying so the + // user is expecting the output to be as small as possible. So we + // should omit unused imports. + // + var did_remove_star_loc = false; + const keep_unused_imports = !p.options.features.trim_unused_imports; + + // TypeScript always trims unused imports. This is important for + // correctness since some imports might be fake (only in the type + // system and used for type-only imports). + if (!keep_unused_imports) { + var found_imports = false; + var is_unused_in_typescript = true; + + if (st.default_name) |default_name| { + found_imports = true; + const symbol = p.symbols.items[default_name.ref.?.innerIndex()]; + + // TypeScript has a separate definition of unused + if (is_typescript_enabled and p.ts_use_counts.items[default_name.ref.?.innerIndex()] != 0) { + is_unused_in_typescript = false; + } + + // Remove the symbol if it's never used outside a dead code region + if (symbol.use_count_estimate == 0) { + st.default_name = null; + } + } + + // Remove the star import if it's unused + if (st.star_name_loc) |_| { + found_imports = true; + const symbol = p.symbols.items[st.namespace_ref.innerIndex()]; + + // TypeScript has a separate definition of unused + if (is_typescript_enabled and p.ts_use_counts.items[st.namespace_ref.innerIndex()] != 0) { + is_unused_in_typescript = false; + } + + // Remove the symbol if it's never used outside a dead code region + if (symbol.use_count_estimate == 0) { + // Make sure we don't remove this if it was used for a property + // access while bundling + var has_any = false; + + if (p.import_items_for_namespace.get(st.namespace_ref)) |entry| { + if (entry.count() > 0) { + has_any = true; + break; + } + } + + if (!has_any) { + st.star_name_loc = null; + did_remove_star_loc = true; + } + } + } + + // Remove items if they are unused + if (st.items.len > 0) { + found_imports = true; + var items_end: usize = 0; + var i: usize = 0; + while (i < st.items.len) : (i += 1) { + const item = st.items[i]; + const ref = item.name.ref.?; + const symbol: Symbol = p.symbols.items[ref.innerIndex()]; + + // TypeScript has a separate definition of unused + if (is_typescript_enabled and p.ts_use_counts.items[ref.innerIndex()] != 0) { + is_unused_in_typescript = false; + } + + // Remove the symbol if it's never used outside a dead code region + if (symbol.use_count_estimate != 0) { + st.items[items_end] = item; + items_end += 1; + } + } + + st.items = st.items[0..items_end]; + } + + // -- Original Comment -- + // Omit this statement if we're parsing TypeScript and all imports are + // unused. Note that this is distinct from the case where there were + // no imports at all (e.g. "import 'foo'"). In that case we want to keep + // the statement because the user is clearly trying to import the module + // for side effects. + // + // This culling is important for correctness when parsing TypeScript + // because a) the TypeScript compiler does ths and we want to match it + // and b) this may be a fake module that only exists in the type system + // and doesn't actually exist in reality. + // + // We do not want to do this culling in JavaScript though because the + // module may have side effects even if all imports are unused. + // -- Original Comment -- + + // jarred: I think, in this project, we want this behavior, even in JavaScript. + // I think this would be a big performance improvement. + // The less you import, the less code you transpile. + // Side-effect imports are nearly always done through identifier-less imports + // e.g. `import 'fancy-stylesheet-thing/style.css';` + // This is a breaking change though. We can make it an option with some guardrail + // so maybe if it errors, it shows a suggestion "retry without trimming unused imports" + if ((is_typescript_enabled and found_imports and is_unused_in_typescript and !p.options.preserve_unused_imports_ts) or + (!is_typescript_enabled and p.options.features.trim_unused_imports and found_imports and st.star_name_loc == null and st.items.len == 0 and st.default_name == null)) + { + // internal imports are presumed to be always used + // require statements cannot be stripped + if (!record.is_internal and !record.was_originally_require) { + record.is_unused = true; + continue; + } + } + } + + const namespace_ref = st.namespace_ref; + const convert_star_to_clause = !p.options.enable_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0; + + if (convert_star_to_clause and !keep_unused_imports) { + st.star_name_loc = null; + } + + record.contains_default_alias = record.contains_default_alias or st.default_name != null; + + const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse + ImportItemForNamespaceMap.init(allocator); + + // ESM requires live bindings + // CommonJS does not require live bindings + // We load ESM in browsers & in Bun.js + // We have to simulate live bindings for cases where the code is bundled + // We do not know at this stage whether or not the import statement is bundled + // This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace + for (st.items) |item| { + const is_default = strings.eqlComptime(item.alias, "default"); + record.contains_default_alias = record.contains_default_alias or is_default; + + const name: LocRef = item.name; + const name_ref = name.ref.?; + + try p.named_imports.put(name_ref, js_ast.NamedImport{ + .alias = item.alias, + .alias_loc = name.loc, + .namespace_ref = namespace_ref, + .import_record_index = st.import_record_index, + }); + + // Make sure the printer prints this as a property access + var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()]; + + symbol.namespace_alias = G.NamespaceAlias{ + .namespace_ref = namespace_ref, + .alias = item.alias, + .import_record_index = st.import_record_index, + .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), + }; + } + + try p.import_records_for_current_part.append(allocator, st.import_record_index); + + if (st.star_name_loc != null) { + record.contains_import_star = true; + } + + if (record.was_originally_require) { + var symbol = &p.symbols.items[namespace_ref.innerIndex()]; + symbol.namespace_alias = G.NamespaceAlias{ + .namespace_ref = namespace_ref, + .alias = "", + .import_record_index = st.import_record_index, + .was_originally_property_access = false, + }; + } + }, + + .s_function => |st| { + if (st.func.flags.contains(.is_export)) { + if (st.func.name) |name| { + const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name; + try p.recordExport(name.loc, original_name, name.ref.?); + + if (p.options.features.hot_module_reloading) { + st.func.flags.remove(.is_export); + } + } else { + try p.log.addRangeError(p.source, logger.Range{ .loc = st.func.open_parens_loc, .len = 2 }, "Exported functions must have a name"); + } + } + }, + .s_class => |st| { + if (st.is_export) { + if (st.class.class_name) |name| { + try p.recordExport(name.loc, p.symbols.items[name.ref.?.innerIndex()].original_name, name.ref.?); + + if (p.options.features.hot_module_reloading) { + st.is_export = false; + } + } else { + try p.log.addRangeError(p.source, logger.Range{ .loc = st.class.body_loc, .len = 0 }, "Exported classes must have a name"); + } + } + }, + .s_local => |st| { + if (st.is_export) { + for (st.decls) |decl| { + p.recordExportedBinding(decl.binding); + } + } + + // Remove unused import-equals statements, since those likely + // correspond to types instead of values + if (st.was_ts_import_equals and !st.is_export and st.decls.len > 0) { + var decl = st.decls[0]; + + // Skip to the underlying reference + var value = decl.value; + if (decl.value) |val| { + while (true) { + if (@as(Expr.Tag, val.data) == .e_dot) { + value = val.data.e_dot.target; + } else { + break; + } + } + } + + // Is this an identifier reference and not a require() call? + if (value) |val| { + if (@as(Expr.Tag, val.data) == .e_identifier) { + // Is this import statement unused? + if (@as(Binding.Tag, decl.binding.data) == .b_identifier and p.symbols.items[decl.binding.data.b_identifier.ref.innerIndex()].use_count_estimate == 0) { + p.ignoreUsage(val.data.e_identifier.ref); + + scanner.removed_import_equals = true; + continue; + } else { + scanner.kept_import_equals = true; + } + } + } + } + + // We must do this at the end to not mess up import = + if (p.options.features.hot_module_reloading and st.is_export) { + st.is_export = false; + } + }, + .s_export_default => |st| { + // This is defer'd so that we still record export default for identifiers + defer { + if (st.default_name.ref) |ref| { + p.recordExport(st.default_name.loc, "default", ref) catch {}; + } + } + + // Rewrite this export to be: + // exports.default = + // But only if it's anonymous + if (p.options.features.hot_module_reloading) { + + // export default can be: + // - an expression + // - a function + // - a class + // it cannot be a declaration! + // we want to avoid adding a new name + // but we must remove the export default clause. + transform_export_default_when_its_anonymous: { + switch (st.value) { + .expr => |ex| { + switch (ex.data) { + .e_identifier => { + continue; + }, + .e_import_identifier => |import_ident| { + st.default_name.ref = import_ident.ref; + continue; + }, + .e_function => |func| { + if (func.func.name) |name_ref| { + if (name_ref.ref != null) { + stmt = p.s(S.Function{ .func = func.func }, ex.loc); + st.default_name.ref = name_ref.ref.?; + break :transform_export_default_when_its_anonymous; + } + } + }, + .e_class => |class| { + if (class.class_name) |name_ref| { + if (name_ref.ref != null) { + stmt = p.s( + S.Class{ + .class = class.*, + }, + ex.loc, + ); + st.default_name.ref = name_ref.ref.?; + break :transform_export_default_when_its_anonymous; + } + } + }, + else => {}, + } + var decls = try allocator.alloc(G.Decl, 1); + decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), .value = ex }; + + stmt = p.s(S.Local{ + .decls = decls, + .kind = S.Local.Kind.k_var, + .is_export = false, + }, ex.loc); + }, + .stmt => |class_or_func| { + switch (class_or_func.data) { + .s_function => |func| { + if (func.func.name) |name_ref| { + if (name_ref.ref != null) { + stmt = class_or_func; + st.default_name.ref = name_ref.ref.?; + break :transform_export_default_when_its_anonymous; + } + } + + var decls = try allocator.alloc(G.Decl, 1); + decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), .value = p.e(E.Function{ .func = func.func }, stmt.loc) }; + + stmt = p.s(S.Local{ + .decls = decls, + .kind = S.Local.Kind.k_var, + .is_export = false, + }, stmt.loc); + }, + .s_class => |class| { + if (class.class.class_name) |name_ref| { + if (name_ref.ref != null) { + stmt = class_or_func; + st.default_name.ref = name_ref.ref.?; + break :transform_export_default_when_its_anonymous; + } + } + + var decls = try allocator.alloc(G.Decl, 1); + decls[0] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), + .value = p.e(E.Class{ + .class_keyword = class.class.class_keyword, + .ts_decorators = class.class.ts_decorators, + .class_name = class.class.class_name, + .extends = class.class.extends, + .body_loc = class.class.body_loc, + .properties = class.class.properties, + .close_brace_loc = class.class.close_brace_loc, + }, stmt.loc), + }; + + stmt = p.s(S.Local{ + .decls = decls, + .kind = S.Local.Kind.k_var, + .is_export = false, + }, stmt.loc); + }, + else => unreachable, + } + }, + } + } + } else if (will_transform_to_common_js) { + const expr: js_ast.Expr = switch (st.value) { + .expr => |exp| exp, + .stmt => |s2| brk2: { + switch (s2.data) { + .s_function => |func| { + break :brk2 p.e(E.Function{ .func = func.func }, s2.loc); + }, + .s_class => |class| { + break :brk2 p.e(class.class, s2.loc); + }, + else => unreachable, + } + }, + }; + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(expr.loc); + export_default_args[1] = expr; + stmt = p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc); + } + }, + .s_export_clause => |st| { + for (st.items) |item| { + try p.recordExport(item.alias_loc, item.alias, item.name.ref.?); + } + + // export clauses simply disappear when we have HMR on, we use NamedExports to regenerate it at the end + if (p.options.features.hot_module_reloading) { + continue; + } + }, + .s_export_star => |st| { + try p.import_records_for_current_part.append(allocator, st.import_record_index); + + if (st.alias) |alias| { + // "export * as ns from 'path'" + try p.named_imports.put(st.namespace_ref, js_ast.NamedImport{ + .alias = null, + .alias_is_star = true, + .alias_loc = alias.loc, + .namespace_ref = Ref.None, + .import_record_index = st.import_record_index, + .is_exported = true, + }); + try p.recordExport(alias.loc, alias.original_name, st.namespace_ref); + } else { + // "export * from 'path'" + try p.export_star_import_records.append(allocator, st.import_record_index); + } + }, + .s_export_from => |st| { + try p.import_records_for_current_part.append(allocator, st.import_record_index); + + for (st.items) |item| { + const ref = item.name.ref orelse p.panic("Expected export from item to have a name {s}", .{st}); + // Note that the imported alias is not item.Alias, which is the + // exported alias. This is somewhat confusing because each + // SExportFrom statement is basically SImport + SExportClause in one. + try p.named_imports.put(ref, js_ast.NamedImport{ + .alias_is_star = false, + .alias = item.original_name, + .alias_loc = item.name.loc, + .namespace_ref = st.namespace_ref, + .import_record_index = st.import_record_index, + .is_exported = true, + }); + try p.recordExport(item.name.loc, item.alias, ref); + } + }, + else => {}, + } + + stmts[stmts_end] = stmt; + stmts_end += 1; + } + scanner.stmts = stmts[0..stmts_end]; + return scanner; + } +}; + +const StaticSymbolName = struct { + internal: string, + primary: string, + backup: string, + + pub const List = struct { + fn NewStaticSymbol(comptime basename: string) StaticSymbolName { + return comptime StaticSymbolName{ + .internal = basename ++ "_" ++ std.fmt.comptimePrint("{x}", .{std.hash.Wyhash.hash(0, basename)}), + .primary = basename, + .backup = "_" ++ basename ++ "$", + }; + } + + fn NewStaticSymbolWithBackup(comptime basename: string, comptime backup: string) StaticSymbolName { + return comptime StaticSymbolName{ + .internal = basename ++ "_" ++ std.fmt.comptimePrint("{x}", .{std.hash.Wyhash.hash(0, basename)}), + .primary = basename, + .backup = backup, + }; + } + + pub const jsx = NewStaticSymbol("jsx"); + pub const jsxs = NewStaticSymbol("jsxs"); + pub const ImportSource = NewStaticSymbol("JSX"); + pub const ClassicImportSource = NewStaticSymbol("JSXClassic"); + pub const jsxFilename = NewStaticSymbolWithBackup("fileName", "jsxFileName"); + pub const Factory = NewStaticSymbol("jsxEl"); + pub const Refresher = NewStaticSymbol("FastRefresh"); + pub const Fragment = NewStaticSymbol("JSXFrag"); + + pub const __name = NewStaticSymbol("__name"); + pub const __toModule = NewStaticSymbol("__toModule"); + pub const __require = NewStaticSymbol("require"); + pub const __cJS2eSM = NewStaticSymbol("__cJS2eSM"); + pub const __export = NewStaticSymbol("__export"); + pub const __reExport = NewStaticSymbol("__reExport"); + pub const __load = NewStaticSymbol("__load"); + pub const @"$$lzy" = NewStaticSymbol("$$lzy"); + pub const __HMRModule = NewStaticSymbol("HMR"); + pub const __HMRClient = NewStaticSymbol("Bun"); + pub const __FastRefreshModule = NewStaticSymbol("FastHMR"); + pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh"); + + pub const @"$$m" = NewStaticSymbol("$$m"); + + pub const __exportValue = NewStaticSymbol("__exportValue"); + pub const __exportDefault = NewStaticSymbol("__exportDefault"); + pub const hmr = NewStaticSymbol("hmr"); + + pub const insert = NewStaticSymbol("insert"); + pub const template = NewStaticSymbol("template"); + pub const wrap = NewStaticSymbol("wrap"); + pub const createComponent = NewStaticSymbol("createComponent"); + pub const setAttribute = NewStaticSymbol("setAttribute"); + pub const effect = NewStaticSymbol("effect"); + pub const delegateEvents = NewStaticSymbol("delegateEvents"); + pub const Solid = NewStaticSymbol("Solid"); + }; +}; + +pub const SideEffects = enum(u1) { + could_have_side_effects, + no_side_effects, + + pub const Result = struct { + side_effects: SideEffects, + ok: bool = false, + value: bool = false, + }; + + pub fn canChangeStrictToLoose(lhs: Expr.Data, rhs: Expr.Data) bool { + const left = lhs.knownPrimitive(); + const right = rhs.knownPrimitive(); + return left == right and left != .unknown and left != .mixed; + } + + pub fn simplifyBoolean(p: anytype, expr: Expr) Expr { + switch (expr.data) { + .e_unary => |e| { + if (e.op == .un_not) { + // "!!a" => "a" + if (e.value.data == .e_unary and e.value.data.e_unary.op == .un_not) { + return simplifyBoolean(p, e.value.data.e_unary.value); + } + + e.value = simplifyBoolean(p, e.value); + } + }, + .e_binary => |e| { + switch (e.op) { + .bin_logical_and => { + const effects = SideEffects.toBoolean(e.right.data); + if (effects.ok and effects.value and effects.side_effects == .no_side_effects) { + // "if (anything && truthyNoSideEffects)" => "if (anything)" + return e.left; + } + }, + .bin_logical_or => { + const effects = SideEffects.toBoolean(e.right.data); + if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) { + // "if (anything || falsyNoSideEffects)" => "if (anything)" + return e.left; + } + }, + else => {}, + } + }, + else => {}, + } + + return expr; + } + + pub const toNumber = Expr.Data.toNumber; + pub const typeof = Expr.Data.toTypeof; + + pub fn isPrimitiveToReorder(data: Expr.Data) bool { + switch (data) { + .e_null, .e_undefined, .e_string, .e_boolean, .e_number, .e_big_int => { + return true; + }, + else => { + return false; + }, + } + } + + pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr { + switch (expr.data) { + .e_null, .e_undefined, .e_missing, .e_boolean, .e_number, .e_big_int, .e_string, .e_this, .e_reg_exp, .e_function, .e_arrow, .e_import_meta => { + return null; + }, + + .e_dot => |dot| { + if (dot.can_be_removed_if_unused) { + return null; + } + }, + .e_identifier => |ident| { + if (ident.must_keep_due_to_with_stmt) { + return expr; + } + + if (ident.can_be_removed_if_unused or p.symbols.items[ident.ref.innerIndex()].kind != .unbound) { + return null; + } + }, + .e_if => |__if__| { + __if__.yes = simpifyUnusedExpr(p, __if__.yes) orelse __if__.yes.toEmpty(); + __if__.no = simpifyUnusedExpr(p, __if__.no) orelse __if__.no.toEmpty(); + + // "foo() ? 1 : 2" => "foo()" + if (__if__.yes.isEmpty() and __if__.no.isEmpty()) { + return simpifyUnusedExpr(p, __if__.test_); + } + + // "foo() ? 1 : bar()" => "foo() || bar()" + if (__if__.yes.isEmpty()) { + return Expr.joinWithLeftAssociativeOp( + .bin_logical_or, + __if__.test_, + __if__.no, + p.allocator, + ); + } + + // "foo() ? bar() : 2" => "foo() && bar()" + if (__if__.no.isEmpty()) { + return Expr.joinWithLeftAssociativeOp( + .bin_logical_and, + __if__.test_, + __if__.yes, + p.allocator, + ); + } + }, + .e_unary => |un| { + // These operators must not have any type conversions that can execute code + // such as "toString" or "valueOf". They must also never throw any exceptions. + switch (un.op) { + .un_void, .un_not => { + return simpifyUnusedExpr(p, un.value); + }, + .un_typeof => { + // "typeof x" must not be transformed into if "x" since doing so could + // cause an exception to be thrown. Instead we can just remove it since + // "typeof x" is special-cased in the standard to never throw. + if (std.meta.activeTag(un.value.data) == .e_identifier) { + return null; + } + + return simpifyUnusedExpr(p, un.value); + }, + + else => {}, + } + }, + + .e_call => |call| { + + // A call that has been marked "__PURE__" can be removed if all arguments + // can be removed. The annotation causes us to ignore the target. + if (call.can_be_unwrapped_if_unused) { + if (call.args.len > 0) { + return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, simpifyUnusedExpr, p.allocator); + } + } + }, + + .e_binary => |bin| { + switch (bin.op) { + // These operators must not have any type conversions that can execute code + // such as "toString" or "valueOf". They must also never throw any exceptions. + .bin_strict_eq, .bin_strict_ne, .bin_comma => { + return Expr.joinWithComma( + simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), + simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), + p.allocator, + ); + }, + + // We can simplify "==" and "!=" even though they can call "toString" and/or + // "valueOf" if we can statically determine that the types of both sides are + // primitives. In that case there won't be any chance for user-defined + // "toString" and/or "valueOf" to be called. + .bin_loose_eq, + .bin_loose_ne, + => { + if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) { + return Expr.joinWithComma(simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), p.allocator); + } + }, + + .bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => { + bin.right = simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(); + // Preserve short-circuit behavior: the left expression is only unused if + // the right expression can be completely removed. Otherwise, the left + // expression is important for the branch. + + if (bin.right.isEmpty()) + return simpifyUnusedExpr(p, bin.left); + }, + + else => {}, + } + }, + + .e_object => { + // Arrays with "..." spread expressions can't be unwrapped because the + // "..." triggers code evaluation via iterators. In that case, just trim + // the other items instead and leave the array expression there. + + var properties_slice = expr.data.e_object.properties.slice(); + var end: usize = 0; + var any_computed = false; + for (properties_slice) |spread| { + end = 0; + any_computed = any_computed or spread.flags.contains(.is_computed); + if (spread.kind == .spread) { + // Spread properties must always be evaluated + for (properties_slice) |prop_| { + var prop = prop_; + if (prop_.kind != .spread) { + if (prop.value != null) { + if (simpifyUnusedExpr(p, prop.value.?)) |value| { + prop.value = value; + } else if (!prop.flags.contains(.is_computed)) { + continue; + } else { + prop.value = p.e(E.Number{ .value = 0.0 }, prop.value.?.loc); + } + } + } + + properties_slice[end] = prop_; + end += 1; + } + + properties_slice = properties_slice[0..end]; + expr.data.e_object.properties = G.Property.List.init(properties_slice); + return expr; + } + } + + if (any_computed) { + // Otherwise, the object can be completely removed. We only need to keep any + // object properties with side effects. Apply this simplification recursively. + // for (properties_slice) |prop| { + // if (prop.flags.is_computed) { + // // Make sure "ToString" is still evaluated on the key + + // } + // } + + // keep this for now because we need better test coverage to do this correctly + return expr; + } + + return null; + }, + .e_array => { + var items = expr.data.e_array.items.slice(); + + for (items) |item| { + if (item.data == .e_spread) { + var end: usize = 0; + for (items) |item__| { + var item_ = item__; + if (item_.data != .e_missing) { + items[end] = item_; + end += 1; + } + + expr.data.e_array.items = ExprNodeList.init(items[0..end]); + return expr; + } + } + } + + // Otherwise, the array can be completely removed. We only need to keep any + // array items with side effects. Apply this simplification recursively. + return Expr.joinAllWithCommaCallback( + items, + @TypeOf(p), + p, + simpifyUnusedExpr, + p.allocator, + ); + }, + + .e_new => |call| { + // A constructor call that has been marked "__PURE__" can be removed if all arguments + // can be removed. The annotation causes us to ignore the target. + if (call.can_be_unwrapped_if_unused) { + if (call.args.len > 0) { + return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, simpifyUnusedExpr, p.allocator); + } + } + }, + else => {}, + } + + return expr; + } + + fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void { + switch (binding.data) { + .b_identifier => { + decls.append(.{ .binding = binding }) catch unreachable; + }, + .b_array => |array| { + for (array.items) |item| { + findIdentifiers(item.binding, decls); + } + }, + .b_object => |obj| { + for (obj.properties) |item| { + findIdentifiers(item.value, decls); + } + }, + else => {}, + } + } + + // If this is in a dead branch, then we want to trim as much dead code as we + // can. Everything can be trimmed except for hoisted declarations ("var" and + // "function"), which affect the parent scope. For example: + // + // function foo() { + // if (false) { var x; } + // x = 1; + // } + // + // We can't trim the entire branch as dead or calling foo() will incorrectly + // assign to a global variable instead. + pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool { + switch (stmt.data) { + // Omit these statements entirely + .s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false, + + .s_local => |local| { + if (local.kind != .k_var) { + // Omit these statements entirely + return false; + } + + // Omit everything except the identifiers + + // common case: single var foo = blah, don't need to allocate + if (local.decls.len == 1 and local.decls[0].binding.data == .b_identifier) { + const prev = local.decls[0]; + stmt.data.s_local.decls[0] = G.Decl{ .binding = prev.binding }; + return true; + } + + var decls = std.ArrayList(G.Decl).initCapacity(allocator, local.decls.len) catch unreachable; + for (local.decls) |decl| { + findIdentifiers(decl.binding, &decls); + } + + local.decls = decls.toOwnedSlice(); + return true; + }, + + .s_block => |block| { + for (block.stmts) |child| { + if (shouldKeepStmtInDeadControlFlow(child, allocator)) { + return true; + } + } + + return false; + }, + + .s_if => |_if_| { + if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) { + return true; + } + + const no = _if_.no orelse return false; + + return shouldKeepStmtInDeadControlFlow(no, allocator); + }, + + .s_while => { + return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator); + }, + + .s_do_while => { + return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator); + }, + + .s_for => |__for__| { + if (__for__.init) |init_| { + if (shouldKeepStmtInDeadControlFlow(init_, allocator)) { + return true; + } + } + + return shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + }, + + .s_for_in => |__for__| { + return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + }, + + .s_for_of => |__for__| { + return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + }, + + .s_label => |label| { + return shouldKeepStmtInDeadControlFlow(label.stmt, allocator); + }, + else => return true, + } + } + + // Returns true if this expression is known to result in a primitive value (i.e. + // null, undefined, boolean, number, bigint, or string), even if the expression + // cannot be removed due to side effects. + pub fn isPrimitiveWithSideEffects(data: Expr.Data) bool { + switch (data) { + .e_null, .e_undefined, .e_boolean, .e_number, .e_big_int, .e_string => { + return true; + }, + .e_unary => |e| { + switch (e.op) { + // number or bigint + .un_pos, + .un_neg, + .un_cpl, + .un_pre_dec, + .un_pre_inc, + .un_post_dec, + .un_post_inc, + // boolean + .un_not, + .un_delete, + // undefined + .un_void, + // string + .un_typeof, + => { + return true; + }, + else => {}, + } + }, + .e_binary => |e| { + switch (e.op) { + // boolean + .bin_lt, + .bin_le, + .bin_gt, + .bin_ge, + .bin_in, + .bin_instanceof, + .bin_loose_eq, + .bin_loose_ne, + .bin_strict_eq, + .bin_strict_ne, + // string, number, or bigint + .bin_add, + .bin_add_assign, + // number or bigint + .bin_sub, + .bin_mul, + .bin_div, + .bin_rem, + .bin_pow, + .bin_sub_assign, + .bin_mul_assign, + .bin_div_assign, + .bin_rem_assign, + .bin_pow_assign, + .bin_shl, + .bin_shr, + .bin_u_shr, + .bin_shl_assign, + .bin_shr_assign, + .bin_u_shr_assign, + .bin_bitwise_or, + .bin_bitwise_and, + .bin_bitwise_xor, + .bin_bitwise_or_assign, + .bin_bitwise_and_assign, + .bin_bitwise_xor_assign, + => { + return true; + }, + + // These always return one of the arguments unmodified + .bin_logical_and, + .bin_logical_or, + .bin_nullish_coalescing, + .bin_logical_and_assign, + .bin_logical_or_assign, + .bin_nullish_coalescing_assign, + => { + return isPrimitiveWithSideEffects(e.left.data) and isPrimitiveWithSideEffects(e.right.data); + }, + .bin_comma => { + return isPrimitiveWithSideEffects(e.right.data); + }, + else => {}, + } + }, + .e_if => |e| { + return isPrimitiveWithSideEffects(e.yes.data) and isPrimitiveWithSideEffects(e.no.data); + }, + else => {}, + } + return false; + } + + pub const toTypeOf = Expr.Data.typeof; + + pub fn toNullOrUndefined(exp: Expr.Data) Result { + switch (exp) { + // Never null or undefined + .e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => { + return Result{ .value = false, .side_effects = SideEffects.no_side_effects, .ok = true }; + }, + + .e_object, .e_array, .e_class => { + return Result{ .value = false, .side_effects = .could_have_side_effects, .ok = true }; + }, + + // always anull or undefined + .e_null, .e_undefined => { + return Result{ .value = true, .side_effects = .no_side_effects, .ok = true }; + }, + + .e_unary => |e| { + switch (e.op) { + // Always number or bigint + .un_pos, + .un_neg, + .un_cpl, + .un_pre_dec, + .un_pre_inc, + .un_post_dec, + .un_post_inc, + + // Always boolean + .un_not, + .un_typeof, + .un_delete, + => { + return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects }; + }, + + // Always undefined + .un_void => { + return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true }; + }, + + else => {}, + } + }, + + .e_binary => |e| { + switch (e.op) { + // always string or number or bigint + .bin_add, + .bin_add_assign, + // always number or bigint + .bin_sub, + .bin_mul, + .bin_div, + .bin_rem, + .bin_pow, + .bin_sub_assign, + .bin_mul_assign, + .bin_div_assign, + .bin_rem_assign, + .bin_pow_assign, + .bin_shl, + .bin_shr, + .bin_u_shr, + .bin_shl_assign, + .bin_shr_assign, + .bin_u_shr_assign, + .bin_bitwise_or, + .bin_bitwise_and, + .bin_bitwise_xor, + .bin_bitwise_or_assign, + .bin_bitwise_and_assign, + .bin_bitwise_xor_assign, + // always boolean + .bin_lt, + .bin_le, + .bin_gt, + .bin_ge, + .bin_in, + .bin_instanceof, + .bin_loose_eq, + .bin_loose_ne, + .bin_strict_eq, + .bin_strict_ne, + => { + return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects }; + }, + + .bin_comma => { + const res = toNullOrUndefined(e.right.data); + if (res.ok) { + return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects }; + } + }, + else => {}, + } + }, + else => {}, + } + + return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects }; + } + + pub fn toBoolean(exp: Expr.Data) Result { + switch (exp) { + .e_null, .e_undefined => { + return Result{ .ok = true, .value = false, .side_effects = .no_side_effects }; + }, + .e_boolean => |e| { + return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects }; + }, + .e_number => |e| { + return Result{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects }; + }, + .e_big_int => |e| { + return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects }; + }, + .e_string => |e| { + return Result{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects }; + }, + .e_function, .e_arrow, .e_reg_exp => { + return Result{ .ok = true, .value = true, .side_effects = .no_side_effects }; + }, + .e_object, .e_array, .e_class => { + return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; + }, + .e_unary => |e_| { + switch (e_.op) { + .un_void => { + return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects }; + }, + .un_typeof => { + // Never an empty string + + return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; + }, + .un_not => { + var result = toBoolean(e_.value.data); + if (result.ok) { + result.value = !result.value; + return result; + } + }, + else => {}, + } + }, + .e_binary => |e_| { + switch (e_.op) { + .bin_logical_or => { + // "anything || truthy" is truthy + const result = toBoolean(e_.right.data); + if (result.value and result.ok) { + return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; + } + }, + .bin_logical_and => { + // "anything && falsy" is falsy + const result = toBoolean(e_.right.data); + if (!result.value and result.ok) { + return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects }; + } + }, + .bin_comma => { + // "anything, truthy/falsy" is truthy/falsy + var result = toBoolean(e_.right.data); + if (result.ok) { + result.side_effects = .could_have_side_effects; + return result; + } + }, + else => {}, + } + }, + else => {}, + } + + return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects }; + } +}; + +const ExprOrLetStmt = struct { + stmt_or_expr: js_ast.StmtOrExpr, + decls: []G.Decl = &([_]G.Decl{}), +}; + +const FunctionKind = enum { stmt, expr }; + +const AsyncPrefixExpression = enum(u2) { + none, + is_yield, + is_async, + is_await, + + const map = ComptimeStringMap(AsyncPrefixExpression, .{ + .{ "yield", .is_yield }, + .{ "await", .is_await }, + .{ "async", .is_async }, + }); + + pub fn find(ident: string) AsyncPrefixExpression { + return map.get(ident) orelse .none; + } +}; + +const IdentifierOpts = struct { + assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, + is_delete_target: bool = false, + was_originally_identifier: bool = false, +}; + +fn statementCaresAboutScope(stmt: Stmt) bool { + switch (stmt.data) { + .s_block, + .s_empty, + .s_debugger, + .s_expr, + .s_if, + .s_for, + .s_for_in, + .s_for_of, + .s_do_while, + .s_while, + .s_with, + .s_try, + .s_switch, + .s_return, + .s_throw, + .s_break, + .s_continue, + .s_directive, + => { + return false; + }, + // This is technically incorrect. + // var does not care about the scope + // However, we are choosing _not_ to relocate vars to the top level + + .s_local => |local| { + return local.kind != .k_var; + }, + else => { + return true; + }, + } +} + +const ExprIn = struct { + // This tells us if there are optional chain expressions (EDot, EIndex, or + // ECall) that are chained on to this expression. Because of the way the AST + // works, chaining expressions on to this expression means they are our + // parent expressions. + // + // Some examples: + // + // a?.b.c // EDot + // a?.b[c] // EIndex + // a?.b() // ECall + // + // Note that this is false if our parent is a node with a OptionalChain + // value of OptionalChainStart. That means it's the start of a new chain, so + // it's not considered part of this one. + // + // Some examples: + // + // a?.b?.c // EDot + // a?.b?.[c] // EIndex + // a?.b?.() // ECall + // + // Also note that this is false if our parent is a node with a OptionalChain + // value of OptionalChainNone. That means it's outside parentheses, which + // means it's no longer part of the chain. + // + // Some examples: + // + // (a?.b).c // EDot + // (a?.b)[c] // EIndex + // (a?.b)() // ECall + // + has_chain_parent: bool = false, + + // If our parent is an ECall node with an OptionalChain value of + // OptionalChainStart, then we will need to store the value for the "this" of + // that call somewhere if the current expression is an optional chain that + // ends in a property access. That's because the value for "this" will be + // used twice: once for the inner optional chain and once for the outer + // optional chain. + // + // Example: + // + // // Original + // a?.b?.(); + // + // // Lowered + // var _a; + // (_a = a == null ? void 0 : a.b) == null ? void 0 : _a.call(a); + // + // In the example above we need to store "a" as the value for "this" so we + // can substitute it back in when we call "_a" if "_a" is indeed present. + // See also "thisArgFunc" and "thisArgWrapFunc" in "exprOut". + store_this_arg_for_parent_optional_chain: bool = false, + + // Certain substitutions of identifiers are disallowed for assignment targets. + // For example, we shouldn't transform "undefined = 1" into "void 0 = 1". This + // isn't something real-world code would do but it matters for conformance + // tests. + assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, +}; + +const ExprOut = struct { + // True if the child node is an optional chain node (EDot, EIndex, or ECall + // with an IsOptionalChain value of true) + child_contains_optional_chain: bool = false, +}; + +const Tup = std.meta.Tuple; + +// This function exists to tie all of these checks together in one place +// This can sometimes show up on benchmarks as a small thing. +fn isEvalOrArguments(name: string) bool { + return strings.eqlComptime(name, "eval") or strings.eqlComptime(name, "arguments"); +} + +const PrependTempRefsOpts = struct { + fn_body_loc: ?logger.Loc = null, + kind: StmtsKind = StmtsKind.none, +}; + +pub const StmtsKind = enum { + none, + loop_body, + fn_body, +}; + +fn notimpl() noreturn { + Global.panic("Not implemented yet!!", .{}); +} + +const ExprBindingTuple = struct { + expr: ?ExprNodeIndex = null, + binding: ?Binding = null, +}; + +const TempRef = struct { + ref: Ref, + value: ?Expr = null, +}; + +const ImportNamespaceCallOrConstruct = struct { + ref: Ref, + is_construct: bool = false, +}; + +const ThenCatchChain = struct { + next_target: js_ast.Expr.Data, + has_multiple_args: bool = false, + has_catch: bool = false, +}; + +const ParsedPath = struct { loc: logger.Loc, text: string }; + +const StrictModeFeature = enum { + with_statement, + delete_bare_name, + for_in_var_init, + eval_or_arguments, + reserved_word, + legacy_octal_literal, + legacy_octal_escape, + if_else_function_stmt, +}; + +const Map = _hash_map.AutoHashMapUnmanaged; + +const List = std.ArrayListUnmanaged; +const ListManaged = std.ArrayList; +const InvalidLoc = struct { + loc: logger.Loc, + kind: Tag = Tag.unknown, + + pub const Tag = enum { + spread, + parenthese, + getter, + setter, + method, + unknown, + }; + + pub fn addError(loc: InvalidLoc, log: *logger.Log, source: *const logger.Source) void { + @setCold(true); + const text = switch (loc.kind) { + .spread => "Unexpected trailing comma after rest element", + .parenthese => "Unexpected parentheses in binding pattern", + .getter => "Unexpected getter in binding pattern", + .setter => "Unexpected setter in binding pattern", + .method => "Unexpected method in binding pattern", + .unknown => "Invalid binding pattern", + }; + log.addError(source, loc.loc, text) catch unreachable; + } +}; +const LocList = ListManaged(InvalidLoc); +const StmtList = ListManaged(Stmt); + +// This hash table is used every time we parse function args +// Rather than allocating a new hash table each time, we can just reuse the previous allocation + +const StringVoidMap = struct { + allocator: Allocator, + map: std.StringHashMapUnmanaged(void) = std.StringHashMapUnmanaged(void){}, + + /// Returns true if the map already contained the given key. + pub fn getOrPutContains(this: *StringVoidMap, key: string) bool { + const entry = this.map.getOrPut(this.allocator, key) catch unreachable; + return entry.found_existing; + } + + pub fn contains(this: *StringVoidMap, key: string) bool { + return this.map.contains(key); + } + + fn init(allocator: Allocator) anyerror!StringVoidMap { + return StringVoidMap{ .allocator = allocator }; + } + + pub fn reset(this: *StringVoidMap) void { + // We must reset or the hash table will contain invalid pointers + this.map.clearRetainingCapacity(); + } + + pub inline fn get(allocator: Allocator) *Node { + return Pool.get(allocator); + } + + pub inline fn release(node: *Node) void { + Pool.release(node); + } + + pub const Pool = ObjectPool(StringVoidMap, init, true, 32); + pub const Node = Pool.Node; +}; +const RefCtx = @import("./ast/base.zig").RefCtx; +const SymbolUseMap = std.HashMapUnmanaged(Ref, js_ast.Symbol.Use, RefCtx, 80); +const StringBoolMap = std.StringHashMapUnmanaged(bool); +const RefMap = std.HashMapUnmanaged(Ref, void, RefCtx, 80); +const RefArrayMap = std.ArrayHashMapUnmanaged(Ref, void, @import("./ast/base.zig").RefHashCtx, false); + +const RefRefMap = std.HashMapUnmanaged(Ref, Ref, RefCtx, 80); +const ImportRecord = importRecord.ImportRecord; +const Flags = js_ast.Flags; +const ScopeOrder = struct { + loc: logger.Loc, + scope: *js_ast.Scope, +}; + +const ParenExprOpts = struct { + async_range: logger.Range = logger.Range.None, + is_async: bool = false, + force_arrow_fn: bool = false, +}; + +const AwaitOrYield = enum(u3) { + allow_ident, + allow_expr, + forbid_all, +}; + +// This is function-specific information used during parsing. It is saved and +// restored on the call stack around code that parses nested functions and +// arrow expressions. +const FnOrArrowDataParse = struct { + async_range: logger.Range = logger.Range.None, + allow_await: AwaitOrYield = AwaitOrYield.allow_ident, + allow_yield: AwaitOrYield = AwaitOrYield.allow_ident, + allow_super_call: bool = false, + allow_super_property: bool = false, + is_top_level: bool = false, + is_constructor: bool = false, + is_typescript_declare: bool = false, + + is_return_disallowed: bool = false, + is_this_disallowed: bool = false, + + has_async_range: bool = false, + arrow_arg_errors: DeferredArrowArgErrors = DeferredArrowArgErrors{}, + track_arrow_arg_errors: bool = false, + + // In TypeScript, forward declarations of functions have no bodies + allow_missing_body_for_type_script: bool = false, + + // Allow TypeScript decorators in function arguments + allow_ts_decorators: bool = false, + + pub fn i() FnOrArrowDataParse { + return FnOrArrowDataParse{ .allow_await = AwaitOrYield.forbid_all }; + } +}; + +// This is function-specific information used during visiting. It is saved and +// restored on the call stack around code that parses nested functions and +// arrow expressions. +const FnOrArrowDataVisit = struct { + // super_index_ref: ?*Ref = null, + + is_arrow: bool = false, + is_async: bool = false, + is_inside_loop: bool = false, + is_inside_switch: bool = false, + is_outside_fn_or_arrow: bool = false, + + // This is used to silence unresolvable imports due to "require" calls inside + // a try/catch statement. The assumption is that the try/catch statement is + // there to handle the case where the reference to "require" crashes. + try_body_count: i32 = 0, +}; + +// This is function-specific information used during visiting. It is saved and +// restored on the call stack around code that parses nested functions (but not +// nested arrow functions). +const FnOnlyDataVisit = struct { + // This is a reference to the magic "arguments" variable that exists inside + // functions in JavaScript. It will be non-nil inside functions and nil + // otherwise. + arguments_ref: ?Ref = null, + + // Arrow functions don't capture the value of "this" and "arguments". Instead, + // the values are inherited from the surrounding context. If arrow functions + // are turned into regular functions due to lowering, we will need to generate + // local variables to capture these values so they are preserved correctly. + this_capture_ref: ?Ref = null, + arguments_capture_ref: ?Ref = null, + + // Inside a static class property initializer, "this" expressions should be + // replaced with the class name. + this_class_static_ref: ?Ref = null, + + // If we're inside an async arrow function and async functions are not + // supported, then we will have to convert that arrow function to a generator + // function. That means references to "arguments" inside the arrow function + // will have to reference a captured variable instead of the real variable. + is_inside_async_arrow_fn: bool = false, + + // If false, disallow "new.target" expressions. We disallow all "new.target" + // expressions at the top-level of the file (i.e. not inside a function or + // a class field). Technically since CommonJS files are wrapped in a function + // you can use "new.target" in node as an alias for "undefined" but we don't + // support that. + is_new_target_allowed: bool = false, + + // If false, the value for "this" is the top-level module scope "this" value. + // That means it's "undefined" for ECMAScript modules and "exports" for + // CommonJS modules. We track this information so that we can substitute the + // correct value for these top-level "this" references at compile time instead + // of passing the "this" expression through to the output and leaving the + // interpretation up to the run-time behavior of the generated code. + // + // If true, the value for "this" is nested inside something (either a function + // or a class declaration). That means the top-level module scope "this" value + // has been shadowed and is now inaccessible. + is_this_nested: bool = false, +}; + +// Due to ES6 destructuring patterns, there are many cases where it's +// impossible to distinguish between an array or object literal and a +// destructuring assignment until we hit the "=" operator later on. +// This object defers errors about being in one state or the other +// until we discover which state we're in. +const DeferredErrors = struct { + // These are errors for expressions + invalid_expr_default_value: ?logger.Range = null, + invalid_expr_after_question: ?logger.Range = null, + array_spread_feature: ?logger.Range = null, + + pub fn isEmpty(self: *DeferredErrors) bool { + return self.invalid_expr_default_value == null and self.invalid_expr_after_question == null and self.array_spread_feature == null; + } + + pub fn mergeInto(self: *DeferredErrors, to: *DeferredErrors) void { + to.invalid_expr_default_value = self.invalid_expr_default_value orelse to.invalid_expr_default_value; + to.invalid_expr_after_question = self.invalid_expr_after_question orelse to.invalid_expr_after_question; + to.array_spread_feature = self.array_spread_feature orelse to.array_spread_feature; + } + + const None = DeferredErrors{ + .invalid_expr_default_value = null, + .invalid_expr_after_question = null, + .array_spread_feature = null, + }; +}; + +const ImportClause = struct { + items: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), + is_single_line: bool = false, + had_type_only_imports: bool = false, +}; + +const ModuleType = enum { esm }; + +const PropertyOpts = struct { + async_range: logger.Range = logger.Range.None, + declare_range: logger.Range = logger.Range.None, + is_async: bool = false, + is_generator: bool = false, + + // Class-related options + is_static: bool = false, + is_class: bool = false, + class_has_extends: bool = false, + allow_ts_decorators: bool = false, + ts_decorators: []Expr = &[_]Expr{}, +}; + +pub const ScanPassResult = struct { + pub const ParsePassSymbolUse = struct { ref: Ref, used: bool = false, import_record_index: u32 }; + pub const NamespaceCounter = struct { count: u16, import_record_index: u32 }; + pub const ParsePassSymbolUsageMap = std.StringArrayHashMap(ParsePassSymbolUse); + import_records: ListManaged(ImportRecord), + named_imports: js_ast.Ast.NamedImports, + used_symbols: ParsePassSymbolUsageMap, + import_records_to_keep: ListManaged(u32), + approximate_newline_count: usize = 0, + + pub fn init(allocator: Allocator) ScanPassResult { + return .{ + .import_records = ListManaged(ImportRecord).init(allocator), + .named_imports = js_ast.Ast.NamedImports.init(allocator), + .used_symbols = ParsePassSymbolUsageMap.init(allocator), + .import_records_to_keep = ListManaged(u32).init(allocator), + .approximate_newline_count = 0, + }; + } + + pub fn reset(scan_pass: *ScanPassResult) void { + scan_pass.named_imports.clearRetainingCapacity(); + scan_pass.import_records.shrinkRetainingCapacity(0); + scan_pass.used_symbols.clearRetainingCapacity(); + scan_pass.approximate_newline_count = 0; + } +}; + +fn MacroContextType() type { + if (comptime Environment.isWasm) { + return ?*anyopaque; + } + + return js_ast.Macro.MacroContext; +} + +pub const Parser = struct { + options: Options, + lexer: js_lexer.Lexer, + log: *logger.Log, + source: *const logger.Source, + define: *Define, + allocator: Allocator, + + pub const Options = struct { + jsx: options.JSX.Pragma, + can_import_from_bundle: bool = false, + ts: bool = false, + keep_names: bool = true, + omit_runtime_for_tests: bool = false, + ignore_dce_annotations: bool = false, + preserve_unused_imports_ts: bool = false, + use_define_for_class_fields: bool = false, + suppress_warnings_about_weird_code: bool = true, + filepath_hash_for_hmr: u32 = 0, + features: RuntimeFeatures = RuntimeFeatures{}, + + tree_shaking: bool = false, + + macro_context: *MacroContextType() = undefined, + + warn_about_unbundled_modules: bool = true, + + // Used when bundling node_modules + enable_bundling: bool = false, + transform_require_to_import: bool = true, + + moduleType: ModuleType = ModuleType.esm, + + pub fn init(jsx: options.JSX.Pragma, loader: options.Loader) Options { + var opts = Options{ + .ts = loader.isTypeScript(), + + .jsx = jsx, + }; + opts.jsx.parse = loader.isJSX(); + return opts; + } + }; + + pub fn scanImports(self: *Parser, scan_pass: *ScanPassResult) !void { + if (self.options.ts and self.options.jsx.parse) { + return try self._scanImports(TSXImportScanner, scan_pass); + } else if (self.options.ts) { + return try self._scanImports(TypeScriptImportScanner, scan_pass); + } else if (self.options.jsx.parse) { + return try self._scanImports(JSXImportScanner, scan_pass); + } else { + return try self._scanImports(JavaScriptImportScanner, scan_pass); + } + } + + fn _scanImports(self: *Parser, comptime ParserType: type, scan_pass: *ScanPassResult) !void { + var p: ParserType = undefined; + + try ParserType.init(self.allocator, self.log, self.source, self.define, self.lexer, self.options, &p); + p.import_records = &scan_pass.import_records; + p.named_imports = &scan_pass.named_imports; + + // The problem with our scan pass approach is type-only imports. + // We don't have accurate symbol counts. + // So we don't have a good way to distuingish between a type-only import and not. + if (comptime ParserType.parser_features.typescript) { + p.parse_pass_symbol_uses = &scan_pass.used_symbols; + } + + // Parse the file in the first pass, but do not bind symbols + var opts = ParseStatementOptions{ .is_module_scope = true }; + + // Parsing seems to take around 2x as much time as visiting. + // Which makes sense. + // June 4: "Parsing took: 18028000" + // June 4: "Rest of this took: 8003000" + _ = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts); + + // + if (comptime ParserType.parser_features.typescript) { + for (scan_pass.import_records.items) |*import_record| { + // Mark everything as unused + // Except: + // - export * as ns from 'foo'; + // - export * from 'foo'; + // - import 'foo'; + // - import("foo") + // - require("foo") + import_record.is_unused = import_record.is_unused or + (import_record.kind == .stmt and + !import_record.was_originally_bare_import and + !import_record.calls_run_time_re_export_fn); + } + + var iter = scan_pass.used_symbols.iterator(); + while (iter.next()) |entry| { + const val = entry.value_ptr; + if (val.used) { + scan_pass.import_records.items[val.import_record_index].is_unused = false; + } + } + } + + // Symbol use counts are unavailable + // So we say "did we parse any JSX?" + // if yes, just automatically add the import so that .bun knows to include the file. + if (self.options.jsx.parse and p.needs_jsx_import) { + _ = p.addImportRecord( + .require, + logger.Loc{ .start = 0 }, + p.options.jsx.import_source, + ); + // Ensure we have both classic and automatic + // This is to handle cases where they use fragments in the automatic runtime + _ = p.addImportRecord( + .require, + logger.Loc{ .start = 0 }, + p.options.jsx.classic_import_source, + ); + } + + scan_pass.approximate_newline_count = p.lexer.approximate_newline_count; + } + + pub fn parse(self: *Parser) !js_ast.Result { + if (comptime Environment.isWasm) { + self.options.ts = true; + self.options.jsx.parse = true; + // if (self.options.features.is_macro_runtime) { + // return try self._parse(TSParserMacro); + // } + + return try self._parse(TSXParser); + } + + if (self.options.ts and self.options.features.is_macro_runtime) return try self._parse(TSParserMacro); + if (!self.options.ts and self.options.features.is_macro_runtime) return try self._parse(JSParserMacro); + + if (self.options.ts and self.options.jsx.parse) { + return if (self.options.jsx.runtime != .solid) try self._parse(TSXParser) else try self._parse(SolidTSXParser); + } else if (self.options.ts) { + return try self._parse(TypeScriptParser); + } else if (self.options.jsx.parse) { + return if (self.options.jsx.runtime != .solid) try self._parse(JSXParser) else try self._parse(SolidJSXParser); + } else { + return try self._parse(JavaScriptParser); + } + } + + fn _parse(self: *Parser, comptime ParserType: type) !js_ast.Result { + var p: ParserType = undefined; + try ParserType.init(self.allocator, self.log, self.source, self.define, self.lexer, self.options, &p); + p.should_fold_numeric_constants = self.options.features.should_fold_numeric_constants; + defer p.lexer.deinit(); + var result: js_ast.Result = undefined; + + // defer { + // if (p.allocated_names_pool) |pool| { + // pool.data = p.allocated_names; + // pool.release(); + // p.allocated_names_pool = null; + // } + // } + + // Consume a leading hashbang comment + var hashbang: string = ""; + if (p.lexer.token == .t_hashbang) { + hashbang = p.lexer.identifier; + try p.lexer.next(); + } + + // Parse the file in the first pass, but do not bind symbols + var opts = ParseStatementOptions{ .is_module_scope = true }; + + // Parsing seems to take around 2x as much time as visiting. + // Which makes sense. + // June 4: "Parsing took: 18028000" + // June 4: "Rest of this took: 8003000" + const stmts = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts); + + try p.prepareForVisitPass(); + + // ESM is always strict mode. I don't think we need this. + // // Strip off a leading "use strict" directive when not bundling + // var directive = ""; + + // Insert a variable for "import.meta" at the top of the file if it was used. + // We don't need to worry about "use strict" directives because this only + // happens when bundling, in which case we are flatting the module scopes of + // all modules together anyway so such directives are meaningless. + // if (!p.import_meta_ref.isSourceIndexNull()) { + // // heap so it lives beyond this function call + // var decls = try p.allocator.alloc(G.Decl, 1); + // decls[0] = Decl{ .binding = p.b(B.Identifier{ + // .ref = p.import_meta_ref, + // }, logger.Loc.Empty), .value = p.e(E.Object{}, logger.Loc.Empty) }; + // var importMetaStatement = p.s(S.Local{ + // .kind = .k_const, + // .decls = decls, + // }, logger.Loc.Empty); + // } + + var before = ListManaged(js_ast.Part).init(p.allocator); + var after = ListManaged(js_ast.Part).init(p.allocator); + var parts = ListManaged(js_ast.Part).init(p.allocator); + + if (!p.options.tree_shaking) { + try p.appendPart(&parts, stmts); + } else { + // When tree shaking is enabled, each top-level statement is potentially a separate part. + for (stmts) |stmt| { + // switch (stmt.data) { + + // } + switch (stmt.data) { + .s_local => |local| { + if (local.decls.len > 1) { + for (local.decls) |decl| { + var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); + sliced.items.len = 1; + var _local = local.*; + var list = try ListManaged(G.Decl).initCapacity(p.allocator, 1); + list.items.len = 1; + list.items[0] = decl; + _local.decls = list.items; + sliced.items[0] = p.s(_local, stmt.loc); + try p.appendPart(&parts, sliced.items); + } + } else { + var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); + sliced.items.len = 1; + sliced.items[0] = stmt; + try p.appendPart(&parts, sliced.items); + } + }, + else => { + var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); + sliced.items.len = 1; + sliced.items[0] = stmt; + try p.appendPart(&parts, sliced.items); + }, + } + } + } + + const uses_dirname = p.symbols.items[p.dirname_ref.innerIndex()].use_count_estimate > 0; + const uses_dynamic_require = p.options.features.dynamic_require and p.symbols.items[p.require_ref.innerIndex()].use_count_estimate > 0; + const uses_filename = p.symbols.items[p.filename_ref.innerIndex()].use_count_estimate > 0; + + if (uses_dynamic_require) { + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); + var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; + var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + var exprs = p.allocator.alloc(Expr, 1) catch unreachable; + exprs[0] = p.e(E.ImportMeta{}, logger.Loc.Empty); + // var require = import.meta.require.bind(import.meta) + decls[0] = .{ + .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), + .value = p.e( + E.Call{ + .target = p.e( + E.Dot{ + .target = p.e( + E.Dot{ + .target = p.e(E.ImportMeta{}, logger.Loc.Empty), + .name = "require", + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + .name = "bind", + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(exprs), + }, + logger.Loc.Empty, + ), + }; + + declared_symbols[0] = .{ .ref = p.require_ref, .is_top_level = true }; + + part_stmts[0] = p.s(S.Local{ + .kind = .k_var, + .decls = decls, + }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .tag = .dirname_filename, + }) catch unreachable; + } + + if (uses_dirname or uses_filename) { + const count = @as(usize, @boolToInt(uses_dirname)) + @as(usize, @boolToInt(uses_filename)); + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, count); + var decls = p.allocator.alloc(G.Decl, count) catch unreachable; + if (uses_dirname) { + decls[0] = .{ + .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), + .value = p.e( + // TODO: test UTF-8 file paths + E.String.init(p.source.path.name.dir), + logger.Loc.Empty, + ), + }; + declared_symbols[0] = .{ .ref = p.dirname_ref, .is_top_level = true }; + } + if (uses_filename) { + decls[@as(usize, @boolToInt(uses_dirname))] = .{ + .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), + .value = p.e( + E.String.init(p.source.path.text), + logger.Loc.Empty, + ), + }; + declared_symbols[@as(usize, @boolToInt(uses_dirname))] = .{ .ref = p.filename_ref, .is_top_level = true }; + } + + // TODO: DeclaredSymbol + var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + part_stmts[0] = p.s(S.Local{ + .kind = .k_var, + .decls = decls, + }, logger.Loc.Empty); + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .tag = .dirname_filename, + }) catch unreachable; + } + + var did_import_fast_refresh = false; + + // Analyze cross-part dependencies for tree shaking and code splitting + var exports_kind = js_ast.ExportsKind.none; + const uses_exports_ref = p.symbols.items[p.exports_ref.innerIndex()].use_count_estimate > 0; + const uses_module_ref = p.symbols.items[p.module_ref.innerIndex()].use_count_estimate > 0; + + var wrapper_expr: ?Expr = null; + + if ((p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0) and !uses_exports_ref) { + exports_kind = .esm; + } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { + exports_kind = .cjs; + if (p.options.transform_require_to_import) { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + + if (p.runtime_imports.__exportDefault == null and p.has_export_default) { + p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); + p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); + } + + wrapper_expr = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args); + p.resolveGeneratedSymbol(&p.runtime_imports.__cJS2eSM.?); + + // Disable HMR if we're wrapping it in CommonJS + // It's technically possible to support this. + // But we need to cut scope for the v0. + p.options.features.hot_module_reloading = false; + p.options.features.react_fast_refresh = false; + p.runtime_imports.__HMRModule = null; + p.runtime_imports.__FastRefreshModule = null; + p.runtime_imports.__FastRefreshRuntime = null; + p.runtime_imports.__HMRClient = null; + } + } else { + exports_kind = .esm; + } + + // Auto-import JSX + if (ParserType.jsx_transform_type == .react) { + const jsx_filename_symbol = p.symbols.items[p.jsx_filename.ref.innerIndex()]; + + { + const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; + const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; + const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; + const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; + + // Currently, React (and most node_modules) ship a CJS version or a UMD version + // but we should assume that it'll pretty much always be CJS + // Given that, we can't directly call import {jsxDEV} from 'react'; + // Instead, we must call require("react").default.jsxDEV + // So a jsx_symbol usage means a jsx_factory_symbol usage + // This is kind of a broken way of doing it because it wouldn't work if it was more than one level deep + if (FeatureFlags.jsx_runtime_is_cjs) { + if (jsx_symbol.use_count_estimate > 0 or jsx_static_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_automatic.ref); + } + + if (jsx_fragment_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_classic.ref); + } + + if (jsx_factory_symbol.use_count_estimate > 0) { + p.recordUsage(p.jsx_classic.ref); + } + } + } + + p.resolveStaticJSXSymbols(); + + if (p.options.features.auto_import_jsx) { + const jsx_classic_symbol = p.symbols.items[p.jsx_classic.ref.innerIndex()]; + const jsx_automatic_symbol = p.symbols.items[p.jsx_automatic.ref.innerIndex()]; + + // JSX auto-imports + // The classic runtime is a different import than the main import + // There are cases where you can use both JSX runtimes in the same file. + // 1. If you use a spread operator like this: <div foo bar key="foo" {...props} baz /> + // 2. If you use a React.Fragment + // So we have to support both. + if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0) { + // These must unfortunately be copied + // p.symbols may grow during this scope + // if it grows, the previous pointers are invalidated + const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; + const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; + const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; + const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; + + const classic_namespace_ref = p.jsx_classic.ref; + const automatic_namespace_ref = p.jsx_automatic.ref; + + const decls_count: u32 = + @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) * 2 + + @intCast(u32, @boolToInt(jsx_static_symbol.use_count_estimate > 0)) * 2 + + @intCast(u32, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_filename_symbol.use_count_estimate > 0)); + + const imports_count = + @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_classic_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + + @intCast(u32, @boolToInt(p.options.features.react_fast_refresh)) + + @intCast(u32, @boolToInt(jsx_static_symbol.use_count_estimate > 0)); + const stmts_count = imports_count + 1; + const symbols_count: u32 = imports_count + decls_count; + const loc = logger.Loc{ .start = 0 }; + + // Preallocate everything we'll need here + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, symbols_count); + var decls = try p.allocator.alloc(G.Decl, decls_count); + var jsx_part_stmts = try p.allocator.alloc(Stmt, stmts_count); + // Use the same array for storing the require call target of potentially both JSX runtimes + var require_call_args_base = p.allocator.alloc(Expr, if (p.options.can_import_from_bundle) 0 else imports_count) catch unreachable; + var import_records = try p.allocator.alloc(u32, imports_count); + + var decl_i: usize = 0; + var declared_symbols_i: usize = 0; + var import_record_i: usize = 0; + var require_call_args_i: usize = 0; + var stmt_i: usize = 0; + + if (jsx_symbol.use_count_estimate > 0 or jsx_static_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = automatic_namespace_ref, .is_top_level = true }; + declared_symbols_i += 1; + + const automatic_identifier = p.e(E.ImportIdentifier{ .ref = automatic_namespace_ref }, loc); + const dot_call_target = brk: { + if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { + break :brk automatic_identifier; + } else { + require_call_args_base[require_call_args_i] = automatic_identifier; + require_call_args_i += 1; + break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]); + } + }; + + if (jsx_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_runtime.ref, .is_top_level = true }; + declared_symbols_i += 1; + + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_runtime.ref, + }, + loc, + ), + .value = p.e( + E.Dot{ + .target = dot_call_target, + .name = p.options.jsx.jsx, + .name_loc = loc, + .can_be_removed_if_unused = true, + }, + loc, + ), + }; + decl_i += 1; + } + + if (jsx_static_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.jsxs_runtime.ref, .is_top_level = true }; + declared_symbols_i += 1; + + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsxs_runtime.ref, + }, + loc, + ), + .value = p.e( + E.Dot{ + .target = dot_call_target, + .name = p.options.jsx.jsx_static, + .name_loc = loc, + .can_be_removed_if_unused = true, + }, + loc, + ), + }; + + decl_i += 1; + } + + if (jsx_filename_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_filename.ref, .is_top_level = true }; + declared_symbols_i += 1; + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_filename.ref, + }, + loc, + ), + .value = p.e(E.String{ .data = p.source.path.pretty }, loc), + }; + decl_i += 1; + } + + // We do not mark this as .require becuase we are already wrapping it manually. + const import_record_id = p.addImportRecord(.internal, loc, p.options.jsx.import_source); + p.import_records.items[import_record_id].tag = .jsx_import; + // When everything is CommonJS + // We import JSX like this: + // var {jsxDev} = require("react/jsx-dev") + + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = automatic_namespace_ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + + stmt_i += 1; + p.named_imports.put( + automatic_namespace_ref, + js_ast.NamedImport{ + .alias = jsx_automatic_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = automatic_namespace_ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, automatic_namespace_ref, .{}) catch unreachable; + import_records[import_record_i] = import_record_id; + import_record_i += 1; + } + + if (jsx_classic_symbol.use_count_estimate > 0) { + const classic_identifier = p.e(E.ImportIdentifier{ .ref = classic_namespace_ref }, loc); + + const dot_call_target = brk: { + // var react = $aopaSD123(); + + if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { + break :brk classic_identifier; + } else { + const require_call_args_start = require_call_args_i; + require_call_args_base[require_call_args_i] = classic_identifier; + require_call_args_i += 1; + break :brk p.callUnbundledRequire(require_call_args_base[require_call_args_start..][0..1]); + } + }; + + if (jsx_factory_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_factory.ref, .is_top_level = true }; + declared_symbols_i += 1; + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_factory.ref, + }, + loc, + ), + .value = p.e( + E.Dot{ + .target = dot_call_target, + .name = p.options.jsx.factory[p.options.jsx.factory.len - 1], + .name_loc = loc, + .can_be_removed_if_unused = true, + }, + loc, + ), + }; + decl_i += 1; + } + + if (jsx_fragment_symbol.use_count_estimate > 0) { + declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_fragment.ref, .is_top_level = true }; + declared_symbols_i += 1; + decls[decl_i] = G.Decl{ + .binding = p.b( + B.Identifier{ + .ref = p.jsx_fragment.ref, + }, + loc, + ), + .value = p.e( + E.Dot{ + .target = dot_call_target, + .name = p.options.jsx.fragment[p.options.jsx.fragment.len - 1], + .name_loc = loc, + .can_be_removed_if_unused = true, + }, + loc, + ), + }; + decl_i += 1; + } + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.classic_import_source); + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = classic_namespace_ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + p.import_records.items[import_record_id].tag = .jsx_classic; + stmt_i += 1; + p.named_imports.put( + classic_namespace_ref, + js_ast.NamedImport{ + .alias = jsx_classic_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = classic_namespace_ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, classic_namespace_ref, .{}) catch unreachable; + import_records[import_record_i] = import_record_id; + declared_symbols[declared_symbols_i] = .{ .ref = classic_namespace_ref, .is_top_level = true }; + declared_symbols_i += 1; + } + + if (p.options.features.react_fast_refresh) { + defer did_import_fast_refresh = true; + p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); + if (!p.options.jsx.use_embedded_refresh_runtime) { + const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; + + declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; + declared_symbols_i += 1; + + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); + p.import_records.items[import_record_id].tag = .react_refresh; + jsx_part_stmts[stmt_i] = p.s(S.Import{ + .namespace_ref = p.jsx_refresh_runtime.ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + + stmt_i += 1; + p.named_imports.put( + p.jsx_refresh_runtime.ref, + js_ast.NamedImport{ + .alias = refresh_runtime_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = p.jsx_refresh_runtime.ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable; + import_records[import_record_i] = import_record_id; + } + p.recordUsage(p.jsx_refresh_runtime.ref); + } + + jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls[0..decl_i] }, loc); + stmt_i += 1; + + before.append(js_ast.Part{ + .stmts = jsx_part_stmts[0..stmt_i], + .declared_symbols = declared_symbols, + .import_record_indices = import_records, + .tag = .jsx_import, + }) catch unreachable; + } + } + + if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { + p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); + p.recordUsage(p.jsx_refresh_runtime.ref); + + if (!p.options.jsx.use_embedded_refresh_runtime) { + if (comptime Environment.allow_assert) + assert(!p.options.enable_bundling); + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); + const loc = logger.Loc.Empty; + const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); + p.import_records.items[import_record_id].tag = .react_refresh; + + var import_stmt = p.s(S.Import{ + .namespace_ref = p.jsx_refresh_runtime.ref, + .star_name_loc = loc, + .is_single_line = true, + .import_record_index = import_record_id, + }, loc); + + const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; + + p.named_imports.put( + p.jsx_refresh_runtime.ref, + js_ast.NamedImport{ + .alias = refresh_runtime_symbol.original_name, + .alias_is_star = true, + .alias_loc = loc, + .namespace_ref = p.jsx_refresh_runtime.ref, + .import_record_index = import_record_id, + }, + ) catch unreachable; + p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable; + var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1); + import_records[0] = import_record_id; + declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; + var part_stmts = try p.allocator.alloc(Stmt, 1); + part_stmts[0] = import_stmt; + + before.append(js_ast.Part{ + .stmts = part_stmts, + .declared_symbols = declared_symbols, + .import_record_indices = import_records, + .tag = .react_fast_refresh, + }) catch unreachable; + } + } + } else if (comptime ParserType.jsx_transform_type == .solid) { + p.resolveGeneratedSymbol(&p.solid.wrap); + p.resolveGeneratedSymbol(&p.solid.insert); + p.resolveGeneratedSymbol(&p.solid.template); + p.resolveGeneratedSymbol(&p.solid.delegateEvents); + p.resolveGeneratedSymbol(&p.solid.createComponent); + p.resolveGeneratedSymbol(&p.solid.setAttribute); + p.resolveGeneratedSymbol(&p.solid.effect); + p.resolveGeneratedSymbol(&p.solid.namespace); + + const import_count = + @as(usize, @boolToInt(p.symbols.items[p.solid.wrap.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.insert.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.template.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.delegateEvents.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.createComponent.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.setAttribute.ref.innerIndex()].use_count_estimate > 0)) + + @as(usize, @boolToInt(p.symbols.items[p.solid.effect.ref.innerIndex()].use_count_estimate > 0)); + var import_items = try p.allocator.alloc(js_ast.ClauseItem, import_count); + + // 1. Inject the part containing template declarations and Solid's import statement + var stmts_to_inject = p.allocator.alloc(Stmt, @as(usize, @boolToInt(p.solid.template_decls.items.len > 0)) + @as(usize, @boolToInt(import_count > 0))) catch unreachable; + var j: usize = 0; + const order = .{ + "createComponent", + "delegateEvents", + "effect", + "insert", + "setAttribute", + "template", + "wrap", + }; + + try p.named_imports.ensureUnusedCapacity(import_count); + try p.is_import_item.ensureUnusedCapacity(p.allocator, @intCast(u32, import_count)); + + if (import_count > 0) { + const import_record_id = p.addImportRecord(.stmt, logger.Loc.Empty, p.options.jsx.import_source); + var declared_symbols = p.allocator.alloc(js_ast.DeclaredSymbol, p.solid.template_decls.items.len) catch unreachable; + + inline for (order) |field_name| { + const ref = @field(p.solid, field_name).ref; + if (p.symbols.items[ref.innerIndex()].use_count_estimate > 0) { + import_items[j] = js_ast.ClauseItem{ + .alias = field_name, + .name = .{ .loc = logger.Loc.Empty, .ref = ref }, + .alias_loc = logger.Loc.Empty, + .original_name = "", + }; + + p.named_imports.putAssumeCapacity( + ref, + js_ast.NamedImport{ + .alias = p.symbols.items[ref.innerIndex()].original_name, + .alias_is_star = false, + .alias_loc = logger.Loc.Empty, + .namespace_ref = p.solid.namespace.ref, + .import_record_index = import_record_id, + }, + ); + p.is_import_item.putAssumeCapacity(ref, .{}); + j += 1; + } + } + + p.import_records.items[import_record_id].tag = .jsx_import; + stmts_to_inject[0] = p.s( + S.Import{ + .namespace_ref = p.solid.namespace.ref, + .star_name_loc = null, + .is_single_line = true, + .import_record_index = import_record_id, + .items = import_items, + }, + logger.Loc.Empty, + ); + if (p.solid.template_decls.items.len > 0) { + for (p.solid.template_decls.items) |_, i| { + declared_symbols[i] = js_ast.DeclaredSymbol{ + .ref = p.solid.template_decls.items[i].binding.data.b_identifier.ref, + .is_top_level = true, + }; + } + stmts_to_inject[1] = p.s( + S.Local{ + .decls = p.solid.template_decls.items, + }, + logger.Loc.Empty, + ); + } + var import_record_ids = p.allocator.alloc(u32, 1) catch unreachable; + import_record_ids[0] = import_record_id; + + before.append(js_ast.Part{ + .stmts = stmts_to_inject, + .declared_symbols = declared_symbols, + .import_record_indices = import_record_ids, + .tag = .jsx_import, + }) catch unreachable; + } + } + + if (p.options.enable_bundling) p.resolveBundlingSymbols(); + + var runtime_imports_iter = p.runtime_imports.iter(); + + const has_cjs_imports = p.cjs_import_stmts.items.len > 0 and p.options.transform_require_to_import; + + p.resolveCommonJSSymbols(); + + // - don't import runtime if we're bundling, it's already included + // - when HMR is enabled, we always need to import the runtime for HMRClient and HMRModule. + // - when HMR is not enabled, we only need any runtime imports if we're importing require() + if (p.options.features.allow_runtime and + !p.options.enable_bundling and + (p.has_called_runtime or p.options.features.hot_module_reloading or has_cjs_imports)) + { + const before_start = before.items.len; + if (p.options.features.hot_module_reloading) { + p.resolveHMRSymbols(); + + if (runtime_imports_iter.next()) |entry| { + std.debug.assert(entry.key == 0); + + // HMRClient.activate(true) + var args_list: []Expr = if (Environment.isDebug) &Prefill.HotModuleReloading.DebugEnabledArgs else &Prefill.HotModuleReloading.DebugDisabled; + + var hmr_module_class_ident = p.e(E.Identifier{ .ref = p.runtime_imports.__HMRClient.?.ref }, logger.Loc.Empty); + const imports = [_]u16{entry.key}; + // TODO: remove these unnecessary allocations + p.generateImportStmt( + RuntimeImports.Name, + &imports, + &before, + p.runtime_imports, + p.s( + S.SExpr{ + .value = p.e(E.Call{ + .target = p.e(E.Dot{ + .target = hmr_module_class_ident, + .name = "activate", + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + .args = ExprNodeList.init(args_list), + }, logger.Loc.Empty), + }, + logger.Loc.Empty, + ), + "import_", + true, + ) catch unreachable; + } + } + + while (runtime_imports_iter.next()) |entry| { + const imports = [_]u16{entry.key}; + // TODO: remove these unnecessary allocations + p.generateImportStmt( + RuntimeImports.Name, + &imports, + &before, + p.runtime_imports, + null, + "import_", + true, + ) catch unreachable; + } + // If we import JSX, we might call require. + // We need to import require before importing JSX. + // But a runtime import may not be necessary until we import JSX. + // So we have to swap it after the fact, instead of just moving this above the JSX import. + if (before_start > 0) { + var j: usize = 0; + while (j < before_start) : (j += 1) { + std.mem.swap(js_ast.Part, &before.items[j], &before.items[before.items.len - j - 1]); + } + } + } + + if (has_cjs_imports) { + var import_records = try p.allocator.alloc(u32, p.cjs_import_stmts.items.len); + var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, p.cjs_import_stmts.items.len); + + for (p.cjs_import_stmts.items) |entry, i| { + const import_statement: *S.Import = entry.data.s_import; + import_records[i] = import_statement.import_record_index; + declared_symbols[i] = .{ + .ref = import_statement.namespace_ref, + .is_top_level = true, + }; + } + + before.append(js_ast.Part{ + .stmts = p.cjs_import_stmts.items, + .declared_symbols = declared_symbols, + .import_record_indices = import_records, + .tag = .cjs_imports, + }) catch unreachable; + } + + var parts_slice: []js_ast.Part = &([_]js_ast.Part{}); + + if (before.items.len > 0 or after.items.len > 0) { + const before_len = before.items.len; + const after_len = after.items.len; + const parts_len = parts.items.len; + + var _parts = try p.allocator.alloc( + js_ast.Part, + before_len + + after_len + + parts_len, + ); + + var remaining_parts = _parts; + if (before_len > 0) { + const parts_to_copy = before.items; + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); + remaining_parts = remaining_parts[parts_to_copy.len..]; + } + + if (parts_len > 0) { + const parts_to_copy = parts.items; + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); + remaining_parts = remaining_parts[parts_to_copy.len..]; + } + + if (after_len > 0) { + const parts_to_copy = after.items; + std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); + } + + parts_slice = _parts; + } else { + after.deinit(); + before.deinit(); + parts_slice = parts.items; + } + + // Pop the module scope to apply the "ContainsDirectEval" rules + // p.popScope(); + + result.ast = try p.toAST(parts_slice, exports_kind, wrapper_expr); + result.ok = true; + + return result; + } + + pub fn init(_options: Options, log: *logger.Log, source: *const logger.Source, define: *Define, allocator: Allocator) !Parser { + const lexer = try js_lexer.Lexer.init(log, source.*, allocator); + return Parser{ + .options = _options, + .allocator = allocator, + .lexer = lexer, + .define = define, + .source = source, + .log = log, + }; + } +}; + +const FindLabelSymbolResult = struct { ref: Ref, is_loop: bool, found: bool = false }; + +const FindSymbolResult = struct { + ref: Ref, + declare_loc: ?logger.Loc = null, + is_inside_with_scope: bool = false, +}; +const ExportClauseResult = struct { + clauses: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), + is_single_line: bool = false, + had_type_only_exports: bool = false, +}; + +const DeferredTsDecorators = struct { + values: []js_ast.Expr, + + // If this turns out to be a "declare class" statement, we need to undo the + // scopes that were potentially pushed while parsing the decorator arguments. + scope_index: usize, +}; + +const LexicalDecl = enum(u8) { forbid, allow_all, allow_fn_inside_if, allow_fn_inside_label }; + +const ParseClassOptions = struct { + ts_decorators: []Expr = &[_]Expr{}, + allow_ts_decorators: bool = false, + is_type_script_declare: bool = false, +}; + +const ParseStatementOptions = struct { + ts_decorators: ?DeferredTsDecorators = null, + lexical_decl: LexicalDecl = .forbid, + is_module_scope: bool = false, + is_namespace_scope: bool = false, + is_export: bool = false, + is_name_optional: bool = false, // For "export default" pseudo-statements, + is_typescript_declare: bool = false, + + pub fn hasDecorators(self: *ParseStatementOptions) bool { + const decs = self.ts_decorators orelse return false; + return decs.values.len > 0; + } +}; + +var e_missing_data = E.Missing{}; +var s_missing = S.Empty{}; +var nullExprData = Expr.Data{ .e_missing = e_missing_data }; +var nullStmtData = Stmt.Data{ .s_empty = s_missing }; +pub const Prefill = struct { + pub const HotModuleReloading = struct { + pub var DebugEnabledArgs = [_]Expr{ + Expr{ .data = .{ .e_boolean = E.Boolean{ .value = true } }, .loc = logger.Loc.Empty }, + }; + pub var DebugDisabled = [_]Expr{ + Expr{ .data = .{ .e_boolean = E.Boolean{ .value = false } }, .loc = logger.Loc.Empty }, + }; + pub var ActivateString = E.String{ + .data = "activate", + }; + pub var ActivateIndex = E.Index{ + .index = .{ + .data = .{ + .e_string = &ActivateString, + }, + .loc = logger.Loc.Empty, + }, + .target = undefined, + }; + }; + pub const StringLiteral = struct { + pub var Key = [3]u8{ 'k', 'e', 'y' }; + pub var Children = [_]u8{ 'c', 'h', 'i', 'l', 'd', 'r', 'e', 'n' }; + pub var Filename = [_]u8{ 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' }; + pub var LineNumber = [_]u8{ 'l', 'i', 'n', 'e', 'N', 'u', 'm', 'b', 'e', 'r' }; + pub var ColumnNumber = [_]u8{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' }; + }; + pub const Value = struct { + pub const EThis = E.This{}; + pub const Zero = E.Number{ .value = 0.0 }; + }; + pub const String = struct { + pub var Key = E.String{ .data = &Prefill.StringLiteral.Key }; + pub var Children = E.String{ .data = &Prefill.StringLiteral.Children }; + pub var Filename = E.String{ .data = &Prefill.StringLiteral.Filename }; + pub var LineNumber = E.String{ .data = &Prefill.StringLiteral.LineNumber }; + pub var ColumnNumber = E.String{ .data = &Prefill.StringLiteral.ColumnNumber }; + }; + pub const Data = struct { + pub var BMissing = B{ .b_missing = BMissing_ }; + pub var BMissing_ = B.Missing{}; + + pub var EMissing = Expr.Data{ .e_missing = EMissing_ }; + pub var EMissing_ = E.Missing{}; + + pub var SEmpty = Stmt.Data{ .s_empty = SEmpty_ }; + pub var SEmpty_ = S.Empty{}; + + pub var Filename = Expr.Data{ .e_string = &Prefill.String.Filename }; + pub var LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber }; + pub var ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber }; + pub const This = Expr.Data{ .e_this = E.This{} }; + pub const Zero = Expr.Data{ .e_number = Value.Zero }; + }; + pub const Runtime = struct { + pub var JSXFilename = "__jsxFilename"; + pub var MarkAsModule = "__markAsModule"; + pub var CommonJS = "__commonJS"; + pub var ReExport = "__reExport"; + pub var ToModule = "__toModule"; + const JSXShortname = "jsx"; + }; +}; + +var keyExprData = Expr.Data{ .e_string = &Prefill.String.Key }; +var jsxChildrenKeyData = Expr.Data{ .e_string = &Prefill.String.Children }; +var nullExprValueData = E.Null{}; +var falseExprValueData = E.Boolean{ .value = false }; +var nullValueExpr = Expr.Data{ .e_null = nullExprValueData }; +var falseValueExpr = Expr.Data{ .e_boolean = E.Boolean{ .value = false } }; + +pub const ImportOrRequireScanResults = struct { + import_records: List(ImportRecord), +}; + +const JSXTransformType = enum { + none, + react, + macro, + solid, +}; + +const SolidJS = struct { + namespace: GeneratedSymbol = undefined, + wrap: GeneratedSymbol = undefined, + insert: GeneratedSymbol = undefined, + template: GeneratedSymbol = undefined, + delegateEvents: GeneratedSymbol = undefined, + createComponent: GeneratedSymbol = undefined, + setAttribute: GeneratedSymbol = undefined, + effect: GeneratedSymbol = undefined, + + events_to_delegate: Events.Bitset = .{}, + template_decls: std.ArrayListUnmanaged(G.Decl) = .{}, + is_in_jsx_component: bool = false, + + pub const Stack = struct { + component_body: std.ArrayListUnmanaged(Stmt) = .{}, + component_body_decls: std.ArrayListUnmanaged(G.Decl) = .{}, + last_template_id: E.Identifier = .{}, + last_element_id: E.Identifier = .{}, + temporary_scope: Scope = Scope{ + .kind = .function_body, + .parent = null, + }, + prev_scope: ?*Scope = null, + node_count: u32 = 0, + + current_template_string: MutableString = .{ + .allocator = undefined, + .list = .{}, + }, + buffered_writer: MutableString.BufferedWriter = undefined, + + element_counter: u32 = 0, + }; + + pub fn generateElementName(this: *SolidJS, allocator: std.mem.Allocator) string { + if (this.component_body_decls.items.len <= prefilled_element_names.len) { + return prefilled_element_names[this.component_body_decls.items.len]; + } + return std.fmt.allocPrint(allocator, "_el${d}", .{this.component_body_decls.items.len}) catch unreachable; + } + + pub fn generateTemplateName(this: *SolidJS, allocator: std.mem.Allocator) string { + if (this.template_decls.items.len <= prefilled_template_names.len) { + return prefilled_template_names[this.template_decls.items.len]; + } + return std.fmt.allocPrint(allocator, "_tmpl${d}", .{this.template_decls.items.len}) catch unreachable; + } + + pub fn generateElement(solid: *SolidJS, p: anytype, template_expression: Expr, value_loc: logger.Loc) !E.Identifier { + var name = solid.generateElementName(p.allocator); + + var prev_scope = p.current_scope; + p.current_scope = &solid.temporary_scope; + const ref = p.declareSymbolMaybeGenerated(.import, value_loc, name, true) catch unreachable; + p.current_scope = prev_scope; + const element = .{ .ref = ref }; + var decl_value: Expr = undefined; + switch (solid.component_body_decls.items.len) { + 0 => { + decl_value = p.e( + E.Call{ + .target = p.e( + E.Dot{ + .name = "cloneNode", + .name_loc = value_loc, + .target = template_expression, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + template_expression.loc, + ), + .args = ExprNodeList.init(true_args), + .can_be_unwrapped_if_unused = true, + }, + value_loc, + ); + p.recordUsage(template_expression.data.e_identifier.ref); + }, + 1 => { + const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref }; + decl_value = p.e( + E.Dot{ + .target = .{ + .data = .{ .e_identifier = ident }, + .loc = value_loc, + }, + .name = "firstChild", + .name_loc = template_expression.loc, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + value_loc, + ); + p.recordUsage(ident.ref); + }, + else => { + const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref }; + decl_value = p.e(E.Dot{ + .target = .{ + .data = .{ .e_identifier = ident }, + .loc = value_loc, + }, + .name_loc = template_expression.loc, + .name = "nextSibling", + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, value_loc); + p.recordUsage(ident.ref); + }, + } + try solid.component_body_decls.append( + p.allocator, + G.Decl{ .binding = p.b(B.Identifier{ .ref = ref }, template_expression.loc), .value = decl_value }, + ); + return element; + } + + pub const Events = enum { + Click, + Change, + Input, + Submit, + KeyDown, + KeyUp, + KeyPress, + MouseDown, + MouseUp, + MouseMove, + MouseEnter, + MouseLeave, + MouseOver, + MouseOut, + Focus, + Blur, + Scroll, + Wheel, + TouchStart, + TouchMove, + TouchEnd, + TouchCancel, + PointerDown, + PointerUp, + PointerMove, + PointerCancel, + PointerEnter, + PointerLeave, + PointerOver, + PointerOut, + GotPointerCapture, + LostPointerCapture, + Select, + ContextMenu, + DragStart, + Drag, + DragEnd, + DragEnter, + DragLeave, + DragOver, + Drop, + Copy, + Cut, + Paste, + CompositionStart, + CompositionUpdate, + CompositionEnd, + + pub const Bitset = std.enums.EnumSet(Events); + }; + + const prefilled_element_names = [_]string{ + "_el", + "_el$1", + "_el$2", + "_el$3", + "_el$4", + "_el$5", + "_el$6", + "_el$7", + "_el$8", + "_el$9", + "_el$10", + "_el$11", + "_el$12", + "_el$13", + "_el$14", + "_el$15", + "_el$16", + "_el$17", + "_el$18", + "_el$19", + "_el$20", + "_el$21", + }; + const prefilled_template_names = [_]string{ + "_tmpl", + "_tmpl$1", + "_tmpl$2", + "_tmpl$3", + "_tmpl$4", + "_tmpl$5", + "_tmpl$6", + "_tmpl$7", + "_tmpl$8", + "_tmpl$9", + "_tmpl$10", + "_tmpl$11", + "_tmpl$12", + "_tmpl$13", + "_tmpl$14", + "_tmpl$15", + "_tmpl$16", + "_tmpl$17", + "_tmpl$18", + "_tmpl$19", + "_tmpl$20", + "_tmpl$21", + }; +}; + +fn GetSolidJSSymbols(comptime jsx: JSXTransformType) type { + if (jsx != .solid) + return void; + + return SolidJS; +} +const ParserFeatures = struct { + typescript: bool = false, + jsx: JSXTransformType = JSXTransformType.none, + scan_only: bool = false, + + // *** How React Fast Refresh works *** + // + // Implmenetations: + // [0]: https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js + // [1]: https://github.com/swc-project/swc/blob/master/ecmascript/transforms/react/src/refresh/mod.rs + // + // Additional reading: + // - https://github.com/facebook/react/issues/16604#issuecomment-528663101 + // - https://github.com/facebook/react/blob/master/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js + // + // From reading[0] and Dan Abramov's comment, there are really five parts. + // 1. At the top of the file: + // 1. Declare a $RefreshReg$ if it doesn't exist + // - This really just does "RefreshRuntime.register(ComponentIdentifier, ComponentIdentifier.name);" + // 2. Run "var _s${componentIndex} = $RefreshSig$()" to generate a function for updating react refresh scoped to the component. So it's one per *component*. + // - This really just does "RefreshRuntime.createSignatureFunctionForTransform();" + // 2. Register all React components[2] defined in the module scope by calling the equivalent of $RefreshReg$(ComponentIdentifier, "ComponentName") + // 3. For each registered component: + // 1. Call "_s()" to mark the first render of this component for "react-refresh/runtime". Call this at the start of the React component's function body + // 2. Track every call expression to a hook[3] inside the component, including: + // - Identifier of the hook function + // - Arguments passed + // 3. For each hook's call expression, generate a signature key which is + // - The hook's identifier ref + // - The S.Decl ("VariableDeclarator")'s source + // "var [foo, bar] = useFooBar();" + // ^--------^ This region, I think. Judging from this line: https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js#L407 + // - For the "useState" hook, also hash the source of the first argument if it exists e.g. useState(foo => true); + // - For the "useReducer" hook, also hash the source of the second argument if it exists e.g. useReducer({}, () => ({})); + // 4. If the hook component is not builtin and is defined inside a component, always reset the component state + // - See this test: https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js#L909 + // 4. From the signature key generated in 3., call one of the following: + // - _s(ComponentIdentifier, hash(signature)); + // - _s(ComponentIdentifier, hash(signature), true /* forceReset */); + // - _s(ComponentIdentifier, hash(signature), false /* forceReset */, () => [customHook1, customHook2, customHook3]); + // Note: This step is only strictly required on rebuild. + // 5. if (isReactComponentBoundary(exports)) enqueueUpdateAndHandleErrors(); + // **** FAQ **** + // [2]: Q: From a parser's perspective, what's a component? + // A: typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z -- https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/ReactFreshBabelPlugin.js#L42-L44 + // [3]: Q: From a parser's perspective, what's a hook? + // A: /^use[A-Z]/ -- https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/ReactFreshBabelPlugin.js#L390 + // + // + // + // react_fast_refresh: bool = false, +}; + +// Our implementation diverges somewhat from the official implementation +// Specifically, we use a subclass of HMRModule - FastRefreshModule +// Instead of creating a globally-scoped +const FastRefresh = struct {}; + +const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef); + +pub const MacroState = struct { + refs: MacroRefs, + prepend_stmts: *ListManaged(Stmt) = undefined, + imports: std.AutoArrayHashMap(i32, Ref), + + pub fn init(allocator: Allocator) MacroState { + return MacroState{ + .refs = MacroRefs.init(allocator), + .prepend_stmts = undefined, + .imports = std.AutoArrayHashMap(i32, Ref).init(allocator), + }; + } +}; + +// workaround for https://github.com/ziglang/zig/issues/10903 +fn NewParser( + comptime parser_features: ParserFeatures, +) type { + return NewParser_( + parser_features.typescript, + parser_features.jsx, + parser_features.scan_only, + ); +} +fn NewParser_( + comptime parser_feature__typescript: bool, + comptime parser_feature__jsx: JSXTransformType, + comptime parser_feature__scan_only: bool, +) type { + const js_parser_features: ParserFeatures = .{ + .typescript = parser_feature__typescript, + .jsx = parser_feature__jsx, + .scan_only = parser_feature__scan_only, + }; + + // P is for Parser! + return struct { + const js_parser_jsx = if (FeatureFlags.force_macro) JSXTransformType.macro else js_parser_features.jsx; + const is_typescript_enabled = js_parser_features.typescript; + const is_jsx_enabled = js_parser_jsx != .none; + const only_scan_imports_and_do_not_visit = js_parser_features.scan_only; + const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord); + const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports; + const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void; + const track_symbol_usage_during_parse_pass = only_scan_imports_and_do_not_visit and is_typescript_enabled; + const ParsePassSymbolUsageType = if (track_symbol_usage_during_parse_pass) *ScanPassResult.ParsePassSymbolUsageMap else void; + + pub const parser_features: ParserFeatures = js_parser_features; + const P = @This(); + pub const jsx_transform_type: JSXTransformType = js_parser_jsx; + const allow_macros = FeatureFlags.is_macro_enabled and jsx_transform_type != .macro; + const MacroCallCountType = if (allow_macros) u32 else u0; + macro: MacroState = undefined, + allocator: Allocator, + options: Parser.Options, + log: *logger.Log, + define: *Define, + source: *const logger.Source, + lexer: js_lexer.Lexer, + allow_in: bool = false, + allow_private_identifiers: bool = false, + + has_top_level_return: bool = false, + latest_return_had_semicolon: bool = false, + has_import_meta: bool = false, + has_es_module_syntax: bool = false, + top_level_await_keyword: logger.Range = logger.Range.None, + fn_or_arrow_data_parse: FnOrArrowDataParse = FnOrArrowDataParse{}, + fn_or_arrow_data_visit: FnOrArrowDataVisit = FnOrArrowDataVisit{}, + fn_only_data_visit: FnOnlyDataVisit = FnOnlyDataVisit{}, + allocated_names: List(string) = .{}, + // allocated_names: ListManaged(string) = ListManaged(string).init(bun.default_allocator), + // allocated_names_pool: ?*AllocatedNamesPool.Node = null, + latest_arrow_arg_loc: logger.Loc = logger.Loc.Empty, + forbid_suffix_after_as_loc: logger.Loc = logger.Loc.Empty, + current_scope: *js_ast.Scope = undefined, + scopes_for_current_part: List(*js_ast.Scope) = .{}, + symbols: ListManaged(js_ast.Symbol) = undefined, + ts_use_counts: List(u32) = .{}, + exports_ref: Ref = Ref.None, + require_ref: Ref = Ref.None, + module_ref: Ref = Ref.None, + filename_ref: Ref = Ref.None, + dirname_ref: Ref = Ref.None, + import_meta_ref: Ref = Ref.None, + promise_ref: ?Ref = null, + scopes_in_order_visitor_index: usize = 0, + has_classic_runtime_warned: bool = false, + macro_call_count: MacroCallCountType = 0, + + /// Used for transforming export default -> module.exports + has_export_default: bool = false, + + hmr_module: GeneratedSymbol = GeneratedSymbol{ .primary = Ref.None, .backup = Ref.None, .ref = Ref.None }, + + has_called_runtime: bool = false, + + cjs_import_stmts: std.ArrayList(Stmt), + + injected_define_symbols: List(Ref) = .{}, + symbol_uses: js_ast.Part.SymbolUseMap = .{}, + declared_symbols: List(js_ast.DeclaredSymbol) = .{}, + declared_symbols_for_reuse: List(js_ast.DeclaredSymbol) = .{}, + runtime_imports: RuntimeImports = RuntimeImports{}, + + parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined, + // duplicate_case_checker: void, + // non_bmp_identifiers: StringBoolMap, + // legacy_octal_literals: void, + // legacy_octal_literals: map[js_ast.E]logger.Range, + + // For lowering private methods + // weak_map_ref: ?Ref, + // weak_set_ref: ?Ref, + // private_getters: RefRefMap, + // private_setters: RefRefMap, + + // These are for TypeScript + should_fold_numeric_constants: bool = false, + emitted_namespace_vars: RefMap = RefMap{}, + is_exported_inside_namespace: RefRefMap = .{}, + known_enum_values: Map(Ref, _hash_map.StringHashMapUnmanaged(f64)) = .{}, + local_type_names: StringBoolMap = StringBoolMap{}, + + // This is the reference to the generated function argument for the namespace, + // which is different than the reference to the namespace itself: + // + // namespace ns { + // } + // + // The code above is transformed into something like this: + // + // var ns1; + // (function(ns2) { + // })(ns1 or (ns1 = {})); + // + // This variable is "ns2" not "ns1". It is only used during the second + // "visit" pass. + enclosing_namespace_arg_ref: ?Ref = null, + + jsx_filename: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsx_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsx_factory: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsx_fragment: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsx_automatic: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsxs_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + jsx_classic: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + // only applicable when is_react_fast_refresh_enabled + jsx_refresh_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + + solid: GetSolidJSSymbols(jsx_transform_type) = if (jsx_transform_type == JSXTransformType.solid) SolidJS{} else void{}, + + bun_jsx_ref: Ref = Ref.None, + + // Imports (both ES6 and CommonJS) are tracked at the top level + import_records: ImportRecordList, + import_records_for_current_part: List(u32) = .{}, + export_star_import_records: List(u32) = .{}, + + // These are for handling ES6 imports and exports + es6_import_keyword: logger.Range = logger.Range.None, + es6_export_keyword: logger.Range = logger.Range.None, + enclosing_class_keyword: logger.Range = logger.Range.None, + import_items_for_namespace: std.AutoHashMapUnmanaged(Ref, ImportItemForNamespaceMap) = .{}, + is_import_item: RefMap = .{}, + named_imports: NamedImportsType, + named_exports: js_ast.Ast.NamedExports, + import_namespace_cc_map: Map(ImportNamespaceCallOrConstruct, bool) = .{}, + + // When we're only scanning the imports + // If they're using the automatic JSX runtime + // We won't know that we need to import JSX robustly because we don't track + // symbol counts. Instead, we ask: + // "Did we parse anything that looked like JSX"? + // If yes, then automatically add the JSX import. + needs_jsx_import: NeedsJSXType, + + // The parser does two passes and we need to pass the scope tree information + // from the first pass to the second pass. That's done by tracking the calls + // to pushScopeForParsePass() and popScope() during the first pass in + // scopesInOrder. + // + // Then, when the second pass calls pushScopeForVisitPass() and popScope(), + // we consume entries from scopesInOrder and make sure they are in the same + // order. This way the second pass can efficiently use the same scope tree + // as the first pass without having to attach the scope tree to the AST. + // + // We need to split this into two passes because the pass that declares the + // symbols must be separate from the pass that binds identifiers to declared + // symbols to handle declaring a hoisted "var" symbol in a nested scope and + // binding a name to it in a parent or sibling scope. + scopes_in_order: ScopeOrderList = .{}, + + // These properties are for the visit pass, which runs after the parse pass. + // The visit pass binds identifiers to declared symbols, does constant + // folding, substitutes compile-time variable definitions, and lowers certain + // syntactic constructs as appropriate. + stmt_expr_value: Expr.Data, + call_target: Expr.Data, + delete_target: Expr.Data, + loop_body: Stmt.Data, + module_scope: *js_ast.Scope = undefined, + is_control_flow_dead: bool = false, + + // Inside a TypeScript namespace, an "export declare" statement can be used + // to cause a namespace to be emitted even though it has no other observable + // effect. This flag is used to implement this feature. + // + // Specifically, namespaces should be generated for all of the following + // namespaces below except for "f", which should not be generated: + // + // namespace a { export declare const a } + // namespace b { export declare let [[b]] } + // namespace c { export declare function c() } + // namespace d { export declare class d {} } + // namespace e { export declare enum e {} } + // namespace f { export declare namespace f {} } + // + // The TypeScript compiler compiles this into the following code (notice "f" + // is missing): + // + // var a; (function (a_1) {})(a or (a = {})); + // var b; (function (b_1) {})(b or (b = {})); + // var c; (function (c_1) {})(c or (c = {})); + // var d; (function (d_1) {})(d or (d = {})); + // var e; (function (e_1) {})(e or (e = {})); + // + // Note that this should not be implemented by declaring symbols for "export + // declare" statements because the TypeScript compiler doesn't generate any + // code for these statements, so these statements are actually references to + // global variables. There is one exception, which is that local variables + // *should* be declared as symbols because they are replaced with. This seems + // like very arbitrary behavior but it's what the TypeScript compiler does, + // so we try to match it. + // + // Specifically, in the following code below "a" and "b" should be declared + // and should be substituted with "ns.a" and "ns.b" but the other symbols + // shouldn't. References to the other symbols actually refer to global + // variables instead of to symbols that are exported from the namespace. + // This is the case as of TypeScript 4.3. I assume this is a TypeScript bug: + // + // namespace ns { + // export declare const a + // export declare let [[b]] + // export declare function c() + // export declare class d { } + // export declare enum e { } + // console.log(a, b, c, d, e) + // } + // + // The TypeScript compiler compiles this into the following code: + // + // var ns; + // (function (ns) { + // console.log(ns.a, ns.b, c, d, e); + // })(ns or (ns = {})); + // + // Relevant issue: https://github.com/evanw/esbuild/issues/1158 + has_non_local_export_declare_inside_namespace: bool = false, + + // This helps recognize the "await import()" pattern. When this is present, + // warnings about non-string import paths will be omitted inside try blocks. + await_target: ?js_ast.Expr.Data = null, + + to_expr_wrapper_namespace: Binding2ExprWrapper.Namespace, + to_expr_wrapper_hoisted: Binding2ExprWrapper.Hoisted, + + // This helps recognize the "import().catch()" pattern. We also try to avoid + // warning about this just like the "try { await import() }" pattern. + then_catch_chain: ThenCatchChain, + + // Temporary variables used for lowering + temp_refs_to_declare: List(TempRef) = .{}, + temp_ref_count: i32 = 0, + + // When bundling, hoisted top-level local variables declared with "var" in + // nested scopes are moved up to be declared in the top-level scope instead. + // The old "var" statements are turned into regular assignments instead. This + // makes it easier to quickly scan the top-level statements for "var" locals + // with the guarantee that all will be found. + relocated_top_level_vars: List(js_ast.LocRef) = .{}, + + // ArrowFunction is a special case in the grammar. Although it appears to be + // a PrimaryExpression, it's actually an AssignmentExpression. This means if + // a AssignmentExpression ends up producing an ArrowFunction then nothing can + // come after it other than the comma operator, since the comma operator is + // the only thing above AssignmentExpression under the Expression rule: + // + // AssignmentExpression: + // ArrowFunction + // ConditionalExpression + // LeftHandSideExpression = AssignmentExpression + // LeftHandSideExpression AssignmentOperator AssignmentExpression + // + // Expression: + // AssignmentExpression + // Expression , AssignmentExpression + // + after_arrow_body_loc: logger.Loc = logger.Loc.Empty, + import_transposer: ImportTransposer, + require_transposer: RequireTransposer, + require_resolve_transposer: RequireResolveTransposer, + + // This is a general place to put lots of Expr objects + expr_list: List(Expr) = .{}, + + scope_order_to_visit: []ScopeOrder = &([_]ScopeOrder{}), + + import_refs_to_always_trim_if_unused: RefArrayMap = .{}, + + pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr { + // The argument must be a string + if (@as(Expr.Tag, arg.data) == .e_string) { + // Ignore calls to import() if the control flow is provably dead here. + // We don't want to spend time scanning the required files if they will + // never be used. + if (p.is_control_flow_dead) { + return p.e(E.Null{}, arg.loc); + } + + const import_record_index = p.addImportRecord(.dynamic, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable); + p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target; + p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; + return p.e(E.Import{ + .expr = arg, + .import_record_index = Ref.toInt(import_record_index), + // .leading_interior_comments = arg.getString(). + }, state.loc); + } + + if (p.options.warn_about_unbundled_modules) { + // Use a debug log so people can see this if they want to + const r = js_lexer.rangeOfIdentifier(p.source, state.loc); + p.log.addRangeDebug(p.source, r, "This \"import\" expression cannot be bundled because the argument is not a string literal") catch unreachable; + } + + return p.e(E.Import{ + .expr = arg, + .import_record_index = Ref.None.sourceIndex(), + }, state.loc); + } + + pub fn transposeRequireResolve(p: *P, arg: Expr, state: anytype) Expr { + // The argument must be a string + if (@as(Expr.Tag, arg.data) == .e_string) { + // Ignore calls to import() if the control flow is provably dead here. + // We don't want to spend time scanning the required files if they will + // never be used. + if (p.is_control_flow_dead) { + return p.e(E.Null{}, arg.loc); + } + + const import_record_index = p.addImportRecord(.require, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable); + p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; + p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; + return p.e(E.RequireOrRequireResolve{ + .import_record_index = Ref.toInt(import_record_index), + // .leading_interior_comments = arg.getString(). + }, arg.loc); + } + + if (p.options.warn_about_unbundled_modules) { + // Use a debug log so people can see this if they want to + const r = js_lexer.rangeOfIdentifier(p.source, arg.loc); + p.log.addRangeDebug(p.source, r, "This \"require.resolve\" expression cannot be bundled because the argument is not a string literal") catch unreachable; + } + + return state; + } + + pub fn transposeRequire(p: *P, arg: Expr, _: anytype) Expr { + switch (arg.data) { + .e_string => |str| { + + // Ignore calls to require() if the control flow is provably dead here. + // We don't want to spend time scanning the required files if they will + // never be used. + if (p.is_control_flow_dead) { + return Expr{ .data = nullExprData, .loc = arg.loc }; + } + + const pathname = str.string(p.allocator) catch unreachable; + + // When we know that we support dynamically requiring this file type + // we can avoid eager loading it + // instead, we can just use the require() function directly. + if (p.options.features.dynamic_require and + !p.options.enable_bundling and + (strings.endsWithComptime(pathname, ".json") or + // strings.endsWithComptime(pathname, ".toml") or + strings.endsWithComptime(pathname, ".node"))) + { + p.ignoreUsage(p.require_ref); + var args = p.allocator.alloc(Expr, 1) catch unreachable; + args[0] = arg; + + return p.e( + E.Call{ + .target = p.e( + E.Dot{ + .target = p.e(E.ImportMeta{}, arg.loc), + .name = "require", + .name_loc = arg.loc, + }, + arg.loc, + ), + .args = js_ast.ExprNodeList.init(args), + .close_paren_loc = arg.loc, + }, + arg.loc, + ); + } + + const import_record_index = p.addImportRecord(.require, arg.loc, pathname); + p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; + p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; + + if (!p.options.transform_require_to_import) { + return p.e(E.Require{ .import_record_index = import_record_index }, arg.loc); + } + + p.import_records.items[import_record_index].was_originally_require = true; + p.import_records.items[import_record_index].contains_import_star = true; + + const symbol_name = p.import_records.items[import_record_index].path.name.nonUniqueNameString(p.allocator); + const cjs_import_name = std.fmt.allocPrint( + p.allocator, + "{s}_{x}_{d}", + .{ + symbol_name, + @truncate( + u16, + std.hash.Wyhash.hash( + 0, + p.import_records.items[import_record_index].path.text, + ), + ), + p.cjs_import_stmts.items.len, + }, + ) catch unreachable; + + const namespace_ref = p.declareSymbol(.hoisted, arg.loc, cjs_import_name) catch unreachable; + + p.cjs_import_stmts.append( + p.s( + S.Import{ + .namespace_ref = namespace_ref, + .star_name_loc = arg.loc, + .is_single_line = true, + .import_record_index = import_record_index, + }, + arg.loc, + ), + ) catch unreachable; + + const args = p.allocator.alloc(Expr, 1) catch unreachable; + args[0] = p.e( + E.ImportIdentifier{ + .ref = namespace_ref, + }, + arg.loc, + ); + + p.ignoreUsage(p.require_ref); + + // require(import_object_assign) + return p.callRuntime(arg.loc, "__require", args); + }, + else => {}, + } + + return arg; + } + + fn isBindingUsed(p: *P, binding: Binding, default_export_ref: Ref) bool { + switch (binding.data) { + .b_identifier => |ident| { + if (default_export_ref.eql(ident.ref)) return true; + if (p.named_imports.contains(ident.ref)) + return true; + + for (p.named_exports.values()) |named_export| { + if (named_export.ref.eql(ident.ref)) + return true; + } + + const symbol: *const Symbol = &p.symbols.items[ident.ref.innerIndex()]; + return symbol.use_count_estimate > 0; + }, + .b_array => |array| { + for (array.items) |item| { + if (isBindingUsed(p, item.binding, default_export_ref)) { + return true; + } + } + + return false; + }, + .b_object => |obj| { + for (obj.properties) |prop| { + if (isBindingUsed(p, prop.value, default_export_ref)) { + return true; + } + } + + return false; + }, + .b_property => |prop| { + return p.isBindingUsed(prop.value, default_export_ref); + }, + + .b_missing => return false, + } + } + + pub fn treeShake(p: *P, parts: *[]js_ast.Part, merge: bool) void { + var parts_: []js_ast.Part = parts.*; + defer { + if (merge and parts_.len > 1) { + var first_none_part: usize = parts_.len; + var stmts_count: usize = 0; + for (parts_) |part, i| { + if (part.tag == .none) { + stmts_count += part.stmts.len; + first_none_part = @minimum(i, first_none_part); + } + } + + if (first_none_part < parts_.len) { + var stmts_list = p.allocator.alloc(Stmt, stmts_count) catch unreachable; + var stmts_remain = stmts_list; + + for (parts_) |part| { + if (part.tag == .none) { + std.mem.copy(Stmt, stmts_remain, part.stmts); + stmts_remain = stmts_remain[part.stmts.len..]; + } + } + + parts_[first_none_part].stmts = stmts_list; + + parts_ = parts_[0 .. first_none_part + 1]; + } + } + + parts.* = parts_; + } + while (parts_.len > 1) { + var parts_end: usize = 0; + var last_end = parts_.len; + var default_export_ref = Ref.None; + if (p.named_exports.get("default")) |named| { + default_export_ref = named.ref; + } + + for (parts_) |part| { + const is_dead = part.can_be_removed_if_unused and can_remove_part: { + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_local => |local| { + if (local.is_export) break :can_remove_part false; + for (local.decls) |decl| { + if (isBindingUsed(p, decl.binding, default_export_ref)) + break :can_remove_part false; + } + }, + .s_if => |if_statement| { + const result = SideEffects.toBoolean(if_statement.test_.data); + if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { + break :can_remove_part false; + } + }, + .s_while => |while_statement| { + const result = SideEffects.toBoolean(while_statement.test_.data); + if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { + break :can_remove_part false; + } + }, + .s_for => |for_statement| { + if (for_statement.test_) |expr| { + const result = SideEffects.toBoolean(expr.data); + if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { + break :can_remove_part false; + } + } + }, + .s_function => |func| { + if (func.func.flags.contains(.is_export)) break :can_remove_part false; + if (func.func.name) |name| { + const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()]; + + if (name.ref.?.eql(default_export_ref) or + symbol.use_count_estimate > 0 or + p.named_exports.contains(symbol.original_name) or + p.named_imports.contains(name.ref.?) or + p.is_import_item.get(name.ref.?) != null) + { + break :can_remove_part false; + } + } + }, + .s_import, + .s_export_clause, + .s_export_from, + .s_export_default, + => break :can_remove_part false, + + .s_class => |class| { + if (class.is_export) break :can_remove_part false; + if (class.class.class_name) |name| { + const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()]; + + if (name.ref.?.eql(default_export_ref) or + symbol.use_count_estimate > 0 or + p.named_exports.contains(symbol.original_name) or + p.named_imports.contains(name.ref.?) or + p.is_import_item.get(name.ref.?) != null) + { + break :can_remove_part false; + } + } + }, + + else => break :can_remove_part false, + } + } + break :can_remove_part true; + }; + + if (is_dead) { + p.clearSymbolUsagesFromDeadPart(part); + + continue; + } + + parts_[parts_end] = part; + parts_end += 1; + } + + parts_.len = parts_end; + if (last_end == parts_.len) { + break; + } + } + } + + const ImportTransposer = ExpressionTransposer(P, P.transposeImport); + const RequireTransposer = ExpressionTransposer(P, P.transposeRequire); + const RequireResolveTransposer = ExpressionTransposer(P, P.transposeRequireResolve); + + const Binding2ExprWrapper = struct { + pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace); + pub const Hoisted = Binding.ToExpr(P, P.wrapIdentifierHoisting); + }; + + fn clearSymbolUsagesFromDeadPart(p: *P, part: js_ast.Part) void { + var symbol_use_refs = part.symbol_uses.keys(); + var symbol_use_values = part.symbol_uses.values(); + var symbols = p.symbols.items; + + for (symbol_use_refs) |ref, i| { + symbols[ref.innerIndex()].use_count_estimate -|= symbol_use_values[i].count_estimate; + } + + for (part.declared_symbols) |declared| { + symbols[declared.ref.innerIndex()].use_count_estimate = 0; + // } + } + } + + pub fn s(_: *P, t: anytype, loc: logger.Loc) Stmt { + const Type = @TypeOf(t); + comptime { + if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) { + @compileError("Attempted to use TypeScript syntax in a non-TypeScript environment"); + } + } + + if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) { + unreachable; + } + + // Output.print("\nStmt: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start }); + if (@typeInfo(Type) == .Pointer) { + // ExportFrom normally becomes import records during the visiting pass + // However, we skip the visiting pass in this mode + // So we must generate a minimum version of it here. + if (comptime only_scan_imports_and_do_not_visit) { + // if (@TypeOf(t) == *S.ExportFrom) { + // switch (call.target.data) { + // .e_identifier => |ident| { + // // is this a require("something") + // if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args[0].data) == .e_string) { + // _ = p.addImportRecord(.require, loc, call.args[0].data.e_string.string(p.allocator) catch unreachable); + // } + // }, + // else => {}, + // } + // } + } + + return Stmt.init(std.meta.Child(Type), t, loc); + } else { + return Stmt.alloc(Type, t, loc); + } + } + + pub fn e(p: *P, t: anytype, loc: logger.Loc) Expr { + const Type = @TypeOf(t); + + comptime { + if (jsx_transform_type == .none) { + if (Type == E.JSXElement or Type == *E.JSXElement) { + @compileError("JSXElement is not supported in this environment"); + } + } + } + + // Output.print("\nExpr: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start }); + if (@typeInfo(Type) == .Pointer) { + if (comptime only_scan_imports_and_do_not_visit) { + if (Type == *E.Call) { + const call: *E.Call = t; + switch (call.target.data) { + .e_identifier => |ident| { + // is this a require("something") + if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args.ptr[0].data) == .e_string) { + _ = p.addImportRecord(.require, loc, call.args.first_().data.e_string.string(p.allocator) catch unreachable); + } + }, + else => {}, + } + } + } + return Expr.init(std.meta.Child(Type), t.*, loc); + } else { + if (comptime only_scan_imports_and_do_not_visit) { + if (Type == E.Call) { + const call: E.Call = t; + switch (call.target.data) { + .e_identifier => |ident| { + // is this a require("something") + if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args.ptr[0].data) == .e_string) { + _ = p.addImportRecord(.require, loc, call.args.first_().data.e_string.string(p.allocator) catch unreachable); + } + }, + else => {}, + } + } + } + return Expr.init(Type, t, loc); + } + } + + pub fn b(p: *P, t: anytype, loc: logger.Loc) Binding { + if (@typeInfo(@TypeOf(t)) == .Pointer) { + return Binding.init(t, loc); + } else { + return Binding.alloc(p.allocator, t, loc); + } + } + + pub fn deinit(parser: *P) void { + parser.allocated_names.deinit(); + parser.scopes_for_current_part.deinit(); + parser.symbols.deinit(); + parser.ts_use_counts.deinit(); + parser.declared_symbols.deinit(); + parser.known_enum_values.deinit(); + parser.import_records.deinit(); + parser.import_records_for_current_part.deinit(); + parser.export_star_import_records.deinit(); + parser.import_items_for_namespace.deinit(); + parser.named_imports.deinit(); + parser.import_namespace_cc_map.deinit(); + parser.scopes_in_order.deinit(); + parser.temp_refs_to_declare.deinit(); + parser.relocated_top_level_vars.deinit(); + } + + pub fn findSymbol(p: *P, loc: logger.Loc, name: string) !FindSymbolResult { + var declare_loc: logger.Loc = undefined; + var is_inside_with_scope = false; + // This function can show up in profiling. + // That's part of why we do this. + // Instead of rehashing `name` for every scope, we do it just once. + const hash = @TypeOf(p.module_scope.members).getHash(name); + const allocator = p.allocator; + + const ref: Ref = brk: { + var _scope: ?*Scope = p.current_scope; + + var did_forbid_argumen = false; + + while (_scope) |scope| : (_scope = _scope.?.parent) { + + // Track if we're inside a "with" statement body + if (scope.kind == .with) { + is_inside_with_scope = true; + } + + // Forbid referencing "arguments" inside class bodies + if (scope.forbid_arguments and !did_forbid_argumen and strings.eqlComptime(name, "arguments")) { + const r = js_lexer.rangeOfIdentifier(p.source, loc); + p.log.addRangeErrorFmt(p.source, r, allocator, "Cannot access \"{s}\" here", .{name}) catch unreachable; + did_forbid_argumen = true; + } + + // Is the symbol a member of this scope? + if (scope.members.getWithHash(name, hash)) |member| { + declare_loc = member.loc; + break :brk member.ref; + } + } + + // Allocate an "unbound" symbol + p.checkForNonBMPCodePoint(loc, name); + const _ref = p.newSymbol(.unbound, name) catch unreachable; + declare_loc = loc; + p.module_scope.members.putWithHash(allocator, name, hash, js_ast.Scope.Member{ .ref = _ref, .loc = logger.Loc.Empty }) catch unreachable; + + break :brk _ref; + }; + + // If we had to pass through a "with" statement body to get to the symbol + // declaration, then this reference could potentially also refer to a + // property on the target object of the "with" statement. We must not rename + // it or we risk changing the behavior of the code. + if (is_inside_with_scope) { + p.symbols.items[ref.innerIndex()].must_not_be_renamed = true; + } + + // Track how many times we've referenced this symbol + p.recordUsage(ref); + + return FindSymbolResult{ + .ref = ref, + .declare_loc = declare_loc, + .is_inside_with_scope = is_inside_with_scope, + }; + } + + pub fn recordExportedBinding(p: *P, binding: Binding) void { + switch (binding.data) { + .b_missing => {}, + .b_identifier => |ident| { + p.recordExport(binding.loc, p.symbols.items[ident.ref.innerIndex()].original_name, ident.ref) catch unreachable; + }, + .b_array => |array| { + for (array.items) |prop| { + p.recordExportedBinding(prop.binding); + } + }, + .b_object => |obj| { + for (obj.properties) |prop| { + p.recordExportedBinding(prop.value); + } + }, + else => { + p.panic("Unexpected binding export type {s}", .{binding}); + }, + } + } + + // If we're auto-importing JSX and it's bundled, we use the bundled version + // This means we need to transform from require(react) to react() + // unless we're building inside of bun, then it's just normal commonjs + pub inline fn callUnbundledRequire(p: *P, require_args: []Expr) Expr { + return p.callRuntime(require_args[0].loc, "__require", require_args); + } + + pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void { + if (p.named_exports.get(alias)) |name| { + // Duplicate exports are an error + var notes = try p.allocator.alloc(logger.Data, 1); + notes[0] = logger.Data{ + .text = try std.fmt.allocPrint(p.allocator, "\"{s}\" was originally exported here", .{alias}), + .location = logger.Location.init_or_nil(p.source, js_lexer.rangeOfIdentifier(p.source, name.alias_loc)), + }; + try p.log.addRangeErrorFmtWithNotes( + p.source, + js_lexer.rangeOfIdentifier(p.source, loc), + p.allocator, + notes, + "Multiple exports with the same name \"{s}\"", + .{std.mem.trim(u8, alias, "\"'")}, + ); + } else { + try p.named_exports.put(alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref }); + } + } + + pub fn recordUsage(p: *P, ref: Ref) void { + // The use count stored in the symbol is used for generating symbol names + // during minification. These counts shouldn't include references inside dead + // code regions since those will be culled. + if (!p.is_control_flow_dead) { + if (comptime Environment.allow_assert) assert(p.symbols.items.len > ref.innerIndex()); + p.symbols.items[ref.innerIndex()].use_count_estimate += 1; + var result = p.symbol_uses.getOrPut(p.allocator, ref) catch unreachable; + if (!result.found_existing) { + result.value_ptr.* = Symbol.Use{ .count_estimate = 1 }; + } else { + result.value_ptr.count_estimate += 1; + } + } + + // The correctness of TypeScript-to-JavaScript conversion relies on accurate + // symbol use counts for the whole file, including dead code regions. This is + // tracked separately in a parser-only data structure. + if (is_typescript_enabled) { + p.ts_use_counts.items[ref.innerIndex()] += 1; + } + } + + fn logArrowArgErrors(p: *P, errors: *DeferredArrowArgErrors) void { + if (errors.invalid_expr_await.len > 0) { + var r = errors.invalid_expr_await; + p.log.addRangeError(p.source, r, "Cannot use an \"await\" expression here") catch unreachable; + } + + if (errors.invalid_expr_yield.len > 0) { + var r = errors.invalid_expr_yield; + p.log.addRangeError(p.source, r, "Cannot use a \"yield\" expression here") catch unreachable; + } + } + + fn keyNameForError(p: *P, key: js_ast.Expr) string { + switch (key.data) { + .e_string => { + return key.data.e_string.string(p.allocator) catch unreachable; + }, + .e_private_identifier => |private| { + return p.loadNameFromRef(private.ref); + // return p.loadNameFromRef() + }, + else => { + return "property"; + }, + } + } + + pub fn handleIdentifier(p: *P, loc: logger.Loc, ident: E.Identifier, _original_name: ?string, opts: IdentifierOpts) Expr { + const ref = ident.ref; + + if ((opts.assign_target != .none or opts.is_delete_target) and p.symbols.items[ref.innerIndex()].kind == .import) { + // Create an error for assigning to an import namespace + const r = js_lexer.rangeOfIdentifier(p.source, loc); + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot assign to import \"{s}\"", .{ + p.symbols.items[ref.innerIndex()].original_name, + }) catch unreachable; + } + + // Substitute an EImportIdentifier now if this is an import item + if (p.is_import_item.contains(ref)) { + return p.e( + E.ImportIdentifier{ .ref = ref, .was_originally_identifier = opts.was_originally_identifier }, + loc, + ); + } + + // Substitute a namespace export reference now if appropriate + if (is_typescript_enabled) { + if (p.is_exported_inside_namespace.get(ref)) |ns_ref| { + const name = p.symbols.items[ref.innerIndex()].original_name; + + // If this is a known enum value, inline the value of the enum + if (p.known_enum_values.get(ns_ref)) |enum_values| { + if (enum_values.get(name)) |number| { + return p.e(E.Number{ .value = number }, loc); + } + } + + // Otherwise, create a property access on the namespace + p.recordUsage(ns_ref); + + return p.e(E.Dot{ .target = p.e(E.Identifier{ .ref = ns_ref }, loc), .name = name, .name_loc = loc }, loc); + } + } + + if (_original_name) |original_name| { + const result = p.findSymbol(loc, original_name) catch unreachable; + var _ident = ident; + _ident.ref = result.ref; + return p.e(_ident, loc); + } + + return p.e(ident, loc); + } + + pub fn generateImportStmt( + p: *P, + import_path: string, + imports: anytype, + parts: *ListManaged(js_ast.Part), + symbols: anytype, + additional_stmt: ?Stmt, + comptime suffix: string, + comptime is_internal: bool, + ) !void { + const allocator = p.allocator; + const import_record_i = p.addImportRecordByRange(.stmt, logger.Range.None, import_path); + var import_record: *ImportRecord = &p.import_records.items[import_record_i]; + import_record.path.namespace = "runtime"; + import_record.is_internal = is_internal; + var import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator); + var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + suffix.len); + var clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len); + var stmts = try allocator.alloc(Stmt, 1 + if (additional_stmt != null) @as(usize, 1) else @as(usize, 0)); + var declared_symbols = try allocator.alloc(js_ast.DeclaredSymbol, imports.len); + std.mem.copy(u8, namespace_identifier[0..suffix.len], suffix); + std.mem.copy( + u8, + namespace_identifier[suffix.len..namespace_identifier.len], + import_path_identifier[0..import_path_identifier.len], + ); + + const namespace_ref = try p.newSymbol(.other, namespace_identifier); + try p.module_scope.generated.append(allocator, namespace_ref); + for (imports) |alias, i| { + const ref = symbols.get(alias) orelse unreachable; + const alias_name = if (@TypeOf(symbols) == RuntimeImports) RuntimeImports.all[alias] else alias; + clause_items[i] = js_ast.ClauseItem{ + .alias = alias_name, + .original_name = alias_name, + .alias_loc = logger.Loc{}, + .name = LocRef{ .ref = ref, .loc = logger.Loc{} }, + }; + declared_symbols[i] = js_ast.DeclaredSymbol{ .ref = ref, .is_top_level = true }; + try p.is_import_item.put(allocator, ref, .{}); + try p.named_imports.put(ref, js_ast.NamedImport{ + .alias = alias_name, + .alias_loc = logger.Loc{}, + .namespace_ref = null, + .import_record_index = import_record_i, + }); + } + + stmts[0] = p.s(S.Import{ + .namespace_ref = namespace_ref, + .items = clause_items, + .import_record_index = import_record_i, + }, logger.Loc{}); + if (additional_stmt) |add| { + stmts[1] = add; + } + + var import_records = try allocator.alloc(@TypeOf(import_record_i), 1); + import_records[0] = import_record_i; + + // Append a single import to the end of the file (ES6 imports are hoisted + // so we don't need to worry about where the import statement goes) + parts.append(js_ast.Part{ + .stmts = stmts, + .declared_symbols = declared_symbols, + .import_record_indices = import_records, + .tag = .runtime, + }) catch unreachable; + } + + pub fn prepareForVisitPass(p: *P) !void { + { + var count: usize = 0; + for (p.scopes_in_order.items) |item| { + if (item != null) { + count += 1; + } + } + var i: usize = 0; + p.scope_order_to_visit = try p.allocator.alloc(ScopeOrder, p.scopes_in_order.items.len); + for (p.scopes_in_order.items) |item| { + if (item) |_item| { + p.scope_order_to_visit[i] = _item; + i += 1; + } + } + } + + try p.pushScopeForVisitPass(js_ast.Scope.Kind.entry, locModuleScope); + p.fn_or_arrow_data_visit.is_outside_fn_or_arrow = true; + p.module_scope = p.current_scope; + p.has_es_module_syntax = p.es6_import_keyword.len > 0 or p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0; + + // ECMAScript modules are always interpreted as strict mode. This has to be + // done before "hoistSymbols" because strict mode can alter hoisting (!). + if (p.es6_import_keyword.len > 0) { + p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_import); + } else if (p.es6_export_keyword.len > 0) { + p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_export); + } else if (p.top_level_await_keyword.len > 0) { + p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_top_level_await); + } + + p.hoistSymbols(p.module_scope); + + var generated_symbols_count: u32 = 3; + + if (p.options.enable_bundling) { + generated_symbols_count += 4; + } + + if (p.options.features.hot_module_reloading) { + generated_symbols_count += 3; + + if (p.options.features.react_fast_refresh) { + generated_symbols_count += 1; + } + } + + if (is_jsx_enabled) { + generated_symbols_count += 7; + + if (p.options.jsx.development) generated_symbols_count += 1; + } + + try p.module_scope.generated.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3); + try p.module_scope.members.ensureCapacity(p.allocator, generated_symbols_count * 3 + p.module_scope.members.count()); + + p.exports_ref = try p.declareCommonJSSymbol(.hoisted, "exports"); + p.module_ref = try p.declareCommonJSSymbol(.hoisted, "module"); + p.require_ref = try p.declareCommonJSSymbol(.unbound, "require"); + p.dirname_ref = try p.declareCommonJSSymbol(.unbound, "__dirname"); + p.filename_ref = try p.declareCommonJSSymbol(.unbound, "__filename"); + + if (p.options.enable_bundling) { + p.runtime_imports.__reExport = try p.declareGeneratedSymbol(.other, "__reExport"); + p.runtime_imports.@"$$m" = try p.declareGeneratedSymbol(.other, "$$m"); + + p.runtime_imports.@"$$lzy" = try p.declareGeneratedSymbol(.other, "$$lzy"); + + p.runtime_imports.__export = try p.declareGeneratedSymbol(.other, "__export"); + p.runtime_imports.__exportValue = try p.declareGeneratedSymbol(.other, "__exportValue"); + p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); + } + + if (p.options.features.hot_module_reloading) { + p.hmr_module = try p.declareGeneratedSymbol(.other, "hmr"); + if (p.options.features.react_fast_refresh) { + if (p.options.jsx.use_embedded_refresh_runtime) { + p.runtime_imports.__FastRefreshRuntime = try p.declareGeneratedSymbol(.other, "__FastRefreshRuntime"); + p.recordUsage(p.runtime_imports.__FastRefreshRuntime.?.ref); + p.jsx_refresh_runtime = p.runtime_imports.__FastRefreshRuntime.?; + } else { + p.jsx_refresh_runtime = try p.declareGeneratedSymbol(.other, "Refresher"); + } + + p.runtime_imports.__FastRefreshModule = try p.declareGeneratedSymbol(.other, "__FastRefreshModule"); + p.recordUsage(p.runtime_imports.__FastRefreshModule.?.ref); + } else { + p.runtime_imports.__HMRModule = try p.declareGeneratedSymbol(.other, "__HMRModule"); + p.recordUsage(p.runtime_imports.__HMRModule.?.ref); + } + + p.runtime_imports.__HMRClient = try p.declareGeneratedSymbol(.other, "__HMRClient"); + p.recordUsage(p.hmr_module.ref); + p.recordUsage(p.runtime_imports.__HMRClient.?.ref); + } + + switch (comptime jsx_transform_type) { + .react => { + if (p.options.jsx.development) { + p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") catch unreachable; + } + p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; + p.jsx_runtime = p.declareGeneratedSymbol(.other, "jsx") catch unreachable; + p.jsxs_runtime = p.declareGeneratedSymbol(.other, "jsxs") catch unreachable; + p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable; + + if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) { + p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable; + } + + if (p.options.jsx.import_source.len > 0) { + p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable; + } + }, + .solid => { + p.solid.insert = p.declareGeneratedSymbol(.other, "insert") catch unreachable; + p.solid.template = p.declareGeneratedSymbol(.other, "template") catch unreachable; + p.solid.wrap = p.declareGeneratedSymbol(.other, "wrap") catch unreachable; + p.solid.namespace = p.declareGeneratedSymbol(.other, "Solid") catch unreachable; + p.solid.delegateEvents = p.declareGeneratedSymbol(.other, "delegateEvents") catch unreachable; + p.solid.createComponent = p.declareGeneratedSymbol(.other, "createComponent") catch unreachable; + p.solid.setAttribute = p.declareGeneratedSymbol(.other, "setAttribute") catch unreachable; + p.solid.effect = p.declareGeneratedSymbol(.other, "effect") catch unreachable; + p.solid.current_template_string = MutableString.initEmpty(p.allocator); + p.solid.buffered_writer = p.solid.current_template_string.bufferedWriter(); + }, + .macro => { + p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable; + BunJSX.bun_jsx_identifier = E.Identifier{ + .ref = p.bun_jsx_ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; + p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; + }, + else => {}, + } + } + + // This won't work for adversarial cases + pub fn resolveGeneratedSymbol(p: *P, generated_symbol: *GeneratedSymbol) void { + if (generated_symbol.ref.isNull()) return; + + if (p.symbols.items[generated_symbol.primary.innerIndex()].use_count_estimate == 0 and + p.symbols.items[generated_symbol.primary.innerIndex()].link.isNull()) + { + p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.primary.innerIndex()].original_name; + return; + } + + if (p.symbols.items[generated_symbol.backup.innerIndex()].use_count_estimate == 0 and + p.symbols.items[generated_symbol.backup.innerIndex()].link.isNull()) + { + p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.backup.innerIndex()].original_name; + return; + } + } + + pub fn resolveCommonJSSymbols(p: *P) void { + if (p.runtime_imports.__require) |*require| { + p.resolveGeneratedSymbol(require); + } + } + + pub fn resolveBundlingSymbols(p: *P) void { + p.recordUsage(p.runtime_imports.@"$$m".?.ref); + + p.resolveGeneratedSymbol(&p.runtime_imports.__reExport.?); + p.resolveGeneratedSymbol(&p.runtime_imports.@"$$m".?); + p.resolveGeneratedSymbol(&p.runtime_imports.@"$$lzy".?); + p.resolveGeneratedSymbol(&p.runtime_imports.__export.?); + p.resolveGeneratedSymbol(&p.runtime_imports.__exportValue.?); + p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); + } + + pub fn resolveHMRSymbols(p: *P) void { + p.resolveGeneratedSymbol(&p.hmr_module); + if (p.runtime_imports.__FastRefreshModule != null) { + p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshModule.?); + if (p.options.jsx.use_embedded_refresh_runtime) + p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshRuntime.?); + } + if (p.runtime_imports.__HMRModule != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRModule.?); + if (p.runtime_imports.__HMRClient != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRClient.?); + } + + pub fn resolveStaticJSXSymbols(p: *P) void { + p.resolveGeneratedSymbol(&p.jsx_runtime); + p.resolveGeneratedSymbol(&p.jsxs_runtime); + p.resolveGeneratedSymbol(&p.jsx_factory); + p.resolveGeneratedSymbol(&p.jsx_fragment); + p.resolveGeneratedSymbol(&p.jsx_classic); + p.resolveGeneratedSymbol(&p.jsx_automatic); + p.resolveGeneratedSymbol(&p.jsx_filename); + } + + fn hoistSymbols(p: *P, scope: *js_ast.Scope) void { + if (!scope.kindStopsHoisting()) { + var iter = scope.members.iterator(); + const allocator = p.allocator; + nextMember: while (iter.next()) |res| { + var symbol = &p.symbols.items[res.value.ref.innerIndex()]; + if (!symbol.isHoisted()) { + continue :nextMember; + } + + // Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope + var __scope = scope.parent; + + var hash: u64 = undefined; + if (__scope) |_scope| { + hash = @TypeOf(_scope.members).getHash(symbol.original_name); + } + + while (__scope) |_scope| { + // Variable declarations hoisted past a "with" statement may actually end + // up overwriting a property on the target of the "with" statement instead + // of initializing the variable. We must not rename them or we risk + // causing a behavior change. + // + // var obj = { foo: 1 } + // with (obj) { var foo = 2 } + // assert(foo === undefined) + // assert(obj.foo === 2) + // + if (_scope.kind == .with) { + symbol.must_not_be_renamed = true; + } + + if (_scope.members.getEntryWithHash(symbol.original_name, hash)) |existing_member_entry| { + const existing_member = &existing_member_entry.value; + const existing_symbol: *const Symbol = &p.symbols.items[existing_member.ref.innerIndex()]; + + // We can hoist the symbol from the child scope into the symbol in + // this scope if: + // + // - The symbol is unbound (i.e. a global variable access) + // - The symbol is also another hoisted variable + // - The symbol is a function of any kind and we're in a function or module scope + // + // Is this unbound (i.e. a global access) or also hoisted? + if (existing_symbol.kind == .unbound or existing_symbol.kind == .hoisted or + (Symbol.isKindFunction(existing_symbol.kind) and (_scope.kind == .entry or _scope.kind == .function_body))) + { + // Silently merge this symbol into the existing symbol + symbol.link = existing_member.ref; + continue :nextMember; + } + + // Otherwise if this isn't a catch identifier, it's a collision + if (existing_symbol.kind != .catch_identifier) { + + // An identifier binding from a catch statement and a function + // declaration can both silently shadow another hoisted symbol + if (symbol.kind != .catch_identifier and symbol.kind != .hoisted_function) { + const r = js_lexer.rangeOfIdentifier(p.source, res.value.loc); + var notes = allocator.alloc(logger.Data, 1) catch unreachable; + notes[0] = + logger.rangeData( + p.source, + r, + std.fmt.allocPrint( + allocator, + "{s} was originally declared here", + .{existing_symbol.original_name}, + ) catch unreachable, + ); + + p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, existing_member_entry.value.loc), allocator, notes, "{s} has already been declared", .{symbol.original_name}) catch unreachable; + } + + continue :nextMember; + } + } + + if (_scope.kindStopsHoisting()) { + _scope.members.putWithHash(allocator, symbol.original_name, hash, res.value) catch unreachable; + break; + } + __scope = _scope.parent; + } + } + } + + for (scope.children.items) |_, i| { + p.hoistSymbols(scope.children.items[i]); + } + } + + inline fn nextScopeInOrderForVisitPass(p: *P) ScopeOrder { + const head = p.scope_order_to_visit[0]; + p.scope_order_to_visit = p.scope_order_to_visit[1..p.scope_order_to_visit.len]; + return head; + } + + fn pushScopeForVisitPass(p: *P, kind: js_ast.Scope.Kind, loc: logger.Loc) !void { + // Output.print("\n+Loc: {d}\n", .{loc.start}); + // for (p.scopes_in_order.items[p.scopes_in_order_visitor_index..p.scopes_in_order.items.len]) |scope_order, i| { + // if (scope_order) |ord| { + // Output.print("Scope ({d}, {d})\n", .{ @enumToInt(ord.scope.kind), ord.loc.start }); + // } + // } + const order = p.nextScopeInOrderForVisitPass(); + + // Sanity-check that the scopes generated by the first and second passes match + if (order.loc.start != loc.start or order.scope.kind != kind) { + p.panic("Expected scope ({s}, {d}) in {s}, found scope ({s}, {d})", .{ kind, loc.start, p.source.path.pretty, order.scope.kind, order.loc.start }); + } + + p.current_scope = order.scope; + + try p.scopes_for_current_part.append(p.allocator, order.scope); + } + + fn pushScopeForParsePass(p: *P, comptime kind: js_ast.Scope.Kind, loc: logger.Loc) !usize { + var parent: *Scope = p.current_scope; + const allocator = p.allocator; + var scope = try allocator.create(Scope); + + scope.* = Scope{ + .kind = kind, + .label_ref = null, + .parent = parent, + .generated = .{}, + }; + + try parent.children.append(allocator, scope); + scope.strict_mode = parent.strict_mode; + + p.current_scope = scope; + + if (comptime !Environment.isRelease) { + // Enforce that scope locations are strictly increasing to help catch bugs + // where the pushed scopes are mistmatched between the first and second passes + if (p.scopes_in_order.items.len > 0) { + var last_i = p.scopes_in_order.items.len - 1; + while (p.scopes_in_order.items[last_i] == null and last_i > 0) { + last_i -= 1; + } + + if (p.scopes_in_order.items[last_i]) |prev_scope| { + if (prev_scope.loc.start >= loc.start) { + p.panic("Scope location {d} must be greater than {d}", .{ loc.start, prev_scope.loc.start }); + } + } + } + } + + // Copy down function arguments into the function body scope. That way we get + // errors if a statement in the function body tries to re-declare any of the + // arguments. + if (comptime kind == js_ast.Scope.Kind.function_body) { + if (comptime Environment.allow_assert) + assert(parent.kind == js_ast.Scope.Kind.function_args); + + var iter = scope.parent.?.members.iterator(); + while (iter.next()) |entry| { + // // Don't copy down the optional function expression name. Re-declaring + // // the name of a function expression is allowed. + const adjacent_kind = p.symbols.items[entry.value.ref.innerIndex()].kind; + if (adjacent_kind != .hoisted_function) { + try scope.members.put(allocator, entry.key, entry.value); + } + } + } + + // Remember the length in case we call popAndDiscardScope() later + const scope_index = p.scopes_in_order.items.len; + try p.scopes_in_order.append(allocator, ScopeOrder{ .loc = loc, .scope = scope }); + // Output.print("\nLoc: {d}\n", .{loc.start}); + return scope_index; + } + + // Note: do not write to "p.log" in this function. Any errors due to conversion + // from expression to binding should be written to "invalidLog" instead. That + // way we can potentially keep this as an expression if it turns out it's not + // needed as a binding after all. + fn convertExprToBinding(p: *P, expr: ExprNodeIndex, invalid_loc: *LocList) ?Binding { + switch (expr.data) { + .e_missing => { + return null; + }, + .e_identifier => |ex| { + return p.b(B.Identifier{ .ref = ex.ref }, expr.loc); + }, + .e_array => |ex| { + if (ex.comma_after_spread) |spread| { + invalid_loc.append(.{ + .loc = spread, + .kind = .spread, + }) catch unreachable; + } + + if (ex.is_parenthesized) { + invalid_loc.append(.{ + .loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc, + .kind = .parenthese, + }) catch unreachable; + } + + // p.markSyntaxFeature(Destructing) + var items = List(js_ast.ArrayBinding).initCapacity(p.allocator, ex.items.len) catch unreachable; + var is_spread = false; + for (ex.items.slice()) |_, i| { + var item = ex.items.ptr[i]; + if (item.data == .e_spread) { + is_spread = true; + item = item.data.e_spread.value; + } + const res = p.convertExprToBindingAndInitializer(&item, invalid_loc, is_spread); + + items.appendAssumeCapacity(js_ast.ArrayBinding{ + // It's valid for it to be missing + // An example: + // Promise.all(promises).then(([, len]) => true); + // ^ Binding is missing there + .binding = res.binding orelse p.b(B.Missing{}, item.loc), + .default_value = res.expr, + }); + } + + return p.b(B.Array{ + .items = items.items, + .has_spread = is_spread, + .is_single_line = ex.is_single_line, + }, expr.loc); + }, + .e_object => |ex| { + if (ex.comma_after_spread) |sp| { + invalid_loc.append(.{ .loc = sp, .kind = .spread }) catch unreachable; + } + + if (ex.is_parenthesized) { + invalid_loc.append(.{ .loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc, .kind = .parenthese }) catch unreachable; + } + // p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "{")) + + var properties = List(B.Property).initCapacity(p.allocator, ex.properties.len) catch unreachable; + for (ex.properties.slice()) |*item| { + if (item.flags.contains(.is_method) or item.kind == .get or item.kind == .set) { + invalid_loc.append(.{ + .loc = item.key.?.loc, + .kind = if (item.flags.contains(.is_method)) + InvalidLoc.Tag.method + else if (item.kind == .get) + InvalidLoc.Tag.getter + else + InvalidLoc.Tag.setter, + }) catch unreachable; + continue; + } + var value = &item.value.?; + const tup = p.convertExprToBindingAndInitializer(value, invalid_loc, false); + const initializer = tup.expr orelse item.initializer; + const is_spread = item.kind == .spread or item.flags.contains(.is_spread); + properties.appendAssumeCapacity(B.Property{ + .flags = Flags.Property.init(.{ + .is_spread = is_spread, + .is_computed = item.flags.contains(.is_computed), + }), + .key = item.key orelse p.e(E.Missing{}, expr.loc), + .value = tup.binding orelse p.b(B.Missing{}, expr.loc), + .default_value = initializer, + }); + } + + return p.b(B.Object{ + .properties = properties.items, + .is_single_line = ex.is_single_line, + }, expr.loc); + }, + else => { + invalid_loc.append(.{ .loc = expr.loc, .kind = .unknown }) catch unreachable; + return null; + }, + } + + return null; + } + + fn convertExprToBindingAndInitializer(p: *P, _expr: *ExprNodeIndex, invalid_log: *LocList, is_spread: bool) ExprBindingTuple { + var initializer: ?ExprNodeIndex = null; + var expr = _expr; + // zig syntax is sometimes painful + switch (expr.*.data) { + .e_binary => |bin| { + if (bin.op == .bin_assign) { + initializer = bin.right; + expr = &bin.left; + } + }, + else => {}, + } + + var bind = p.convertExprToBinding(expr.*, invalid_log); + if (initializer) |initial| { + const equalsRange = p.source.rangeOfOperatorBefore(initial.loc, "="); + if (is_spread) { + p.log.addRangeError(p.source, equalsRange, "A rest argument cannot have a default initializer") catch unreachable; + } else { + // p.markSyntaxFeature(); + } + } + return ExprBindingTuple{ .binding = bind, .expr = initializer }; + } + + fn forbidLexicalDecl(p: *P, loc: logger.Loc) !void { + try p.log.addError(p.source, loc, "Cannot use a declaration in a single-statement context"); + } + + /// If we attempt to parse TypeScript syntax outside of a TypeScript file + /// make it a compile error + inline fn markTypeScriptOnly(_: *const P) void { + if (comptime !is_typescript_enabled) { + @compileError("This function can only be used in TypeScript"); + } + + // explicitly mark it as unreachable in the hopes that the function doesn't exist at all + if (!is_typescript_enabled) { + unreachable; + } + } + + fn logExprErrors(p: *P, errors: *DeferredErrors) void { + if (errors.invalid_expr_default_value) |r| { + p.log.addRangeError( + p.source, + r, + "Unexpected \"=\"", + ) catch unreachable; + } + + if (errors.invalid_expr_after_question) |r| { + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Unexpected {s}", .{p.source.contents[r.loc.i()..r.endI()]}) catch unreachable; + } + + // if (errors.array_spread_feature) |err| { + // p.markSyntaxFeature(compat.ArraySpread, errors.arraySpreadFeature) + // } + } + + // This assumes the "function" token has already been parsed + + fn parseFnStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, asyncRange: ?logger.Range) !Stmt { + const is_generator = p.lexer.token == T.t_asterisk; + const is_async = asyncRange != null; + + if (is_generator) { + // p.markSyntaxFeature(compat.Generator, p.lexer.Range()) + try p.lexer.next(); + } else if (is_async) { + // p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) + } + + switch (opts.lexical_decl) { + .forbid => { + try p.forbidLexicalDecl(loc); + }, + + // Allow certain function statements in certain single-statement contexts + .allow_fn_inside_if, .allow_fn_inside_label => { + if (opts.is_typescript_declare or is_generator or is_async) { + try p.forbidLexicalDecl(loc); + } + }, + else => {}, + } + + var name: ?js_ast.LocRef = null; + var nameText: string = ""; + + // The name is optional for "export default function() {}" pseudo-statements + if (!opts.is_name_optional or p.lexer.token == T.t_identifier) { + var nameLoc = p.lexer.loc(); + nameText = p.lexer.identifier; + try p.lexer.expect(T.t_identifier); + // Difference + const ref = try p.newSymbol(Symbol.Kind.other, nameText); + name = js_ast.LocRef{ + .loc = nameLoc, + .ref = ref, + }; + } + + // Even anonymous functions can have TypeScript type parameters + if (is_typescript_enabled) { + try p.skipTypeScriptTypeParameters(); + } + + // Introduce a fake block scope for function declarations inside if statements + var ifStmtScopeIndex: usize = 0; + var hasIfScope = opts.lexical_decl == .allow_fn_inside_if; + if (hasIfScope) { + ifStmtScopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.block, loc); + } + + var scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc()); + var func = try p.parseFn(name, FnOrArrowDataParse{ + .async_range = asyncRange orelse logger.Range.None, + .has_async_range = asyncRange != null, + .allow_await = if (is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, + .allow_yield = if (is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, + .is_typescript_declare = opts.is_typescript_declare, + + // Only allow omitting the body if we're parsing TypeScript + .allow_missing_body_for_type_script = is_typescript_enabled, + }); + + if (comptime is_typescript_enabled) { + // Don't output anything if it's just a forward declaration of a function + if (opts.is_typescript_declare or func.flags.contains(.is_forward_declaration)) { + p.popAndDiscardScope(scopeIndex); + + // Balance the fake block scope introduced above + if (hasIfScope) { + p.popScope(); + } + + if (opts.is_typescript_declare and opts.is_namespace_scope and opts.is_export) { + p.has_non_local_export_declare_inside_namespace = true; + } + + return p.s(S.TypeScript{}, loc); + } + } + + p.popScope(); + + // Only declare the function after we know if it had a body or not. Otherwise + // TypeScript code such as this will double-declare the symbol: + // + // function foo(): void; + // function foo(): void {} + // + if (name) |*name_| { + const kind = if (is_generator or is_async) Symbol.Kind.generator_or_async_function else Symbol.Kind.hoisted_function; + name_.ref = try p.declareSymbol(kind, name_.loc, nameText); + func.name = name_.*; + } + + func.flags.setPresent(.has_if_scope, hasIfScope); + func.flags.setPresent(.is_export, opts.is_export); + + // Balance the fake block scope introduced above + if (hasIfScope) { + p.popScope(); + } + + return p.s(S.Function{ + .func = func, + }, func.open_parens_loc); + } + + fn popAndDiscardScope(p: *P, scope_index: usize) void { + // Move up to the parent scope + var to_discard = p.current_scope; + var parent = to_discard.parent orelse unreachable; + + p.current_scope = parent; + + // Truncate the scope order where we started to pretend we never saw this scope + p.scopes_in_order.shrinkRetainingCapacity(scope_index); + + var children = parent.children; + // Remove the last child from the parent scope + var last = children.items.len - 1; + if (children.items[last] != to_discard) { + p.panic("Internal error", .{}); + } + + _ = children.popOrNull(); + } + + fn parseFn(p: *P, name: ?js_ast.LocRef, opts: FnOrArrowDataParse) anyerror!G.Fn { + // if data.allowAwait and data.allowYield { + // p.markSyntaxFeature(compat.AsyncGenerator, data.asyncRange) + // } + + var func = G.Fn{ + .name = name, + + .flags = Flags.Function.init(.{ + .has_rest_arg = false, + .is_async = opts.allow_await == .allow_expr, + .is_generator = opts.allow_yield == .allow_expr, + }), + + .arguments_ref = null, + .open_parens_loc = p.lexer.loc(), + }; + try p.lexer.expect(T.t_open_paren); + + // Await and yield are not allowed in function arguments + var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); + + p.fn_or_arrow_data_parse.allow_await = if (opts.allow_await == .allow_expr) + AwaitOrYield.forbid_all + else + AwaitOrYield.allow_ident; + + p.fn_or_arrow_data_parse.allow_yield = if (opts.allow_yield == .allow_expr) + AwaitOrYield.forbid_all + else + AwaitOrYield.allow_ident; + + // If "super()" is allowed in the body, it's allowed in the arguments + p.fn_or_arrow_data_parse.allow_super_call = opts.allow_super_call; + p.fn_or_arrow_data_parse.allow_super_property = opts.allow_super_property; + + var args = List(G.Arg){}; + while (p.lexer.token != T.t_close_paren) { + // Skip over "this" type annotations + if (is_typescript_enabled and p.lexer.token == T.t_this) { + try p.lexer.next(); + if (p.lexer.token == T.t_colon) { + try p.lexer.next(); + try p.skipTypeScriptType(js_ast.Op.Level.lowest); + } + if (p.lexer.token != T.t_comma) { + break; + } + + try p.lexer.next(); + continue; + } + + var ts_decorators: []ExprNodeIndex = &([_]ExprNodeIndex{}); + if (opts.allow_ts_decorators) { + ts_decorators = try p.parseTypeScriptDecorators(); + } + + if (!func.flags.contains(.has_rest_arg) and p.lexer.token == T.t_dot_dot_dot) { + // p.markSyntaxFeature + try p.lexer.next(); + func.flags.insert(.has_rest_arg); + } + + var is_typescript_ctor_field = false; + var is_identifier = p.lexer.token == T.t_identifier; + var text = p.lexer.identifier; + var arg = try p.parseBinding(); + + if (comptime is_typescript_enabled) { + if (is_identifier and opts.is_constructor) { + // Skip over TypeScript accessibility modifiers, which turn this argument + // into a class field when used inside a class constructor. This is known + // as a "parameter property" in TypeScript. + while (true) { + switch (p.lexer.token) { + .t_identifier, .t_open_brace, .t_open_bracket => { + if (!js_lexer.TypeScriptAccessibilityModifier.has(text)) { + break; + } + + is_typescript_ctor_field = true; + + // TypeScript requires an identifier binding + if (p.lexer.token != .t_identifier) { + try p.lexer.expect(.t_identifier); + } + text = p.lexer.identifier; + + // Re-parse the binding (the current binding is the TypeScript keyword) + arg = try p.parseBinding(); + }, + else => { + break; + }, + } + } + } + + // "function foo(a?) {}" + if (p.lexer.token == .t_question) { + try p.lexer.next(); + } + + // "function foo(a: any) {}" + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + } + + var parseStmtOpts = ParseStatementOptions{}; + p.declareBinding(.hoisted, &arg, &parseStmtOpts) catch unreachable; + + var default_value: ?ExprNodeIndex = null; + if (!func.flags.contains(.has_rest_arg) and p.lexer.token == .t_equals) { + // p.markSyntaxFeature + try p.lexer.next(); + default_value = try p.parseExpr(.comma); + } + + args.append(p.allocator, G.Arg{ + .ts_decorators = ExprNodeList.init(ts_decorators), + .binding = arg, + .default = default_value, + + // We need to track this because it affects code generation + .is_typescript_ctor_field = is_typescript_ctor_field, + }) catch unreachable; + + if (p.lexer.token != .t_comma) { + break; + } + + if (func.flags.contains(.has_rest_arg)) { + // JavaScript does not allow a comma after a rest argument + if (opts.is_typescript_declare) { + // TypeScript does allow a comma after a rest argument in a "declare" context + try p.lexer.next(); + } else { + try p.lexer.expect(.t_close_paren); + } + + break; + } + + try p.lexer.next(); + } + if (args.items.len > 0) { + func.args = args.items; + } + + // Reserve the special name "arguments" in this scope. This ensures that it + // shadows any variable called "arguments" in any parent scopes. But only do + // this if it wasn't already declared above because arguments are allowed to + // be called "arguments", in which case the real "arguments" is inaccessible. + if (!p.current_scope.members.contains("arguments")) { + func.arguments_ref = p.declareSymbolMaybeGenerated(.arguments, func.open_parens_loc, "arguments", true) catch unreachable; + p.symbols.items[func.arguments_ref.?.innerIndex()].must_not_be_renamed = true; + } + + try p.lexer.expect(.t_close_paren); + p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); + + // "function foo(): any {}" + if (is_typescript_enabled and p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypescriptReturnType(); + } + + // "function foo(): any;" + if (opts.allow_missing_body_for_type_script and p.lexer.token != .t_open_brace) { + try p.lexer.expectOrInsertSemicolon(); + func.flags.insert(.is_forward_declaration); + return func; + } + var tempOpts = opts; + func.body = try p.parseFnBody(&tempOpts); + + return func; + } + + // pub fn parseBinding(p: *P) + + pub inline fn skipTypescriptReturnType(p: *P) anyerror!void { + try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_return_type = true }); + } + + pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex { + if (!is_typescript_enabled) { + return &([_]ExprNodeIndex{}); + } + + var decorators = ListManaged(ExprNodeIndex).init(p.allocator); + while (p.lexer.token == T.t_at) { + try p.lexer.next(); + + // Parse a new/call expression with "exprFlagTSDecorator" so we ignore + // EIndex expressions, since they may be part of a computed property: + // + // class Foo { + // @foo ['computed']() {} + // } + // + // This matches the behavior of the TypeScript compiler. + try decorators.append(try p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator)); + } + + return decorators.items; + } + + inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void { + p.markTypeScriptOnly(); + try p.skipTypeScriptTypeWithOpts(level, .{}); + } + + fn skipTypeScriptBinding(p: *P) anyerror!void { + p.markTypeScriptOnly(); + switch (p.lexer.token) { + .t_identifier, .t_this => { + try p.lexer.next(); + }, + .t_open_bracket => { + try p.lexer.next(); + + // "[, , a]" + + while (p.lexer.token == .t_comma) { + try p.lexer.next(); + } + // "[a, b]" + while (p.lexer.token != .t_close_bracket) { + try p.skipTypeScriptBinding(); + + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + + try p.lexer.expect(.t_close_bracket); + }, + .t_open_brace => { + try p.lexer.next(); + + while (p.lexer.token != .t_close_brace) { + var found_identifier = false; + + switch (p.lexer.token) { + .t_identifier => { + found_identifier = true; + try p.lexer.next(); + }, + + // "{1: y}" + // "{'x': y}" + .t_string_literal, .t_numeric_literal => { + try p.lexer.next(); + }, + + else => { + if (p.lexer.isIdentifierOrKeyword()) { + // "{if: x}" + try p.lexer.next(); + } else { + try p.lexer.unexpected(); + return error.Backtrack; + } + }, + } + + if (p.lexer.token == .t_colon or !found_identifier) { + try p.lexer.expect(.t_colon); + try p.skipTypeScriptBinding(); + } + + if (p.lexer.token != .t_comma) { + break; + } + + try p.lexer.next(); + } + + try p.lexer.expect(.t_close_brace); + }, + else => { + // try p.lexer.unexpected(); + return error.Backtrack; + }, + } + } + + fn skipTypescriptFnArgs(p: *P) anyerror!void { + p.markTypeScriptOnly(); + + try p.lexer.expect(.t_open_paren); + + while (p.lexer.token != .t_close_paren) { + // "(...a)" + if (p.lexer.token == .t_dot_dot_dot) { + try p.lexer.next(); + } + + try p.skipTypeScriptBinding(); + + // "(a?)" + if (p.lexer.token == .t_question) { + try p.lexer.next(); + } + + // "(a: any)" + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + + // "(a, b)" + if (p.lexer.token != .t_comma) { + break; + } + + try p.lexer.next(); + } + + try p.lexer.expect(.t_close_paren); + } + + // This is a spot where the TypeScript grammar is highly ambiguous. Here are + // some cases that are valid: + // + // let x = (y: any): (() => {}) => { }; + // let x = (y: any): () => {} => { }; + // let x = (y: any): (y) => {} => { }; + // let x = (y: any): (y[]) => {}; + // let x = (y: any): (a | b) => {}; + // + // Here are some cases that aren't valid: + // + // let x = (y: any): (y) => {}; + // let x = (y: any): (y) => {return 0}; + // let x = (y: any): asserts y is (y) => {}; + // + fn skipTypeScriptParenOrFnType(p: *P) anyerror!void { + p.markTypeScriptOnly(); + + if (p.trySkipTypeScriptArrowArgsWithBacktracking()) { + try p.skipTypescriptReturnType(); + } else { + try p.lexer.expect(.t_open_paren); + try p.skipTypeScriptType(.lowest); + try p.lexer.expect(.t_close_paren); + } + } + + fn skipTypeScriptTypeWithOpts(p: *P, level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions) anyerror!void { + p.markTypeScriptOnly(); + + while (true) { + switch (p.lexer.token) { + .t_numeric_literal, + .t_big_integer_literal, + .t_string_literal, + .t_no_substitution_template_literal, + .t_true, + .t_false, + .t_null, + .t_void, + .t_const, + => { + try p.lexer.next(); + }, + + .t_this => { + try p.lexer.next(); + + // "function check(): this is boolean" + if (p.lexer.isContextualKeyword("is") and !p.lexer.has_newline_before) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + return; + } + }, + .t_minus => { + // "-123" + // "-123n" + try p.lexer.next(); + + if (p.lexer.token == .t_big_integer_literal) { + try p.lexer.next(); + } else { + try p.lexer.expect(.t_numeric_literal); + } + }, + .t_ampersand, .t_bar => { + // Support things like "type Foo = | A | B" and "type Foo = & A & B" + try p.lexer.next(); + continue; + }, + .t_import => { + // "import('fs')" + try p.lexer.next(); + try p.lexer.expect(.t_open_paren); + try p.lexer.expect(.t_string_literal); + try p.lexer.expect(.t_close_paren); + }, + .t_new => { + // "new () => Foo" + // "new <T>() => Foo<T>" + try p.lexer.next(); + try p.skipTypeScriptTypeParameters(); + try p.skipTypeScriptParenOrFnType(); + }, + .t_less_than => { + // "<T>() => Foo<T>" + try p.skipTypeScriptTypeParameters(); + try p.skipTypeScriptParenOrFnType(); + }, + .t_open_paren => { + // "(number | string)" + try p.skipTypeScriptParenOrFnType(); + }, + .t_identifier => { + const kind = TypeScript.Identifier.IMap.get(p.lexer.identifier) orelse .normal; + + if (kind == .prefix) { + try p.lexer.next(); + try p.skipTypeScriptType(.prefix); + break; + } + + var check_type_parameters = true; + + switch (kind) { + .unique => { + try p.lexer.next(); + + // "let foo: unique symbol" + + if (p.lexer.isContextualKeyword("symbol")) { + try p.lexer.next(); + break; + } + }, + .abstract => { + try p.lexer.next(); + + // "let foo: abstract new () => {}" added in TypeScript 4.2 + if (p.lexer.token == .t_new) { + continue; + } + }, + .asserts => { + try p.lexer.next(); + + // "function assert(x: boolean): asserts x" + // "function assert(x: boolean): asserts x is boolean" + + if (opts.is_return_type and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) { + try p.lexer.next(); + } + }, + .primitive => { + try p.lexer.next(); + check_type_parameters = false; + }, + else => { + try p.lexer.next(); + }, + } + + // "function assert(x: any): x is boolean" + + if (p.lexer.isContextualKeyword("is") and !p.lexer.has_newline_before) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + return; + } + + // "let foo: any \n <number>foo" must not become a single type + if (check_type_parameters and !p.lexer.has_newline_before) { + _ = try p.skipTypeScriptTypeArguments(false); + } + }, + .t_typeof => { + try p.lexer.next(); + if (p.lexer.token == .t_import) { + // "typeof import('fs')" + continue; + } else { + // "typeof x" + // "typeof x.y" + + while (true) { + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expected(.t_identifier); + } + + try p.lexer.next(); + if (p.lexer.token != .t_dot) { + break; + } + + try p.lexer.next(); + } + } + }, + .t_open_bracket => { + // "[number, string]" + // "[first: number, second: string]" + try p.lexer.next(); + + while (p.lexer.token != .t_close_bracket) { + if (p.lexer.token == .t_dot_dot_dot) { + try p.lexer.next(); + } + try p.skipTypeScriptType(.lowest); + if (p.lexer.token == .t_question) { + try p.lexer.next(); + } + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + try p.lexer.expect(.t_close_bracket); + }, + .t_open_brace => { + try p.skipTypeScriptObjectType(); + }, + .t_template_head => { + // "`${'a' | 'b'}-${'c' | 'd'}`" + + while (true) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + try p.lexer.rescanCloseBraceAsTemplateToken(); + + if (p.lexer.token == .t_template_tail) { + try p.lexer.next(); + break; + } + } + }, + + else => { + try p.lexer.unexpected(); + return error.Backtrack; + }, + } + break; + } + + while (true) { + switch (p.lexer.token) { + .t_bar => { + if (level.gte(.bitwise_or)) { + return; + } + try p.lexer.next(); + try p.skipTypeScriptType(.bitwise_or); + }, + .t_ampersand => { + if (level.gte(.bitwise_and)) { + return; + } + + try p.lexer.next(); + try p.skipTypeScriptType(.bitwise_and); + }, + .t_exclamation => { + // A postfix "!" is allowed in JSDoc types in TypeScript, which are only + // present in comments. While it's not valid in a non-comment position, + // it's still parsed and turned into a soft error by the TypeScript + // compiler. It turns out parsing this is important for correctness for + // "as" casts because the "!" token must still be consumed. + if (p.lexer.has_newline_before) { + return; + } + + try p.lexer.next(); + }, + .t_dot => { + try p.lexer.next(); + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + try p.lexer.next(); + _ = try p.skipTypeScriptTypeArguments(false); + }, + .t_open_bracket => { + // "{ ['x']: string \n ['y']: string }" must not become a single type + if (p.lexer.has_newline_before) { + return; + } + try p.lexer.next(); + if (p.lexer.token != .t_close_bracket) { + try p.skipTypeScriptType(.lowest); + } + try p.lexer.expect(.t_close_bracket); + }, + .t_extends => { + // "{ x: number \n extends: boolean }" must not become a single type + if (p.lexer.has_newline_before or level.gte(.conditional)) { + return; + } + + try p.lexer.next(); + + // The type following "extends" is not permitted to be another conditional type + try p.skipTypeScriptType(.conditional); + try p.lexer.expect(.t_question); + try p.skipTypeScriptType(.lowest); + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + }, + else => { + return; + }, + } + } + } + fn skipTypeScriptObjectType(p: *P) anyerror!void { + p.markTypeScriptOnly(); + + try p.lexer.expect(.t_open_brace); + + while (p.lexer.token != .t_close_brace) { + // "{ -readonly [K in keyof T]: T[K] }" + // "{ +readonly [K in keyof T]: T[K] }" + if (p.lexer.token == .t_plus or p.lexer.token == .t_minus) { + try p.lexer.next(); + } + + // Skip over modifiers and the property identifier + var found_key = false; + while (p.lexer.isIdentifierOrKeyword() or p.lexer.token == .t_string_literal or p.lexer.token == .t_numeric_literal) { + try p.lexer.next(); + found_key = true; + } + + if (p.lexer.token == .t_open_bracket) { + // Index signature or computed property + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + + // "{ [key: string]: number }" + // "{ readonly [K in keyof T]: T[K] }" + switch (p.lexer.token) { + .t_colon => { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + }, + .t_in => { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + if (p.lexer.isContextualKeyword("as")) { + // "{ [K in keyof T as `get-${K}`]: T[K] }" + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + }, + else => {}, + } + + try p.lexer.expect(.t_close_bracket); + + // "{ [K in keyof T]+?: T[K] }" + // "{ [K in keyof T]-?: T[K] }" + switch (p.lexer.token) { + .t_plus, .t_minus => { + try p.lexer.next(); + }, + else => {}, + } + + found_key = true; + } + + // "?" indicates an optional property + // "!" indicates an initialization assertion + if (found_key and (p.lexer.token == .t_question or p.lexer.token == .t_exclamation)) { + try p.lexer.next(); + } + + // Type parameters come right after the optional mark + try p.skipTypeScriptTypeParameters(); + + switch (p.lexer.token) { + .t_colon => { + // Regular property + if (!found_key) { + try p.lexer.expect(.t_identifier); + } + + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + }, + .t_open_paren => { + // Method signature + try p.skipTypescriptFnArgs(); + + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypescriptReturnType(); + } + }, + else => { + if (!found_key) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + }, + } + switch (p.lexer.token) { + .t_close_brace => {}, + .t_comma, .t_semicolon => { + try p.lexer.next(); + }, + else => { + if (!p.lexer.has_newline_before) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + }, + } + } + try p.lexer.expect(.t_close_brace); + } + + fn processImportStatement(p: *P, stmt_: S.Import, path: ParsedPath, loc: logger.Loc, was_originally_bare_import: bool) anyerror!Stmt { + const is_macro = FeatureFlags.is_macro_enabled and js_ast.Macro.isMacroPath(path.text); + var stmt = stmt_; + if (is_macro) { + const id = p.addImportRecord(.stmt, path.loc, path.text); + p.import_records.items[id].path.namespace = js_ast.Macro.namespace; + p.import_records.items[id].is_unused = true; + + if (stmt.default_name) |name_loc| { + const name = p.loadNameFromRef(name_loc.ref.?); + const ref = try p.declareSymbol(.other, name_loc.loc, name); + try p.is_import_item.put(p.allocator, ref, .{}); + try p.macro.refs.put(ref, id); + } + + for (stmt.items) |item| { + const name = p.loadNameFromRef(item.name.ref.?); + const ref = try p.declareSymbol(.other, item.name.loc, name); + try p.is_import_item.put(p.allocator, ref, .{}); + try p.macro.refs.put(ref, id); + } + + return p.s(S.Empty{}, loc); + } + + const macro_remap = if ((comptime allow_macros) and !is_macro) + p.options.macro_context.getRemap(path.text) + else + null; + + stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text); + p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import; + + if (stmt.star_name_loc) |star| { + const name = p.loadNameFromRef(stmt.namespace_ref); + + stmt.namespace_ref = try p.declareSymbol(.import, star, name); + + if (comptime track_symbol_usage_during_parse_pass) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = stmt.namespace_ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } + } else { + var path_name = fs.PathName.init(strings.append(p.allocator, "import_", path.text) catch unreachable); + const name = try path_name.nonUniqueNameString(p.allocator); + stmt.namespace_ref = try p.newSymbol(.other, name); + var scope: *Scope = p.current_scope; + try scope.generated.append(p.allocator, stmt.namespace_ref); + } + + var item_refs = ImportItemForNamespaceMap.init(p.allocator); + const count_excluding_namespace = @intCast(u16, stmt.items.len) + + @intCast(u16, @boolToInt(stmt.default_name != null)); + + try item_refs.ensureUnusedCapacity(count_excluding_namespace); + // Even though we allocate ahead of time here + // we cannot use putAssumeCapacity because a symbol can have existing links + // those may write to this hash table, so this estimate may be innaccurate + try p.is_import_item.ensureUnusedCapacity(p.allocator, count_excluding_namespace); + var remap_count: u32 = 0; + // Link the default item to the namespace + if (stmt.default_name) |*name_loc| { + outer: { + const name = p.loadNameFromRef(name_loc.ref.?); + const ref = try p.declareSymbol(.import, name_loc.loc, name); + name_loc.ref = ref; + try p.is_import_item.put(p.allocator, ref, .{}); + + if (macro_remap) |*remap| { + if (remap.get("default")) |remapped_path| { + const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path); + try p.macro.refs.put(ref, new_import_id); + + p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace; + p.import_records.items[new_import_id].is_unused = true; + if (comptime only_scan_imports_and_do_not_visit) { + p.import_records.items[new_import_id].is_internal = true; + p.import_records.items[new_import_id].path.is_disabled = true; + } + stmt.default_name = null; + remap_count += 1; + break :outer; + } + } + + if (comptime track_symbol_usage_during_parse_pass) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } + + if (is_macro) { + try p.macro.refs.put(ref, stmt.import_record_index); + stmt.default_name = null; + break :outer; + } + + if (comptime ParsePassSymbolUsageType != void) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } + + item_refs.putAssumeCapacity(name, name_loc.*); + } + } + var i: usize = 0; + var end: usize = 0; + + while (i < stmt.items.len) : (i += 1) { + var item: js_ast.ClauseItem = stmt.items[i]; + const name = p.loadNameFromRef(item.name.ref orelse unreachable); + const ref = try p.declareSymbol(.import, item.name.loc, name); + item.name.ref = ref; + + try p.is_import_item.put(p.allocator, ref, .{}); + p.checkForNonBMPCodePoint(item.alias_loc, item.alias); + + if (macro_remap) |*remap| { + if (remap.get(item.alias)) |remapped_path| { + const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path); + try p.macro.refs.put(ref, new_import_id); + + p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace; + p.import_records.items[new_import_id].is_unused = true; + if (comptime only_scan_imports_and_do_not_visit) { + p.import_records.items[new_import_id].is_internal = true; + p.import_records.items[new_import_id].path.is_disabled = true; + } + remap_count += 1; + continue; + } + } + + if (comptime track_symbol_usage_during_parse_pass) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } + + item_refs.putAssumeCapacity(item.alias, item.name); + stmt.items[end] = item; + end += 1; + } + stmt.items = stmt.items[0..end]; + + // If we remapped the entire import away + // i.e. import {graphql} "react-relay" + + if (remap_count > 0 and stmt.items.len == 0 and stmt.default_name == null) { + p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace; + p.import_records.items[stmt.import_record_index].is_unused = true; + + if (comptime only_scan_imports_and_do_not_visit) { + p.import_records.items[stmt.import_record_index].path.is_disabled = true; + p.import_records.items[stmt.import_record_index].is_internal = true; + } + + return p.s(S.Empty{}, loc); + } else if (remap_count > 0) { + item_refs.shrinkAndFree(stmt.items.len + @as(usize, @boolToInt(stmt.default_name != null))); + } + + // Track the items for this namespace + try p.import_items_for_namespace.put(p.allocator, stmt.namespace_ref, item_refs); + return p.s(stmt, loc); + } + + // This is the type parameter declarations that go with other symbol + // declarations (class, function, type, etc.) + fn skipTypeScriptTypeParameters(p: *P) anyerror!void { + p.markTypeScriptOnly(); + + if (p.lexer.token == .t_less_than) { + try p.lexer.next(); + + while (true) { + try p.lexer.expect(.t_identifier); + // "class Foo<T extends number> {}" + if (p.lexer.token == .t_extends) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + // "class Foo<T = void> {}" + if (p.lexer.token == .t_equals) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + if (p.lexer.token == .t_greater_than) { + break; + } + } + try p.lexer.expectGreaterThan(false); + } + } + + fn createDefaultName(p: *P, loc: logger.Loc) !js_ast.LocRef { + var identifier = try std.fmt.allocPrint(p.allocator, "{s}_default", .{try p.source.path.name.nonUniqueNameString(p.allocator)}); + + const name = js_ast.LocRef{ .loc = loc, .ref = try p.newSymbol(Symbol.Kind.other, identifier) }; + + var scope = p.current_scope; + + try scope.generated.append(p.allocator, name.ref.?); + + return name; + } + + pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !Ref { + const inner_index = Ref.toInt(p.symbols.items.len); + try p.symbols.append(Symbol{ + .kind = kind, + .original_name = identifier, + }); + + if (is_typescript_enabled) { + try p.ts_use_counts.append(p.allocator, 0); + } + + return Ref.init(inner_index, Ref.toInt(p.source.index), false); + } + + fn parseLabelName(p: *P) !?js_ast.LocRef { + if (p.lexer.token != .t_identifier or p.lexer.has_newline_before) { + return null; + } + + const name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(p.lexer.identifier) }; + try p.lexer.next(); + return name; + } + + fn parseClassStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) !Stmt { + var name: ?js_ast.LocRef = null; + var class_keyword = p.lexer.range(); + if (p.lexer.token == .t_class) { + //marksyntaxfeature + try p.lexer.next(); + } else { + try p.lexer.expected(.t_class); + } + + var is_identifier = p.lexer.token == .t_identifier; + + if (!opts.is_name_optional or (is_identifier and (!is_typescript_enabled or !strings.eqlComptime(p.lexer.identifier, "interface")))) { + var name_loc = p.lexer.loc(); + var name_text = p.lexer.identifier; + try p.lexer.expect(.t_identifier); + + // We must return here + // or the lexer will crash loop! + // example: + // export class {} + if (!is_identifier) { + return error.SyntaxError; + } + + if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) { + try p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here"); + } + + name = LocRef{ .loc = name_loc, .ref = null }; + if (!opts.is_typescript_declare) { + (name orelse unreachable).ref = p.declareSymbol(.class, name_loc, name_text) catch unreachable; + } + } + + // Even anonymous classes can have TypeScript type parameters + if (is_typescript_enabled) { + try p.skipTypeScriptTypeParameters(); + } + var class_opts = ParseClassOptions{ + .allow_ts_decorators = true, + .is_type_script_declare = opts.is_typescript_declare, + }; + if (opts.ts_decorators) |dec| { + class_opts.ts_decorators = dec.values; + } + + const scope_index = p.pushScopeForParsePass(.class_name, loc) catch unreachable; + const class = try p.parseClass(class_keyword, name, class_opts); + + if (comptime is_typescript_enabled) { + if (opts.is_typescript_declare) { + p.popAndDiscardScope(scope_index); + if (opts.is_namespace_scope and opts.is_export) { + p.has_non_local_export_declare_inside_namespace = true; + } + + return p.s(S.TypeScript{}, loc); + } + } + + p.popScope(); + return p.s(S.Class{ + .class = class, + .is_export = opts.is_export, + }, loc); + } + + // For HMR, we must convert syntax like this: + // export function leftPad() { + // export const guy = GUY_FIERI_ASCII_ART; + // export class Bacon {} + // export default GuyFieriAsciiArt; + // export {Bacon}; + // export {Bacon as default}; + // to: + // var __hmr__module = new __hmr_HMRModule(file_id, import.meta); + // (__hmr__module._load = function() { + // __hmr__module.exports.leftPad = function () {}; + // __hmr__module.exports.npmProgressBar33 = true; + // __hmr__module.exports.Bacon = class {}; + // })(); + // export { __hmr__module.exports.leftPad as leftPad, __hmr__module.exports.npmProgressBar33 as npmProgressBar33, __hmr__module } + // + // + // + // At bottom of the file: + // - + // var __hmr__exports = new HMRModule({ + // leftPad: () => leftPad, + // npmProgressBar33 () => npmProgressBar33, + // default: () => GuyFieriAsciiArt, + // [__hmr_ModuleIDSymbol]: + //}); + // export { __hmr__exports.leftPad as leftPad, __hmr__ } + // - + // Then: + // if () { + // + // } + + // pub fn maybeRewriteExportSymbol(p: *P, ) + + fn defaultNameForExpr(p: *P, expr: Expr, loc: logger.Loc) LocRef { + switch (expr.data) { + .e_function => |func_container| { + if (func_container.func.name) |_name| { + if (_name.ref) |ref| { + return LocRef{ .loc = loc, .ref = ref }; + } + } + }, + .e_identifier => |ident| { + return LocRef{ .loc = loc, .ref = ident.ref }; + }, + .e_import_identifier => |ident| { + if (!allow_macros or (allow_macros and !p.macro.refs.contains(ident.ref))) { + return LocRef{ .loc = loc, .ref = ident.ref }; + } + }, + .e_class => |class| { + if (class.class_name) |_name| { + if (_name.ref) |ref| { + return LocRef{ .loc = loc, .ref = ref }; + } + } + }, + else => {}, + } + + return createDefaultName(p, loc) catch unreachable; + } + + fn parseStmt(p: *P, opts: *ParseStatementOptions) anyerror!Stmt { + var loc = p.lexer.loc(); + + switch (p.lexer.token) { + .t_semicolon => { + try p.lexer.next(); + return Stmt.empty(); + }, + + .t_export => { + var previousExportKeyword = p.es6_export_keyword; + if (opts.is_module_scope) { + p.es6_export_keyword = p.lexer.range(); + } else if (!opts.is_namespace_scope) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + try p.lexer.next(); + + // TypeScript decorators only work on class declarations + // "@decorator export class Foo {}" + // "@decorator export abstract class Foo {}" + // "@decorator export default class Foo {}" + // "@decorator export default abstract class Foo {}" + // "@decorator export declare class Foo {}" + // "@decorator export declare abstract class Foo {}" + if (opts.ts_decorators != null and p.lexer.token != js_lexer.T.t_class and + p.lexer.token != js_lexer.T.t_default and + !p.lexer.isContextualKeyword("abstract") and + !p.lexer.isContextualKeyword("declare")) + { + try p.lexer.expected(js_lexer.T.t_class); + } + + switch (p.lexer.token) { + T.t_class, T.t_const, T.t_function, T.t_var => { + opts.is_export = true; + return p.parseStmt(opts); + }, + + T.t_import => { + // "export import foo = bar" + if (is_typescript_enabled and (opts.is_module_scope or opts.is_namespace_scope)) { + opts.is_export = true; + return p.parseStmt(opts); + } + + try p.lexer.unexpected(); + return error.SyntaxError; + }, + + T.t_enum => { + if (!is_typescript_enabled) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + opts.is_export = true; + return p.parseStmt(opts); + }, + + T.t_identifier => { + if (p.lexer.isContextualKeyword("let")) { + opts.is_export = true; + return p.parseStmt(opts); + } + + if (comptime is_typescript_enabled) { + if (opts.is_typescript_declare and p.lexer.isContextualKeyword("as")) { + // "export as namespace ns;" + try p.lexer.next(); + try p.lexer.expectContextualKeyword("namespace"); + try p.lexer.expect(T.t_identifier); + try p.lexer.expectOrInsertSemicolon(); + + return p.s(S.TypeScript{}, loc); + } + } + + if (p.lexer.isContextualKeyword("async")) { + var asyncRange = p.lexer.range(); + try p.lexer.next(); + if (p.lexer.has_newline_before) { + try p.log.addRangeError(p.source, asyncRange, "Unexpected newline after \"async\""); + } + + try p.lexer.expect(T.t_function); + opts.is_export = true; + return try p.parseFnStmt(loc, opts, asyncRange); + } + + if (is_typescript_enabled) { + if (TypeScript.Identifier.forStr(p.lexer.identifier)) |ident| { + switch (ident) { + .s_type => { + // "export type foo = ..." + const type_range = p.lexer.range(); + try p.lexer.next(); + if (p.lexer.has_newline_before) { + try p.log.addErrorFmt(p.source, type_range.end(), p.allocator, "Unexpected newline after \"type\"", .{}); + return error.SynaxError; + } + var skipper = ParseStatementOptions{ .is_module_scope = opts.is_module_scope, .is_export = true }; + try p.skipTypeScriptTypeStmt(&skipper); + return p.s(S.TypeScript{}, loc); + }, + .s_namespace, .s_abstract, .s_module, .s_interface => { + // "export namespace Foo {}" + // "export abstract class Foo {}" + // "export module Foo {}" + // "export interface Foo {}" + opts.is_export = true; + return try p.parseStmt(opts); + }, + .s_declare => { + // "export declare class Foo {}" + opts.is_export = true; + opts.lexical_decl = .allow_all; + opts.is_typescript_declare = true; + return try p.parseStmt(opts); + }, + } + } + } + + try p.lexer.unexpected(); + return error.SyntaxError; + }, + + T.t_default => { + if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + var defaultLoc = p.lexer.loc(); + try p.lexer.next(); + + // TypeScript decorators only work on class declarations + // "@decorator export default class Foo {}" + // "@decorator export default abstract class Foo {}" + if (opts.ts_decorators != null and p.lexer.token != T.t_class and !p.lexer.isContextualKeyword("abstract")) { + try p.lexer.expected(T.t_class); + } + + if (p.lexer.isContextualKeyword("async")) { + var async_range = p.lexer.range(); + try p.lexer.next(); + var defaultName: js_ast.LocRef = undefined; + if (p.lexer.token == T.t_function and !p.lexer.has_newline_before) { + try p.lexer.next(); + var stmtOpts = ParseStatementOptions{ + .is_name_optional = true, + .lexical_decl = .allow_all, + }; + var stmt = try p.parseFnStmt(loc, &stmtOpts, async_range); + if (@as(Stmt.Tag, stmt.data) == .s_type_script) { + // This was just a type annotation + return stmt; + } + + if (stmt.data.s_function.func.name) |name| { + defaultName = js_ast.LocRef{ .loc = name.loc, .ref = name.ref }; + } else { + defaultName = try p.createDefaultName(defaultLoc); + } + var value = js_ast.StmtOrExpr{ .stmt = stmt }; + return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); + } + + defaultName = try createDefaultName(p, loc); + + const prefix_expr = try p.parseAsyncPrefixExpr(async_range, Level.comma); + var expr = try p.parseSuffix(prefix_expr, Level.comma, null, Expr.EFlags.none); + try p.lexer.expectOrInsertSemicolon(); + var value = js_ast.StmtOrExpr{ .expr = expr }; + p.has_export_default = true; + return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); + } + + if (p.lexer.token == .t_function or p.lexer.token == .t_class or p.lexer.isContextualKeyword("interface")) { + var _opts = ParseStatementOptions{ + .ts_decorators = opts.ts_decorators, + .is_name_optional = true, + .lexical_decl = .allow_all, + }; + var stmt = try p.parseStmt(&_opts); + + const default_name: js_ast.LocRef = default_name_getter: { + switch (stmt.data) { + // This was just a type annotation + .s_type_script => { + return stmt; + }, + + .s_function => |func_container| { + if (func_container.func.name) |name| { + break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; + } else {} + }, + .s_class => |class| { + if (class.class.class_name) |name| { + break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; + } else {} + }, + else => {}, + } + + break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; + }; + p.has_export_default = true; + return p.s( + S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, + loc, + ); + } + + const is_identifier = p.lexer.token == .t_identifier; + const name = p.lexer.identifier; + var expr = try p.parseExpr(.comma); + + // Handle the default export of an abstract class in TypeScript + if (is_typescript_enabled and is_identifier and (p.lexer.token == .t_class or opts.ts_decorators != null) and strings.eqlComptime(name, "abstract")) { + switch (expr.data) { + .e_identifier => { + var stmtOpts = ParseStatementOptions{ + .ts_decorators = opts.ts_decorators, + .is_name_optional = true, + }; + const stmt: Stmt = try p.parseClassStmt(loc, &stmtOpts); + + // Use the statement name if present, since it's a better name + const default_name: js_ast.LocRef = default_name_getter: { + switch (stmt.data) { + // This was just a type annotation + .s_type_script => { + return stmt; + }, + + .s_function => |func_container| { + if (func_container.func.name) |_name| { + break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; + } else {} + }, + .s_class => |class| { + if (class.class.class_name) |_name| { + break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; + } else {} + }, + else => {}, + } + + break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; + }; + p.has_export_default = true; + return p.s(S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, loc); + }, + else => { + p.panic("internal error: unexpected", .{}); + }, + } + } + + try p.lexer.expectOrInsertSemicolon(); + + // Use the expression name if present, since it's a better name + p.has_export_default = true; + return p.s( + S.ExportDefault{ + .default_name = p.defaultNameForExpr(expr, defaultLoc), + .value = js_ast.StmtOrExpr{ + .expr = expr, + }, + }, + loc, + ); + }, + T.t_asterisk => { + if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + var namespace_ref: Ref = Ref.None; + var alias: ?js_ast.G.ExportStarAlias = null; + var path: ParsedPath = undefined; + + if (p.lexer.isContextualKeyword("as")) { + // "export * as ns from 'path'" + try p.lexer.next(); + const name = try p.parseClauseAlias("export"); + namespace_ref = try p.storeNameInRef(name); + alias = G.ExportStarAlias{ .loc = p.lexer.loc(), .original_name = name }; + try p.lexer.next(); + try p.lexer.expectContextualKeyword("from"); + path = try p.parsePath(); + } else { + // "export * from 'path'" + try p.lexer.expectContextualKeyword("from"); + path = try p.parsePath(); + const name = try fs.PathName.init(path.text).nonUniqueNameString(p.allocator); + namespace_ref = try p.storeNameInRef(name); + } + + var import_record_index = p.addImportRecord( + ImportKind.stmt, + path.loc, + path.text, + // TODO: import assertions + // path.assertions + ); + + if (comptime track_symbol_usage_during_parse_pass) { + // In the scan pass, we need _some_ way of knowing *not* to mark as unused + p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + } + + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.ExportStar{ + .namespace_ref = namespace_ref, + .alias = alias, + .import_record_index = import_record_index, + }, loc); + }, + T.t_open_brace => { + if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + const export_clause = try p.parseExportClause(); + if (p.lexer.isContextualKeyword("from")) { + try p.lexer.expectContextualKeyword("from"); + const parsedPath = try p.parsePath(); + + try p.lexer.expectOrInsertSemicolon(); + + if (comptime is_typescript_enabled) { + // export {type Foo} from 'bar'; + // -> + // nothing + // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA + if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { + return p.s(S.TypeScript{}, loc); + } + } + + const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text); + var path_name = fs.PathName.init(strings.append(p.allocator, "import_", parsedPath.text) catch unreachable); + const namespace_ref = p.storeNameInRef(path_name.nonUniqueNameString(p.allocator) catch unreachable) catch unreachable; + + if (comptime track_symbol_usage_during_parse_pass) { + // In the scan pass, we need _some_ way of knowing *not* to mark as unused + p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; + } + + return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc); + } + try p.lexer.expectOrInsertSemicolon(); + + if (comptime is_typescript_enabled) { + // export {type Foo}; + // -> + // nothing + // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA + if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { + return p.s(S.TypeScript{}, loc); + } + } + + return p.s(S.ExportClause{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line }, loc); + }, + T.t_equals => { + // "export = value;" + + p.es6_export_keyword = previousExportKeyword; // This wasn't an ESM export statement after all + if (is_typescript_enabled) { + try p.lexer.next(); + var value = try p.parseExpr(.lowest); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.ExportEquals{ .value = value }, loc); + } + try p.lexer.unexpected(); + return error.SyntaxError; + }, + else => { + try p.lexer.unexpected(); + return error.SyntaxError; + }, + } + }, + + .t_function => { + try p.lexer.next(); + return try p.parseFnStmt(loc, opts, null); + }, + .t_enum => { + if (!is_typescript_enabled) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + return p.parseTypescriptEnumStmt(loc, opts); + }, + .t_at => { + // Parse decorators before class statements, which are potentially exported + if (is_typescript_enabled) { + const scope_index = p.scopes_in_order.items.len; + const ts_decorators = try p.parseTypeScriptDecorators(); + + // If this turns out to be a "declare class" statement, we need to undo the + // scopes that were potentially pushed while parsing the decorator arguments. + // That can look like any one of the following: + // + // "@decorator declare class Foo {}" + // "@decorator declare abstract class Foo {}" + // "@decorator export declare class Foo {}" + // "@decorator export declare abstract class Foo {}" + // + opts.ts_decorators = DeferredTsDecorators{ + .values = ts_decorators, + .scope_index = scope_index, + }; + + // "@decorator class Foo {}" + // "@decorator abstract class Foo {}" + // "@decorator declare class Foo {}" + // "@decorator declare abstract class Foo {}" + // "@decorator export class Foo {}" + // "@decorator export abstract class Foo {}" + // "@decorator export declare class Foo {}" + // "@decorator export declare abstract class Foo {}" + // "@decorator export default class Foo {}" + // "@decorator export default abstract class Foo {}" + if (p.lexer.token != .t_class and p.lexer.token != .t_export and !p.lexer.isContextualKeyword("abstract") and !p.lexer.isContextualKeyword("declare")) { + try p.lexer.expected(.t_class); + } + + return p.parseStmt(opts); + } + // notimpl(); + + try p.lexer.unexpected(); + return error.SyntaxError; + }, + .t_class => { + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(loc); + } + + return try p.parseClassStmt(loc, opts); + }, + .t_var => { + try p.lexer.next(); + const decls = try p.parseAndDeclareDecls(.hoisted, opts); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.Local{ .kind = .k_var, .decls = decls, .is_export = opts.is_export }, loc); + }, + .t_const => { + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(loc); + } + // p.markSyntaxFeature(compat.Const, p.lexer.Range()) + + try p.lexer.next(); + + if (is_typescript_enabled and p.lexer.token == T.t_enum) { + return p.parseTypescriptEnumStmt(loc, opts); + } + + const decls = try p.parseAndDeclareDecls(.cconst, opts); + try p.lexer.expectOrInsertSemicolon(); + + if (!opts.is_typescript_declare) { + try p.requireInitializers(decls); + } + + // When HMR is enabled, replace all const/let exports with var + const kind = if (p.options.features.hot_module_reloading and opts.is_export) S.Local.Kind.k_var else S.Local.Kind.k_const; + return p.s(S.Local{ .kind = kind, .decls = decls, .is_export = opts.is_export }, loc); + }, + .t_if => { + try p.lexer.next(); + try p.lexer.expect(.t_open_paren); + const test_ = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{ + .lexical_decl = .allow_fn_inside_if, + }; + const yes = try p.parseStmt(&stmtOpts); + var no: ?Stmt = null; + if (p.lexer.token == .t_else) { + try p.lexer.next(); + stmtOpts = ParseStatementOptions{ + .lexical_decl = .allow_fn_inside_if, + }; + no = try p.parseStmt(&stmtOpts); + } + + return p.s(S.If{ + .test_ = test_, + .yes = yes, + .no = no, + }, loc); + }, + .t_do => { + try p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + try p.lexer.expect(.t_while); + try p.lexer.expect(.t_open_paren); + const test_ = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + + // This is a weird corner case where automatic semicolon insertion applies + // even without a newline present + if (p.lexer.token == .t_semicolon) { + try p.lexer.next(); + } + return p.s(S.DoWhile{ .body = body, .test_ = test_ }, loc); + }, + .t_while => { + try p.lexer.next(); + + try p.lexer.expect(.t_open_paren); + const test_ = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + + return p.s(S.While{ + .body = body, + .test_ = test_, + }, loc); + }, + .t_with => { + try p.lexer.next(); + try p.lexer.expect(.t_open_paren); + const test_ = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + + const body_loc = p.lexer.loc(); + _ = try p.pushScopeForParsePass(.block, body_loc); + defer p.popScope(); + + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + + return p.s(S.With{ .body = body, .body_loc = body_loc, .value = test_ }, loc); + }, + .t_switch => { + try p.lexer.next(); + + try p.lexer.expect(.t_open_paren); + const test_ = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + + const body_loc = p.lexer.loc(); + _ = try p.pushScopeForParsePass(.block, body_loc); + defer p.popScope(); + + try p.lexer.expect(.t_open_brace); + var cases = ListManaged(js_ast.Case).init(p.allocator); + var foundDefault = false; + var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; + var value: ?js_ast.Expr = null; + while (p.lexer.token != .t_close_brace) { + var body = StmtList.init(p.allocator); + value = null; + if (p.lexer.token == .t_default) { + if (foundDefault) { + try p.log.addRangeError(p.source, p.lexer.range(), "Multiple default clauses are not allowed"); + return error.SyntaxError; + } + + foundDefault = true; + try p.lexer.next(); + try p.lexer.expect(.t_colon); + } else { + try p.lexer.expect(.t_case); + value = try p.parseExpr(.lowest); + try p.lexer.expect(.t_colon); + } + + caseBody: while (true) { + switch (p.lexer.token) { + .t_close_brace, .t_case, .t_default => { + break :caseBody; + }, + else => { + stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; + try body.append(try p.parseStmt(&stmtOpts)); + }, + } + } + try cases.append(js_ast.Case{ .value = value, .body = body.items, .loc = logger.Loc.Empty }); + } + try p.lexer.expect(.t_close_brace); + return p.s(S.Switch{ .test_ = test_, .body_loc = body_loc, .cases = cases.items }, loc); + }, + .t_try => { + try p.lexer.next(); + const body_loc = p.lexer.loc(); + try p.lexer.expect(.t_open_brace); + _ = try p.pushScopeForParsePass(.block, loc); + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); + p.popScope(); + try p.lexer.next(); + + var catch_: ?js_ast.Catch = null; + var finally: ?js_ast.Finally = null; + + if (p.lexer.token == .t_catch) { + const catch_loc = p.lexer.loc(); + _ = try p.pushScopeForParsePass(.block, catch_loc); + try p.lexer.next(); + var binding: ?js_ast.Binding = null; + + // The catch binding is optional, and can be omitted + // jarred: TIL! + if (p.lexer.token != .t_open_brace) { + try p.lexer.expect(.t_open_paren); + var value = try p.parseBinding(); + + // Skip over types + if (is_typescript_enabled and p.lexer.token == .t_colon) { + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + } + + try p.lexer.expect(.t_close_paren); + + // Bare identifiers are a special case + var kind = Symbol.Kind.other; + switch (value.data) { + .b_identifier => { + kind = .catch_identifier; + }, + else => {}, + } + stmtOpts = ParseStatementOptions{}; + try p.declareBinding(kind, &value, &stmtOpts); + binding = value; + } + + try p.lexer.expect(.t_open_brace); + stmtOpts = ParseStatementOptions{}; + const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); + try p.lexer.next(); + catch_ = js_ast.Catch{ + .loc = catch_loc, + .binding = binding, + .body = stmts, + }; + p.popScope(); + } + + if (p.lexer.token == .t_finally or catch_ == null) { + const finally_loc = p.lexer.loc(); + _ = try p.pushScopeForParsePass(.block, finally_loc); + try p.lexer.expect(.t_finally); + try p.lexer.expect(.t_open_brace); + stmtOpts = ParseStatementOptions{}; + const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); + try p.lexer.next(); + finally = js_ast.Finally{ .loc = finally_loc, .stmts = stmts }; + p.popScope(); + } + + return p.s( + S.Try{ .body_loc = body_loc, .body = body, .catch_ = catch_, .finally = finally }, + loc, + ); + }, + .t_for => { + _ = try p.pushScopeForParsePass(.block, loc); + defer p.popScope(); + + try p.lexer.next(); + + // "for await (let x of y) {}" + var isForAwait = p.lexer.isContextualKeyword("await"); + if (isForAwait) { + const await_range = p.lexer.range(); + if (p.fn_or_arrow_data_parse.allow_await != .allow_expr) { + try p.log.addRangeError(p.source, await_range, "Cannot use \"await\" outside an async function"); + isForAwait = false; + } else { + // TODO: improve error handling here + // didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange) + if (p.fn_or_arrow_data_parse.is_top_level) { + p.top_level_await_keyword = await_range; + // p.markSyntaxFeature(compat.TopLevelAwait, awaitRange) + } + } + try p.lexer.next(); + } + + try p.lexer.expect(.t_open_paren); + + var init_: ?Stmt = null; + var test_: ?Expr = null; + var update: ?Expr = null; + + // "in" expressions aren't allowed here + p.allow_in = false; + + var bad_let_range: ?logger.Range = null; + if (p.lexer.isContextualKeyword("let")) { + bad_let_range = p.lexer.range(); + } + + var decls: []G.Decl = &([_]G.Decl{}); + var init_loc = p.lexer.loc(); + var is_var = false; + switch (p.lexer.token) { + // for (var ) + .t_var => { + is_var = true; + try p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + decls = try p.parseAndDeclareDecls(.hoisted, &stmtOpts); + init_ = p.s(S.Local{ .kind = .k_var, .decls = decls }, init_loc); + }, + // for (const ) + .t_const => { + try p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + decls = try p.parseAndDeclareDecls(.cconst, &stmtOpts); + init_ = p.s(S.Local{ .kind = .k_const, .decls = decls }, init_loc); + }, + // for (;) + .t_semicolon => {}, + else => { + var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; + + const res = try p.parseExprOrLetStmt(&stmtOpts); + switch (res.stmt_or_expr) { + .stmt => |stmt| { + bad_let_range = null; + init_ = stmt; + }, + .expr => |expr| { + init_ = p.s(S.SExpr{ + .value = expr, + }, init_loc); + }, + } + }, + } + + // "in" expressions are allowed again + p.allow_in = true; + + // Detect for-of loops + if (p.lexer.isContextualKeyword("of") or isForAwait) { + if (bad_let_range) |r| { + try p.log.addRangeError(p.source, r, "\"let\" must be wrapped in parentheses to be used as an expression here"); + return error.SyntaxError; + } + + if (isForAwait and !p.lexer.isContextualKeyword("of")) { + if (init_ != null) { + try p.lexer.expectedString("\"of\""); + } else { + try p.lexer.unexpected(); + return error.SyntaxError; + } + } + + try p.forbidInitializers(decls, "of", false); + try p.lexer.next(); + const value = try p.parseExpr(.comma); + try p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + return p.s(S.ForOf{ .is_await = isForAwait, .init = init_ orelse unreachable, .value = value, .body = body }, loc); + } + + // Detect for-in loops + if (p.lexer.token == .t_in) { + try p.forbidInitializers(decls, "in", is_var); + try p.lexer.next(); + const value = try p.parseExpr(.lowest); + try p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + return p.s(S.ForIn{ .init = init_ orelse unreachable, .value = value, .body = body }, loc); + } + + // Only require "const" statement initializers when we know we're a normal for loop + if (init_) |init_stmt| { + switch (init_stmt.data) { + .s_local => { + if (init_stmt.data.s_local.kind == .k_const) { + try p.requireInitializers(decls); + } + }, + else => {}, + } + } + + try p.lexer.expect(.t_semicolon); + if (p.lexer.token != .t_semicolon) { + test_ = try p.parseExpr(.lowest); + } + + try p.lexer.expect(.t_semicolon); + + if (p.lexer.token != .t_close_paren) { + update = try p.parseExpr(.lowest); + } + + try p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{}; + const body = try p.parseStmt(&stmtOpts); + return p.s( + S.For{ .init = init_, .test_ = test_, .update = update, .body = body }, + loc, + ); + }, + .t_import => { + const previous_import_keyword = p.es6_import_keyword; + p.es6_import_keyword = p.lexer.range(); + try p.lexer.next(); + var stmt: S.Import = S.Import{ + .namespace_ref = Ref.None, + .import_record_index = std.math.maxInt(u32), + }; + var was_originally_bare_import = false; + + // "export import foo = bar" + if ((opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) and p.lexer.token != .t_identifier) { + try p.lexer.expected(.t_identifier); + } + + switch (p.lexer.token) { + // "import('path')" + // "import.meta" + .t_open_paren, .t_dot => { + p.es6_import_keyword = previous_import_keyword; // this wasn't an esm import statement after all + const expr = try p.parseSuffix(try p.parseImportExpr(loc, .lowest), .lowest, null, Expr.EFlags.none); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.SExpr{ + .value = expr, + }, loc); + }, + .t_string_literal, .t_no_substitution_template_literal => { + // "import 'path'" + if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + was_originally_bare_import = true; + }, + .t_asterisk => { + // "import * as ns from 'path'" + if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + try p.lexer.expectContextualKeyword("as"); + stmt = S.Import{ + .namespace_ref = try p.storeNameInRef(p.lexer.identifier), + .star_name_loc = p.lexer.loc(), + .import_record_index = std.math.maxInt(u32), + }; + try p.lexer.expect(.t_identifier); + try p.lexer.expectContextualKeyword("from"); + }, + .t_open_brace => { + // "import {item1, item2} from 'path'" + if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + var importClause = try p.parseImportClause(); + if (comptime is_typescript_enabled) { + if (importClause.had_type_only_imports and importClause.items.len == 0) { + try p.lexer.expectContextualKeyword("from"); + _ = try p.parsePath(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.TypeScript{}, loc); + } + } + + stmt = S.Import{ + .namespace_ref = Ref.None, + .import_record_index = std.math.maxInt(u32), + .items = importClause.items, + .is_single_line = importClause.is_single_line, + }; + try p.lexer.expectContextualKeyword("from"); + }, + .t_identifier => { + // "import defaultItem from 'path'" + // "import foo = bar" + if (!opts.is_module_scope and (!opts.is_namespace_scope)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + var default_name = p.lexer.identifier; + stmt = S.Import{ .namespace_ref = Ref.None, .import_record_index = std.math.maxInt(u32), .default_name = LocRef{ + .loc = p.lexer.loc(), + .ref = try p.storeNameInRef(default_name), + } }; + try p.lexer.next(); + + if (comptime is_typescript_enabled) { + // Skip over type-only imports + if (strings.eqlComptime(default_name, "type")) { + switch (p.lexer.token) { + .t_identifier => { + if (!strings.eqlComptime(p.lexer.identifier, "from")) { + default_name = p.lexer.identifier; + stmt.default_name.?.loc = p.lexer.loc(); + try p.lexer.next(); + + if (p.lexer.token == .t_equals) { + // "import type foo = require('bar');" + // "import type foo = bar.baz;" + opts.is_typescript_declare = true; + return try p.parseTypeScriptImportEqualsStmt(loc, opts, stmt.default_name.?.loc, default_name); + } else { + // "import type foo from 'bar';" + try p.lexer.expectContextualKeyword("from"); + _ = try p.parsePath(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.TypeScript{}, loc); + } + } + }, + .t_asterisk => { + // "import type * as foo from 'bar';" + try p.lexer.next(); + try p.lexer.expectContextualKeyword("as"); + try p.lexer.expect(.t_identifier); + try p.lexer.expectContextualKeyword("from"); + _ = try p.parsePath(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.TypeScript{}, loc); + }, + + .t_open_brace => { + // "import type {foo} from 'bar';" + _ = try p.parseImportClause(); + try p.lexer.expectContextualKeyword("from"); + _ = try p.parsePath(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.TypeScript{}, loc); + }, + else => {}, + } + } + + // Parse TypeScript import assignment statements + if (p.lexer.token == .t_equals or opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) { + p.es6_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all; + return p.parseTypeScriptImportEqualsStmt(loc, opts, logger.Loc.Empty, default_name); + } + } + + if (p.lexer.token == .t_comma) { + try p.lexer.next(); + + switch (p.lexer.token) { + // "import defaultItem, * as ns from 'path'" + .t_asterisk => { + try p.lexer.next(); + try p.lexer.expectContextualKeyword("as"); + stmt.namespace_ref = try p.storeNameInRef(p.lexer.identifier); + stmt.star_name_loc = p.lexer.loc(); + try p.lexer.expect(.t_identifier); + }, + // "import defaultItem, {item1, item2} from 'path'" + .t_open_brace => { + const importClause = try p.parseImportClause(); + + stmt.items = importClause.items; + stmt.is_single_line = importClause.is_single_line; + }, + else => { + try p.lexer.unexpected(); + return error.SyntaxError; + }, + } + } + + try p.lexer.expectContextualKeyword("from"); + }, + else => { + try p.lexer.unexpected(); + return error.SyntaxError; + }, + } + + const path = try p.parsePath(); + try p.lexer.expectOrInsertSemicolon(); + + return try p.processImportStatement(stmt, path, loc, was_originally_bare_import); + }, + .t_break => { + try p.lexer.next(); + const name = try p.parseLabelName(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.Break{ .label = name }, loc); + }, + .t_continue => { + try p.lexer.next(); + const name = try p.parseLabelName(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.Continue{ .label = name }, loc); + }, + .t_return => { + if (p.fn_or_arrow_data_parse.is_return_disallowed) { + try p.log.addRangeError(p.source, p.lexer.range(), "A return statement cannot be used here"); + } + try p.lexer.next(); + var value: ?Expr = null; + if ((p.lexer.token != .t_semicolon and + !p.lexer.has_newline_before and + p.lexer.token != .t_close_brace and + p.lexer.token != .t_end_of_file)) + { + value = try p.parseExpr(.lowest); + } + p.latest_return_had_semicolon = p.lexer.token == .t_semicolon; + try p.lexer.expectOrInsertSemicolon(); + + return p.s(S.Return{ .value = value }, loc); + }, + .t_throw => { + try p.lexer.next(); + if (p.lexer.has_newline_before) { + try p.log.addError(p.source, logger.Loc{ + .start = loc.start + 5, + }, "Unexpected newline after \"throw\""); + return error.SyntaxError; + } + const expr = try p.parseExpr(.lowest); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.Throw{ .value = expr }, loc); + }, + .t_debugger => { + try p.lexer.next(); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.Debugger{}, loc); + }, + .t_open_brace => { + _ = try p.pushScopeForParsePass(.block, loc); + defer p.popScope(); + try p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); + try p.lexer.next(); + return p.s(S.Block{ + .stmts = stmts, + }, loc); + }, + + else => { + const is_identifier = p.lexer.token == .t_identifier; + const name = p.lexer.identifier; + var emiss = E.Missing{}; + // Parse either an async function, an async expression, or a normal expression + var expr: Expr = Expr{ .loc = loc, .data = Expr.Data{ .e_missing = emiss } }; + if (is_identifier and strings.eqlComptime(p.lexer.raw(), "async")) { + var async_range = p.lexer.range(); + try p.lexer.next(); + if (p.lexer.token == .t_function and !p.lexer.has_newline_before) { + try p.lexer.next(); + + return try p.parseFnStmt(async_range.loc, opts, async_range); + } + + expr = try p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, .lowest), .lowest, null, Expr.EFlags.none); + } else { + const exprOrLet = try p.parseExprOrLetStmt(opts); + switch (exprOrLet.stmt_or_expr) { + .stmt => |stmt| { + try p.lexer.expectOrInsertSemicolon(); + return stmt; + }, + .expr => |_expr| { + expr = _expr; + }, + } + } + if (is_identifier) { + switch (expr.data) { + .e_identifier => |ident| { + if (p.lexer.token == .t_colon and !opts.hasDecorators()) { + _ = try p.pushScopeForParsePass(.label, loc); + defer p.popScope(); + + // Parse a labeled statement + try p.lexer.next(); + + const _name = LocRef{ .loc = expr.loc, .ref = ident.ref }; + var nestedOpts = ParseStatementOptions{}; + + switch (opts.lexical_decl) { + .allow_all, .allow_fn_inside_label => { + nestedOpts.lexical_decl = .allow_fn_inside_label; + }, + else => {}, + } + var stmt = try p.parseStmt(&nestedOpts); + return p.s(S.Label{ .name = _name, .stmt = stmt }, loc); + } + }, + else => {}, + } + + if (is_typescript_enabled) { + if (js_lexer.TypescriptStmtKeyword.List.get(name)) |ts_stmt| { + switch (ts_stmt) { + .ts_stmt_type => { + if (p.lexer.token == .t_identifier and !p.lexer.has_newline_before) { + // "type Foo = any" + var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; + try p.skipTypeScriptTypeStmt(&stmtOpts); + return p.s(S.TypeScript{}, loc); + } + }, + .ts_stmt_namespace, .ts_stmt_module => { + // "namespace Foo {}" + // "module Foo {}" + // "declare module 'fs' {}" + // "declare module 'fs';" + if (((opts.is_module_scope or opts.is_namespace_scope) and (p.lexer.token == .t_identifier or + (p.lexer.token == .t_string_literal and opts.is_typescript_declare)))) + { + return p.parseTypeScriptNamespaceStmt(loc, opts); + } + }, + .ts_stmt_interface => { + // "interface Foo {}" + var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; + + try p.skipTypeScriptInterfaceStmt(&stmtOpts); + return p.s(S.TypeScript{}, loc); + }, + .ts_stmt_abstract => { + if (p.lexer.token == .t_class or opts.ts_decorators != null) { + return try p.parseClassStmt(loc, opts); + } + }, + .ts_stmt_global => { + // "declare module 'fs' { global { namespace NodeJS {} } }" + if (opts.is_namespace_scope and opts.is_typescript_declare and p.lexer.token == .t_open_brace) { + try p.lexer.next(); + _ = try p.parseStmtsUpTo(.t_close_brace, opts); + try p.lexer.next(); + return p.s(S.TypeScript{}, loc); + } + }, + .ts_stmt_declare => { + opts.lexical_decl = .allow_all; + opts.is_typescript_declare = true; + + // "@decorator declare class Foo {}" + // "@decorator declare abstract class Foo {}" + if (opts.ts_decorators != null and p.lexer.token != .t_class and !p.lexer.isContextualKeyword("abstract")) { + try p.lexer.expected(.t_class); + } + + // "declare global { ... }" + if (p.lexer.isContextualKeyword("global")) { + try p.lexer.next(); + try p.lexer.expect(.t_open_brace); + _ = try p.parseStmtsUpTo(.t_close_brace, opts); + try p.lexer.next(); + return p.s(S.TypeScript{}, loc); + } + + // "declare const x: any" + const stmt = try p.parseStmt(opts); + if (opts.ts_decorators) |decs| { + p.discardScopesUpTo(decs.scope_index); + } + + // Unlike almost all uses of "declare", statements that use + // "export declare" with "var/let/const" inside a namespace affect + // code generation. They cause any declared bindings to be + // considered exports of the namespace. Identifier references to + // those names must be converted into property accesses off the + // namespace object: + // + // namespace ns { + // export declare const x + // export function y() { return x } + // } + // + // (ns as any).x = 1 + // console.log(ns.y()) + // + // In this example, "return x" must be replaced with "return ns.x". + // This is handled by replacing each "export declare" statement + // inside a namespace with an "export var" statement containing all + // of the declared bindings. That "export var" statement will later + // cause identifiers to be transformed into property accesses. + if (opts.is_namespace_scope and opts.is_export) { + var decls: []G.Decl = &([_]G.Decl{}); + switch (stmt.data) { + .s_local => |local| { + var _decls = try ListManaged(G.Decl).initCapacity(p.allocator, local.decls.len); + for (local.decls) |decl| { + try extractDeclsForBinding(decl.binding, &_decls); + } + decls = _decls.items; + }, + else => {}, + } + + if (decls.len > 0) { + return p.s(S.Local{ + .kind = .k_var, + .is_export = true, + .decls = decls, + }, loc); + } + } + + return p.s(S.TypeScript{}, loc); + }, + } + } + } + } + // Output.print("\n\nmVALUE {s}:{s}\n", .{ expr, name }); + try p.lexer.expectOrInsertSemicolon(); + return p.s(S.SExpr{ .value = expr }, loc); + }, + } + + return js_ast.Stmt.empty(); + } + + fn discardScopesUpTo(p: *P, scope_index: usize) void { + // Remove any direct children from their parent + var scope = p.current_scope; + var children = scope.children; + + for (p.scopes_in_order.items[scope_index..]) |_child| { + const child = _child orelse continue; + + if (child.scope.parent == p.current_scope) { + var i: usize = children.items.len - 1; + while (i >= 0) { + if (children.items[i] == child.scope) { + _ = children.orderedRemove(i); + break; + } + i -= 1; + } + } + } + + // Truncate the scope order where we started to pretend we never saw this scope + p.scopes_in_order.shrinkRetainingCapacity(scope_index); + } + + fn skipTypeScriptTypeStmt(p: *P, opts: *ParseStatementOptions) anyerror!void { + if (opts.is_export and p.lexer.token == .t_open_brace) { + // "export type {foo}" + // "export type {foo} from 'bar'" + _ = try p.parseExportClause(); + if (p.lexer.isContextualKeyword("from")) { + try p.lexer.next(); + _ = try p.parsePath(); + } + try p.lexer.expectOrInsertSemicolon(); + return; + } + + const name = p.lexer.identifier; + try p.lexer.expect(.t_identifier); + + if (opts.is_module_scope) { + p.local_type_names.put(p.allocator, name, true) catch unreachable; + } + + try p.skipTypeScriptTypeParameters(); + try p.lexer.expect(.t_equals); + try p.skipTypeScriptType(.lowest); + try p.lexer.expectOrInsertSemicolon(); + } + + fn parseTypeScriptNamespaceStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt { + // "namespace foo {}"; + const name_loc = p.lexer.loc(); + const name_text = p.lexer.identifier; + try p.lexer.next(); + + var name = LocRef{ .loc = name_loc, .ref = null }; + const scope_index = try p.pushScopeForParsePass(.entry, loc); + + const old_has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace; + p.has_non_local_export_declare_inside_namespace = false; + + var stmts: ListManaged(Stmt) = ListManaged(Stmt).init(p.allocator); + + if (p.lexer.token == .t_dot) { + const dot_loc = p.lexer.loc(); + try p.lexer.next(); + + var _opts = ParseStatementOptions{ + .is_export = true, + .is_namespace_scope = true, + .is_typescript_declare = opts.is_typescript_declare, + }; + stmts.append(try p.parseTypeScriptNamespaceStmt(dot_loc, &_opts)) catch unreachable; + } else if (opts.is_typescript_declare and p.lexer.token != .t_open_brace) { + try p.lexer.expectOrInsertSemicolon(); + } else { + try p.lexer.expect(.t_open_brace); + var _opts = ParseStatementOptions{ + .is_namespace_scope = true, + .is_typescript_declare = opts.is_typescript_declare, + }; + stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, try p.parseStmtsUpTo(.t_close_brace, &_opts)); + try p.lexer.next(); + } + const has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace; + p.has_non_local_export_declare_inside_namespace = old_has_non_local_export_declare_inside_namespace; + + // Import assignments may be only used in type expressions, not value + // expressions. If this is the case, the TypeScript compiler removes + // them entirely from the output. That can cause the namespace itself + // to be considered empty and thus be removed. + var import_equal_count: usize = 0; + const _stmts: []Stmt = stmts.items; + for (_stmts) |stmt| { + switch (stmt.data) { + .s_local => |local| { + if (local.was_ts_import_equals and !local.is_export) { + import_equal_count += 1; + } + }, + else => {}, + } + } + + // TypeScript omits namespaces without values. These namespaces + // are only allowed to be used in type expressions. They are + // allowed to be exported, but can also only be used in type + // expressions when imported. So we shouldn't count them as a + // real export either. + // + // TypeScript also strangely counts namespaces containing only + // "export declare" statements as non-empty even though "declare" + // statements are only type annotations. We cannot omit the namespace + // in that case. See https://github.com/evanw/esbuild/issues/1158. + if ((stmts.items.len == import_equal_count and !has_non_local_export_declare_inside_namespace) or opts.is_typescript_declare) { + p.popAndDiscardScope(scope_index); + if (opts.is_module_scope) { + p.local_type_names.put(p.allocator, name_text, true) catch unreachable; + } + return p.s(S.TypeScript{}, loc); + } + + var arg_ref: ?Ref = null; + if (!opts.is_typescript_declare) { + // Avoid a collision with the namespace closure argument variable if the + // namespace exports a symbol with the same name as the namespace itself: + // + // namespace foo { + // export let foo = 123 + // console.log(foo) + // } + // + // TypeScript generates the following code in this case: + // + // var foo; + // (function (foo_1) { + // foo_1.foo = 123; + // console.log(foo_1.foo); + // })(foo || (foo = {})); + // + if (p.current_scope.members.contains(name_text)) { + // Add a "_" to make tests easier to read, since non-bundler tests don't + // run the renamer. For external-facing things the renamer will avoid + // collisions automatically so this isn't important for correctness. + arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; + p.current_scope.generated.append(p.allocator, arg_ref.?) catch unreachable; + } else { + arg_ref = p.newSymbol(.hoisted, name_text) catch unreachable; + } + } + p.popScope(); + + if (!opts.is_typescript_declare) { + name.ref = p.declareSymbol(.ts_namespace, name_loc, name_text) catch unreachable; + } + + return p.s( + S.Namespace{ .name = name, .arg = arg_ref orelse Ref.None, .stmts = stmts.items, .is_export = opts.is_export }, + loc, + ); + } + + fn skipTypeScriptInterfaceStmt(p: *P, opts: *ParseStatementOptions) !void { + const name = p.lexer.identifier; + try p.lexer.expect(.t_identifier); + + if (opts.is_module_scope) { + p.local_type_names.put(p.allocator, name, true) catch unreachable; + } + + try p.skipTypeScriptTypeParameters(); + + if (p.lexer.token == .t_extends) { + try p.lexer.next(); + + while (true) { + try p.skipTypeScriptType(.lowest); + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + } + + if (p.lexer.isContextualKeyword("implements")) { + try p.lexer.next(); + while (true) { + try p.skipTypeScriptType(.lowest); + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + } + + try p.skipTypeScriptObjectType(); + } + + // This assumes the caller has already parsed the "import" token + + fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) anyerror!Stmt { + try p.lexer.expect(.t_equals); + + const kind = S.Local.Kind.k_const; + const name = p.lexer.identifier; + var value = p.e(E.Identifier{ .ref = p.storeNameInRef(name) catch unreachable }, p.lexer.loc()); + try p.lexer.expect(.t_identifier); + + if (strings.eqlComptime(name, "require") and p.lexer.token == .t_open_paren) { + // "import ns = require('x')" + try p.lexer.next(); + const path = p.e(p.lexer.toEString(), p.lexer.loc()); + try p.lexer.expect(.t_string_literal); + try p.lexer.expect(.t_close_paren); + const args = try ExprNodeList.one(p.allocator, path); + value.data = .{ .e_call = Expr.Data.Store.All.append(E.Call, E.Call{ .target = value, .close_paren_loc = p.lexer.loc(), .args = args }) }; + } else { + // "import Foo = Bar" + // "import Foo = Bar.Baz" + while (p.lexer.token == .t_dot) { + try p.lexer.next(); + value.data = .{ .e_dot = Expr.Data.Store.All.append( + E.Dot, + E.Dot{ .target = value, .name = p.lexer.identifier, .name_loc = p.lexer.loc() }, + ) }; + try p.lexer.expect(.t_identifier); + } + } + + try p.lexer.expectOrInsertSemicolon(); + + if (opts.is_typescript_declare) { + // "import type foo = require('bar');" + // "import type foo = bar.baz;" + return p.s(S.TypeScript{}, loc); + } + + const ref = p.declareSymbol(.cconst, default_name_loc, default_name) catch unreachable; + var decls = p.allocator.alloc(Decl, 1) catch unreachable; + decls[0] = Decl{ + .binding = p.b(B.Identifier{ .ref = ref }, default_name_loc), + .value = value, + }; + return p.s(S.Local{ .kind = kind, .decls = decls, .is_export = opts.is_export, .was_ts_import_equals = true }, loc); + } + + fn parseClauseAlias(p: *P, kind: string) !string { + const loc = p.lexer.loc(); + + // The alias may now be a string (see https://github.com/tc39/ecma262/pull/2154) + if (p.lexer.token == .t_string_literal) { + if (p.lexer.string_literal_is_ascii) { + return p.lexer.string_literal_slice; + } else if (p.lexer.utf16ToStringWithValidation(p.lexer.string_literal)) |alias| { + return alias; + } else |_| { + const r = p.source.rangeOfString(loc); + // TODO: improve error message + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Invalid {s} alias because it contains an unpaired Unicode surrogate (like emoji)", .{kind}); + return p.source.textForRange(r); + } + } + + // The alias may be a keyword + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + + const alias = p.lexer.identifier; + p.checkForNonBMPCodePoint(loc, alias); + return alias; + } + + fn parseImportClause( + p: *P, + ) !ImportClause { + var items = ListManaged(js_ast.ClauseItem).init(p.allocator); + try p.lexer.expect(.t_open_brace); + var is_single_line = !p.lexer.has_newline_before; + // this variable should not exist if we're not in a typescript file + var had_type_only_imports = if (comptime is_typescript_enabled) + false + else + void{}; + + while (p.lexer.token != .t_close_brace) { + // The alias may be a keyword; + const isIdentifier = p.lexer.token == .t_identifier; + const alias_loc = p.lexer.loc(); + const alias = try p.parseClauseAlias("import"); + var name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(alias) }; + var original_name = alias; + try p.lexer.next(); + + const probably_type_only_import = if (comptime is_typescript_enabled) + strings.eqlComptime(alias, "type") and + p.lexer.token != .t_comma and + p.lexer.token != .t_close_brace + else + false; + + // "import { type xx } from 'mod'" + // "import { type xx as yy } from 'mod'" + // "import { type 'xx' as yy } from 'mod'" + // "import { type as } from 'mod'" + // "import { type as as } from 'mod'" + // "import { type as as as } from 'mod'" + if (probably_type_only_import) { + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + if (p.lexer.isContextualKeyword("as")) { + original_name = p.lexer.identifier; + name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) }; + try p.lexer.next(); + + if (p.lexer.token == .t_identifier) { + + // "import { type as as as } from 'mod'" + // "import { type as as foo } from 'mod'" + had_type_only_imports = true; + try p.lexer.next(); + } else { + // "import { type as as } from 'mod'" + + try items.append(.{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }); + } + } else if (p.lexer.token == .t_identifier) { + had_type_only_imports = true; + + // "import { type as xxx } from 'mod'" + original_name = p.lexer.identifier; + name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) }; + try p.lexer.expect(.t_identifier); + + if (isEvalOrArguments(original_name)) { + const r = p.source.rangeOfString(name.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use {s} as an identifier here", .{original_name}); + } + + try items.append(.{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }); + } + } else { + const is_identifier = p.lexer.token == .t_identifier; + + // "import { type xx } from 'mod'" + // "import { type xx as yy } from 'mod'" + // "import { type if as yy } from 'mod'" + // "import { type 'xx' as yy } from 'mod'" + _ = try p.parseClauseAlias("import"); + try p.lexer.next(); + + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + + try p.lexer.expect(.t_identifier); + } else if (!is_identifier) { + // An import where the name is a keyword must have an alias + try p.lexer.expectedString("\"as\""); + } + had_type_only_imports = true; + } + } else { + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + original_name = p.lexer.identifier; + name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(original_name) }; + try p.lexer.expect(.t_identifier); + } else if (!isIdentifier) { + // An import where the name is a keyword must have an alias + try p.lexer.expectedString("\"as\""); + } + + // Reject forbidden names + if (isEvalOrArguments(original_name)) { + const r = js_lexer.rangeOfIdentifier(p.source, name.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use \"{s}\" as an identifier here", .{original_name}); + } + + try items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }); + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + try p.lexer.next(); + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + try p.lexer.expect(.t_close_brace); + return ImportClause{ + .items = items.items, + .is_single_line = is_single_line, + .had_type_only_imports = if (comptime is_typescript_enabled) + had_type_only_imports + else + false, + }; + } + + fn forbidInitializers(p: *P, decls: []G.Decl, comptime loop_type: string, is_var: bool) !void { + switch (decls.len) { + 0 => {}, + 1 => { + if (decls[0].value) |value| { + if (is_var) { + + // This is a weird special case. Initializers are allowed in "var" + // statements with identifier bindings. + return; + } + + try p.log.addError(p.source, value.loc, comptime std.fmt.comptimePrint("for-{s} loop variables cannot have an initializer", .{loop_type})); + } + }, + else => { + try p.log.addError(p.source, decls[0].binding.loc, comptime std.fmt.comptimePrint("for-{s} loops must have a single declaration", .{loop_type})); + }, + } + } + + fn parseExprOrLetStmt(p: *P, opts: *ParseStatementOptions) !ExprOrLetStmt { + var let_range = p.lexer.range(); + var raw = p.lexer.raw(); + if (p.lexer.token != .t_identifier or !strings.eqlComptime(raw, "let")) { + // Output.print("HI", .{}); + return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = try p.parseExpr(.lowest) } }; + } + + try p.lexer.next(); + + switch (p.lexer.token) { + .t_identifier, .t_open_bracket, .t_open_brace => { + if (opts.lexical_decl == .allow_all or !p.lexer.has_newline_before or p.lexer.token == .t_open_bracket) { + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(let_range.loc); + } + + const decls = try p.parseAndDeclareDecls(.other, opts); + return ExprOrLetStmt{ + .stmt_or_expr = js_ast.StmtOrExpr{ + .stmt = p.s(S.Local{ + // Replace all "export let" with "export var" when HMR is enabled + .kind = if (opts.is_export and p.options.features.hot_module_reloading) .k_var else .k_let, + .decls = decls, + .is_export = opts.is_export, + }, let_range.loc), + }, + .decls = decls, + }; + } + }, + else => {}, + } + + const ref = p.storeNameInRef(raw) catch unreachable; + const expr = p.e(E.Identifier{ .ref = ref }, let_range.loc); + return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = try p.parseSuffix(expr, .lowest, null, Expr.EFlags.none) } }; + } + + fn requireInitializers(p: *P, decls: []G.Decl) !void { + for (decls) |decl| { + if (decl.value == null) { + switch (decl.binding.data) { + .b_identifier => |ident| { + const r = js_lexer.rangeOfIdentifier(p.source, decl.binding.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "The constant \"{s}\" must be initialized", .{p.symbols.items[ident.ref.innerIndex()].original_name}); + // return;/ + }, + else => { + try p.log.addError(p.source, decl.binding.loc, "This constant must be initialized"); + }, + } + } + } + } + + fn parseBinding(p: *P) anyerror!Binding { + var loc = p.lexer.loc(); + + switch (p.lexer.token) { + .t_identifier => { + const name = p.lexer.identifier; + if ((p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name, "await")) or (p.fn_or_arrow_data_parse.allow_yield != .allow_ident and strings.eqlComptime(name, "yield"))) { + // TODO: add fmt to addRangeError + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"yield\" or \"await\" here.") catch unreachable; + } + + const ref = p.storeNameInRef(name) catch unreachable; + try p.lexer.next(); + return p.b(B.Identifier{ .ref = ref }, loc); + }, + .t_open_bracket => { + try p.lexer.next(); + var is_single_line = !p.lexer.has_newline_before; + var items = ListManaged(js_ast.ArrayBinding).init(p.allocator); + var has_spread = false; + + // "in" expressions are allowed + const old_allow_in = p.allow_in; + p.allow_in = true; + + while (p.lexer.token != .t_close_bracket) { + if (p.lexer.token == .t_comma) { + items.append(js_ast.ArrayBinding{ + .binding = Binding{ .data = Prefill.Data.BMissing, .loc = p.lexer.loc() }, + }) catch unreachable; + } else { + if (p.lexer.token == .t_dot_dot_dot) { + try p.lexer.next(); + has_spread = true; + + // This was a bug in the ES2015 spec that was fixed in ES2016 + if (p.lexer.token != .t_identifier) { + // p.markSyntaxFeature(compat.NestedRestBinding, p.lexer.Range()) + + } + } + + const binding = try p.parseBinding(); + + var default_value: ?Expr = null; + if (!has_spread and p.lexer.token == .t_equals) { + try p.lexer.next(); + default_value = try p.parseExpr(.comma); + } + + items.append(js_ast.ArrayBinding{ .binding = binding, .default_value = default_value }) catch unreachable; + + // Commas after spread elements are not allowed + if (has_spread and p.lexer.token == .t_comma) { + p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; + return error.SyntaxError; + } + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.next(); + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + p.allow_in = old_allow_in; + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.expect(.t_close_bracket); + return p.b(B.Array{ + .items = items.items, + .has_spread = has_spread, + .is_single_line = is_single_line, + }, loc); + }, + .t_open_brace => { + // p.markSyntaxFeature(compat.Destructuring, p.lexer.Range()) + try p.lexer.next(); + var is_single_line = !p.lexer.has_newline_before; + var properties = ListManaged(js_ast.B.Property).init(p.allocator); + + // "in" expressions are allowed + const old_allow_in = p.allow_in; + p.allow_in = true; + + while (p.lexer.token != .t_close_brace) { + var property = try p.parsePropertyBinding(); + properties.append(property) catch unreachable; + + // Commas after spread elements are not allowed + if (property.flags.contains(.is_spread) and p.lexer.token == .t_comma) { + p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; + return error.SyntaxError; + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.next(); + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + p.allow_in = old_allow_in; + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.expect(.t_close_brace); + + return p.b(B.Object{ + .properties = properties.items, + .is_single_line = is_single_line, + }, loc); + }, + else => {}, + } + + try p.lexer.expect(.t_identifier); + return Binding{ .loc = loc, .data = Prefill.Data.BMissing }; + } + + pub fn parsePropertyBinding(p: *P) anyerror!B.Property { + var key: js_ast.Expr = Expr{ .loc = logger.Loc.Empty, .data = Prefill.Data.EMissing }; + var is_computed = false; + + switch (p.lexer.token) { + .t_dot_dot_dot => { + try p.lexer.next(); + const value = p.b( + B.Identifier{ + .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable, + }, + p.lexer.loc(), + ); + try p.lexer.expect(.t_identifier); + return B.Property{ + .key = p.e(E.Missing{}, p.lexer.loc()), + + .flags = Flags.Property.init(.{ .is_spread = true }), + .value = value, + }; + }, + .t_numeric_literal => { + key = p.e(E.Number{ + .value = p.lexer.number, + }, p.lexer.loc()); + // check for legacy octal literal + try p.lexer.next(); + }, + .t_string_literal => { + key = try p.parseStringLiteral(); + }, + .t_big_integer_literal => { + key = p.e(E.BigInt{ + .value = p.lexer.identifier, + }, p.lexer.loc()); + // p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) + try p.lexer.next(); + }, + .t_open_bracket => { + is_computed = true; + try p.lexer.next(); + key = try p.parseExpr(.comma); + try p.lexer.expect(.t_close_bracket); + }, + else => { + const name = p.lexer.identifier; + const loc = p.lexer.loc(); + + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + + try p.lexer.next(); + + key = p.e(E.String{ .data = name }, loc); + + if (p.lexer.token != .t_colon and p.lexer.token != .t_open_paren) { + const ref = p.storeNameInRef(name) catch unreachable; + const value = p.b(B.Identifier{ .ref = ref }, loc); + var default_value: ?Expr = null; + if (p.lexer.token == .t_equals) { + try p.lexer.next(); + default_value = try p.parseExpr(.comma); + } + + return B.Property{ + .key = key, + .value = value, + .default_value = default_value, + }; + } + }, + } + + try p.lexer.expect(.t_colon); + const value = try p.parseBinding(); + + var default_value: ?Expr = null; + if (p.lexer.token == .t_equals) { + try p.lexer.next(); + default_value = try p.parseExpr(.comma); + } + + return B.Property{ + .flags = Flags.Property.init(.{ + .is_computed = is_computed, + }), + .key = key, + .value = value, + .default_value = default_value, + }; + } + + fn parseAndDeclareDecls(p: *P, kind: Symbol.Kind, opts: *ParseStatementOptions) anyerror![]G.Decl { + var decls = ListManaged(G.Decl).init(p.allocator); + + while (true) { + // Forbid "let let" and "const let" but not "var let" + if ((kind == .other or kind == .cconst) and p.lexer.isContextualKeyword("let")) { + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"let\" as an identifier here") catch unreachable; + } + + var value: ?js_ast.Expr = null; + var local = try p.parseBinding(); + p.declareBinding(kind, &local, opts) catch unreachable; + + // Skip over types + if (comptime is_typescript_enabled) { + // "let foo!" + var is_definite_assignment_assertion = p.lexer.token == .t_exclamation; + if (is_definite_assignment_assertion) { + try p.lexer.next(); + } + + // "let foo: number" + if (is_definite_assignment_assertion or p.lexer.token == .t_colon) { + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + } + + // If we end with a .t_close_paren, that's a bug. It means we aren't following the last parenthese + + if (comptime Environment.allow_assert) + assert(p.lexer.token != .t_close_paren); + } + + if (p.lexer.token == .t_equals) { + try p.lexer.next(); + value = try p.parseExpr(.comma); + } + + decls.append(G.Decl{ + .binding = local, + .value = value, + }) catch unreachable; + + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + + return decls.items; + } + + pub fn parseTypescriptEnumStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt { + try p.lexer.expect(.t_enum); + const name_loc = p.lexer.loc(); + const name_text = p.lexer.identifier; + try p.lexer.expect(.t_identifier); + var name = LocRef{ .loc = name_loc, .ref = Ref.None }; + var arg_ref = Ref.None; + if (!opts.is_typescript_declare) { + name.ref = try p.declareSymbol(.ts_enum, name_loc, name_text); + _ = try p.pushScopeForParsePass(.entry, loc); + } + + try p.lexer.expect(.t_open_brace); + + var values = std.ArrayList(js_ast.EnumValue).init(p.allocator); + while (p.lexer.token != .t_close_brace) { + var value = js_ast.EnumValue{ .loc = p.lexer.loc(), .ref = Ref.None, .name = undefined, .value = null }; + var needs_symbol = false; + + // Parse the name + if (p.lexer.token == .t_string_literal) { + value.name = p.lexer.toEString(); + } else if (p.lexer.isIdentifierOrKeyword()) { + value.name = E.String{ .data = p.lexer.identifier }; + needs_symbol = true; + } else { + try p.lexer.expect(.t_identifier); + } + try p.lexer.next(); + + // Identifiers can be referenced by other values + + if (!opts.is_typescript_declare and needs_symbol) { + value.ref = try p.declareSymbol(.other, value.loc, try value.name.string(p.allocator)); + } + + // Parse the initializer + if (p.lexer.token == .t_equals) { + try p.lexer.next(); + value.value = try p.parseExpr(.comma); + } + + values.append(value) catch unreachable; + + if (p.lexer.token != .t_comma and p.lexer.token != .t_semicolon) { + break; + } + + try p.lexer.next(); + } + + if (!opts.is_typescript_declare) { + // Avoid a collision with the enum closure argument variable if the + // enum exports a symbol with the same name as the enum itself: + // + // enum foo { + // foo = 123, + // bar = foo, + // } + // + // TypeScript generates the following code in this case: + // + // var foo; + // (function (foo) { + // foo[foo["foo"] = 123] = "foo"; + // foo[foo["bar"] = 123] = "bar"; + // })(foo || (foo = {})); + // + // Whereas in this case: + // + // enum foo { + // bar = foo as any, + // } + // + // TypeScript generates the following code: + // + // var foo; + // (function (foo) { + // foo[foo["bar"] = foo] = "bar"; + // })(foo || (foo = {})); + // + if (p.current_scope.members.contains(name_text)) { + // Add a "_" to make tests easier to read, since non-bundler tests don't + // run the renamer. For external-facing things the renamer will avoid + // collisions automatically so this isn't important for correctness. + arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; + p.current_scope.generated.append(p.allocator, arg_ref) catch unreachable; + } else { + arg_ref = p.declareSymbol(.hoisted, name_loc, name_text) catch unreachable; + } + + p.popScope(); + } + + try p.lexer.expect(.t_close_brace); + + if (opts.is_typescript_declare) { + if (opts.is_namespace_scope and opts.is_export) { + p.has_non_local_export_declare_inside_namespace = true; + } + + return p.s(S.TypeScript{}, loc); + } + + return p.s(S.Enum{ + .name = name, + .arg = arg_ref, + .values = values.toOwnedSlice(), + .is_export = opts.is_export, + }, loc); + } + + fn parseExportClause(p: *P) !ExportClauseResult { + var items = ListManaged(js_ast.ClauseItem).initCapacity(p.allocator, 1) catch unreachable; + try p.lexer.expect(.t_open_brace); + var is_single_line = !p.lexer.has_newline_before; + var first_non_identifier_loc = logger.Loc{ .start = 0 }; + var had_type_only_exports = false; + + while (p.lexer.token != .t_close_brace) { + var alias = try p.parseClauseAlias("export"); + var alias_loc = p.lexer.loc(); + + const name = LocRef{ + .loc = alias_loc, + .ref = p.storeNameInRef(alias) catch unreachable, + }; + const original_name = alias; + + // The name can actually be a keyword if we're really an "export from" + // statement. However, we won't know until later. Allow keywords as + // identifiers for now and throw an error later if there's no "from". + // + // // This is fine + // export { default } from 'path' + // + // // This is a syntax error + // export { default } + // + if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) { + first_non_identifier_loc = p.lexer.loc(); + } + try p.lexer.next(); + + if (comptime is_typescript_enabled) { + if (strings.eqlComptime(alias, "type") and p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + + if (p.lexer.isContextualKeyword("as")) { + alias = try p.parseClauseAlias("export"); + alias_loc = p.lexer.loc(); + try p.lexer.next(); + + if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { + // "export { type as as as }" + // "export { type as as foo }" + // "export { type as as 'foo' }" + _ = p.parseClauseAlias("export") catch ""; + had_type_only_exports = true; + try p.lexer.next(); + } else { + // "export { type as as }" + items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }) catch unreachable; + } + } else if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { + // "export { type as xxx }" + // "export { type as 'xxx' }" + alias = try p.parseClauseAlias("export"); + alias_loc = p.lexer.loc(); + try p.lexer.next(); + + items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }) catch unreachable; + } else { + had_type_only_exports = true; + } + } else { + // The name can actually be a keyword if we're really an "export from" + // statement. However, we won't know until later. Allow keywords as + // identifiers for now and throw an error later if there's no "from". + // + // // This is fine + // export { default } from 'path' + // + // // This is a syntax error + // export { default } + // + if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) { + first_non_identifier_loc = p.lexer.loc(); + } + + // "export { type xx }" + // "export { type xx as yy }" + // "export { type xx as if }" + // "export { type default } from 'path'" + // "export { type default as if } from 'path'" + // "export { type xx as 'yy' }" + // "export { type 'xx' } from 'mod'" + _ = p.parseClauseAlias("export") catch ""; + try p.lexer.next(); + + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + _ = p.parseClauseAlias("export") catch ""; + try p.lexer.next(); + } + + had_type_only_exports = true; + } + } else { + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + alias = try p.parseClauseAlias("export"); + alias_loc = p.lexer.loc(); + + try p.lexer.next(); + } + + items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }) catch unreachable; + } + } else { + if (p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + alias = try p.parseClauseAlias("export"); + alias_loc = p.lexer.loc(); + + try p.lexer.next(); + } + + items.append(js_ast.ClauseItem{ + .alias = alias, + .alias_loc = alias_loc, + .name = name, + .original_name = original_name, + }) catch unreachable; + } + + // we're done if there's no comma + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.next(); + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + try p.lexer.expect(.t_close_brace); + + // Throw an error here if we found a keyword earlier and this isn't an + // "export from" statement after all + if (first_non_identifier_loc.start != 0 and !p.lexer.isContextualKeyword("from")) { + const r = js_lexer.rangeOfIdentifier(p.source, first_non_identifier_loc); + try p.lexer.addRangeError(r, "Expected identifier but found \"{s}\"", .{p.source.textForRange(r)}, true); + return error.SyntaxError; + } + + return ExportClauseResult{ + .clauses = items.items, + .is_single_line = is_single_line, + .had_type_only_exports = had_type_only_exports, + }; + } + + pub fn parsePath(p: *P) !ParsedPath { + var path = ParsedPath{ + .loc = p.lexer.loc(), + .text = p.lexer.string_literal_slice, + }; + + if (p.lexer.token == .t_no_substitution_template_literal) { + try p.lexer.next(); + } else { + try p.lexer.expect(.t_string_literal); + } + + // For now, we silently strip import assertions + if (!p.lexer.has_newline_before and p.lexer.isContextualKeyword("assert")) { + try p.lexer.next(); + try p.lexer.expect(.t_open_brace); + + while (p.lexer.token != .t_close_brace) { + // Parse the key + if (p.lexer.isIdentifierOrKeyword()) {} else if (p.lexer.token == .t_string_literal) {} else { + try p.lexer.expect(.t_identifier); + } + + try p.lexer.next(); + try p.lexer.expect(.t_colon); + + try p.lexer.expect(.t_string_literal); + + if (p.lexer.token != .t_comma) { + break; + } + + try p.lexer.next(); + } + + try p.lexer.expect(.t_close_brace); + } + + return path; + } + + // TODO: + pub fn checkForNonBMPCodePoint(_: *P, _: logger.Loc, _: string) void {} + + fn parseStmtsUpTo(p: *P, eend: js_lexer.T, _opts: *ParseStatementOptions) ![]Stmt { + var opts = _opts.*; + var stmts = StmtList.init(p.allocator); + + var returnWithoutSemicolonStart: i32 = -1; + opts.lexical_decl = .allow_all; + var isDirectivePrologue = true; + + while (true) { + for (p.lexer.comments_to_preserve_before.items) |comment| { + try stmts.append(p.s(S.Comment{ + .text = comment.text, + }, p.lexer.loc())); + } + p.lexer.comments_to_preserve_before.shrinkRetainingCapacity(0); + + if (p.lexer.token == eend) { + break; + } + + var current_opts = opts; + var stmt = try p.parseStmt(¤t_opts); + + // Skip TypeScript types entirely + if (is_typescript_enabled) { + switch (stmt.data) { + .s_type_script => { + continue; + }, + else => {}, + } + } + + // Parse one or more directives at the beginning + if (isDirectivePrologue) { + isDirectivePrologue = false; + switch (stmt.data) { + .s_expr => |expr| { + switch (expr.value.data) { + .e_string => |str| { + if (!str.prefer_template) { + isDirectivePrologue = true; + + if (str.eqlComptime("use strict")) { + // Track "use strict" directives + p.current_scope.strict_mode = .explicit_strict_mode; + } else if (str.eqlComptime("use asm")) { + stmt.data = Prefill.Data.SEmpty; + } + } + }, + else => {}, + } + }, + else => {}, + } + } + + try stmts.append(stmt); + + // Warn about ASI and return statements. Here's an example of code with + // this problem: https://github.com/rollup/rollup/issues/3729 + if (!p.options.suppress_warnings_about_weird_code) { + var needsCheck = true; + switch (stmt.data) { + .s_return => |ret| { + if (ret.value == null and !p.latest_return_had_semicolon) { + returnWithoutSemicolonStart = stmt.loc.start; + needsCheck = false; + } + }, + else => {}, + } + + if (needsCheck and returnWithoutSemicolonStart != -1) { + switch (stmt.data) { + .s_expr => { + try p.log.addWarning( + p.source, + logger.Loc{ .start = returnWithoutSemicolonStart + 6 }, + "The following expression is not returned because of an automatically-inserted semicolon", + ); + }, + else => {}, + } + + returnWithoutSemicolonStart = -1; + } + } + } + + return stmts.toOwnedSlice(); + } + + fn markStrictModeFeature(p: *P, feature: StrictModeFeature, r: logger.Range, detail: string) !void { + const can_be_transformed = feature == StrictModeFeature.for_in_var_init; + const text = switch (feature) { + .with_statement => "With statements", + .delete_bare_name => "\"delete\" of a bare identifier", + .for_in_var_init => "Variable initializers within for-in loops", + .eval_or_arguments => try std.fmt.allocPrint(p.allocator, "Declarations with the name {s}", .{detail}), + .reserved_word => try std.fmt.allocPrint(p.allocator, "\"{s}\" is a reserved word and", .{detail}), + .legacy_octal_literal => "Legacy octal literals", + .legacy_octal_escape => "Legacy octal escape sequences", + .if_else_function_stmt => "Function declarations inside if statements", + // else => { + // text = "This feature"; + // }, + }; + + var scope = p.current_scope; + if (p.isStrictMode()) { + var why: string = ""; + var where: logger.Range = logger.Range.None; + switch (scope.strict_mode) { + .implicit_strict_mode_import => { + where = p.es6_import_keyword; + }, + .implicit_strict_mode_export => { + where = p.es6_export_keyword; + }, + .implicit_strict_mode_top_level_await => { + where = p.top_level_await_keyword; + }, + .implicit_strict_mode_class => { + why = "All code inside a class is implicitly in strict mode"; + where = p.enclosing_class_keyword; + }, + else => {}, + } + if (why.len == 0) { + why = try std.fmt.allocPrint(p.allocator, "This file is implicitly in strict mode because of the \"{s}\" keyword here", .{p.source.textForRange(where)}); + } + + try p.log.addRangeErrorWithNotes(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used in strict mode", .{text}), &([_]logger.Data{logger.rangeData(p.source, where, why)})); + } else if (!can_be_transformed and p.isStrictModeOutputFormat()) { + try p.log.addRangeError(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used with esm due to strict mode", .{text})); + } + } + + pub inline fn isStrictMode(p: *P) bool { + return p.current_scope.strict_mode != .sloppy_mode; + } + + pub inline fn isStrictModeOutputFormat(_: *P) bool { + return true; + } + + pub fn declareCommonJSSymbol(p: *P, comptime kind: Symbol.Kind, comptime name: string) !Ref { + const name_hash = comptime @TypeOf(p.module_scope.members).getHash(name); + const member = p.module_scope.members.getWithHash(name, name_hash); + + // If the code declared this symbol using "var name", then this is actually + // not a collision. For example, node will let you do this: + // + // var exports; + // module.exports.foo = 123; + // console.log(exports.foo); + // + // This works because node's implementation of CommonJS wraps the entire + // source file like this: + // + // (function(require, exports, module, __filename, __dirname) { + // var exports; + // module.exports.foo = 123; + // console.log(exports.foo); + // }) + // + // Both the "exports" argument and "var exports" are hoisted variables, so + // they don't collide. + if (member) |_member| { + if (p.symbols.items[_member.ref.innerIndex()].kind == .hoisted and kind == .hoisted and !p.has_es_module_syntax) { + return _member.ref; + } + } + + // Create a new symbol if we didn't merge with an existing one above + const ref = try p.newSymbol(kind, name); + + if (member == null) { + try p.module_scope.members.putWithHash(p.allocator, name, name_hash, Scope.Member{ .ref = ref, .loc = logger.Loc.Empty }); + return ref; + } + + // If the variable was declared, then it shadows this symbol. The code in + // this module will be unable to reference this symbol. However, we must + // still add the symbol to the scope so it gets minified (automatically- + // generated code may still reference the symbol). + try p.module_scope.generated.append(p.allocator, ref); + return ref; + } + + fn declareGeneratedSymbol(p: *P, kind: Symbol.Kind, comptime name: string) !GeneratedSymbol { + const static = @field(StaticSymbolName.List, name); + return GeneratedSymbol{ + .backup = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.backup, true), + .primary = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.primary, true), + .ref = try declareSymbolMaybeGenerated(p, kind, logger.Loc.Empty, static.internal, true), + }; + } + + fn declareSymbol(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string) !Ref { + return try @call(.{ .modifier = .always_inline }, declareSymbolMaybeGenerated, .{ p, kind, loc, name, false }); + } + + fn declareSymbolMaybeGenerated(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string, comptime is_generated: bool) !Ref { + // p.checkForNonBMPCodePoint(loc, name) + + if (comptime !is_generated) { + + // Forbid declaring a symbol with a reserved word in strict mode + if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) { + try p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, loc), name); + } + } + + // Allocate a new symbol + var ref = try p.newSymbol(kind, name); + + const scope = p.current_scope; + var entry = try scope.members.getOrPut(p.allocator, name); + if (entry.found_existing) { + const existing = entry.entry.value; + var symbol: *Symbol = &p.symbols.items[existing.ref.innerIndex()]; + + if (comptime !is_generated) { + switch (scope.canMergeSymbols(symbol.kind, kind, is_typescript_enabled)) { + .forbidden => { + var notes = try p.allocator.alloc(logger.Data, 1); + notes[0] = + logger.rangeData( + p.source, + js_lexer.rangeOfIdentifier(p.source, existing.loc), + std.fmt.allocPrint( + p.allocator, + "{s} was originally declared here", + .{symbol.original_name}, + ) catch unreachable, + ); + + p.log.addRangeErrorFmtWithNotes( + p.source, + js_lexer.rangeOfIdentifier(p.source, loc), + p.allocator, + notes, + "\"{s}\" has already been declared", + .{symbol.original_name}, + ) catch unreachable; + + return existing.ref; + }, + .keep_existing => { + ref = existing.ref; + }, + .replace_with_new => { + symbol.link = ref; + }, + .become_private_get_set_pair => { + ref = existing.ref; + symbol.kind = .private_get_set_pair; + }, + .become_private_static_get_set_pair => { + ref = existing.ref; + symbol.kind = .private_static_get_set_pair; + }, + + .overwrite_with_new => {}, + // else => unreachable, + } + } else { + // Ensure that EImportIdentifier is created for the symbol in handleIdentifier + if (symbol.kind == .import and kind != .import) { + try p.is_import_item.put(p.allocator, ref, .{}); + } + + p.symbols.items[ref.innerIndex()].link = existing.ref; + } + } + + entry.entry.value = js_ast.Scope.Member{ .ref = ref, .loc = loc }; + if (comptime is_generated) { + try p.module_scope.generated.append(p.allocator, ref); + } + return ref; + } + + fn validateFunctionName(p: *P, func: G.Fn, kind: FunctionKind) void { + if (func.name) |name| { + const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name; + + if (func.flags.contains(.is_async) and strings.eqlComptime(original_name, "await")) { + p.log.addRangeError( + p.source, + js_lexer.rangeOfIdentifier(p.source, name.loc), + "An async function cannot be named \"await\"", + ) catch unreachable; + } else if (kind == .expr and func.flags.contains(.is_generator) and strings.eqlComptime(original_name, "yield")) { + p.log.addRangeError( + p.source, + js_lexer.rangeOfIdentifier(p.source, name.loc), + "An generator function expression cannot be named \"yield\"", + ) catch unreachable; + } + } + } + + fn parseFnExpr(p: *P, loc: logger.Loc, is_async: bool, async_range: logger.Range) !Expr { + try p.lexer.next(); + const is_generator = p.lexer.token == T.t_asterisk; + if (is_generator) { + // p.markSyntaxFeature() + try p.lexer.next(); + } else if (is_async) { + // p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) + } + + var name: ?js_ast.LocRef = null; + + _ = p.pushScopeForParsePass(.function_args, loc) catch unreachable; + + // The name is optional + if (p.lexer.token == .t_identifier) { + const text = p.lexer.identifier; + + // Don't declare the name "arguments" since it's shadowed and inaccessible + name = js_ast.LocRef{ + .loc = p.lexer.loc(), + .ref = if (text.len > 0 and !strings.eqlComptime(text, "arguments")) + try p.declareSymbol(.hoisted_function, p.lexer.loc(), text) + else + try p.newSymbol(.hoisted_function, text), + }; + + try p.lexer.next(); + } + + // Even anonymous functions can have TypeScript type parameters + if (comptime is_typescript_enabled) { + try p.skipTypeScriptTypeParameters(); + } + + const func = try p.parseFn(name, FnOrArrowDataParse{ + .async_range = async_range, + .allow_await = if (is_async) .allow_expr else .allow_ident, + .allow_yield = if (is_generator) .allow_expr else .allow_ident, + }); + + p.validateFunctionName(func, .expr); + p.popScope(); + + return p.e(js_ast.E.Function{ + .func = func, + }, loc); + } + + fn parseFnBody(p: *P, data: *FnOrArrowDataParse) !G.FnBody { + var oldFnOrArrowData = p.fn_or_arrow_data_parse; + var oldAllowIn = p.allow_in; + p.fn_or_arrow_data_parse = data.*; + p.allow_in = true; + + const loc = p.lexer.loc(); + _ = try p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc()); + defer p.popScope(); + + try p.lexer.expect(.t_open_brace); + var opts = ParseStatementOptions{}; + const stmts = try p.parseStmtsUpTo(.t_close_brace, &opts); + try p.lexer.next(); + + p.allow_in = oldAllowIn; + p.fn_or_arrow_data_parse = oldFnOrArrowData; + return G.FnBody{ .loc = loc, .stmts = stmts }; + } + + fn parseArrowBody(p: *P, args: []js_ast.G.Arg, data: *FnOrArrowDataParse) !E.Arrow { + var arrow_loc = p.lexer.loc(); + + // Newlines are not allowed before "=>" + if (p.lexer.has_newline_before) { + try p.log.addRangeError(p.source, p.lexer.range(), "Unexpected newline before \"=>\""); + return error.SyntaxError; + } + + try p.lexer.expect(T.t_equals_greater_than); + + for (args) |*arg| { + var opts = ParseStatementOptions{}; + try p.declareBinding(Symbol.Kind.hoisted, &arg.binding, &opts); + } + + // The ability to use "this" and "super()" is inherited by arrow functions + data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call; + data.allow_super_property = p.fn_or_arrow_data_parse.allow_super_property; + data.is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed; + + if (p.lexer.token == .t_open_brace) { + var body = try p.parseFnBody(data); + p.after_arrow_body_loc = p.lexer.loc(); + return E.Arrow{ .args = args, .body = body }; + } + + _ = try p.pushScopeForParsePass(Scope.Kind.function_body, arrow_loc); + defer p.popScope(); + + var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); + + p.fn_or_arrow_data_parse = data.*; + var expr = try p.parseExpr(Level.comma); + p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); + + var stmts = try p.allocator.alloc(Stmt, 1); + stmts[0] = p.s(S.Return{ .value = expr }, expr.loc); + return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } }; + } + + fn declareBinding(p: *P, kind: Symbol.Kind, binding: *BindingNodeIndex, opts: *ParseStatementOptions) !void { + switch (binding.data) { + .b_missing => {}, + .b_identifier => |bind| { + if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) { + bind.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(bind.ref)); + } + }, + + .b_array => |bind| { + for (bind.items) |_, i| { + p.declareBinding(kind, &bind.items[i].binding, opts) catch unreachable; + } + }, + + .b_object => |bind| { + for (bind.properties) |*prop| { + p.declareBinding(kind, &prop.value, opts) catch unreachable; + } + }, + + else => { + // @compileError("Missing binding type"); + }, + } + } + + // This is where the allocate memory to the heap for AST objects. + // This is a short name to keep the code more readable. + // It also swallows errors, but I think that's correct here. + // We can handle errors via the log. + // We'll have to deal with @wasmHeapGrow or whatever that thing is. + pub inline fn mm(self: *P, comptime ast_object_type: type, instance: anytype) *ast_object_type { + var obj = self.allocator.create(ast_object_type) catch unreachable; + obj.* = instance; + return obj; + } + + // mmmm memmory allocation + pub inline fn m(self: *P, kind: anytype) *@TypeOf(kind) { + return self.mm(@TypeOf(kind), kind); + } + + pub fn storeNameInRef(p: *P, name: string) !Ref { + if (comptime track_symbol_usage_during_parse_pass) { + if (p.parse_pass_symbol_uses.getPtr(name)) |res| { + res.used = true; + } + } + + if (@ptrToInt(p.source.contents.ptr) <= @ptrToInt(name.ptr) and (@ptrToInt(name.ptr) + name.len) <= (@ptrToInt(p.source.contents.ptr) + p.source.contents.len)) { + const start = Ref.toInt(@ptrToInt(name.ptr) - @ptrToInt(p.source.contents.ptr)); + const end = Ref.toInt(name.len); + return Ref.initSourceEnd(.{ .source_index = start, .inner_index = end, .is_source_contents_slice = true }); + } else { + const inner_index = Ref.toInt(p.allocated_names.items.len); + try p.allocated_names.append(p.allocator, name); + return Ref.initSourceEnd(.{ .source_index = std.math.maxInt(Ref.Int), .inner_index = inner_index, .is_source_contents_slice = false }); + } + } + + pub fn loadNameFromRef(p: *P, ref: Ref) string { + if (ref.isSourceContentsSlice()) { + return p.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()]; + } else if (ref.sourceIndex() == std.math.maxInt(Ref.Int)) { + if (comptime Environment.allow_assert) + assert(ref.innerIndex() < p.allocated_names.items.len); + return p.allocated_names.items[ref.innerIndex()]; + } else { + return p.symbols.items[ref.innerIndex()].original_name; + } + } + + // This parses an expression. This assumes we've already parsed the "async" + // keyword and are currently looking at the following token. + pub fn parseAsyncPrefixExpr(p: *P, async_range: logger.Range, level: Level) !Expr { + // "async function() {}" + if (!p.lexer.has_newline_before and p.lexer.token == T.t_function) { + return try p.parseFnExpr(async_range.loc, true, async_range); + } + + // Check the precedence level to avoid parsing an arrow function in + // "new async () => {}". This also avoids parsing "new async()" as + // "new (async())()" instead. + if (!p.lexer.has_newline_before and level.lt(.member)) { + switch (p.lexer.token) { + // "async => {}" + .t_equals_greater_than => { + if (level.lte(.assign)) { + var args = try p.allocator.alloc(G.Arg, 1); + args[0] = G.Arg{ .binding = p.b( + B.Identifier{ + .ref = try p.storeNameInRef("async"), + }, + async_range.loc, + ) }; + _ = p.pushScopeForParsePass(.function_args, async_range.loc) catch unreachable; + var data = FnOrArrowDataParse{}; + var arrow_body = try p.parseArrowBody(args, &data); + p.popScope(); + return p.e(arrow_body, async_range.loc); + } + }, + // "async x => {}" + .t_identifier => { + if (level.lte(.assign)) { + // p.markLoweredSyntaxFeature(); + + const ref = try p.storeNameInRef(p.lexer.identifier); + var args = try p.allocator.alloc(G.Arg, 1); + args[0] = G.Arg{ .binding = p.b( + B.Identifier{ + .ref = ref, + }, + async_range.loc, + ) }; + try p.lexer.next(); + + _ = try p.pushScopeForParsePass(.function_args, async_range.loc); + defer p.popScope(); + + var data = FnOrArrowDataParse{ + .allow_await = .allow_expr, + }; + var arrowBody = try p.parseArrowBody(args, &data); + arrowBody.is_async = true; + return p.e(arrowBody, async_range.loc); + } + }, + + // "async()" + // "async () => {}" + .t_open_paren => { + try p.lexer.next(); + return p.parseParenExpr(async_range.loc, level, ParenExprOpts{ .is_async = true, .async_range = async_range }); + }, + + // "async<T>()" + // "async <T>() => {}" + .t_less_than => { + if (is_typescript_enabled and p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()) { + try p.lexer.next(); + return p.parseParenExpr(async_range.loc, level, ParenExprOpts{ .is_async = true, .async_range = async_range }); + } + }, + + else => {}, + } + } + + // "async" + // "async + 1" + return p.e( + E.Identifier{ .ref = try p.storeNameInRef("async") }, + async_range.loc, + ); + } + + pub const Backtracking = struct { + pub inline fn lexerBacktracker(p: *P, func: anytype) bool { + p.markTypeScriptOnly(); + var old_lexer = std.mem.toBytes(p.lexer); + const old_log_disabled = p.lexer.is_log_disabled; + p.lexer.is_log_disabled = true; + + defer p.lexer.is_log_disabled = old_log_disabled; + var backtrack = false; + func(p) catch |err| { + switch (err) { + error.Backtrack => { + backtrack = true; + }, + else => {}, + } + }; + + if (backtrack) { + p.lexer = std.mem.bytesToValue(@TypeOf(p.lexer), &old_lexer); + } + + return !backtrack; + } + + pub fn skipTypeScriptTypeParametersThenOpenParenWithBacktracking(p: *P) anyerror!void { + try p.skipTypeScriptTypeParameters(); + if (p.lexer.token != .t_open_paren) { + // try p.lexer.unexpected(); return error.SyntaxError; + return error.Backtrack; + } + } + + pub fn skipTypeScriptArrowArgsWithBacktracking(p: *P) anyerror!void { + try p.skipTypescriptFnArgs(); + p.lexer.expect(.t_equals_greater_than) catch + return error.Backtrack; + } + + pub fn skipTypeScriptTypeArgumentsWithBacktracking(p: *P) anyerror!void { + _ = try p.skipTypeScriptTypeArguments(false); + + // Check the token after this and backtrack if it's the wrong one + if (!TypeScript.canFollowTypeArgumentsInExpression(p.lexer.token)) { + // try p.lexer.unexpected(); return error.SyntaxError; + return error.Backtrack; + } + } + + pub fn skipTypeScriptArrowReturnTypeWithBacktracking(p: *P) anyerror!void { + p.lexer.expect(.t_colon) catch + return error.Backtrack; + + try p.skipTypescriptReturnType(); + // Check the token after this and backtrack if it's the wrong one + if (p.lexer.token != .t_equals_greater_than) { + // try p.lexer.unexpected(); return error.SyntaxError; + return error.Backtrack; + } + } + }; + + pub fn trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking(p: *P) bool { + return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptTypeParametersThenOpenParenWithBacktracking); + } + + pub fn trySkipTypeScriptTypeArgumentsWithBacktracking(p: *P) bool { + return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptTypeArgumentsWithBacktracking); + } + + pub fn trySkipTypeScriptArrowReturnTypeWithBacktracking(p: *P) bool { + return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowReturnTypeWithBacktracking); + } + + pub fn trySkipTypeScriptArrowArgsWithBacktracking(p: *P) bool { + return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowArgsWithBacktracking); + } + + pub inline fn parseExprOrBindings(p: *P, level: Level, errors: ?*DeferredErrors) anyerror!Expr { + return try p.parseExprCommon(level, errors, Expr.EFlags.none); + } + + pub inline fn parseExpr(p: *P, level: Level) anyerror!Expr { + return try p.parseExprCommon(level, null, Expr.EFlags.none); + } + + pub inline fn parseExprWithFlags(p: *P, level: Level, flags: Expr.EFlags) anyerror!Expr { + return try p.parseExprCommon(level, null, flags); + } + + pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { + const had_pure_comment_before = p.lexer.has_pure_comment_before and !p.options.ignore_dce_annotations; + var expr = try p.parsePrefix(level, errors, flags); + + // There is no formal spec for "__PURE__" comments but from reverse- + // engineering, it looks like they apply to the next CallExpression or + // NewExpression. So in "/* @__PURE__ */ a().b() + c()" the comment applies + // to the expression "a().b()". + + if (had_pure_comment_before and level.lt(.call)) { + expr = try p.parseSuffix(expr, @intToEnum(Level, @enumToInt(Level.call) - 1), errors, flags); + switch (expr.data) { + .e_call => |ex| { + ex.can_be_unwrapped_if_unused = true; + }, + .e_new => |ex| { + ex.can_be_unwrapped_if_unused = true; + }, + else => {}, + } + } + + return try p.parseSuffix(expr, level, errors, flags); + } + + pub inline fn addImportRecord(p: *P, kind: ImportKind, loc: logger.Loc, name: string) u32 { + return p.addImportRecordByRange(kind, p.source.rangeOfString(loc), name); + } + + pub fn addImportRecordByRange(p: *P, kind: ImportKind, range: logger.Range, name: string) u32 { + var index = p.import_records.items.len; + const record = ImportRecord{ + .kind = kind, + .range = range, + .path = fs.Path.init(name), + }; + p.import_records.append(record) catch unreachable; + return @intCast(u32, index); + } + + pub fn popScope(p: *P) void { + const current_scope = p.current_scope; + // We cannot rename anything inside a scope containing a direct eval() call + if (current_scope.contains_direct_eval) { + var iter = current_scope.members.iterator(); + while (iter.next()) |member| { + + // Using direct eval when bundling is not a good idea in general because + // esbuild must assume that it can potentially reach anything in any of + // the containing scopes. We try to make it work but this isn't possible + // in some cases. + // + // For example, symbols imported using an ESM import are a live binding + // to the underlying symbol in another file. This is emulated during + // scope hoisting by erasing the ESM import and just referencing the + // underlying symbol in the flattened bundle directly. However, that + // symbol may have a different name which could break uses of direct + // eval: + // + // // Before bundling + // import { foo as bar } from './foo.js' + // console.log(eval('bar')) + // + // // After bundling + // let foo = 123 // The contents of "foo.js" + // console.log(eval('bar')) + // + // There really isn't any way to fix this. You can't just rename "foo" to + // "bar" in the example above because there may be a third bundled file + // that also contains direct eval and imports the same symbol with a + // different conflicting import alias. And there is no way to store a + // live binding to the underlying symbol in a variable with the import's + // name so that direct eval can access it: + // + // // After bundling + // let foo = 123 // The contents of "foo.js" + // const bar = /* cannot express a live binding to "foo" here */ + // console.log(eval('bar')) + // + // Technically a "with" statement could potentially make this work (with + // a big hit to performance), but they are deprecated and are unavailable + // in strict mode. This is a non-starter since all ESM code is strict mode. + // + // So while we still try to obey the requirement that all symbol names are + // pinned when direct eval is present, we make an exception for top-level + // symbols in an ESM file when bundling is enabled. We make no guarantee + // that "eval" will be able to reach these symbols and we allow them to be + // renamed or removed by tree shaking. + // if (p.currentScope.parent == null and p.has_es_module_syntax) { + // continue; + // } + + p.symbols.items[member.value.ref.innerIndex()].must_not_be_renamed = true; + } + } + + p.current_scope = current_scope.parent orelse p.panic("Internal error: attempted to call popScope() on the topmost scope", .{}); + } + + pub fn markExprAsParenthesized(_: *P, expr: *Expr) void { + switch (expr.data) { + .e_array => |ex| { + ex.is_parenthesized = true; + }, + .e_object => |ex| { + ex.is_parenthesized = true; + }, + else => { + return; + }, + } + } + + pub fn parseYieldExpr(p: *P, loc: logger.Loc) !ExprNodeIndex { + // Parse a yield-from expression, which yields from an iterator + const isStar = p.lexer.token == T.t_asterisk; + + if (isStar) { + if (p.lexer.has_newline_before) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + try p.lexer.next(); + } + + var value: ?ExprNodeIndex = null; + switch (p.lexer.token) { + .t_close_brace, .t_close_paren, .t_colon, .t_comma, .t_semicolon => {}, + else => { + if (isStar or !p.lexer.has_newline_before) { + value = try p.parseExpr(.yield); + } + }, + } + + return p.e(E.Yield{ + .value = value, + .is_star = isStar, + }, loc); + } + + pub fn parseProperty(p: *P, kind: Property.Kind, opts: *PropertyOpts, errors: ?*DeferredErrors) anyerror!?G.Property { + var key: Expr = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_missing = E.Missing{} } }; + var key_range = p.lexer.range(); + var is_computed = false; + + switch (p.lexer.token) { + .t_numeric_literal => { + key = p.e(E.Number{ + .value = p.lexer.number, + }, p.lexer.loc()); + // p.checkForLegacyOctalLiteral() + try p.lexer.next(); + }, + .t_string_literal => { + key = try p.parseStringLiteral(); + }, + .t_big_integer_literal => { + key = p.e(E.BigInt{ .value = p.lexer.identifier }, p.lexer.loc()); + // markSyntaxFeature + try p.lexer.next(); + }, + .t_private_identifier => { + if (!opts.is_class or opts.ts_decorators.len > 0) { + try p.lexer.expected(.t_identifier); + } + + key = p.e(E.PrivateIdentifier{ .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable }, p.lexer.loc()); + try p.lexer.next(); + }, + .t_open_bracket => { + is_computed = true; + // p.markSyntaxFeature(compat.objectExtensions, p.lexer.range()) + try p.lexer.next(); + const wasIdentifier = p.lexer.token == .t_identifier; + const expr = try p.parseExpr(.comma); + + if (comptime is_typescript_enabled) { + + // Handle index signatures + if (p.lexer.token == .t_colon and wasIdentifier and opts.is_class) { + switch (expr.data) { + .e_identifier => { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + try p.lexer.expect(.t_close_bracket); + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + try p.lexer.expectOrInsertSemicolon(); + + // Skip this property entirely + return null; + }, + else => {}, + } + } + } + + try p.lexer.expect(.t_close_bracket); + key = expr; + }, + .t_asterisk => { + if (kind != .normal or opts.is_generator) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + opts.is_generator = true; + return try p.parseProperty(.normal, opts, errors); + }, + + else => { + const name = p.lexer.identifier; + const raw = p.lexer.raw(); + const name_range = p.lexer.range(); + + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + + try p.lexer.next(); + + // Support contextual keywords + if (kind == .normal and !opts.is_generator) { + // Does the following token look like a key? + const couldBeModifierKeyword = p.lexer.isIdentifierOrKeyword() or switch (p.lexer.token) { + .t_open_bracket, .t_numeric_literal, .t_string_literal, .t_asterisk, .t_private_identifier => true, + else => false, + }; + + // If so, check for a modifier keyword + if (couldBeModifierKeyword) { + // TODO: micro-optimization, use a smaller list for non-typescript files. + if (js_lexer.PropertyModifierKeyword.List.get(name)) |keyword| { + switch (keyword) { + .p_get => { + if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_get) { + // p.markSyntaxFeautre(ObjectAccessors, name_range) + return try p.parseProperty(.get, opts, null); + } + }, + + .p_set => { + if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_set) { + // p.markSyntaxFeautre(ObjectAccessors, name_range) + return try p.parseProperty(.set, opts, null); + } + }, + .p_async => { + if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_async and !p.lexer.has_newline_before) { + opts.is_async = true; + opts.async_range = name_range; + + // p.markSyntaxFeautre(ObjectAccessors, name_range) + return try p.parseProperty(kind, opts, null); + } + }, + .p_static => { + if (!opts.is_static and !opts.is_async and opts.is_class and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_get) == .p_static) { + opts.is_static = true; + return try p.parseProperty(kind, opts, null); + } + }, + .p_private, .p_protected, .p_public, .p_readonly, .p_abstract, .p_declare, .p_override => { + // Skip over TypeScript keywords + if (opts.is_class and is_typescript_enabled and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == keyword) { + return try p.parseProperty(kind, opts, null); + } + }, + } + } + } else if (p.lexer.token == .t_open_brace and strings.eqlComptime(name, "static")) { + const loc = p.lexer.loc(); + try p.lexer.next(); + + const old_fn_or_arrow_data_parse = p.fn_or_arrow_data_parse; + p.fn_or_arrow_data_parse = .{ + .is_return_disallowed = true, + .allow_super_property = true, + .allow_await = .forbid_all, + }; + + _ = try p.pushScopeForParsePass(.class_static_init, loc); + var _parse_opts = ParseStatementOptions{}; + var stmts = try p.parseStmtsUpTo(.t_close_brace, &_parse_opts); + + p.popScope(); + + p.fn_or_arrow_data_parse = old_fn_or_arrow_data_parse; + try p.lexer.expect(.t_close_brace); + + var block = p.allocator.create( + G.ClassStaticBlock, + ) catch unreachable; + + block.* = G.ClassStaticBlock{ + .stmts = js_ast.BabyList(Stmt).init(stmts), + .loc = loc, + }; + + return G.Property{ + .kind = .class_static_block, + .class_static_block = block, + }; + } + } + + key = p.e(E.String{ .data = name }, name_range.loc); + + // Parse a shorthand property + const isShorthandProperty = !opts.is_class and + kind == .normal and + p.lexer.token != .t_colon and + p.lexer.token != .t_open_paren and + p.lexer.token != .t_less_than and + !opts.is_generator and + !opts.is_async and + !js_lexer.Keywords.has(name); + + if (isShorthandProperty) { + if ((p.fn_or_arrow_data_parse.allow_await != .allow_ident and + strings.eqlComptime(name, "await")) or + (p.fn_or_arrow_data_parse.allow_yield != .allow_ident and + strings.eqlComptime(name, "yield"))) + { + if (strings.eqlComptime(name, "await")) { + p.log.addRangeError(p.source, name_range, "Cannot use \"await\" here") catch unreachable; + } else { + p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" here") catch unreachable; + } + } + + const ref = p.storeNameInRef(name) catch unreachable; + const value = p.e(E.Identifier{ .ref = ref }, key.loc); + + // Destructuring patterns have an optional default value + var initializer: ?Expr = null; + if (errors != null and p.lexer.token == .t_equals) { + errors.?.invalid_expr_default_value = p.lexer.range(); + try p.lexer.next(); + initializer = try p.parseExpr(.comma); + } + + return G.Property{ + .kind = kind, + .key = key, + .value = value, + .initializer = initializer, + .flags = Flags.Property.init(.{ + .was_shorthand = true, + }), + }; + } + }, + } + + if (comptime is_typescript_enabled) { + // "class X { foo?: number }" + // "class X { foo!: number }" + if (opts.is_class and (p.lexer.token == .t_question or p.lexer.token == .t_exclamation)) { + try p.lexer.next(); + } + + // "class X { foo?<T>(): T }" + // "const x = { foo<T>(): T {} }" + try p.skipTypeScriptTypeParameters(); + } + + // Parse a class field with an optional initial value + if (opts.is_class and kind == .normal and !opts.is_async and !opts.is_generator and p.lexer.token != .t_open_paren) { + var initializer: ?Expr = null; + + // Forbid the names "constructor" and "prototype" in some cases + if (!is_computed) { + switch (key.data) { + .e_string => |str| { + if (str.eqlComptime("constructor") or (opts.is_static and str.eqlComptime("prototype"))) { + // TODO: fmt error message to include string value. + p.log.addRangeError(p.source, key_range, "Invalid field name") catch unreachable; + } + }, + else => {}, + } + } + + if (comptime is_typescript_enabled) { + // Skip over types + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + } + + if (p.lexer.token == .t_equals) { + if (comptime is_typescript_enabled) { + if (!opts.declare_range.isEmpty()) { + try p.log.addRangeError(p.source, p.lexer.range(), "Class fields that use \"declare\" cannot be initialized"); + } + } + + try p.lexer.next(); + + // "this" and "super" property access is allowed in field initializers + const old_is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed; + const old_allow_super_property = p.fn_or_arrow_data_parse.allow_super_property; + p.fn_or_arrow_data_parse.is_this_disallowed = false; + p.fn_or_arrow_data_parse.allow_super_property = true; + + initializer = try p.parseExpr(.comma); + + p.fn_or_arrow_data_parse.is_this_disallowed = old_is_this_disallowed; + p.fn_or_arrow_data_parse.allow_super_property = old_allow_super_property; + } + + // Special-case private identifiers + switch (key.data) { + .e_private_identifier => |*private| { + const name = p.loadNameFromRef(private.ref); + if (strings.eqlComptime(name, "#constructor")) { + p.log.addRangeError(p.source, key_range, "Invalid field name \"#constructor\"") catch unreachable; + } + + var declare: js_ast.Symbol.Kind = undefined; + if (opts.is_static) { + declare = .private_static_field; + } else { + declare = .private_field; + } + private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable; + }, + else => {}, + } + + try p.lexer.expectOrInsertSemicolon(); + + return G.Property{ + .ts_decorators = ExprNodeList.init(opts.ts_decorators), + .kind = kind, + .flags = Flags.Property.init(.{ + .is_computed = is_computed, + .is_static = opts.is_static, + }), + .key = key, + .initializer = initializer, + }; + } + + // Parse a method expression + if (p.lexer.token == .t_open_paren or kind != .normal or opts.is_class or opts.is_async or opts.is_generator) { + if (p.lexer.token == .t_open_paren and kind != .get and kind != .set) { + // markSyntaxFeature object extensions + } + + const loc = p.lexer.loc(); + const scope_index = p.pushScopeForParsePass(.function_args, loc) catch unreachable; + var is_constructor = false; + + // Forbid the names "constructor" and "prototype" in some cases + if (opts.is_class and !is_computed) { + switch (key.data) { + .e_string => |str| { + if (!opts.is_static and str.eqlComptime("constructor")) { + if (kind == .get) { + p.log.addRangeError(p.source, key_range, "Class constructor cannot be a getter") catch unreachable; + } else if (kind == .set) { + p.log.addRangeError(p.source, key_range, "Class constructor cannot be a setter") catch unreachable; + } else if (opts.is_async) { + p.log.addRangeError(p.source, key_range, "Class constructor cannot be an async function") catch unreachable; + } else if (opts.is_generator) { + p.log.addRangeError(p.source, key_range, "Class constructor cannot be a generator function") catch unreachable; + } else { + is_constructor = true; + } + } else if (opts.is_static and str.eqlComptime("prototype")) { + p.log.addRangeError(p.source, key_range, "Invalid static method name \"prototype\"") catch unreachable; + } + }, + else => {}, + } + } + + var func = try p.parseFn(null, FnOrArrowDataParse{ + .async_range = opts.async_range, + .has_async_range = !opts.async_range.isEmpty(), + .allow_await = if (opts.is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, + .allow_yield = if (opts.is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, + .allow_super_call = opts.class_has_extends and is_constructor, + .allow_super_property = true, + .allow_ts_decorators = opts.allow_ts_decorators, + .is_constructor = is_constructor, + + // Only allow omitting the body if we're parsing TypeScript class + .allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class, + }); + + // "class Foo { foo(): void; foo(): void {} }" + if (func.flags.contains(.is_forward_declaration)) { + // Skip this property entirely + p.popAndDiscardScope(scope_index); + return null; + } + + p.popScope(); + func.flags.insert(.is_unique_formal_parameters); + const value = p.e(E.Function{ .func = func }, loc); + + // Enforce argument rules for accessors + switch (kind) { + .get => { + if (func.args.len > 0) { + const r = js_lexer.rangeOfIdentifier(p.source, func.args[0].binding.loc); + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Getter {s} must have zero arguments", .{p.keyNameForError(key)}) catch unreachable; + } + }, + .set => { + if (func.args.len != 1) { + var r = js_lexer.rangeOfIdentifier(p.source, if (func.args.len > 0) func.args[0].binding.loc else loc); + if (func.args.len > 1) { + r = js_lexer.rangeOfIdentifier(p.source, func.args[1].binding.loc); + } + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Setter {s} must have exactly 1 argument (there are {d})", .{ p.keyNameForError(key), func.args.len }) catch unreachable; + } + }, + else => {}, + } + + // Special-case private identifiers + switch (key.data) { + .e_private_identifier => |*private| { + var declare: Symbol.Kind = undefined; + var suffix: string = ""; + switch (kind) { + .get => { + if (opts.is_static) { + declare = .private_static_get; + } else { + declare = .private_get; + } + suffix = "_get"; + }, + .set => { + if (opts.is_static) { + declare = .private_static_set; + } else { + declare = .private_set; + } + suffix = "_set"; + }, + else => { + if (opts.is_static) { + declare = .private_static_method; + } else { + declare = .private_method; + } + suffix = "_fn"; + }, + } + + const name = p.loadNameFromRef(private.ref); + if (strings.eqlComptime(name, "#constructor")) { + p.log.addRangeError(p.source, key_range, "Invalid method name \"#constructor\"") catch unreachable; + } + private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable; + }, + else => {}, + } + + return G.Property{ + .ts_decorators = ExprNodeList.init(opts.ts_decorators), + .kind = kind, + .flags = Flags.Property.init(.{ + .is_computed = is_computed, + .is_method = true, + .is_static = opts.is_static, + }), + .key = key, + .value = value, + }; + } + + // Parse an object key/value pair + try p.lexer.expect(.t_colon); + const value = try p.parseExprOrBindings(.comma, errors); + + return G.Property{ + .kind = kind, + .flags = Flags.Property.init(.{ + .is_computed = is_computed, + }), + .key = key, + .value = value, + }; + } + + // By the time we call this, the identifier and type parameters have already + // been parsed. We need to start parsing from the "extends" clause. + pub fn parseClass(p: *P, class_keyword: logger.Range, name: ?js_ast.LocRef, class_opts: ParseClassOptions) !G.Class { + var extends: ?Expr = null; + + if (p.lexer.token == .t_extends) { + try p.lexer.next(); + extends = try p.parseExpr(.new); + + // TypeScript's type argument parser inside expressions backtracks if the + // first token after the end of the type parameter list is "{", so the + // parsed expression above will have backtracked if there are any type + // arguments. This means we have to re-parse for any type arguments here. + // This seems kind of wasteful to me but it's what the official compiler + // does and it probably doesn't have that high of a performance overhead + // because "extends" clauses aren't that frequent, so it should be ok. + if (comptime is_typescript_enabled) { + _ = try p.skipTypeScriptTypeArguments(false); // isInsideJSXElement + } + } + + if (comptime is_typescript_enabled) { + if (p.lexer.isContextualKeyword("implements")) { + try p.lexer.next(); + + while (true) { + try p.skipTypeScriptType(.lowest); + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + } + } + + var body_loc = p.lexer.loc(); + try p.lexer.expect(T.t_open_brace); + var properties = ListManaged(G.Property).init(p.allocator); + + // Allow "in" and private fields inside class bodies + const old_allow_in = p.allow_in; + const old_allow_private_identifiers = p.allow_private_identifiers; + p.allow_in = true; + p.allow_private_identifiers = true; + + // A scope is needed for private identifiers + const scopeIndex = p.pushScopeForParsePass(.class_body, body_loc) catch unreachable; + + var opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null }; + while (p.lexer.token != T.t_close_brace) { + if (p.lexer.token == .t_semicolon) { + try p.lexer.next(); + continue; + } + + opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null }; + + // Parse decorators for this property + const first_decorator_loc = p.lexer.loc(); + if (opts.allow_ts_decorators) { + opts.ts_decorators = try p.parseTypeScriptDecorators(); + } else { + opts.ts_decorators = &[_]Expr{}; + } + + // This property may turn out to be a type in TypeScript, which should be ignored + if (try p.parseProperty(.normal, &opts, null)) |property| { + properties.append(property) catch unreachable; + + // Forbid decorators on class constructors + if (opts.ts_decorators.len > 0) { + switch ((property.key orelse p.panic("Internal error: Expected property {s} to have a key.", .{property})).data) { + .e_string => |str| { + if (str.eqlComptime("constructor")) { + p.log.addError(p.source, first_decorator_loc, "TypeScript does not allow decorators on class constructors") catch unreachable; + } + }, + else => {}, + } + } + } + } + + if (class_opts.is_type_script_declare) { + p.popAndDiscardScope(scopeIndex); + } else { + p.popScope(); + } + + p.allow_in = old_allow_in; + p.allow_private_identifiers = old_allow_private_identifiers; + const close_brace_loc = p.lexer.loc(); + try p.lexer.expect(.t_close_brace); + + return G.Class{ + .class_name = name, + .extends = extends, + .close_brace_loc = close_brace_loc, + .ts_decorators = ExprNodeList.init(class_opts.ts_decorators), + .class_keyword = class_keyword, + .body_loc = body_loc, + .properties = properties.toOwnedSlice(), + }; + } + + pub fn skipTypeScriptTypeArguments(p: *P, comptime isInsideJSXElement: bool) anyerror!bool { + p.markTypeScriptOnly(); + switch (p.lexer.token) { + .t_less_than, .t_less_than_equals, .t_less_than_less_than, .t_less_than_less_than_equals => {}, + else => { + return false; + }, + } + + try p.lexer.expectLessThan(false); + + while (true) { + try p.skipTypeScriptType(.lowest); + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + + // This type argument list must end with a ">" + try p.lexer.expectGreaterThan(isInsideJSXElement); + return true; + } + + pub fn parseTemplateParts(p: *P, _: bool) ![]E.TemplatePart { + var parts = ListManaged(E.TemplatePart).initCapacity(p.allocator, 1) catch unreachable; + // Allow "in" inside template literals + var oldAllowIn = p.allow_in; + p.allow_in = true; + + parseTemplatePart: while (true) { + try p.lexer.next(); + const value = try p.parseExpr(.lowest); + const tail_loc = p.lexer.loc(); + try p.lexer.rescanCloseBraceAsTemplateToken(); + + var tail = p.lexer.toEString(); + + parts.append(E.TemplatePart{ + .value = value, + .tail_loc = tail_loc, + .tail = tail, + }) catch unreachable; + + if (p.lexer.token == .t_template_tail) { + try p.lexer.next(); + break :parseTemplatePart; + } + if (comptime Environment.allow_assert) + assert(p.lexer.token != .t_end_of_file); + } + + p.allow_in = oldAllowIn; + + return parts.toOwnedSlice(); + } + + // This assumes the caller has already checked for TStringLiteral or TNoSubstitutionTemplateLiteral + pub fn parseStringLiteral(p: *P) anyerror!Expr { + const loc = p.lexer.loc(); + var str = p.lexer.toEString(); + str.prefer_template = p.lexer.token == .t_no_substitution_template_literal; + + const expr = p.e(str, loc); + try p.lexer.next(); + return expr; + } + + pub fn parseCallArgs(p: *P) anyerror!ExprListLoc { + // Allow "in" inside call arguments + const old_allow_in = p.allow_in; + p.allow_in = true; + defer p.allow_in = old_allow_in; + + var args = ListManaged(Expr).init(p.allocator); + try p.lexer.expect(.t_open_paren); + + while (p.lexer.token != .t_close_paren) { + const loc = p.lexer.loc(); + const is_spread = p.lexer.token == .t_dot_dot_dot; + if (is_spread) { + // p.mark_syntax_feature(compat.rest_argument, p.lexer.range()); + try p.lexer.next(); + } + var arg = try p.parseExpr(.comma); + if (is_spread) { + arg = p.e(E.Spread{ .value = arg }, loc); + } + args.append(arg) catch unreachable; + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + const close_paren_loc = p.lexer.loc(); + try p.lexer.expect(.t_close_paren); + return ExprListLoc{ .list = ExprNodeList.fromList(args), .loc = close_paren_loc }; + } + + pub fn parseSuffix(p: *P, _left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { + var left = _left; + var optional_chain: ?js_ast.OptionalChain = null; + while (true) { + if (p.lexer.loc().start == p.after_arrow_body_loc.start) { + while (true) { + switch (p.lexer.token) { + .t_comma => { + if (level.gte(.comma)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ + .op = .bin_comma, + .left = left, + .right = try p.parseExpr(.comma), + }, left.loc); + }, + else => { + return left; + }, + } + } + } + + if (comptime is_typescript_enabled) { + // Stop now if this token is forbidden to follow a TypeScript "as" cast + if (p.forbid_suffix_after_as_loc.start > -1 and p.lexer.loc().start == p.forbid_suffix_after_as_loc.start) { + return left; + } + } + + // Reset the optional chain flag by default. That way we won't accidentally + // treat "c.d" as OptionalChainContinue in "a?.b + c.d". + var old_optional_chain = optional_chain; + optional_chain = null; + switch (p.lexer.token) { + .t_dot => { + try p.lexer.next(); + if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) { + // "a.#b" + // "a?.b.#c" + switch (left.data) { + .e_super => { + try p.lexer.expected(.t_identifier); + }, + else => {}, + } + + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); + try p.lexer.next(); + const ref = p.storeNameInRef(name) catch unreachable; + left = p.e(E.Index{ + .target = left, + .index = p.e( + E.PrivateIdentifier{ + .ref = ref, + }, + name_loc, + ), + .optional_chain = old_optional_chain, + }, left.loc); + } else { + // "a.b" + // "a?.b.c" + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); + try p.lexer.next(); + + left = p.e(E.Dot{ .target = left, .name = name, .name_loc = name_loc, .optional_chain = old_optional_chain }, left.loc); + } + + optional_chain = old_optional_chain; + }, + .t_question_dot => { + try p.lexer.next(); + var optional_start = js_ast.OptionalChain.start; + + // TODO: Remove unnecessary optional chains + // if p.options.mangleSyntax { + // if isNullOrUndefined, _, ok := toNullOrUndefinedWithSideEffects(left.Data); ok and !isNullOrUndefined { + // optionalStart = js_ast.OptionalChainNone + // } + // } + + switch (p.lexer.token) { + .t_open_bracket => { + // "a?.[b]" + try p.lexer.next(); + + // allow "in" inside the brackets; + const old_allow_in = p.allow_in; + p.allow_in = true; + + const index = try p.parseExpr(.lowest); + + p.allow_in = old_allow_in; + + try p.lexer.expect(.t_close_bracket); + left = p.e( + E.Index{ .target = left, .index = index, .optional_chain = optional_start }, + left.loc, + ); + }, + + .t_open_paren => { + // "a?.()" + if (level.gte(.call)) { + return left; + } + + const list_loc = try p.parseCallArgs(); + left = p.e(E.Call{ + .target = left, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_start, + }, left.loc); + }, + .t_less_than => { + // "a?.<T>()" + if (comptime !is_typescript_enabled) { + try p.lexer.expected(.t_identifier); + return error.SyntaxError; + } + + _ = try p.skipTypeScriptTypeArguments(false); + if (p.lexer.token != .t_open_paren) { + try p.lexer.expected(.t_open_paren); + } + + if (level.gte(.call)) { + return left; + } + + const list_loc = try p.parseCallArgs(); + left = p.e(E.Call{ + .target = left, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_start, + }, left.loc); + }, + else => { + if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) { + // "a?.#b" + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); + try p.lexer.next(); + const ref = p.storeNameInRef(name) catch unreachable; + left = p.e(E.Index{ + .target = left, + .index = p.e( + E.PrivateIdentifier{ + .ref = ref, + }, + name_loc, + ), + .optional_chain = optional_start, + }, left.loc); + } else { + // "a?.b" + if (!p.lexer.isIdentifierOrKeyword()) { + try p.lexer.expect(.t_identifier); + } + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); + try p.lexer.next(); + + left = p.e(E.Dot{ + .target = left, + .name = name, + .name_loc = name_loc, + .optional_chain = optional_start, + }, left.loc); + } + }, + } + + // Only continue if we have started + if (optional_start == .start) { + optional_start = .ccontinue; + } + }, + .t_no_substitution_template_literal => { + if (old_optional_chain != null) { + p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable; + } + // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); + const head = p.lexer.toEString(); + try p.lexer.next(); + left = p.e(E.Template{ + .tag = left, + .head = head, + }, left.loc); + }, + .t_template_head => { + if (old_optional_chain != null) { + p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable; + } + // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); + const head = p.lexer.toEString(); + const partsGroup = try p.parseTemplateParts(true); + const tag = left; + left = p.e(E.Template{ .tag = tag, .head = head, .parts = partsGroup }, left.loc); + }, + .t_open_bracket => { + // When parsing a decorator, ignore EIndex expressions since they may be + // part of a computed property: + // + // class Foo { + // @foo ['computed']() {} + // } + // + // This matches the behavior of the TypeScript compiler. + if (flags == .ts_decorator) { + return left; + } + + try p.lexer.next(); + + // Allow "in" inside the brackets + const old_allow_in = p.allow_in; + p.allow_in = true; + + const index = try p.parseExpr(.lowest); + + p.allow_in = old_allow_in; + + try p.lexer.expect(.t_close_bracket); + + left = p.e(E.Index{ + .target = left, + .index = index, + .optional_chain = old_optional_chain, + }, left.loc); + optional_chain = old_optional_chain; + }, + .t_open_paren => { + if (level.gte(.call)) { + return left; + } + + const list_loc = try p.parseCallArgs(); + left = p.e( + E.Call{ + .target = left, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = old_optional_chain, + }, + left.loc, + ); + optional_chain = old_optional_chain; + }, + .t_question => { + if (level.gte(.conditional)) { + return left; + } + try p.lexer.next(); + + // Stop now if we're parsing one of these: + // "(a?) => {}" + // "(a?: b) => {}" + // "(a?, b?) => {}" + if (is_typescript_enabled and left.loc.start == p.latest_arrow_arg_loc.start and (p.lexer.token == .t_colon or + p.lexer.token == .t_close_paren or p.lexer.token == .t_comma)) + { + if (errors == null) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + errors.?.invalid_expr_after_question = p.lexer.range(); + return left; + } + + // Allow "in" in between "?" and ":" + const old_allow_in = p.allow_in; + p.allow_in = true; + + const yes = try p.parseExpr(.comma); + + p.allow_in = old_allow_in; + + try p.lexer.expect(.t_colon); + const no = try p.parseExpr(.comma); + + left = p.e(E.If{ + .test_ = left, + .yes = yes, + .no = no, + }, left.loc); + }, + .t_exclamation => { + // Skip over TypeScript non-null assertions + if (p.lexer.has_newline_before) { + return left; + } + + if (!is_typescript_enabled) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + if (level.gte(.postfix)) { + return left; + } + + try p.lexer.next(); + optional_chain = old_optional_chain; + }, + .t_minus_minus => { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Unary{ .op = .un_post_dec, .value = left }, left.loc); + }, + .t_plus_plus => { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Unary{ .op = .un_post_inc, .value = left }, left.loc); + }, + .t_comma => { + if (level.gte(.comma)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_comma, .left = left, .right = try p.parseExpr(.comma) }, left.loc); + }, + .t_plus => { + if (level.gte(.add)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_add, .left = left, .right = try p.parseExpr(.add) }, left.loc); + }, + .t_plus_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_add_assign, .left = left, .right = try p.parseExpr(@intToEnum(Op.Level, @enumToInt(Op.Level.assign) - 1)) }, left.loc); + }, + .t_minus => { + if (level.gte(.add)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_sub, .left = left, .right = try p.parseExpr(.add) }, left.loc); + }, + .t_minus_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_sub_assign, .left = left, .right = try p.parseExpr(Op.Level.sub(Op.Level.assign, 1)) }, left.loc); + }, + .t_asterisk => { + if (level.gte(.multiply)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_mul, .left = left, .right = try p.parseExpr(.multiply) }, left.loc); + }, + .t_asterisk_asterisk => { + if (level.gte(.exponentiation)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_pow, .left = left, .right = try p.parseExpr(Op.Level.exponentiation.sub(1)) }, left.loc); + }, + .t_asterisk_asterisk_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_pow_assign, .left = left, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + }, + .t_asterisk_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_mul_assign, .left = left, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + }, + .t_percent => { + if (level.gte(.multiply)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_rem, .left = left, .right = try p.parseExpr(Op.Level.multiply) }, left.loc); + }, + .t_percent_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_rem_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_slash => { + if (level.gte(.multiply)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_div, .left = left, .right = try p.parseExpr(Level.multiply) }, left.loc); + }, + .t_slash_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_div_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_loose_eq, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); + }, + .t_exclamation_equals => { + if (level.gte(.equals)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_loose_ne, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); + }, + .t_equals_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_strict_eq, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); + }, + .t_exclamation_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_strict_ne, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); + }, + .t_less_than => { + // TypeScript allows type arguments to be specified with angle brackets + // inside an expression. Unlike in other languages, this unfortunately + // appears to require backtracking to parse. + if (is_typescript_enabled and p.trySkipTypeScriptTypeArgumentsWithBacktracking()) { + optional_chain = old_optional_chain; + continue; + } + + if (level.gte(.compare)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_lt, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + .t_less_than_equals => { + if (level.gte(.compare)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_le, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + .t_greater_than => { + if (level.gte(.compare)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_gt, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + .t_greater_than_equals => { + if (level.gte(.compare)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_ge, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + .t_less_than_less_than => { + if (level.gte(.shift)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl, .left = left, .right = try p.parseExpr(.shift) }, left.loc); + }, + .t_less_than_less_than_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_greater_than_greater_than => { + if (level.gte(.shift)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shr, .left = left, .right = try p.parseExpr(.shift) }, left.loc); + }, + .t_greater_than_greater_than_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shr_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_greater_than_greater_than_greater_than => { + if (level.gte(.shift)) { + return left; + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_u_shr, .left = left, .right = try p.parseExpr(.shift) }, left.loc); + }, + .t_greater_than_greater_than_greater_than_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_u_shr_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_question_question => { + if (level.gte(.nullish_coalescing)) { + return left; + } + try p.lexer.next(); + const prev = left; + left = p.e(E.Binary{ .op = .bin_nullish_coalescing, .left = prev, .right = try p.parseExpr(.nullish_coalescing) }, left.loc); + }, + .t_question_question_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_nullish_coalescing_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_bar_bar => { + if (level.gte(.logical_or)) { + return left; + } + + // Prevent "||" inside "??" from the right + if (level.eql(.nullish_coalescing)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + const right = try p.parseExpr(.logical_or); + left = p.e(E.Binary{ .op = Op.Code.bin_logical_or, .left = left, .right = right }, left.loc); + + if (level.lt(.nullish_coalescing)) { + left = try p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); + + if (p.lexer.token == .t_question_question) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + } + }, + .t_bar_bar_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_or_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_ampersand_ampersand => { + if (level.gte(.logical_and)) { + return left; + } + + // Prevent "&&" inside "??" from the right + if (level.eql(.nullish_coalescing)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_and, .left = left, .right = try p.parseExpr(.logical_and) }, left.loc); + + // Prevent "&&" inside "??" from the left + if (level.lt(.nullish_coalescing)) { + left = try p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); + + if (p.lexer.token == .t_question_question) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + } + }, + .t_ampersand_ampersand_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_and_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_bar => { + if (level.gte(.bitwise_or)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_or, .left = left, .right = try p.parseExpr(.bitwise_or) }, left.loc); + }, + .t_bar_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_or_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_ampersand => { + if (level.gte(.bitwise_and)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_and, .left = left, .right = try p.parseExpr(.bitwise_and) }, left.loc); + }, + .t_ampersand_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_and_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_caret => { + if (level.gte(.bitwise_xor)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_xor, .left = left, .right = try p.parseExpr(.bitwise_xor) }, left.loc); + }, + .t_caret_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_xor_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_equals => { + if (level.gte(.assign)) { + return left; + } + + try p.lexer.next(); + + left = p.e(E.Binary{ .op = .bin_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_in => { + if (level.gte(.compare) or !p.allow_in) { + return left; + } + + // Warn about "!a in b" instead of "!(a in b)" + switch (left.data) { + .e_unary => |unary| { + if (unary.op == .un_not) { + // TODO: + // p.log.addRangeWarning(source: ?Source, r: Range, text: string) + } + }, + else => {}, + } + + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_in, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + .t_instanceof => { + if (level.gte(.compare)) { + return left; + } + + // Warn about "!a instanceof b" instead of "!(a instanceof b)". Here's an + // example of code with this problem: https://github.com/mrdoob/three.js/pull/11182. + if (!p.options.suppress_warnings_about_weird_code) { + switch (left.data) { + .e_unary => |unary| { + if (unary.op == .un_not) { + // TODO: + // p.log.addRangeWarning(source: ?Source, r: Range, text: string) + } + }, + else => {}, + } + } + try p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_instanceof, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + }, + else => { + // Handle the TypeScript "as" operator + if (is_typescript_enabled and level.lt(.compare) and !p.lexer.has_newline_before and p.lexer.isContextualKeyword("as")) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + + // These tokens are not allowed to follow a cast expression. This isn't + // an outright error because it may be on a new line, in which case it's + // the start of a new expression when it's after a cast: + // + // x = y as z + // (something); + // + switch (p.lexer.token) { + .t_plus_plus, + .t_minus_minus, + .t_no_substitution_template_literal, + .t_template_head, + .t_open_paren, + .t_open_bracket, + .t_question_dot, + => { + p.forbid_suffix_after_as_loc = p.lexer.loc(); + return left; + }, + else => {}, + } + + if (p.lexer.token.isAssign()) { + p.forbid_suffix_after_as_loc = p.lexer.loc(); + return left; + } + continue; + } + + return left; + }, + } + } + } + + pub const MacroVisitor = struct { + p: *P, + + loc: logger.Loc, + + pub fn visitImport(this: MacroVisitor, import_data: js_ast.Macro.JSNode.ImportData) void { + var p = this.p; + + const record_id = p.addImportRecord(.stmt, this.loc, import_data.path); + var record: *ImportRecord = &p.import_records.items[record_id]; + record.was_injected_by_macro = true; + p.macro.imports.ensureUnusedCapacity(import_data.import.items.len) catch unreachable; + var import = import_data.import; + import.import_record_index = record_id; + + p.is_import_item.ensureUnusedCapacity( + p.allocator, + @intCast(u32, p.is_import_item.count() + import.items.len), + ) catch unreachable; + + for (import.items) |*clause| { + const import_hash_name = clause.original_name; + + if (strings.eqlComptime(clause.alias, "default")) { + var non_unique_name = record.path.name.nonUniqueNameString(p.allocator) catch unreachable; + clause.original_name = std.fmt.allocPrint(p.allocator, "{s}_default", .{non_unique_name}) catch unreachable; + record.contains_default_alias = true; + } + const name_ref = p.declareSymbol(.import, this.loc, clause.original_name) catch unreachable; + clause.name = LocRef{ .loc = this.loc, .ref = name_ref }; + + p.is_import_item.putAssumeCapacity(name_ref, .{}); + + p.macro.imports.putAssumeCapacity(js_ast.Macro.JSNode.SymbolMap.generateImportHash(import_hash_name, import_data.path), name_ref); + + // Ensure we don't accidentally think this is an export from + } + + p.macro.prepend_stmts.append(p.s(import, this.loc)) catch unreachable; + } + }; + + pub fn panic(p: *P, comptime str: string, args: anytype) noreturn { + @setCold(true); + var panic_buffer = p.allocator.alloc(u8, 32 * 1024) catch unreachable; + var panic_stream = std.io.fixedBufferStream(panic_buffer); + p.log.addRangeErrorFmt(p.source, p.lexer.range(), p.allocator, str, args) catch unreachable; + + p.log.printForLogLevel( + panic_stream.writer(), + ) catch unreachable; + Global.panic("{s}", .{panic_buffer[0..panic_stream.pos]}); + } + + pub fn parsePrefix(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { + const loc = p.lexer.loc(); + const l = @enumToInt(level); + // Output.print("Parse Prefix {s}:{s} @{s} ", .{ p.lexer.token, p.lexer.raw(), @tagName(level) }); + + switch (p.lexer.token) { + .t_super => { + const superRange = p.lexer.range(); + try p.lexer.next(); + + switch (p.lexer.token) { + .t_open_paren => { + if (l < @enumToInt(Level.call) and p.fn_or_arrow_data_parse.allow_super_call) { + return p.e(E.Super{}, loc); + } + }, + .t_dot, .t_open_bracket => { + if (p.fn_or_arrow_data_parse.allow_super_property) { + return p.e(E.Super{}, loc); + } + }, + else => {}, + } + + p.log.addRangeError(p.source, superRange, "Unexpected \"super\"") catch unreachable; + return p.e(E.Super{}, loc); + }, + .t_open_paren => { + try p.lexer.next(); + + // Arrow functions aren't allowed in the middle of expressions + if (level.gt(.assign)) { + // Allow "in" inside parentheses + const oldAllowIn = p.allow_in; + p.allow_in = true; + + var value = try p.parseExpr(Level.lowest); + p.markExprAsParenthesized(&value); + try p.lexer.expect(.t_close_paren); + + p.allow_in = oldAllowIn; + return value; + } + + return p.parseParenExpr(loc, level, ParenExprOpts{}); + }, + .t_false => { + try p.lexer.next(); + return p.e(E.Boolean{ .value = false }, loc); + }, + .t_true => { + try p.lexer.next(); + return p.e(E.Boolean{ .value = true }, loc); + }, + .t_null => { + try p.lexer.next(); + return p.e(E.Null{}, loc); + }, + .t_this => { + if (p.fn_or_arrow_data_parse.is_this_disallowed) { + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"this\" here") catch unreachable; + } + try p.lexer.next(); + return Expr{ .data = Prefill.Data.This, .loc = loc }; + }, + .t_private_identifier => { + if (!p.allow_private_identifiers or !p.allow_in or level.gte(.compare)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + const name = p.lexer.identifier; + try p.lexer.next(); + + // Check for "#foo in bar" + if (p.lexer.token != .t_in) { + try p.lexer.expected(.t_in); + } + + return p.e(E.PrivateIdentifier{ .ref = try p.storeNameInRef(name) }, loc); + }, + .t_identifier => { + const name = p.lexer.identifier; + const name_range = p.lexer.range(); + const raw = p.lexer.raw(); + + try p.lexer.next(); + + // Handle async and await expressions + switch (AsyncPrefixExpression.find(name)) { + .is_async => { + if ((raw.ptr == name.ptr and raw.len == name.len) or AsyncPrefixExpression.find(raw) == .is_async) { + return try p.parseAsyncPrefixExpr(name_range, level); + } + }, + + .is_await => { + switch (p.fn_or_arrow_data_parse.allow_await) { + .forbid_all => { + p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be used here") catch unreachable; + }, + .allow_expr => { + if (AsyncPrefixExpression.find(raw) != .is_await) { + p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be escaped") catch unreachable; + } else { + if (p.fn_or_arrow_data_parse.is_top_level) { + p.top_level_await_keyword = name_range; + } + + if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) { + p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_await = name_range; + } + + const value = try p.parseExpr(.prefix); + if (p.lexer.token == T.t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Await{ .value = value }, loc); + } + }, + else => {}, + } + }, + + .is_yield => { + switch (p.fn_or_arrow_data_parse.allow_yield) { + .forbid_all => { + p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be used here") catch unreachable; + }, + .allow_expr => { + if (AsyncPrefixExpression.find(raw) != .is_yield) { + p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be escaped") catch unreachable; + } else { + if (level.gt(.assign)) { + p.log.addRangeError(p.source, name_range, "Cannot use a \"yield\" here without parentheses") catch unreachable; + } + + if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) { + p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_yield = name_range; + } + + return p.parseYieldExpr(loc); + } + }, + // .allow_ident => { + + // }, + else => { + // Try to gracefully recover if "yield" is used in the wrong place + if (!p.lexer.has_newline_before) { + switch (p.lexer.token) { + .t_null, .t_identifier, .t_false, .t_true, .t_numeric_literal, .t_big_integer_literal, .t_string_literal => { + p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" outside a generator function") catch unreachable; + }, + else => {}, + } + } + }, + } + }, + .none => {}, + } + + // Handle the start of an arrow expression + if (p.lexer.token == .t_equals_greater_than and level.lte(.assign)) { + const ref = p.storeNameInRef(name) catch unreachable; + var args = p.allocator.alloc(Arg, 1) catch unreachable; + args[0] = Arg{ .binding = p.b(B.Identifier{ + .ref = ref, + }, loc) }; + + _ = p.pushScopeForParsePass(.function_args, loc) catch unreachable; + defer p.popScope(); + + var fn_or_arrow_data = FnOrArrowDataParse{}; + const ret = p.e(try p.parseArrowBody(args, &fn_or_arrow_data), loc); + return ret; + } + + const ref = p.storeNameInRef(name) catch unreachable; + + return Expr.initIdentifier(ref, loc); + }, + .t_string_literal, .t_no_substitution_template_literal => { + return try p.parseStringLiteral(); + }, + .t_template_head => { + const head = p.lexer.toEString(); + + const parts = try p.parseTemplateParts(false); + + // Check if TemplateLiteral is unsupported. We don't care for this product.` + // if () + + return p.e(E.Template{ + .head = head, + .parts = parts, + }, loc); + }, + .t_numeric_literal => { + const value = p.e(E.Number{ .value = p.lexer.number }, loc); + // p.checkForLegacyOctalLiteral() + try p.lexer.next(); + return value; + }, + .t_big_integer_literal => { + const value = p.lexer.identifier; + // markSyntaxFeature bigInt + try p.lexer.next(); + return p.e(E.BigInt{ .value = value }, loc); + }, + .t_slash, .t_slash_equals => { + try p.lexer.scanRegExp(); + // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later + defer p.lexer.regex_flags_start = null; + const value = p.lexer.raw(); + try p.lexer.next(); + + return p.e(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc); + }, + .t_void => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ + .op = .un_void, + .value = value, + }, loc); + }, + .t_typeof => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ .op = .un_typeof, .value = value }, loc); + }, + .t_delete => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + if (value.data == .e_index) { + if (value.data.e_index.index.data == .e_private_identifier) { + const private = value.data.e_index.index.data.e_private_identifier; + const name = p.loadNameFromRef(private.ref); + const range = logger.Range{ .loc = value.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeErrorFmt(p.source, range, p.allocator, "Deleting the private name \"{s}\" is forbidden", .{name}) catch unreachable; + } + } + + return p.e(E.Unary{ .op = .un_delete, .value = value }, loc); + }, + .t_plus => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ .op = .un_pos, .value = value }, loc); + }, + .t_minus => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ .op = .un_neg, .value = value }, loc); + }, + .t_tilde => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ .op = .un_cpl, .value = value }, loc); + }, + .t_exclamation => { + try p.lexer.next(); + const value = try p.parseExpr(.prefix); + if (p.lexer.token == .t_asterisk_asterisk) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + return p.e(E.Unary{ .op = .un_not, .value = value }, loc); + }, + .t_minus_minus => { + try p.lexer.next(); + return p.e(E.Unary{ .op = .un_pre_dec, .value = try p.parseExpr(.prefix) }, loc); + }, + .t_plus_plus => { + try p.lexer.next(); + return p.e(E.Unary{ .op = .un_pre_inc, .value = try p.parseExpr(.prefix) }, loc); + }, + .t_function => { + return try p.parseFnExpr(loc, false, logger.Range.None); + }, + .t_class => { + const classKeyword = p.lexer.range(); + // markSyntaxFEatuer class + try p.lexer.next(); + var name: ?js_ast.LocRef = null; + + _ = p.pushScopeForParsePass(.class_name, loc) catch unreachable; + + // Parse an optional class name + if (p.lexer.token == .t_identifier) { + const name_text = p.lexer.identifier; + if (!is_typescript_enabled or !strings.eqlComptime(name_text, "implements")) { + if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) { + p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here") catch unreachable; + } + + name = js_ast.LocRef{ + .loc = p.lexer.loc(), + .ref = p.newSymbol( + .other, + name_text, + ) catch unreachable, + }; + try p.lexer.next(); + } + } + + // Even anonymous classes can have TypeScript type parameters + if (is_typescript_enabled) { + try p.skipTypeScriptTypeParameters(); + } + + const class = try p.parseClass(classKeyword, name, ParseClassOptions{}); + p.popScope(); + + return p.e(class, loc); + }, + .t_new => { + try p.lexer.next(); + + // Special-case the weird "new.target" expression here + if (p.lexer.token == .t_dot) { + try p.lexer.next(); + + if (p.lexer.token != .t_identifier or !strings.eqlComptime(p.lexer.raw(), "target")) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + const range = logger.Range{ .loc = loc, .len = p.lexer.range().end().start - loc.start }; + + try p.lexer.next(); + return p.e(E.NewTarget{ .range = range }, loc); + } + + const target = try p.parseExprWithFlags(.member, flags); + var args = ExprNodeList{}; + + if (comptime is_typescript_enabled) { + // Skip over TypeScript non-null assertions + if (p.lexer.token == .t_exclamation and !p.lexer.has_newline_before) { + try p.lexer.next(); + } + + // Skip over TypeScript type arguments here if there are any + if (p.lexer.token == .t_less_than) { + _ = p.trySkipTypeScriptTypeArgumentsWithBacktracking(); + } + } + + var close_parens_loc = logger.Loc.Empty; + if (p.lexer.token == .t_open_paren) { + const call_args = try p.parseCallArgs(); + args = call_args.list; + close_parens_loc = call_args.loc; + } + + return p.e(E.New{ + .target = target, + .args = args, + .close_parens_loc = close_parens_loc, + }, loc); + }, + .t_open_bracket => { + try p.lexer.next(); + var is_single_line = !p.lexer.has_newline_before; + var items = ListManaged(Expr).init(p.allocator); + var self_errors = DeferredErrors{}; + var comma_after_spread = logger.Loc{}; + + // Allow "in" inside arrays + const old_allow_in = p.allow_in; + p.allow_in = true; + + while (p.lexer.token != .t_close_bracket) { + switch (p.lexer.token) { + .t_comma => { + items.append(Expr{ .data = Prefill.Data.EMissing, .loc = p.lexer.loc() }) catch unreachable; + }, + .t_dot_dot_dot => { + if (errors != null) + errors.?.array_spread_feature = p.lexer.range(); + + const dots_loc = p.lexer.loc(); + try p.lexer.next(); + items.append( + p.e(E.Spread{ .value = try p.parseExprOrBindings(.comma, &self_errors) }, dots_loc), + ) catch unreachable; + + // Commas are not allowed here when destructuring + if (p.lexer.token == .t_comma) { + comma_after_spread = p.lexer.loc(); + } + }, + else => { + items.append( + try p.parseExprOrBindings(.comma, &self_errors), + ) catch unreachable; + }, + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + try p.lexer.next(); + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + const close_bracket_loc = p.lexer.loc(); + try p.lexer.expect(.t_close_bracket); + p.allow_in = old_allow_in; + + // Is this a binding pattern? + if (p.willNeedBindingPattern()) { + // noop + } else if (errors == null) { + // Is this an expression? + p.logExprErrors(&self_errors); + } else { + // In this case, we can't distinguish between the two yet + self_errors.mergeInto(errors.?); + } + return p.e(E.Array{ + .items = ExprNodeList.fromList(items), + .comma_after_spread = comma_after_spread.toNullable(), + .is_single_line = is_single_line, + .close_bracket_loc = close_bracket_loc, + }, loc); + }, + .t_open_brace => { + try p.lexer.next(); + var is_single_line = !p.lexer.has_newline_before; + var properties = ListManaged(G.Property).init(p.allocator); + var self_errors = DeferredErrors{}; + var comma_after_spread: logger.Loc = logger.Loc{}; + + // Allow "in" inside object literals + const old_allow_in = p.allow_in; + p.allow_in = true; + + while (p.lexer.token != .t_close_brace) { + if (p.lexer.token == .t_dot_dot_dot) { + try p.lexer.next(); + properties.append(G.Property{ .kind = .spread, .value = try p.parseExpr(.comma) }) catch unreachable; + + // Commas are not allowed here when destructuring + if (p.lexer.token == .t_comma) { + comma_after_spread = p.lexer.loc(); + } + } else { + // This property may turn out to be a type in TypeScript, which should be ignored + var propertyOpts = PropertyOpts{}; + if (try p.parseProperty(.normal, &propertyOpts, &self_errors)) |prop| { + if (comptime Environment.allow_assert) { + assert(prop.key != null or prop.value != null); + } + properties.append(prop) catch unreachable; + } + } + + if (p.lexer.token != .t_comma) { + break; + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + try p.lexer.next(); + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + } + + if (p.lexer.has_newline_before) { + is_single_line = false; + } + + const close_brace_loc = p.lexer.loc(); + try p.lexer.expect(.t_close_brace); + p.allow_in = old_allow_in; + + if (p.willNeedBindingPattern()) { + // Is this a binding pattern? + } else if (errors == null) { + // Is this an expression? + p.logExprErrors(&self_errors); + } else { + // In this case, we can't distinguish between the two yet + self_errors.mergeInto(errors.?); + } + + return p.e(E.Object{ + .properties = G.Property.List.fromList(properties), + .comma_after_spread = if (comma_after_spread.start > 0) + comma_after_spread + else + null, + .is_single_line = is_single_line, + .close_brace_loc = close_brace_loc, + }, loc); + }, + .t_less_than => { + // This is a very complicated and highly ambiguous area of TypeScript + // syntax. Many similar-looking things are overloaded. + // + // TS: + // + // A type cast: + // <A>(x) + // <[]>(x) + // <A[]>(x) + // + // An arrow function with type parameters: + // <A>(x) => {} + // <A, B>(x) => {} + // <A = B>(x) => {} + // <A extends B>(x) => {} + // + // TSX: + // + // A JSX element: + // <A>(x) => {}</A> + // <A extends>(x) => {}</A> + // <A extends={false}>(x) => {}</A> + // + // An arrow function with type parameters: + // <A, B>(x) => {} + // <A extends B>(x) => {} + // + // A syntax error: + // <[]>(x) + // <A[]>(x) + // <A>(x) => {} + // <A = B>(x) => {} + if (comptime is_typescript_enabled and is_jsx_enabled) { + var oldLexer = std.mem.toBytes(p.lexer); + + try p.lexer.next(); + // Look ahead to see if this should be an arrow function instead + var is_ts_arrow_fn = false; + + if (p.lexer.token == .t_identifier) { + try p.lexer.next(); + if (p.lexer.token == .t_comma) { + is_ts_arrow_fn = true; + } else if (p.lexer.token == .t_extends) { + try p.lexer.next(); + is_ts_arrow_fn = p.lexer.token != .t_equals and p.lexer.token != .t_greater_than; + } + } + + // Restore the lexer + p.lexer = std.mem.bytesToValue(@TypeOf(p.lexer), &oldLexer); + + if (is_ts_arrow_fn) { + try p.skipTypeScriptTypeParameters(); + try p.lexer.expect(.t_open_paren); + return try p.parseParenExpr(loc, level, ParenExprOpts{ .force_arrow_fn = true }); + } + } + + if (is_jsx_enabled) { + // Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<" + try p.lexer.nextInsideJSXElement(); + const element = try p.parseJSXElement(loc); + + // The call to parseJSXElement() above doesn't consume the last + // TGreaterThan because the caller knows what Next() function to call. + // Use Next() instead of NextInsideJSXElement() here since the next + // token is an expression. + try p.lexer.next(); + return element; + } + + if (is_typescript_enabled) { + // This is either an old-style type cast or a generic lambda function + + // "<T>(x)" + // "<T>(x) => {}" + if (p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()) { + try p.lexer.expect(.t_open_paren); + return p.parseParenExpr(loc, level, ParenExprOpts{}); + } + + // "<T>x" + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + try p.lexer.expectGreaterThan(false); + return p.parsePrefix(level, errors, flags); + } + + try p.lexer.unexpected(); + return error.SyntaxError; + }, + .t_import => { + try p.lexer.next(); + return p.parseImportExpr(loc, level); + }, + else => { + try p.lexer.unexpected(); + return error.SyntaxError; + }, + } + return error.SyntaxError; + } + + // esbuild's version of this function is much more complicated. + // I'm not sure why defines is strictly relevant for this case + // do people do <API_URL>? + fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { + p.recordUsage(ref); + return p.e(E.Identifier{ + .ref = ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, loc); + } + + // Note: The caller has already parsed the "import" keyword + fn parseImportExpr(p: *P, loc: logger.Loc, level: Level) anyerror!Expr { + // Parse an "import.meta" expression + if (p.lexer.token == .t_dot) { + p.es6_import_keyword = js_lexer.rangeOfIdentifier(p.source, loc); + try p.lexer.next(); + if (p.lexer.isContextualKeyword("meta")) { + try p.lexer.next(); + p.has_import_meta = true; + return p.e(E.ImportMeta{}, loc); + } else { + try p.lexer.expectedString("\"meta\""); + } + } + + if (level.gt(.call)) { + const r = js_lexer.rangeOfIdentifier(p.source, loc); + p.log.addRangeError(p.source, r, "Cannot use an \"import\" expression here without parentheses") catch unreachable; + } + + // allow "in" inside call arguments; + var old_allow_in = p.allow_in; + p.allow_in = true; + + p.lexer.preserve_all_comments_before = true; + try p.lexer.expect(.t_open_paren); + const comments = p.lexer.comments_to_preserve_before.toOwnedSlice(); + p.lexer.preserve_all_comments_before = false; + + const value = try p.parseExpr(.comma); + + if (p.lexer.token == .t_comma) { + // "import('./foo.json', )" + try p.lexer.next(); + + if (p.lexer.token != .t_close_paren) { + // for now, we silently strip import assertions + // "import('./foo.json', { assert: { type: 'json' } })" + _ = try p.parseExpr(.comma); + + if (p.lexer.token == .t_comma) { + // "import('./foo.json', { assert: { type: 'json' } }, , )" + try p.lexer.next(); + } + } + } + + try p.lexer.expect(.t_close_paren); + + p.allow_in = old_allow_in; + + if (comptime only_scan_imports_and_do_not_visit) { + if (value.data == .e_string and value.data.e_string.isUTF8() and value.data.e_string.isPresent()) { + const import_record_index = p.addImportRecord(.dynamic, value.loc, value.data.e_string.slice(p.allocator)); + + return p.e(E.Import{ + .expr = value, + .leading_interior_comments = comments, + .import_record_index = import_record_index, + }, loc); + } + } + + return p.e(E.Import{ .expr = value, .leading_interior_comments = comments, .import_record_index = 0 }, loc); + } + + fn parseJSXPropValueIdentifier(p: *P, previous_string_with_backslash_loc: *logger.Loc) !Expr { + // Use NextInsideJSXElement() not Next() so we can parse a JSX-style string literal + try p.lexer.nextInsideJSXElement(); + if (p.lexer.token == .t_string_literal) { + previous_string_with_backslash_loc.start = std.math.max(p.lexer.loc().start, p.lexer.previous_backslash_quote_in_jsx.loc.start); + const expr = p.e(p.lexer.toEString(), previous_string_with_backslash_loc.*); + + try p.lexer.nextInsideJSXElement(); + return expr; + } else { + // Use Expect() not ExpectInsideJSXElement() so we can parse expression tokens + try p.lexer.expect(.t_open_brace); + const value = try p.parseExpr(.lowest); + + try p.lexer.expectInsideJSXElement(.t_close_brace); + return value; + } + } + + fn parseJSXElement(p: *P, loc: logger.Loc) anyerror!Expr { + if (only_scan_imports_and_do_not_visit) { + p.needs_jsx_import = true; + } + + var tag = try JSXTag.parse(P, p); + + // The tag may have TypeScript type arguments: "<Foo<T>/>" + if (is_typescript_enabled) { + // Pass a flag to the type argument skipper because we need to call + _ = try p.skipTypeScriptTypeArguments(true); + } + + var previous_string_with_backslash_loc = logger.Loc{}; + var properties = G.Property.List{}; + var key_prop: ?ExprNodeIndex = null; + var flags = Flags.JSXElement.Bitset{}; + var start_tag: ?ExprNodeIndex = null; + + // Fragments don't have props + // Fragments of the form "React.Fragment" are not parsed as fragments. + if (@as(JSXTag.TagType, tag.data) == .tag) { + start_tag = tag.data.tag; + var spread_loc: logger.Loc = logger.Loc.Empty; + var props = ListManaged(G.Property).init(p.allocator); + var key_prop_i: i32 = -1; + var spread_prop_i: i32 = -1; + var i: i32 = 0; + parse_attributes: while (true) { + switch (p.lexer.token) { + .t_identifier => { + defer i += 1; + // Parse the prop name + var key_range = p.lexer.range(); + const prop_name_literal = p.lexer.identifier; + const special_prop = E.JSXElement.SpecialProp.Map.get(prop_name_literal) orelse E.JSXElement.SpecialProp.any; + try p.lexer.nextInsideJSXElement(); + + if (special_prop == .key) { + + // <ListItem key> + if (p.lexer.token != .t_equals) { + // Unlike Babel, we're going to just warn here and move on. + try p.log.addWarning(p.source, key_range.loc, "\"key\" prop ignored. Must be a string, number or symbol."); + continue; + } + + key_prop_i = i; + key_prop = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc); + continue; + } + + const prop_name = p.e(E.String{ .data = prop_name_literal }, key_range.loc); + + // Parse the value + var value: Expr = undefined; + if (p.lexer.token != .t_equals) { + + // Implicitly true value + // <button selected> + value = p.e(E.Boolean{ .value = true }, logger.Loc{ .start = key_range.loc.start + key_range.len }); + } else { + value = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc); + if (comptime jsx_transform_type == .solid) { + switch (value.knownPrimitive()) { + .unknown => { + flags.insert(.has_any_dynamic); + }, + else => {}, + } + } + } + + try props.append(G.Property{ .key = prop_name, .value = value }); + }, + .t_open_brace => { + defer i += 1; + // Use Next() not ExpectInsideJSXElement() so we can parse "..." + try p.lexer.next(); + + switch (p.lexer.token) { + .t_dot_dot_dot => { + try p.lexer.next(); + + spread_prop_i = i; + spread_loc = p.lexer.loc(); + try props.append(G.Property{ .value = try p.parseExpr(.comma), .kind = .spread }); + }, + // This implements + // <div {foo} /> + // -> + // <div foo={foo} /> + T.t_identifier => { + // we need to figure out what the key they mean is + // to do that, we must determine the key name + const expr = try p.parseExpr(Level.lowest); + + const key = brk: { + switch (expr.data) { + .e_import_identifier => |ident| { + break :brk p.e(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); + }, + .e_identifier => |ident| { + break :brk p.e(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); + }, + .e_dot => |dot| { + break :brk p.e(E.String{ .data = dot.name }, dot.name_loc); + }, + .e_index => |index| { + if (index.index.data == .e_string) { + break :brk index.index; + } + }, + else => {}, + } + + // If we get here, it's invalid + try p.log.addError(p.source, expr.loc, "Invalid JSX prop shorthand, must be identifier, dot or string"); + return error.SyntaxError; + }; + + if (comptime jsx_transform_type == .solid) { + switch (expr.knownPrimitive()) { + .unknown => { + flags.insert(.has_any_dynamic); + }, + else => {}, + } + } + + try props.append(G.Property{ .value = expr, .key = key, .kind = .normal }); + }, + // This implements + // <div {"foo"} /> + // <div {'foo'} /> + // -> + // <div foo="foo" /> + // note: template literals are not supported, operations on strings are not supported either + T.t_string_literal => { + const key = p.e(p.lexer.toEString(), p.lexer.loc()); + try p.lexer.next(); + try props.append(G.Property{ .value = key, .key = key, .kind = .normal }); + }, + + else => try p.lexer.unexpected(), + } + + try p.lexer.nextInsideJSXElement(); + }, + else => { + break :parse_attributes; + }, + } + } + + const is_key_before_rest = key_prop_i > -1 and spread_prop_i > key_prop_i; + flags.setPresent(.is_key_before_rest, is_key_before_rest); + if (is_key_before_rest and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) { + try p.log.addWarning(p.source, spread_loc, "\"key\" prop before a {...spread} is deprecated in JSX. Falling back to classic runtime."); + p.has_classic_runtime_warned = true; + } + properties = G.Property.List.fromList(props); + } + + // People sometimes try to use the output of "JSON.stringify()" as a JSX + // attribute when automatically-generating JSX code. Doing so is incorrect + // because JSX strings work like XML instead of like JS (since JSX is XML-in- + // JS). Specifically, using a backslash before a quote does not cause it to + // be escaped: + // + // JSX ends the "content" attribute here and sets "content" to 'some so-called \\' + // v + // <Button content="some so-called \"button text\"" /> + // ^ + // There is no "=" after the JSX attribute "text", so we expect a ">" + // + // This code special-cases this error to provide a less obscure error message. + if (p.lexer.token == .t_syntax_error and strings.eqlComptime(p.lexer.raw(), "\\") and previous_string_with_backslash_loc.start > 0) { + const r = p.lexer.range(); + // Not dealing with this right now. + try p.log.addRangeError(p.source, r, "Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead"); + return error.SyntaxError; + } + + // A slash here is a self-closing element + if (p.lexer.token == .t_slash) { + const close_tag_loc = p.lexer.loc(); + // Use NextInsideJSXElement() not Next() so we can parse ">>" as ">" + + try p.lexer.nextInsideJSXElement(); + + if (p.lexer.token != .t_greater_than) { + try p.lexer.expected(.t_greater_than); + } + + return p.e(E.JSXElement{ + .tag = start_tag, + .properties = properties, + .key = key_prop, + .flags = flags, + .close_tag_loc = close_tag_loc, + }, loc); + } + + // Use ExpectJSXElementChild() so we parse child strings + try p.lexer.expectJSXElementChild(.t_greater_than); + var children = ListManaged(Expr).init(p.allocator); + // var last_element_i: usize = 0; + + while (true) { + switch (p.lexer.token) { + .t_string_literal => { + try children.append(p.e(p.lexer.toEString(), loc)); + try p.lexer.nextJSXElementChild(); + }, + .t_open_brace => { + // Use Next() instead of NextJSXElementChild() here since the next token is an expression + try p.lexer.next(); + + // The "..." here is ignored (it's used to signal an array type in TypeScript) + if (p.lexer.token == .t_dot_dot_dot and is_typescript_enabled) { + try p.lexer.next(); + } + + // The expression is optional, and may be absent + if (p.lexer.token != .t_close_brace) { + if (comptime jsx_transform_type == .solid) { + const child = try p.parseExpr(.lowest); + switch (child.knownPrimitive()) { + .unknown => { + flags.insert(.has_any_dynamic); + }, + else => {}, + } + try children.append(child); + } else { + try children.append(try p.parseExpr(.lowest)); + } + } + + // Use ExpectJSXElementChild() so we parse child strings + try p.lexer.expectJSXElementChild(.t_close_brace); + }, + .t_less_than => { + const less_than_loc = p.lexer.loc(); + try p.lexer.nextInsideJSXElement(); + + if (p.lexer.token != .t_slash) { + // This is a child element + const child = try p.parseJSXElement(less_than_loc); + if (comptime jsx_transform_type == .solid) { + // if (!flags.contains(.has_dynamic_children)) { + // if (@as(Expr.Tag, child.data) == .e_jsx_element) { + // if (child.data.e_jsx_element.flags.contains(.has_dynamic_children) or child.data.e_jsx_element.flags.contains(.has_dynamic_prop)) { + // flags.insert(.has_dynamic_children); + + // } + // } else { + // switch (child.knownPrimitive()) { + // .unknown => { + // flags.insert(.has_dynamic_children); + // }, + // else => {}, + // } + // } + // } + + if (!flags.contains(.has_any_dynamic)) { + if (@as(Expr.Tag, child.data) == .e_jsx_element) { + if (child.data.e_jsx_element.flags.contains(.has_any_dynamic)) { + flags.insert(.has_any_dynamic); + } + } else { + switch (child.knownPrimitive()) { + .unknown => { + flags.insert(.has_any_dynamic); + }, + else => {}, + } + } + } + + children.append(child) catch unreachable; + } else { + children.append(p.parseJSXElement(less_than_loc) catch unreachable) catch unreachable; + } + + // The call to parseJSXElement() above doesn't consume the last + // TGreaterThan because the caller knows what Next() function to call. + // Use NextJSXElementChild() here since the next token is an element + // child. + try p.lexer.nextJSXElementChild(); + continue; + } + + // This is the closing element + try p.lexer.nextInsideJSXElement(); + const end_tag = try JSXTag.parse(P, p); + + if (!strings.eql(end_tag.name, tag.name)) { + try p.log.addRangeErrorFmt(p.source, end_tag.range, p.allocator, "Expected closing tag </{s}> to match opening tag <{s}>", .{ + end_tag.name, + tag.name, + }); + return error.SyntaxError; + } + + if (p.lexer.token != .t_greater_than) { + try p.lexer.expected(.t_greater_than); + } + + return p.e(E.JSXElement{ + .tag = end_tag.data.asExpr(), + .children = ExprNodeList.fromList(children), + .properties = properties, + .key = key_prop, + .flags = flags, + .close_tag_loc = end_tag.range.loc, + }, loc); + }, + else => { + try p.lexer.unexpected(); + return error.SyntaxError; + }, + } + } + } + + fn willNeedBindingPattern(p: *P) bool { + return switch (p.lexer.token) { + // "[a] = b;" + .t_equals => true, + // "for ([a] in b) {}" + .t_in => !p.allow_in, + // "for ([a] of b) {}" + .t_identifier => !p.allow_in and p.lexer.isContextualKeyword("of"), + else => false, + }; + } + + fn appendPart(p: *P, parts: *ListManaged(js_ast.Part), stmts: []Stmt) !void { + // Reuse the memory if possible + // This is reusable if the last part turned out to be dead + p.symbol_uses.clearRetainingCapacity(); + p.declared_symbols.clearRetainingCapacity(); + p.scopes_for_current_part.clearRetainingCapacity(); + p.import_records_for_current_part.clearRetainingCapacity(); + + const allocator = p.allocator; + var opts = PrependTempRefsOpts{}; + var partStmts = ListManaged(Stmt).fromOwnedSlice(allocator, stmts); + try p.visitStmtsAndPrependTempRefs(&partStmts, &opts); + + // Insert any relocated variable statements now + if (p.relocated_top_level_vars.items.len > 0) { + var already_declared = RefMap{}; + var already_declared_allocator_stack = std.heap.stackFallback(1024, allocator); + var already_declared_allocator = already_declared_allocator_stack.get(); + defer if (already_declared_allocator_stack.fixed_buffer_allocator.end_index >= 1023) already_declared.deinit(already_declared_allocator); + + for (p.relocated_top_level_vars.items) |*local| { + // Follow links because "var" declarations may be merged due to hoisting + while (local.ref != null) { + const link = p.symbols.items[local.ref.?.innerIndex()].link; + if (link.isNull()) { + break; + } + local.ref = link; + } + const ref = local.ref orelse continue; + var declaration_entry = try already_declared.getOrPut(already_declared_allocator, ref); + if (!declaration_entry.found_existing) { + const decls = try allocator.alloc(G.Decl, 1); + decls[0] = Decl{ + .binding = p.b(B.Identifier{ .ref = ref }, local.loc), + }; + try partStmts.append(p.s(S.Local{ .decls = decls }, local.loc)); + } + } + p.relocated_top_level_vars.clearRetainingCapacity(); + + // Follow links because "var" declarations may be merged due to hoisting + + // while (true) { + // const link = p.symbols.items[local.ref.innerIndex()].link; + // } + } + + if (partStmts.items.len > 0) { + const _stmts = partStmts.toOwnedSlice(); + + try parts.append(js_ast.Part{ + .stmts = _stmts, + .symbol_uses = p.symbol_uses, + .declared_symbols = p.declared_symbols.toOwnedSlice( + p.allocator, + ), + .import_record_indices = p.import_records_for_current_part.toOwnedSlice( + p.allocator, + ), + .scopes = p.scopes_for_current_part.toOwnedSlice(p.allocator), + .can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts), + }); + p.symbol_uses = .{}; + } else if (p.declared_symbols.items.len > 0 or p.symbol_uses.count() > 0) { + // if the part is dead, invalidate all the usage counts + p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols.items, .symbol_uses = p.symbol_uses }); + } + } + + fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool { + switch (binding.data) { + .b_array => |bi| { + for (bi.items) |*item| { + if (!p.bindingCanBeRemovedIfUnused(item.binding)) { + return false; + } + + if (item.default_value) |*default| { + if (!p.exprCanBeRemovedIfUnused(default)) { + return false; + } + } + } + }, + .b_object => |bi| { + for (bi.properties) |*property| { + if (!property.flags.contains(.is_spread) and !p.exprCanBeRemovedIfUnused(&property.key)) { + return false; + } + + if (!p.bindingCanBeRemovedIfUnused(property.value)) { + return false; + } + + if (property.default_value) |*default| { + if (!p.exprCanBeRemovedIfUnused(default)) { + return false; + } + } + } + }, + else => {}, + } + + return true; + } + + fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool { + for (stmts) |stmt| { + switch (stmt.data) { + // These never have side effects + .s_function, .s_empty => {}, + + // Let these be removed if they are unused. Note that we also need to + // check if the imported file is marked as "sideEffects: false" before we + // can remove a SImport statement. Otherwise the import must be kept for + // its side effects. + .s_import => {}, + .s_class => |st| { + if (!p.classCanBeRemovedIfUnused(&st.class)) { + return false; + } + }, + .s_expr => |st| { + if (st.does_not_affect_tree_shaking) { + // Expressions marked with this are automatically generated and have + // no side effects by construction. + break; + } + + if (!p.exprCanBeRemovedIfUnused(&st.value)) { + return false; + } + }, + .s_local => |st| { + for (st.decls) |*decl| { + if (!p.bindingCanBeRemovedIfUnused(decl.binding)) { + return false; + } + + if (decl.value) |*decl_value| { + if (!p.exprCanBeRemovedIfUnused(decl_value)) { + return false; + } + } + } + }, + + .s_try => |try_| { + if (!p.stmtsCanBeRemovedIfUnused(try_.body) or (try_.finally != null and !p.stmtsCanBeRemovedIfUnused(try_.finally.?.stmts))) { + return false; + } + }, + + // Exports are tracked separately, so this isn't necessary + .s_export_clause, .s_export_from => {}, + + .s_export_default => |st| { + switch (st.value) { + .stmt => |s2| { + switch (s2.data) { + .s_expr => |s_expr| { + if (!p.exprCanBeRemovedIfUnused(&s_expr.value)) { + return false; + } + }, + + // These never have side effects + .s_function => {}, + + .s_class => { + if (!p.classCanBeRemovedIfUnused(&s2.data.s_class.class)) { + return false; + } + }, + else => { + Global.panic("Unexpected type in export default: {s}", .{s2}); + }, + } + }, + .expr => |*exp| { + if (!p.exprCanBeRemovedIfUnused(exp)) { + return false; + } + }, + } + }, + else => { + return false; + }, + } + } + + return true; + } + + fn visitStmtsAndPrependTempRefs(p: *P, stmts: *ListManaged(Stmt), opts: *PrependTempRefsOpts) !void { + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + + p.temp_refs_to_declare.deinit(p.allocator); + p.temp_refs_to_declare = @TypeOf(p.temp_refs_to_declare){}; + p.temp_ref_count = 0; + + try p.visitStmts(stmts, opts.kind); + + // Prepend values for "this" and "arguments" + if (opts.fn_body_loc != null) { + // Capture "this" + if (p.fn_only_data_visit.this_capture_ref) |ref| { + try p.temp_refs_to_declare.append(p.allocator, TempRef{ + .ref = ref, + .value = p.e(E.This{}, opts.fn_body_loc orelse p.panic("Internal error: Expected opts.fn_body_loc to exist", .{})), + }); + } + } + } + + fn recordDeclaredSymbol(p: *P, ref: Ref) !void { + try p.declared_symbols.append(p.allocator, js_ast.DeclaredSymbol{ + .ref = ref, + .is_top_level = p.current_scope == p.module_scope, + }); + } + + // public for JSNode.JSXWriter usage + pub fn visitExpr(p: *P, expr: Expr) Expr { + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + // Inline to avoid the extra unnecessary function call in the stack + return @call(.{ .modifier = .always_inline }, P.visitExprInOut, .{ p, expr, ExprIn{} }); + } + + fn visitFunc(p: *P, _func: G.Fn, open_parens_loc: logger.Loc) G.Fn { + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + + var func = _func; + const old_fn_or_arrow_data = p.fn_or_arrow_data_visit; + const old_fn_only_data = p.fn_only_data_visit; + p.fn_or_arrow_data_visit = FnOrArrowDataVisit{ .is_async = func.flags.contains(.is_async) }; + p.fn_only_data_visit = FnOnlyDataVisit{ .is_this_nested = true, .arguments_ref = func.arguments_ref }; + + if (func.name) |name| { + if (name.ref) |name_ref| { + p.recordDeclaredSymbol(name_ref) catch unreachable; + const symbol_name = p.loadNameFromRef(name_ref); + if (isEvalOrArguments(symbol_name)) { + p.markStrictModeFeature(.eval_or_arguments, js_lexer.rangeOfIdentifier(p.source, name.loc), symbol_name) catch unreachable; + } + } + } + + const body = func.body; + + p.pushScopeForVisitPass(.function_args, open_parens_loc) catch unreachable; + p.visitArgs( + func.args, + VisitArgsOpts{ + .has_rest_arg = func.flags.contains(.has_rest_arg), + .body = body.stmts, + .is_unique_formal_parameters = true, + }, + ); + + p.pushScopeForVisitPass(.function_body, body.loc) catch unreachable; + var stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, body.stmts); + var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body, .fn_body_loc = body.loc }; + p.visitStmtsAndPrependTempRefs(&stmts, &temp_opts) catch unreachable; + func.body = G.FnBody{ .stmts = stmts.toOwnedSlice(), .loc = body.loc }; + + p.popScope(); + p.popScope(); + + p.fn_or_arrow_data_visit = old_fn_or_arrow_data; + p.fn_only_data_visit = old_fn_only_data; + return func; + } + + fn maybeKeepExprSymbolName(p: *P, expr: Expr, original_name: string, was_anonymous_named_expr: bool) Expr { + return if (was_anonymous_named_expr) p.keepExprSymbolName(expr, original_name) else expr; + } + + fn valueForThis(p: *P, loc: logger.Loc) ?Expr { + // Substitute "this" if we're inside a static class property initializer + if (p.fn_only_data_visit.this_class_static_ref) |ref| { + p.recordUsage(ref); + return p.e(E.Identifier{ .ref = ref }, loc); + } + + // oroigianlly was !=- modepassthrough + if (!p.fn_only_data_visit.is_this_nested) { + if (p.has_es_module_syntax) { + // In an ES6 module, "this" is supposed to be undefined. Instead of + // doing this at runtime using "fn.call(undefined)", we do it at + // compile time using expression substitution here. + return Expr{ .loc = loc, .data = nullValueExpr }; + } else { + // In a CommonJS module, "this" is supposed to be the same as "exports". + // Instead of doing this at runtime using "fn.call(module.exports)", we + // do it at compile time using expression substitution here. + p.recordUsage(p.exports_ref); + return p.e(E.Identifier{ .ref = p.exports_ref }, loc); + } + } + + return null; + } + + fn isValidAssignmentTarget(p: *P, expr: Expr) bool { + return switch (expr.data) { + .e_identifier => |ident| !isEvalOrArguments(p.loadNameFromRef(ident.ref)), + .e_dot => |e| e.optional_chain == null, + .e_index => |e| e.optional_chain == null, + .e_array => |e| !e.is_parenthesized, + .e_object => |e| !e.is_parenthesized, + else => false, + }; + } + + fn visitExprInOut(p: *P, expr: Expr, in: ExprIn) Expr { + if (in.assign_target != .none and !p.isValidAssignmentTarget(expr)) { + p.log.addError(p.source, expr.loc, "Invalid assignment target") catch unreachable; + } + + // Output.print("\nVisit: {s} - {d}\n", .{ @tagName(expr.data), expr.loc.start }); + switch (expr.data) { + .e_null, .e_super, .e_boolean, .e_big_int, .e_reg_exp, .e_undefined => {}, + + .e_new_target => |_| { + // this error is not necessary and it is causing breakages + // if (!p.fn_only_data_visit.is_new_target_allowed) { + // p.log.addRangeError(p.source, target.range, "Cannot use \"new.target\" here") catch unreachable; + // } + }, + + .e_string => { + + // If you're using this, you're probably not using 0-prefixed legacy octal notation + // if e.LegacyOctalLoc.Start > 0 { + }, + .e_number => { + + // idc about legacy octal loc + }, + .e_this => { + if (p.valueForThis(expr.loc)) |exp| { + return exp; + } + + // // Capture "this" inside arrow functions that will be lowered into normal + // // function expressions for older language environments + // if p.fnOrArrowDataVisit.isArrow && p.options.unsupportedJSFeatures.Has(compat.Arrow) && p.fnOnlyDataVisit.isThisNested { + // return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{} + // } + }, + + .e_import_meta => { + // TODO: delete import.meta might not work + const is_delete_target = std.meta.activeTag(p.delete_target) == .e_import_meta; + + if (p.define.dots.get("meta")) |meta| { + for (meta) |define| { + if (p.isDotDefineMatch(expr, define.parts)) { + // Substitute user-specified defines + return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); + } + } + } + + if (!p.import_meta_ref.isNull()) { + p.recordUsage(p.import_meta_ref); + return p.e(E.Identifier{ .ref = p.import_meta_ref }, expr.loc); + } + }, + .e_spread => |exp| { + exp.value = p.visitExpr(exp.value); + }, + .e_identifier => { + var e_ = expr.data.e_identifier; + const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_identifier and expr.data.e_identifier.ref.eql(p.delete_target.e_identifier.ref); + + const name = p.loadNameFromRef(e_.ref); + if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) { + p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, expr.loc), name) catch unreachable; + } + + const result = p.findSymbol(expr.loc, name) catch unreachable; + + e_.must_keep_due_to_with_stmt = result.is_inside_with_scope; + e_.ref = result.ref; + + // TODO: fix the underyling cause here + // The problem seems to be that result.ref.innerIndex() is not always set. + + // Handle assigning to a constant + // if (in.assign_target != .none and p.symbols.items[result.ref.innerIndex()].kind == .cconst) { + // const r = js_lexer.rangeOfIdentifier(p.source, expr.loc); + // p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot assign to {s} because it is a constant", .{name}) catch unreachable; + // } + + var original_name: ?string = null; + + // Substitute user-specified defines for unbound symbols + if (p.symbols.items[e_.ref.innerIndex()].kind == .unbound and !result.is_inside_with_scope and !is_delete_target) { + if (p.define.identifiers.get(name)) |def| { + if (!def.isUndefined()) { + const newvalue = p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &def); + + // Don't substitute an identifier for a non-identifier if this is an + // assignment target, since it'll cause a syntax error + if (@as(Expr.Tag, newvalue.data) == .e_identifier or in.assign_target == .none) { + return newvalue; + } + + original_name = def.original_name; + } + + // Copy the side effect flags over in case this expression is unused + if (def.can_be_removed_if_unused) { + e_.can_be_removed_if_unused = true; + } + if (def.call_can_be_unwrapped_if_unused and !p.options.ignore_dce_annotations) { + e_.call_can_be_unwrapped_if_unused = true; + } + } + } + + return p.handleIdentifier(expr.loc, e_, original_name, IdentifierOpts{ + .assign_target = in.assign_target, + .is_delete_target = is_delete_target, + .was_originally_identifier = true, + }); + }, + + .e_jsx_element => |e_| { + switch (comptime jsx_transform_type) { + .macro => { + const WriterType = js_ast.Macro.JSNode.NewJSXWriter(P); + var writer = WriterType.initWriter(p, &BunJSX.bun_jsx_identifier); + return writer.writeFunctionCall(e_.*); + }, + .solid => { + // The rules: + // 1. HTML string literals of static JSX elements are generated & escaped, injected at the top of the file + // 1a. Static elements are contiguous in the HTML, but dynamic elements get a marker string during if client-side hydration + // Each element becomes a declaration in the top-level scope of the JSX expression (i.e. either the anonymous IIFE or an array) + // Those elements may be markers + // The final count of the markers is passed to the template function + // 3. The first element in a a group of elements becomes .cloneNode(true) + // Subsequent elements call .nextSibling on the previous element. + // The specific function differs if SSR is enabled and if client-side hydration is enabled. + // 4. Non-static JSX children are added like this: + // insert(topElement, createComponent(MyComponent, props), markerElement) + // 5. Non-statically analyzable attributes are added like this: + // setAttribute(topElement, "data-foo", "bar") + // 6. + var solid = &p.solid; + const old_is_in_jsx_component = solid.is_in_jsx_component; + solid.is_in_jsx_component = true; + defer solid.is_in_jsx_component = old_is_in_jsx_component; + + if (!old_is_in_jsx_component) { + solid.current_template_string.reset(); + solid.buffered_writer.pos = 0; + solid.component_body.clearRetainingCapacity(); + solid.component_body_decls.clearRetainingCapacity(); + + // prepend an empty statement + // this will later become an S.Local for the decls + solid.component_body.append(p.allocator, p.s(S.Empty{}, expr.loc)) catch unreachable; + + solid.last_element_id = E.Identifier{}; + solid.prev_scope = p.current_scope; + solid.temporary_scope.reset(); + solid.node_count = 0; + solid.temporary_scope.kind = .function_body; + solid.temporary_scope.parent = p.current_scope; + + solid.last_template_id.ref = Ref.None; + } + + var writer = &solid.buffered_writer; + + // The JSX tag used + const tag: Expr = tagger: { + if (e_.tag) |_tag| { + break :tagger p.visitExpr(_tag); + } else { + break :tagger p.e(E.Array{}, expr.loc); + } + }; + + const jsx_props = e_.properties.slice(); + + var template_expression = Expr{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; + var element: ?E.Identifier = null; + var needs_end_bracket = false; + var children = e_.children.slice(); + defer { + if (old_is_in_jsx_component) { + if (element) |el| { + solid.last_element_id = el; + } + } + } + switch (tag.data) { + .e_string => { + // write the template + _ = writer.writeAll("<") catch unreachable; + _ = writer.writeString(tag.data.e_string) catch unreachable; + needs_end_bracket = true; + + var wrote_anything = false; + for (jsx_props) |*property, i| { + if (property.kind != .spread) { + property.key = p.visitExpr(e_.properties.ptr[i].key.?); + } + + if (property.value != null) { + property.value = p.visitExpr(e_.properties.ptr[i].value.?); + + if (property.kind != .spread) { + var key = property.key.?.data.e_string; + + const is_event_listener = key.hasPrefixComptime("on:"); + const is_class = !is_event_listener and + // TODO: should this be case-insensitive? + (key.eqlComptime("class") or key.eqlComptime("className")); + + const primitive = property.value.?.knownPrimitive(); + const is_dynamic = !primitive.isStatic(); + const appears_in_template = !is_event_listener and !is_dynamic; + if (appears_in_template) { + _ = writer.writeAll(" ") catch unreachable; + wrote_anything = true; + } + + if (is_class and !is_dynamic) { + _ = writer.writeAll("class") catch unreachable; + } else if (!is_dynamic) { + _ = writer.writeString(key) catch unreachable; + } + if (appears_in_template) { + switch (primitive) { + .number, .string => { + if (property.value.?.data == .e_string) { + const str = property.value.?.data.e_string; + if (str.len() > 0) { + _ = writer.writeAll("=") catch unreachable; + writer.writeHTMLAttributeValueString(str) catch unreachable; + } + } else { + writer.writer().print("={d}", .{property.value.?.data.e_number.value}) catch unreachable; + } + }, + // TODO: should "null" be written? + // TODO: should "undefined" be written? + .@"null", .@"undefined" => {}, + .boolean => { + // existence of an HTML attribute implicitly is true + // so we only need to write if it's false + if (!property.value.?.data.e_boolean.value) { + // TODO: verify that SolidJS writes the same value + _ = writer.writeAll("=false") catch unreachable; + } + }, + + else => unreachable, + } + } else { + if (template_expression.data.e_identifier.ref.isNull()) { + var new_template_name = solid.generateTemplateName(p.allocator); + // declare the template in the module scope + p.current_scope = p.module_scope; + solid.last_template_id = .{ + .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; + p.current_scope = solid.prev_scope.?; + template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; + } + if (element == null) { + element = solid.generateElement( + p, + template_expression, + property.value.?.loc, + ) catch unreachable; + } + + var stmt: Stmt = undefined; + if (!is_event_listener) { + var args = p.allocator.alloc(Expr, 4) catch unreachable; + args[0] = p.e(element.?, expr.loc); + if (is_class) { + args[1] = p.e(E.String.init("className"), property.key.?.loc); + } else { + args[1] = property.key.?; + } + + args[2] = property.value.?; + + // setAttribute(template_expression, key, value); + const setAttr = p.e( + E.Call{ + .target = p.e( + E.Identifier{ + .ref = solid.setAttribute.ref, + .can_be_removed_if_unused = false, + .call_can_be_unwrapped_if_unused = false, + }, + property.value.?.loc, + ), + .args = ExprNodeList.init(args[0..3]), + }, + property.key.?.loc, + ); + + p.recordUsage(solid.setAttribute.ref); + if (args[2].data == .e_identifier or args[2].data == .e_import_identifier) { + if (args[2].data == .e_identifier) p.recordUsage(args[2].data.e_identifier.ref); + if (args[2].data == .e_import_identifier) p.recordUsage(args[2].data.e_import_identifier.ref); + stmt = p.s(S.SExpr{ .value = setAttr }, property.value.?.loc); + } else { + var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + stmts[0] = p.s(S.Return{ .value = setAttr }, property.value.?.loc); + + args[3] = p.e( + E.Arrow{ + .args = &[_]G.Arg{}, + .body = G.FnBody{ + .stmts = stmts, + .loc = args[2].loc, + }, + }, + property.value.?.loc, + ); + stmt = p.s(S.SExpr{ + .value = p.e( + E.Call{ + .target = p.e( + E.Identifier{ + .ref = solid.effect.ref, + }, + ), + .args = ExprNodeList.init(args[3..4]), + }, + ), + }, property.value.?.loc); + } + } else { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + + // on:MyEvent => MyEvent + property.key.?.data.e_string.data = property.key.?.data.e_string.data[3..]; + args[0] = property.key.?; + args[1] = property.value.?; + // $element.addEventListener("MyEvent", (e) => { ... }); + const addEventListener = p.e( + E.Call{ + .target = p.e( + E.Dot{ + .target = p.e( + element.?, + expr.loc, + ), + .name = "addEventListener", + .name_loc = property.key.?.loc, + }, + property.key.?.loc, + ), + .args = ExprNodeList.init(args), + }, + property.key.?.loc, + ); + + p.recordUsage(element.?.ref); + stmt = p.s(S.SExpr{ .value = addEventListener }, property.value.?.loc); + } + + solid.component_body.append(p.allocator, stmt) catch unreachable; + } + } else {} + } + + if (property.initializer != null) { + property.initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); + } + } + + var wrote_any_children = false; + for (children) |*el, k| { + if (needs_end_bracket and el.data == .e_jsx_element) { + _ = writer.writeAll(">") catch unreachable; + solid.node_count += 1; + + needs_end_bracket = false; + } + + const child = p.visitExpr(el.*); + switch (child.data) { + // skip it + .e_missing => {}, + + // we need to serialize it to HTML + // it's probably a text node + .e_string => |str| { + if (str.len() > 0) { + if (needs_end_bracket) { + _ = writer.writeAll(">") catch unreachable; + solid.node_count += 1; + needs_end_bracket = false; + } + writer.writeHTMLAttributeValueString(str) catch unreachable; + wrote_any_children = true; + } + }, + .e_number => |str| { + if (needs_end_bracket) { + _ = writer.writeAll(">") catch unreachable; + needs_end_bracket = false; + } + writer.writer().print("{d}", .{str.value}) catch unreachable; + wrote_any_children = true; + }, + + // debug assertion that we don't get here + .e_jsx_element => unreachable, + + else => { + if (template_expression.data.e_identifier.ref.isNull()) { + var new_template_name = solid.generateTemplateName(p.allocator); + // declare the template in the module scope + p.current_scope = p.module_scope; + solid.last_template_id = .{ + .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; + p.current_scope = solid.prev_scope.?; + template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; + } + p.recordUsage(solid.insert.ref); + p.recordUsage(template_expression.data.e_identifier.ref); + var args = p.allocator.alloc(Expr, 3) catch unreachable; + args[0] = template_expression; + args[1] = child; + args[2] = if (k != children.len - 1 and !solid.last_element_id.ref.eql(Ref.None)) + p.e(solid.last_element_id, expr.loc) + else + p.e(E.Null{}, expr.loc); + solid.node_count += 1; + solid.component_body.append( + p.allocator, + p.s( + S.SExpr{ + .value = p.e( + E.Call{ + .target = p.e(E.ImportIdentifier{ .ref = solid.insert.ref }, child.loc), + .args = ExprNodeList.init(args), + }, + child.loc, + ), + }, + child.loc, + ), + ) catch unreachable; + }, + } + } + + if (wrote_any_children) { + solid.node_count += 1; + _ = writer.writeAll("</") catch unreachable; + _ = writer.writeString(tag.data.e_string) catch unreachable; + _ = writer.writeAll(">") catch unreachable; + } else if (needs_end_bracket) { + _ = writer.writeAll("/>") catch unreachable; + } + + // this is the root of a template tag, we just finished + // <div> + // /* some stuff in here */ + // </div> + // ^ + // we are here! + if (!old_is_in_jsx_component) { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + + // we are done, so it's time to turn our template into a string we can write + // note that we are writing as UTF-8 but the input may be UTF-16 or UTF-8, depending. + if (writer.pos < writer.buffer.len and writer.context.list.items.len == 0) { + args[0] = p.e(E.String.init(p.allocator.dupe(u8, writer.buffer[0..writer.pos]) catch unreachable), expr.loc); + } else if (writer.pos == 0 and writer.context.list.items.len == 0) { + args[0] = p.e(E.String.init(""), expr.loc); + } else { + const total = writer.context.list.items.len + writer.pos; + var buffer = p.allocator.alloc(u8, total) catch unreachable; + @memcpy(buffer.ptr, writer.context.list.items.ptr, writer.context.list.items.len); + @memcpy(buffer.ptr + writer.context.list.items.len, &writer.buffer, writer.buffer.len); + args[0] = p.e(E.String.init(buffer), expr.loc); + } + + args[1] = p.e(E.Number{ .value = @intToFloat(f64, solid.node_count) }, expr.loc); + solid.node_count = 0; + + if (template_expression.data.e_identifier.ref.isNull()) { + var new_template_name = solid.generateTemplateName(p.allocator); + // declare the template in the module scope + p.current_scope = p.module_scope; + solid.last_template_id = .{ + .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; + p.current_scope = solid.prev_scope.?; + template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; + } + + solid.template_decls.append( + p.allocator, + G.Decl{ + .binding = p.b(B.Identifier{ .ref = template_expression.data.e_identifier.ref }, template_expression.loc), + .value = p.e( + E.Call{ + .args = ExprNodeList.init(args), + .target = p.e( + E.ImportIdentifier{ + .ref = solid.template.ref, + }, + expr.loc, + ), + .can_be_unwrapped_if_unused = true, + }, + template_expression.loc, + ), + }, + ) catch unreachable; + p.recordUsage(solid.template.ref); + + if (p.is_control_flow_dead) { + return p.e(E.Missing{}, expr.loc); + } + + // 1 means it was actually static + // that means we can just turn it into a single $template.cloneNode(true) + if (solid.component_body.items.len == 1) { + return p.e(E.Call{ + .target = p.e( + E.Dot{ + .name = "cloneNode", + .name_loc = expr.loc, + .target = template_expression, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + template_expression.loc, + ), + .args = ExprNodeList.init(true_args), + .can_be_unwrapped_if_unused = true, + }, expr.loc); + } + if (solid.component_body_decls.items.len == 0) { + solid.component_body_decls.ensureTotalCapacityPrecise(p.allocator, 1) catch unreachable; + solid.component_body_decls.appendAssumeCapacity(G.Decl{ + .binding = p.b(B.Identifier{ .ref = solid.last_template_id.ref }, expr.loc), + .value = p.e(E.Call{ + .target = p.e( + E.Dot{ + .name = "cloneNode", + .name_loc = expr.loc, + .target = template_expression, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + template_expression.loc, + ), + .args = ExprNodeList.init(true_args), + .can_be_unwrapped_if_unused = true, + }, expr.loc), + }); + } + + // we need to wrap the template in a function + const ret = p.e(E.Identifier{ .ref = solid.component_body_decls.items[0].binding.data.b_identifier.ref }, expr.loc); + solid.component_body.items[0] = p.s(S.Local{ .decls = solid.component_body_decls.toOwnedSlice(p.allocator) }, expr.loc); + solid.component_body.append(p.allocator, p.s(S.Return{ .value = ret }, expr.loc)) catch unreachable; + return p.e( + E.Arrow{ .args = &[_]G.Arg{}, .body = G.FnBody{ .stmts = solid.component_body.toOwnedSlice(p.allocator), .loc = expr.loc } }, + expr.loc, + ); + // we don't need to return anything because it's a static element that will live in the template + } else { + return p.e(E.Missing{}, expr.loc); + } + }, + .e_import_identifier, .e_identifier => { + var out_props = p.allocator.alloc(G.Property, jsx_props.len + @as(usize, @boolToInt(e_.key != null)) + @as(usize, @boolToInt(e_.children.len > 0))) catch unreachable; + var out_props_i: usize = 0; + for (jsx_props) |property, i| { + if (property.kind != .spread) { + e_.properties.ptr[i].key = p.visitExpr(e_.properties.ptr[i].key.?); + } + + if (property.value != null) { + e_.properties.ptr[i].value = p.visitExpr(e_.properties.ptr[i].value.?); + } + + if (property.initializer != null) { + e_.properties.ptr[i].initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); + } + + if (property.kind != .spread) { + const kind = if (property.value.?.data == .e_arrow or property.value.?.data == .e_function) G.Property.Kind.get else G.Property.Kind.normal; + out_props[out_props_i] = G.Property{ + .key = property.key, + .value = property.value, + .kind = kind, + }; + out_props_i += 1; + } + } + + if (e_.key) |k| { + const key = p.visitExpr(k); + if (key.data != .e_missing) { + const kind = if (key.data == .e_arrow or key.data == .e_function) Property.Kind.get else Property.Kind.normal; + out_props[out_props_i] = G.Property{ + .key = p.e(Prefill.String.Key, k.loc), + .value = key, + .kind = kind, + }; + out_props_i += 1; + } + } + + var out_child_i: usize = 0; + for (children) |child, j| { + children[j] = p.visitExpr(child); + if (children[j].data != .e_missing) { + children[out_child_i] = children[j]; + out_child_i += 1; + } + } + + if (out_child_i > 0) { + const kind = Property.Kind.get; + + out_props[out_props_i] = G.Property{ + .key = p.e(Prefill.String.Children, expr.loc), + .value = p.e(E.Array{ .items = ExprNodeList.init(children[0..out_child_i]) }, expr.loc), + .kind = kind, + }; + out_props_i += 1; + } + + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = tag; + args[1] = p.e(E.Object{ + .properties = G.Property.List.init(out_props[0..out_props_i]), + }, expr.loc); + p.recordUsage(solid.createComponent.ref); + return p.e( + E.Call{ + .target = p.e(E.ImportIdentifier{ .ref = solid.createComponent.ref }, expr.loc), + .args = ExprNodeList.init(args), + .close_paren_loc = e_.close_tag_loc, + }, + expr.loc, + ); + }, + .e_array => {}, + else => unreachable, + } + }, + .react => { + const tag: Expr = tagger: { + if (e_.tag) |_tag| { + break :tagger p.visitExpr(_tag); + } else { + break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref); + } + }; + + const jsx_props = e_.properties.slice(); + for (jsx_props) |property, i| { + if (property.kind != .spread) { + e_.properties.ptr[i].key = p.visitExpr(e_.properties.ptr[i].key.?); + } + + if (property.value != null) { + e_.properties.ptr[i].value = p.visitExpr(e_.properties.ptr[i].value.?); + } + + if (property.initializer != null) { + e_.properties.ptr[i].initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); + } + } + + if (e_.key) |key| { + e_.key = p.visitExpr(key); + } + + const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.contains(.is_key_before_rest)) options.JSX.Runtime.automatic else options.JSX.Runtime.classic; + var children_count = e_.children.len; + + const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.slice(p.allocator)); + + children_count = if (is_childless_tag) 0 else children_count; + + if (children_count != e_.children.len) { + // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. + // ^ from react-dom + p.log.addWarningFmt(p.source, tag.loc, p.allocator, "<{s} /> is a void element and must not have \"children\"", .{tag.data.e_string.slice(p.allocator)}) catch {}; + } + + // TODO: maybe we should split these into two different AST Nodes + // That would reduce the amount of allocations a little + switch (runtime) { + .classic => { + // Arguments to createElement() + const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable; + // There are at least two args: + // - name of the tag + // - props + var i: usize = 1; + args[0] = tag; + if (e_.properties.len > 0) { + if (e_.key) |key| { + var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable; + std.mem.copy(G.Property, props, e_.properties.slice()); + props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key }; + args[1] = p.e(E.Object{ .properties = G.Property.List.init(props) }, expr.loc); + } else { + args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc); + } + i = 2; + } else { + args[1] = p.e(E.Null{}, expr.loc); + i = 2; + } + + const children_elements = e_.children.slice()[0..children_count]; + for (children_elements) |child| { + args[i] = p.visitExpr(child); + i += @intCast(usize, @boolToInt(args[i].data != .e_missing)); + } + + // Call createElement() + return p.e(E.Call{ + .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref), + .args = ExprNodeList.init(args[0..i]), + // Enable tree shaking + .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + .close_paren_loc = e_.close_tag_loc, + }, expr.loc); + }, + // function jsxDEV(type, config, maybeKey, source, self) { + .automatic => { + // Either: + // jsxDEV(type, arguments, key, isStaticChildren, source, self) + // jsx(type, arguments, key) + const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; + const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable; + args[0] = tag; + const allocator = p.allocator; + var props = e_.properties.list(); + // arguments needs to be like + // { + // ...props, + // children: [el1, el2] + // } + + { + var last_child: u32 = 0; + var children = e_.children.slice()[0..children_count]; + for (children) |child| { + e_.children.ptr[last_child] = p.visitExpr(child); + // if tree-shaking removes the element, we must also remove it here. + last_child += @intCast(u32, @boolToInt(e_.children.ptr[last_child].data != .e_missing)); + } + e_.children.len = last_child; + } + + const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; + + // Babel defines static jsx as children.len > 1 + const is_static_jsx = e_.children.len > 1; + + // Optimization: if the only non-child prop is a spread object + // we can just pass the object as the first argument + // this goes as deep as there are spreads + // <div {{...{...{...{...foo}}}}} /> + // -> + // <div {{...foo}} /> + // jsx("div", {...foo}) + while (props.items.len == 1 and props.items[0].kind == .spread and props.items[0].value.?.data == .e_object) { + props = props.items[0].value.?.data.e_object.properties.list(); + } + + // if (p.options.jsx.development) { + switch (e_.children.len) { + 0 => {}, + 1 => { + props.append(allocator, G.Property{ + .key = children_key, + .value = e_.children.ptr[0], + }) catch unreachable; + }, + else => { + props.append(allocator, G.Property{ + .key = children_key, + .value = p.e(E.Array{ + .items = e_.children, + .is_single_line = e_.children.len < 2, + }, e_.close_tag_loc), + }) catch unreachable; + }, + } + + args[1] = p.e(E.Object{ + .properties = G.Property.List.fromList(props), + }, expr.loc); + + if (e_.key) |key| { + args[2] = key; + } else { + // if (maybeKey !== undefined) + args[2] = Expr{ + .loc = expr.loc, + .data = .{ + .e_undefined = E.Undefined{}, + }, + }; + } + + if (p.options.jsx.development) { + // is the return type of the first child an array? + // It's dynamic + // Else, it's static + args[3] = Expr{ + .loc = expr.loc, + .data = .{ + .e_boolean = .{ + .value = is_static_jsx, + }, + }, + }; + + if (include_filename) { + var source = p.allocator.alloc(G.Property, 2) catch unreachable; + p.recordUsage(p.jsx_filename.ref); + source[0] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, + .value = p.e(E.Identifier{ + .ref = p.jsx_filename.ref, + .can_be_removed_if_unused = true, + }, expr.loc), + }; + + source[1] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, + .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + }; + + // Officially, they ask for columnNumber. But I don't see any usages of it in the code! + // source[2] = G.Property{ + // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, + // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + // }; + args[4] = p.e(E.Object{ + .properties = G.Property.List.init(source), + }, expr.loc); + + // When disabled, this must specifically be undefined + // Not an empty object + // See this code from react: + // > if (source !== undefined) { + // > var fileName = source.fileName.replace(/^.*[\\\/]/, ""); + // > var lineNumber = source.lineNumber; + // > return "\n\nCheck your code at " + fileName + ":" + lineNumber + "."; + // > } + } else { + args[4] = p.e(E.Undefined{}, expr.loc); + } + + args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; + } + + return p.e(E.Call{ + .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), + .args = ExprNodeList.init(args), + // Enable tree shaking + .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + .was_jsx_element = true, + .close_paren_loc = e_.close_tag_loc, + }, expr.loc); + }, + else => unreachable, + } + }, + else => unreachable, + } + }, + + .e_template => |e_| { + if (e_.tag) |tag| { + e_.tag = p.visitExpr(tag); + + if (comptime allow_macros) { + if (e_.tag.?.data == .e_import_identifier) { + const ref = e_.tag.?.data.e_import_identifier.ref; + + if (p.macro.refs.get(ref)) |import_record_id| { + const name = p.symbols.items[ref.innerIndex()].original_name; + p.ignoreUsage(ref); + if (p.is_control_flow_dead) { + return p.e(E.Undefined{}, e_.tag.?.loc); + } + p.macro_call_count += 1; + const record = &p.import_records.items[import_record_id]; + // We must visit it to convert inline_identifiers and record usage + const macro_result = (p.options.macro_context.call( + record.path.text, + p.source.path.sourceDir(), + p.log, + p.source, + record.range, + expr, + &.{}, + name, + MacroVisitor, + MacroVisitor{ + .p = p, + .loc = expr.loc, + }, + ) catch return expr); + + if (macro_result.data != .e_template) { + return p.visitExpr(macro_result); + } + } + } + } + } + + for (e_.parts) |*part| { + part.value = p.visitExpr(part.value); + } + }, + + .inline_identifier => |id| { + const ref = p.macro.imports.get(id) orelse { + p.panic("Internal error: missing identifier from macro: {d}", .{id}); + }; + + if (!p.is_control_flow_dead) { + p.recordUsage(ref); + } + + return p.e( + E.ImportIdentifier{ + .was_originally_identifier = false, + .ref = ref, + }, + expr.loc, + ); + }, + + .e_binary => |e_| { + switch (e_.left.data) { + // Special-case private identifiers + .e_private_identifier => |_private| { + if (e_.op == .bin_in) { + var private = _private; + const name = p.loadNameFromRef(private.ref); + const result = p.findSymbol(e_.left.loc, name) catch unreachable; + private.ref = result.ref; + + // Unlike regular identifiers, there are no unbound private identifiers + const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind; + if (!Symbol.isKindPrivate(kind)) { + const r = logger.Range{ .loc = e_.left.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable; + } + + e_.right = p.visitExpr(e_.right); + e_.left = .{ .data = .{ .e_private_identifier = private }, .loc = e_.left.loc }; + + // privateSymbolNeedsToBeLowered + return expr; + } + }, + else => {}, + } + + const is_call_target = @as(Expr.Tag, p.call_target) == .e_binary and expr.data.e_binary == p.call_target.e_binary; + // const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary; + const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); + + if (comptime jsx_transform_type == .macro) { + if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) { + // foo instanceof <string /> + // -> + // bunJSX.isNodeType(foo, 13) + + // <string /> instanceof foo + // -> + // bunJSX.isNodeType(foo, 13) + var call_args = p.allocator.alloc(Expr, 2) catch unreachable; + call_args[0] = e_.left; + call_args[1] = e_.right; + + if (e_.right.data == .e_jsx_element) { + const jsx_element = e_.right.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.slice(p.allocator); + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[1] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + + if (e_.left.data == .e_jsx_element) { + const jsx_element = e_.left.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.slice(p.allocator); + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[0] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + + return p.e( + E.Call{ + .target = p.e( + E.Dot{ + .name = "isNodeType", + .name_loc = expr.loc, + .target = p.e(BunJSX.bun_jsx_identifier, expr.loc), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + expr.loc, + ), + .args = ExprNodeList.init(call_args), + .can_be_unwrapped_if_unused = true, + }, + expr.loc, + ); + } + } + + e_.left = p.visitExprInOut(e_.left, ExprIn{ + .assign_target = e_.op.binaryAssignTarget(), + }); + + // Mark the control flow as dead if the branch is never taken + switch (e_.op) { + .bin_logical_or => { + const side_effects = SideEffects.toBoolean(e_.left.data); + if (side_effects.ok and side_effects.value) { + // "true || dead" + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.right = p.visitExpr(e_.right); + p.is_control_flow_dead = old; + } else { + e_.right = p.visitExpr(e_.right); + } + }, + .bin_logical_and => { + const side_effects = SideEffects.toBoolean(e_.left.data); + if (side_effects.ok and !side_effects.value) { + // "false && dead" + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.right = p.visitExpr(e_.right); + p.is_control_flow_dead = old; + } else { + e_.right = p.visitExpr(e_.right); + } + }, + .bin_nullish_coalescing => { + const side_effects = SideEffects.toNullOrUndefined(e_.left.data); + if (side_effects.ok and !side_effects.value) { + // "notNullOrUndefined ?? dead" + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.right = p.visitExpr(e_.right); + p.is_control_flow_dead = old; + } else { + e_.right = p.visitExpr(e_.right); + } + }, + else => { + e_.right = p.visitExpr(e_.right); + }, + } + + // Always put constants on the right for equality comparisons to help + // reduce the number of cases we have to check during pattern matching. We + // can only reorder expressions that do not have any side effects. + switch (e_.op) { + .bin_loose_eq, .bin_loose_ne, .bin_strict_eq, .bin_strict_ne => { + if (SideEffects.isPrimitiveToReorder(e_.left.data) and !SideEffects.isPrimitiveToReorder(e_.right.data)) { + const _left = e_.left; + const _right = e_.right; + e_.left = _right; + e_.right = _left; + } + }, + else => {}, + } + + switch (e_.op) { + .bin_comma => { + // notimpl(); + }, + .bin_loose_eq => { + const equality = e_.left.data.eql(e_.right.data); + if (equality.ok) { + return p.e( + E.Boolean{ .value = equality.equal }, + expr.loc, + ); + } + + // const after_op_loc = locAfterOp(e_.); + // TODO: warn about equality check + // TODO: warn about typeof string + + }, + .bin_strict_eq => { + const equality = e_.left.data.eql(e_.right.data); + if (equality.ok) { + return p.e(E.Boolean{ .value = equality.equal }, expr.loc); + } + + // const after_op_loc = locAfterOp(e_.); + // TODO: warn about equality check + // TODO: warn about typeof string + }, + .bin_loose_ne => { + const equality = e_.left.data.eql(e_.right.data); + if (equality.ok) { + return p.e(E.Boolean{ .value = !equality.equal }, expr.loc); + } + // const after_op_loc = locAfterOp(e_.); + // TODO: warn about equality check + // TODO: warn about typeof string + + // "x != void 0" => "x != null" + if (@as(Expr.Tag, e_.right.data) == .e_undefined) { + e_.right = p.e(E.Null{}, e_.right.loc); + } + }, + .bin_strict_ne => { + const equality = e_.left.data.eql(e_.right.data); + if (equality.ok) { + return p.e(E.Boolean{ .value = !equality.equal }, expr.loc); + } + }, + .bin_nullish_coalescing => { + const nullorUndefined = SideEffects.toNullOrUndefined(e_.left.data); + if (nullorUndefined.ok) { + if (!nullorUndefined.value) { + return e_.left; + } else if (nullorUndefined.side_effects == .no_side_effects) { + // "(null ?? fn)()" => "fn()" + // "(null ?? this.fn)" => "this.fn" + // "(null ?? this.fn)()" => "(0, this.fn)()" + if (is_call_target and e_.right.hasValueForThisInCall()) { + return Expr.joinWithComma(Expr{ .data = .{ .e_number = .{ .value = 0.0 } }, .loc = e_.left.loc }, e_.right, p.allocator); + } + + return e_.right; + } + } + }, + .bin_logical_or => { + const side_effects = SideEffects.toBoolean(e_.left.data); + if (side_effects.ok and side_effects.value) { + return e_.left; + } else if (side_effects.ok and side_effects.side_effects == .no_side_effects) { + // "(0 || fn)()" => "fn()" + // "(0 || this.fn)" => "this.fn" + // "(0 || this.fn)()" => "(0, this.fn)()" + if (is_call_target and e_.right.hasValueForThisInCall()) { + return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator); + } + + return e_.right; + } + }, + .bin_logical_and => { + const side_effects = SideEffects.toBoolean(e_.left.data); + if (side_effects.ok) { + if (!side_effects.value) { + return e_.left; + } else if (side_effects.side_effects == .no_side_effects) { + // "(1 && fn)()" => "fn()" + // "(1 && this.fn)" => "this.fn" + // "(1 && this.fn)()" => "(0, this.fn)()" + if (is_call_target and e_.right.hasValueForThisInCall()) { + return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator); + } + + return e_.right; + } + } + + // TODO: + // "(1 && fn)()" => "fn()" + // "(1 && this.fn)" => "this.fn" + // "(1 && this.fn)()" => "(0, this.fn)()" + }, + .bin_add => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + return p.e(E.Number{ .value = vals[0] + vals[1] }, expr.loc); + } + } + + if (foldStringAddition(e_.left, e_.right)) |res| { + return res; + } + }, + .bin_sub => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + return p.e(E.Number{ .value = vals[0] - vals[1] }, expr.loc); + } + } + }, + .bin_mul => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + return p.e(E.Number{ .value = vals[0] * vals[1] }, expr.loc); + } + } + }, + .bin_div => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + return p.e(E.Number{ .value = vals[0] / vals[1] }, expr.loc); + } + } + }, + .bin_rem => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // is this correct? + return p.e(E.Number{ .value = std.math.mod(f64, vals[0], vals[1]) catch 0.0 }, expr.loc); + } + } + }, + .bin_pow => { + if (p.should_fold_numeric_constants) { + if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + return p.e(E.Number{ .value = std.math.pow(f64, vals[0], vals[1]) }, expr.loc); + } + } + }, + .bin_shl => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) << @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + .bin_shr => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + .bin_u_shr => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + .bin_bitwise_and => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + .bin_bitwise_or => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + .bin_bitwise_xor => { + // TODO: + // if (p.should_fold_numeric_constants) { + // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { + // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); + // } + // } + }, + // --------------------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------------------- + .bin_assign => { + + // Optionally preserve the name + if (@as(Expr.Tag, e_.left.data) == .e_identifier) { + e_.right = p.maybeKeepExprSymbolName(e_.right, p.symbols.items[e_.left.data.e_identifier.ref.innerIndex()].original_name, was_anonymous_named_expr); + } + }, + .bin_add_assign => { + // notimpl(); + }, + .bin_sub_assign => { + // notimpl(); + }, + .bin_mul_assign => { + // notimpl(); + }, + .bin_div_assign => { + // notimpl(); + }, + .bin_rem_assign => { + // notimpl(); + }, + .bin_pow_assign => { + // notimpl(); + }, + .bin_shl_assign => { + // notimpl(); + }, + .bin_shr_assign => { + // notimpl(); + }, + .bin_u_shr_assign => { + // notimpl(); + }, + .bin_bitwise_or_assign => { + // notimpl(); + }, + .bin_bitwise_and_assign => { + // notimpl(); + }, + .bin_bitwise_xor_assign => { + // notimpl(); + }, + .bin_nullish_coalescing_assign => { + // notimpl(); + }, + .bin_logical_and_assign => { + // notimpl(); + }, + .bin_logical_or_assign => { + // notimpl(); + }, + else => {}, + } + }, + .e_index => |e_| { + const is_call_target = std.meta.activeTag(p.call_target) == .e_index and expr.data.e_index == p.call_target.e_index; + const is_delete_target = std.meta.activeTag(p.delete_target) == .e_index and expr.data.e_index == p.delete_target.e_index; + + const target = p.visitExprInOut(e_.target, ExprIn{ + // this is awkward due to a zig compiler bug + .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == js_ast.OptionalChain.ccontinue, + }); + e_.target = target; + + switch (e_.index.data) { + .e_private_identifier => |_private| { + var private = _private; + const name = p.loadNameFromRef(private.ref); + const result = p.findSymbol(e_.index.loc, name) catch unreachable; + private.ref = result.ref; + + // Unlike regular identifiers, there are no unbound private identifiers + const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind; + var r: logger.Range = undefined; + if (!Symbol.isKindPrivate(kind)) { + r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable; + } else { + if (in.assign_target != .none and (kind == .private_method or kind == .private_static_method)) { + r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to read-only method \"{s}\" will throw", .{name}) catch unreachable; + } else if (in.assign_target != .none and (kind == .private_get or kind == .private_static_get)) { + r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to getter-only property \"{s}\" will throw", .{name}) catch unreachable; + } else if (in.assign_target != .replace and (kind == .private_set or kind == .private_static_set)) { + r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; + p.log.addRangeWarningFmt(p.source, r, p.allocator, "Reading from setter-only property \"{s}\" will throw", .{name}) catch unreachable; + } + } + + e_.index = .{ .data = .{ .e_private_identifier = private }, .loc = e_.index.loc }; + }, + else => { + const index = p.visitExpr(e_.index); + e_.index = index; + }, + } + + if (e_.optional_chain == null and e_.index.data == .e_string and e_.index.data.e_string.isUTF8()) { + const literal = e_.index.data.e_string.slice(p.allocator); + if (p.maybeRewritePropertyAccess( + expr.loc, + e_.target, + literal, + e_.index.loc, + is_call_target, + )) |val| { + return val; + } + + // delete process.env["NODE_ENV"] + // shouldn't be transformed into + // delete undefined + if (!is_delete_target and !is_call_target) { + // We check for defines here as well + // esbuild doesn't do this + // In a lot of codebases, people will sometimes do: + // process.env["NODE_ENV"] + // Often not intentionally + // So we want to be able to detect this and still Do The Right Thing + if (p.define.dots.get(literal)) |parts| { + for (parts) |define| { + if (p.isDotDefineMatch(expr, define.parts)) { + if (!define.data.isUndefined()) { + return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); + } + + return p.e(E.Undefined{}, expr.loc); + } + } + } + } + // "foo"[2] + } else if (e_.optional_chain == null and target.data == .e_string and e_.index.data == .e_number and target.data.e_string.isUTF8() and e_.index.data.e_number.value >= 0) { + const literal = target.data.e_string.slice(p.allocator); + const index = e_.index.data.e_number.toUsize(); + if (literal.len > index) { + return p.e(E.String{ .data = literal[index .. index + 1] }, expr.loc); + } + } + // Create an error for assigning to an import namespace when bundling. Even + // though this is a run-time error, we make it a compile-time error when + // bundling because scope hoisting means these will no longer be run-time + // errors. + if ((in.assign_target != .none or is_delete_target) and @as(Expr.Tag, e_.target.data) == .e_identifier and p.symbols.items[e_.target.data.e_identifier.ref.innerIndex()].kind == .import) { + const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc); + p.log.addRangeErrorFmt( + p.source, + r, + p.allocator, + "Cannot assign to property on import \"{s}\"", + .{p.symbols.items[e_.target.data.e_identifier.ref.innerIndex()].original_name}, + ) catch unreachable; + } + + return p.e(e_, expr.loc); + }, + .e_unary => |e_| { + switch (e_.op) { + .un_typeof => { + const id_before = std.meta.activeTag(e_.value.data) == Expr.Tag.e_identifier; + e_.value = p.visitExprInOut(e_.value, ExprIn{ .assign_target = e_.op.unaryAssignTarget() }); + const id_after = std.meta.activeTag(e_.value.data) == Expr.Tag.e_identifier; + + // The expression "typeof (0, x)" must not become "typeof x" if "x" + // is unbound because that could suppress a ReferenceError from "x" + if (!id_before and id_after and p.symbols.items[e_.value.data.e_identifier.ref.innerIndex()].kind == .unbound) { + e_.value = Expr.joinWithComma( + Expr{ .loc = e_.value.loc, .data = Prefill.Data.Zero }, + e_.value, + p.allocator, + ); + } + + if (SideEffects.typeof(e_.value.data)) |typeof| { + return p.e(E.String{ .data = typeof }, expr.loc); + } + }, + .un_delete => { + e_.value = p.visitExprInOut(e_.value, ExprIn{ .has_chain_parent = true }); + }, + else => { + e_.value = p.visitExprInOut(e_.value, ExprIn{ .assign_target = e_.op.unaryAssignTarget() }); + + // Post-process the unary expression + + switch (e_.op) { + .un_not => { + e_.value = SideEffects.simplifyBoolean(p, e_.value); + + const side_effects = SideEffects.toBoolean(e_.value.data); + if (side_effects.ok) { + return p.e(E.Boolean{ .value = !side_effects.value }, expr.loc); + } + + if (e_.value.maybeSimplifyNot(p.allocator)) |exp| { + return exp; + } + }, + .un_void => { + if (p.exprCanBeRemovedIfUnused(&e_.value)) { + return p.e(E.Undefined{}, e_.value.loc); + } + }, + .un_pos => { + if (SideEffects.toNumber(e_.value.data)) |num| { + return p.e(E.Number{ .value = num }, expr.loc); + } + }, + .un_neg => { + if (SideEffects.toNumber(e_.value.data)) |num| { + return p.e(E.Number{ .value = -num }, expr.loc); + } + }, + + //////////////////////////////////////////////////////////////////////////////// + + .un_pre_dec => { + // TODO: private fields + }, + .un_pre_inc => { + // TODO: private fields + }, + .un_post_dec => { + // TODO: private fields + }, + .un_post_inc => { + // TODO: private fields + }, + else => {}, + } + + // "-(a, b)" => "a, -b" + if (switch (e_.op) { + .un_delete, .un_typeof => false, + else => true, + }) { + switch (e_.value.data) { + .e_binary => |comma| { + if (comma.op == .bin_comma) { + return Expr.joinWithComma( + comma.left, + p.e( + E.Unary{ + .op = e_.op, + .value = comma.right, + }, + comma.right.loc, + ), + p.allocator, + ); + } + }, + else => {}, + } + } + }, + } + }, + .e_dot => |e_| { + const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_dot and expr.data.e_dot == p.delete_target.e_dot; + const is_call_target = @as(Expr.Tag, p.call_target) == .e_dot and expr.data.e_dot == p.call_target.e_dot; + + if (p.define.dots.get(e_.name)) |parts| { + for (parts) |define| { + if (p.isDotDefineMatch(expr, define.parts)) { + // Substitute user-specified defines + if (!define.data.isUndefined()) { + return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); + } + + // Copy the side effect flags over in case this expression is unused + if (define.data.can_be_removed_if_unused) { + e_.can_be_removed_if_unused = true; + } + + if (define.data.call_can_be_unwrapped_if_unused and !p.options.ignore_dce_annotations) { + e_.call_can_be_unwrapped_if_unused = true; + } + + break; + } + } + } + + // Track ".then().catch()" chains + if (is_call_target and @as(Expr.Tag, p.then_catch_chain.next_target) == .e_dot and p.then_catch_chain.next_target.e_dot == expr.data.e_dot) { + if (strings.eqlComptime(e_.name, "catch")) { + p.then_catch_chain = ThenCatchChain{ + .next_target = e_.target.data, + .has_catch = true, + }; + } else if (strings.eqlComptime(e_.name, "then")) { + p.then_catch_chain = ThenCatchChain{ + .next_target = e_.target.data, + .has_catch = p.then_catch_chain.has_catch or p.then_catch_chain.has_multiple_args, + }; + } + } + + e_.target = p.visitExpr(e_.target); + if (e_.optional_chain == null) { + if (p.maybeRewritePropertyAccess( + expr.loc, + e_.target, + e_.name, + e_.name_loc, + is_call_target, + )) |_expr| { + return _expr; + } + + if (comptime allow_macros) { + if (p.macro_call_count > 0 and e_.target.data == .e_object and e_.target.data.e_object.was_originally_macro) { + if (e_.target.get(e_.name)) |obj| { + return obj; + } + } + } + } + }, + .e_if => |e_| { + const is_call_target = @as(Expr.Data, p.call_target) == .e_if and expr.data.e_if == p.call_target.e_if; + + e_.test_ = p.visitExpr(e_.test_); + + e_.test_ = SideEffects.simplifyBoolean(p, e_.test_); + + const side_effects = SideEffects.toBoolean(e_.test_.data); + + if (!side_effects.ok) { + e_.yes = p.visitExpr(e_.yes); + e_.no = p.visitExpr(e_.no); + } else { + // Mark the control flow as dead if the branch is never taken + if (side_effects.value) { + // "true ? live : dead" + e_.yes = p.visitExpr(e_.yes); + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.no = p.visitExpr(e_.no); + p.is_control_flow_dead = old; + + if (side_effects.side_effects == .could_have_side_effects) { + return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.e(E.Missing{}, e_.test_.loc), e_.yes, p.allocator); + } + + // "(1 ? fn : 2)()" => "fn()" + // "(1 ? this.fn : 2)" => "this.fn" + // "(1 ? this.fn : 2)()" => "(0, this.fn)()" + if (is_call_target and e_.yes.hasValueForThisInCall()) { + return p.e(E.Number{ .value = 0 }, e_.test_.loc).joinWithComma(e_.yes, p.allocator); + } + + return e_.yes; + } else { + // "false ? dead : live" + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.yes = p.visitExpr(e_.yes); + p.is_control_flow_dead = old; + e_.no = p.visitExpr(e_.no); + + // "(a, false) ? b : c" => "a, c" + if (side_effects.side_effects == .could_have_side_effects) { + return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.e(E.Missing{}, e_.test_.loc), e_.no, p.allocator); + } + + // "(1 ? fn : 2)()" => "fn()" + // "(1 ? this.fn : 2)" => "this.fn" + // "(1 ? this.fn : 2)()" => "(0, this.fn)()" + if (is_call_target and e_.no.hasValueForThisInCall()) { + return p.e(E.Number{ .value = 0 }, e_.test_.loc).joinWithComma(e_.no, p.allocator); + } + return e_.no; + } + } + }, + .e_await => |e_| { + p.await_target = e_.value.data; + e_.value = p.visitExpr(e_.value); + }, + .e_yield => |e_| { + if (e_.value) |val| { + e_.value = p.visitExpr(val); + } + }, + .e_array => |e_| { + if (in.assign_target != .none) { + p.maybeCommaSpreadError(e_.comma_after_spread); + } + var items = e_.items.slice(); + for (items) |*item| { + switch (item.data) { + .e_missing => {}, + .e_spread => |spread| { + spread.value = p.visitExprInOut(spread.value, ExprIn{ .assign_target = in.assign_target }); + }, + .e_binary => |e2| { + if (in.assign_target != .none and e2.op == .bin_assign) { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(e2.right); + e2.left = p.visitExprInOut(e2.left, ExprIn{ .assign_target = .replace }); + e2.right = p.visitExpr(e2.right); + + if (@as(Expr.Tag, e2.left.data) == .e_identifier) { + e2.right = p.maybeKeepExprSymbolName( + e2.right, + p.symbols.items[e2.left.data.e_identifier.ref.innerIndex()].original_name, + was_anonymous_named_expr, + ); + } + } else { + item.* = p.visitExprInOut(item.*, ExprIn{ .assign_target = in.assign_target }); + } + }, + else => { + item.* = p.visitExprInOut(item.*, ExprIn{ .assign_target = in.assign_target }); + }, + } + } + }, + .e_object => |e_| { + if (in.assign_target != .none) { + p.maybeCommaSpreadError(e_.comma_after_spread); + } + + var has_spread = false; + var has_proto = false; + var i: usize = 0; + while (i < e_.properties.len) : (i += 1) { + var property = e_.properties.ptr[i]; + + if (property.kind != .spread) { + property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{})); + const key = property.key.?; + // Forbid duplicate "__proto__" properties according to the specification + if (!property.flags.contains(.is_computed) and + !property.flags.contains(.was_shorthand) and + !property.flags.contains(.is_method) and + in.assign_target == .none and + key.data.isStringValue() and + strings.eqlComptime( + // __proto__ is utf8, assume it lives in refs + key.data.e_string.slice(p.allocator), + "__proto__", + )) { + if (has_proto) { + const r = js_lexer.rangeOfIdentifier(p.source, key.loc); + p.log.addRangeError(p.source, r, "Cannot specify the \"__proto__\" property more than once per object") catch unreachable; + } + has_proto = true; + } + } else { + has_spread = true; + } + + // Extract the initializer for expressions like "({ a: b = c } = d)" + if (in.assign_target != .none and property.initializer == null and property.value != null) { + switch (property.value.?.data) { + .e_binary => |bin| { + if (bin.op == .bin_assign) { + property.initializer = bin.right; + property.value = bin.left; + } + }, + else => {}, + } + } + + if (property.value != null) { + property.value = p.visitExprInOut(property.value.?, ExprIn{ .assign_target = in.assign_target }); + } + + if (property.initializer != null) { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(property.initializer orelse unreachable); + property.initializer = p.visitExpr(property.initializer.?); + + if (property.value) |val| { + if (@as(Expr.Tag, val.data) == .e_identifier) { + property.initializer = p.maybeKeepExprSymbolName( + property.initializer orelse unreachable, + p.symbols.items[val.data.e_identifier.ref.innerIndex()].original_name, + was_anonymous_named_expr, + ); + } + } + } + + e_.properties.ptr[i] = property; + } + }, + .e_import => |e_| { + const state = TransposeState{ + // we must check that the await_target is an e_import or it will crash + // example from next.js where not checking causes a panic: + // ``` + // const { + // normalizeLocalePath, + // } = require('../shared/lib/i18n/normalize-locale-path') as typeof import('../shared/lib/i18n/normalize-locale-path') + // ``` + .is_await_target = if (p.await_target != null) p.await_target.? == .e_import and p.await_target.?.e_import == e_ else false, + .is_then_catch_target = p.then_catch_chain.has_catch and std.meta.activeTag(p.then_catch_chain.next_target) == .e_import and expr.data.e_import == p.then_catch_chain.next_target.e_import, + .loc = e_.expr.loc, + }; + + e_.expr = p.visitExpr(e_.expr); + return p.import_transposer.maybeTransposeIf(e_.expr, state); + }, + .e_call => |e_| { + p.call_target = e_.target.data; + + p.then_catch_chain = ThenCatchChain{ + .next_target = e_.target.data, + .has_multiple_args = e_.args.len >= 2, + .has_catch = @as(Expr.Tag, p.then_catch_chain.next_target) == .e_call and p.then_catch_chain.next_target.e_call == expr.data.e_call and p.then_catch_chain.has_catch, + }; + + e_.target = p.visitExprInOut(e_.target, ExprIn{ + .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue, + }); + var could_be_require_resolve: bool = false; + + // Copy the call side effect flag over if this is a known target + switch (e_.target.data) { + .e_identifier => |ident| { + e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or ident.call_can_be_unwrapped_if_unused; + }, + .e_dot => |dot| { + e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or dot.call_can_be_unwrapped_if_unused; + // Prepare to recognize "require.resolve()" calls + could_be_require_resolve = (e_.args.len >= 1 and + dot.optional_chain == null and + @as(Expr.Tag, dot.target.data) == .e_identifier and + dot.target.data.e_identifier.ref.eql(p.require_ref) and + strings.eqlComptime(dot.name, "resolve")); + }, + else => {}, + } + + const is_macro_ref: bool = if (comptime FeatureFlags.is_macro_enabled and + jsx_transform_type != .macro) + e_.target.data == .e_import_identifier and p.macro.refs.contains(e_.target.data.e_import_identifier.ref) + else + false; + + { + const old_ce = p.options.ignore_dce_annotations; + defer p.options.ignore_dce_annotations = old_ce; + if (is_macro_ref) + p.options.ignore_dce_annotations = true; + + for (e_.args.slice()) |_, i| { + const arg = e_.args.ptr[i]; + e_.args.ptr[i] = p.visitExpr(arg); + } + } + + if (e_.optional_chain == null and @as(Expr.Tag, e_.target.data) == .e_identifier and e_.target.data.e_identifier.ref.eql(p.require_ref)) { + e_.can_be_unwrapped_if_unused = false; + + // Heuristic: omit warnings inside try/catch blocks because presumably + // the try/catch statement is there to handle the potential run-time + // error from the unbundled require() call failing. + if (e_.args.len == 1) { + const first = e_.args.first_(); + switch (first.data) { + .e_string => { + // require(FOO) => require(FOO) + return p.transposeRequire(first, null); + }, + .e_if => { + // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') + // This makes static analysis later easier + return p.require_transposer.maybeTransposeIf(first, null); + }, + else => {}, + } + } + + if (p.options.warn_about_unbundled_modules) { + const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc); + p.log.addRangeDebug(p.source, r, "This call to \"require\" will not be bundled because it has multiple arguments") catch unreachable; + } + } + + if (could_be_require_resolve) { + // Ignore calls to require.resolve() if the control flow is provably + // dead here. We don't want to spend time scanning the required files + // if they will never be used. + if (p.is_control_flow_dead) { + return p.e(E.Null{}, expr.loc); + } + + if (p.options.features.dynamic_require) { + p.ignoreUsage(p.require_ref); + // require.resolve(FOO) => import.meta.resolveSync(FOO) + // require.resolve(FOO) => import.meta.resolveSync(FOO, pathsObject) + return p.e( + E.Call{ + .target = p.e( + E.Dot{ + .target = p.e(E.ImportMeta{}, e_.target.loc), + .name = "resolveSync", + .name_loc = e_.target.data.e_dot.name_loc, + }, + e_.target.loc, + ), + .args = e_.args, + .close_paren_loc = e_.close_paren_loc, + }, + expr.loc, + ); + } + + if (e_.args.len == 1) { + const first = e_.args.first_(); + switch (first.data) { + .e_string => { + // require(FOO) => require(FOO) + return p.transposeRequireResolve(first, expr); + }, + .e_if => { + // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') + // This makes static analysis later easier + return p.require_resolve_transposer.maybeTransposeIf(first, expr); + }, + else => {}, + } + } + } + + if (comptime allow_macros) { + if (is_macro_ref) { + const ref = e_.target.data.e_import_identifier.ref; + const import_record_id = p.macro.refs.get(ref).?; + p.ignoreUsage(ref); + if (p.is_control_flow_dead) { + return p.e(E.Undefined{}, e_.target.loc); + } + const name = p.symbols.items[ref.innerIndex()].original_name; + const record = &p.import_records.items[import_record_id]; + const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } }; + const start_error_count = p.log.msgs.items.len; + p.macro_call_count += 1; + const macro_result = + p.options.macro_context.call( + record.path.text, + p.source.path.sourceDir(), + p.log, + p.source, + record.range, + copied, + &.{}, + name, + MacroVisitor, + MacroVisitor{ .p = p, .loc = expr.loc }, + ) catch |err| { + if (err == error.MacroFailed) { + if (p.log.msgs.items.len == start_error_count) { + p.log.addError(p.source, expr.loc, "macro threw exception") catch unreachable; + } + } else { + p.log.addErrorFmt(p.source, expr.loc, p.allocator, "{s} error in macro", .{@errorName(err)}) catch unreachable; + } + return expr; + }; + + if (macro_result.data != .e_call) { + return p.visitExpr(macro_result); + } + } + } + + return expr; + }, + .e_new => |e_| { + e_.target = p.visitExpr(e_.target); + // p.warnA + + for (e_.args.slice()) |*arg| { + arg.* = p.visitExpr(arg.*); + } + }, + .e_arrow => |e_| { + const old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_visit); + p.fn_or_arrow_data_visit = FnOrArrowDataVisit{ + .is_arrow = true, + .is_async = e_.is_async, + }; + + // Mark if we're inside an async arrow function. This value should be true + // even if we're inside multiple arrow functions and the closest inclosing + // arrow function isn't async, as long as at least one enclosing arrow + // function within the current enclosing function is async. + const old_inside_async_arrow_fn = p.fn_only_data_visit.is_inside_async_arrow_fn; + p.fn_only_data_visit.is_inside_async_arrow_fn = e_.is_async or p.fn_only_data_visit.is_inside_async_arrow_fn; + + p.pushScopeForVisitPass(.function_args, expr.loc) catch unreachable; + var dupe = p.allocator.dupe(Stmt, e_.body.stmts) catch unreachable; + + p.visitArgs(e_.args, VisitArgsOpts{ + .has_rest_arg = e_.has_rest_arg, + .body = dupe, + .is_unique_formal_parameters = true, + }); + p.pushScopeForVisitPass(.function_body, e_.body.loc) catch unreachable; + + var stmts_list = ListManaged(Stmt).fromOwnedSlice(p.allocator, dupe); + var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body }; + p.visitStmtsAndPrependTempRefs(&stmts_list, &temp_opts) catch unreachable; + p.allocator.free(e_.body.stmts); + e_.body.stmts = stmts_list.toOwnedSlice(); + p.popScope(); + p.popScope(); + + p.fn_only_data_visit.is_inside_async_arrow_fn = old_inside_async_arrow_fn; + p.fn_or_arrow_data_visit = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_visit), &old_fn_or_arrow_data); + }, + .e_function => |e_| { + e_.func = p.visitFunc(e_.func, expr.loc); + if (e_.func.name) |name| { + return p.keepExprSymbolName(expr, p.symbols.items[name.ref.?.innerIndex()].original_name); + } + }, + .e_class => |e_| { + + // This might be wrong. + _ = p.visitClass(expr.loc, e_); + }, + else => {}, + } + return expr; + } + + fn visitArgs(p: *P, args: []G.Arg, opts: VisitArgsOpts) void { + const strict_loc = fnBodyContainsUseStrict(opts.body); + const has_simple_args = isSimpleParameterList(args, opts.has_rest_arg); + var duplicate_args_check: ?*StringVoidMap.Node = null; + defer { + if (duplicate_args_check) |checker| { + StringVoidMap.release(checker); + } + } + + // Section 15.2.1 Static Semantics: Early Errors: "It is a Syntax Error if + // FunctionBodyContainsUseStrict of FunctionBody is true and + // IsSimpleParameterList of FormalParameters is false." + if (strict_loc != null and !has_simple_args) { + p.log.addRangeError(p.source, p.source.rangeOfString(strict_loc.?), "Cannot use a \"use strict\" directive in a function with a non-simple parameter list") catch unreachable; + } + + // Section 15.1.1 Static Semantics: Early Errors: "Multiple occurrences of + // the same BindingIdentifier in a FormalParameterList is only allowed for + // functions which have simple parameter lists and which are not defined in + // strict mode code." + if (opts.is_unique_formal_parameters or strict_loc != null or !has_simple_args or p.isStrictMode()) { + duplicate_args_check = StringVoidMap.get(bun.default_allocator); + } + + var i: usize = 0; + var duplicate_args_check_ptr: ?*StringVoidMap = if (duplicate_args_check != null) + &duplicate_args_check.?.data + else + null; + + while (i < args.len) : (i += 1) { + if (args[i].ts_decorators.len > 0) { + args[i].ts_decorators = p.visitTSDecorators(args[i].ts_decorators); + } + + p.visitBinding(args[i].binding, duplicate_args_check_ptr); + if (args[i].default) |default| { + args[i].default = p.visitExpr(default); + } + } + } + + pub fn visitTSDecorators(p: *P, decs: ExprNodeList) ExprNodeList { + var i: usize = 0; + while (i < decs.len) : (i += 1) { + decs.ptr[i] = p.visitExpr(decs.ptr[i]); + } + + return decs; + } + + pub fn keepExprSymbolName(_: *P, _value: Expr, _: string) Expr { + return _value; + // var start = p.expr_list.items.len; + // p.expr_list.ensureUnusedCapacity(2) catch unreachable; + // p.expr_list.appendAssumeCapacity(_value); + // p.expr_list.appendAssumeCapacity(p.e(E.String{ + // .utf8 = name, + // }, _value.loc)); + + // var value = p.callRuntime(_value.loc, "ℹ", p.expr_list.items[start..p.expr_list.items.len]); + // // Make sure tree shaking removes this if the function is never used + // value.getCall().can_be_unwrapped_if_unused = true; + // return value; + } + + pub fn fnBodyContainsUseStrict(body: []Stmt) ?logger.Loc { + for (body) |stmt| { + switch (stmt.data) { + .s_comment => { + continue; + }, + .s_directive => |dir| { + if (strings.utf16EqlString(dir.value, "use strict")) { + return stmt.loc; + } + }, + else => {}, + } + } + + return null; + } + + pub fn isSimpleParameterList(args: []G.Arg, has_rest_arg: bool) bool { + if (has_rest_arg) { + return false; + } + + for (args) |arg| { + if (@as(Binding.Tag, arg.binding.data) != .b_identifier or arg.default != null) { + return false; + } + } + + return true; + } + + pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool { + if (class.extends) |*extends| { + if (!p.exprCanBeRemovedIfUnused(extends)) { + return false; + } + } + + for (class.properties) |*property| { + if (property.kind == .class_static_block) { + if (!p.stmtsCanBeRemovedIfUnused(property.class_static_block.?.stmts.slice())) { + return false; + } + continue; + } + + if (!p.exprCanBeRemovedIfUnused(&(property.key orelse unreachable))) { + return false; + } + + if (property.value) |*val| { + if (!p.exprCanBeRemovedIfUnused(val)) { + return false; + } + } + + if (property.initializer) |*val| { + if (!p.exprCanBeRemovedIfUnused(val)) { + return false; + } + } + } + + return true; + } + + // TODO: + // When React Fast Refresh is enabled, anything that's a JSX component should not be removable + // This is to improve the reliability of fast refresh between page loads. + pub fn exprCanBeRemovedIfUnused(p: *P, expr: *const Expr) bool { + switch (expr.data) { + .e_null, + .e_undefined, + .e_missing, + .e_boolean, + .e_number, + .e_big_int, + .e_string, + .e_this, + .e_reg_exp, + .e_function, + .e_arrow, + .e_import_meta, + => { + return true; + }, + + .e_dot => |ex| { + return ex.can_be_removed_if_unused; + }, + .e_class => |ex| { + return p.classCanBeRemovedIfUnused(ex); + }, + .e_identifier => |ex| { + if (ex.must_keep_due_to_with_stmt) { + return false; + } + + // Unbound identifiers cannot be removed because they can have side effects. + // One possible side effect is throwing a ReferenceError if they don't exist. + // Another one is a getter with side effects on the global object: + // + // Object.defineProperty(globalThis, 'x', { + // get() { + // sideEffect(); + // }, + // }); + // + // Be very careful about this possibility. It's tempting to treat all + // identifier expressions as not having side effects but that's wrong. We + // must make sure they have been declared by the code we are currently + // compiling before we can tell that they have no side effects. + // + // Note that we currently ignore ReferenceErrors due to TDZ access. This is + // incorrect but proper TDZ analysis is very complicated and would have to + // be very conservative, which would inhibit a lot of optimizations of code + // inside closures. This may need to be revisited if it proves problematic. + if (ex.can_be_removed_if_unused or p.symbols.items[ex.ref.innerIndex()].kind != .unbound) { + return true; + } + }, + .e_import_identifier => { + + // References to an ES6 import item are always side-effect free in an + // ECMAScript environment. + // + // They could technically have side effects if the imported module is a + // CommonJS module and the import item was translated to a property access + // (which esbuild's bundler does) and the property has a getter with side + // effects. + // + // But this is very unlikely and respecting this edge case would mean + // disabling tree shaking of all code that references an export from a + // CommonJS module. It would also likely violate the expectations of some + // developers because the code *looks* like it should be able to be tree + // shaken. + // + // So we deliberately ignore this edge case and always treat import item + // references as being side-effect free. + return true; + }, + .e_if => |ex| { + return p.exprCanBeRemovedIfUnused(&ex.test_) and + (p.isSideEffectFreeUnboundIdentifierRef( + ex.yes, + ex.test_, + true, + ) or + p.exprCanBeRemovedIfUnused(&ex.yes)) and + (p.isSideEffectFreeUnboundIdentifierRef( + ex.no, + ex.test_, + false, + ) or p.exprCanBeRemovedIfUnused( + &ex.no, + )); + }, + .e_array => |ex| { + for (ex.items.slice()) |*item| { + if (!p.exprCanBeRemovedIfUnused(item)) { + return false; + } + } + + return true; + }, + .e_object => |ex| { + for (ex.properties.slice()) |*property| { + + // The key must still be evaluated if it's computed or a spread + if (property.kind == .spread or property.flags.contains(.is_computed) or property.flags.contains(.is_spread)) { + return false; + } + + if (property.value) |*val| { + if (!p.exprCanBeRemovedIfUnused(val)) { + return false; + } + } + } + return true; + }, + .e_call => |ex| { + + // A call that has been marked "__PURE__" can be removed if all arguments + // can be removed. The annotation causes us to ignore the target. + if (ex.can_be_unwrapped_if_unused) { + for (ex.args.slice()) |*arg| { + if (!p.exprCanBeRemovedIfUnused(arg)) { + return false; + } + } + return true; + } + }, + .e_new => |ex| { + + // A call that has been marked "__PURE__" can be removed if all arguments + // can be removed. The annotation causes us to ignore the target. + if (ex.can_be_unwrapped_if_unused) { + for (ex.args.slice()) |*arg| { + if (!p.exprCanBeRemovedIfUnused(arg)) { + return false; + } + } + + return true; + } + }, + .e_unary => |ex| { + switch (ex.op) { + // These operators must not have any type conversions that can execute code + // such as "toString" or "valueOf". They must also never throw any exceptions. + .un_void, .un_not => { + return p.exprCanBeRemovedIfUnused(&ex.value); + }, + + // The "typeof" operator doesn't do any type conversions so it can be removed + // if the result is unused and the operand has no side effects. However, it + // has a special case where if the operand is an identifier expression such + // as "typeof x" and "x" doesn't exist, no reference error is thrown so the + // operation has no side effects. + // + // Note that there *is* actually a case where "typeof x" can throw an error: + // when "x" is being referenced inside of its TDZ (temporal dead zone). TDZ + // checks are not yet handled correctly by bun or esbuild, so this possibility is + // currently ignored. + .un_typeof => { + if (ex.value.data == .e_identifier) { + return true; + } + + return p.exprCanBeRemovedIfUnused(&ex.value); + }, + + else => {}, + } + }, + .e_binary => |ex| { + switch (ex.op) { + // These operators must not have any type conversions that can execute code + // such as "toString" or "valueOf". They must also never throw any exceptions. + .bin_strict_eq, + .bin_strict_ne, + .bin_comma, + .bin_nullish_coalescing, + => return p.exprCanBeRemovedIfUnused(&ex.left) and p.exprCanBeRemovedIfUnused(&ex.right), + + // Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed + .bin_logical_or => return p.exprCanBeRemovedIfUnused(&ex.left) and + (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, false) or p.exprCanBeRemovedIfUnused(&ex.right)), + + // Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed + .bin_logical_and => return p.exprCanBeRemovedIfUnused(&ex.left) and + (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, true) or p.exprCanBeRemovedIfUnused(&ex.right)), + + // For "==" and "!=", pretend the operator was actually "===" or "!==". If + // we know that we can convert it to "==" or "!=", then we can consider the + // operator itself to have no side effects. This matters because our mangle + // logic will convert "typeof x === 'object'" into "typeof x == 'object'" + // and since "typeof x === 'object'" is considered to be side-effect free, + // we must also consider "typeof x == 'object'" to be side-effect free. + .bin_loose_eq, .bin_loose_ne => return SideEffects.canChangeStrictToLoose( + ex.left.data, + ex.right.data, + ) and + p.exprCanBeRemovedIfUnused(&ex.left) and p.exprCanBeRemovedIfUnused(&ex.right), + else => {}, + } + }, + .e_template => |templ| { + if (templ.tag == null) { + for (templ.parts) |part| { + if (!p.exprCanBeRemovedIfUnused(&part.value) or part.value.knownPrimitive() == .unknown) { + return false; + } + } + } + + return true; + }, + else => {}, + } + + return false; + } + + fn isSideEffectFreeUnboundIdentifierRef(p: *P, value: Expr, guard_condition: Expr, is_yes_branch: bool) bool { + if (value.data != .e_identifier or + p.symbols.items[value.data.e_identifier.ref.innerIndex()].kind != .unbound or + guard_condition.data != .e_binary) + return false; + + const binary = guard_condition.data.e_binary.*; + + switch (binary.op) { + .bin_strict_eq, .bin_strict_ne, .bin_loose_eq, .bin_loose_ne => { + // typeof x !== 'undefined' + var typeof: Expr.Data = binary.left.data; + var compare: Expr.Data = binary.right.data; + // typeof 'undefined' !== x + if (typeof == .e_string) { + typeof = binary.right.data; + compare = binary.left.data; + } + + // this order because Expr.Data Tag is not a pointer + // so it should be slightly faster to compare + if (compare != .e_string or + typeof != .e_unary) + return false; + const unary = typeof.e_unary.*; + + if (unary.op != .un_typeof or unary.value.data != .e_identifier) + return false; + + const id = value.data.e_identifier.ref; + const id2 = unary.value.data.e_identifier.ref; + return ((compare.e_string.eqlComptime("undefined") == is_yes_branch) == + (binary.op == .bin_strict_ne or binary.op == .bin_loose_ne)) and + id.eql(id2); + }, + else => return false, + } + } + + fn jsxStringsToMemberExpressionAutomatic(p: *P, loc: logger.Loc, is_static: bool) Expr { + return p.jsxStringsToMemberExpression(loc, if (is_static and !p.options.jsx.development) + p.jsxs_runtime.ref + else + p.jsx_runtime.ref); + } + + fn maybeRelocateVarsToTopLevel(p: *P, decls: []const G.Decl, mode: RelocateVars.Mode) RelocateVars { + // Only do this when the scope is not already top-level and when we're not inside a function. + if (p.current_scope == p.module_scope) { + return .{ .ok = false }; + } + + var scope = p.current_scope; + while (!scope.kindStopsHoisting()) { + if (comptime Environment.allow_assert) assert(scope.parent != null); + scope = scope.parent.?; + } + + if (scope != p.module_scope) { + return .{ .ok = false }; + } + + var value: Expr = Expr{ .loc = logger.Loc.Empty, .data = Expr.Data{ .e_missing = E.Missing{} } }; + var any_initializers = false; + for (decls) |decl| { + const binding = Binding.toExpr( + &decl.binding, + p.to_expr_wrapper_hoisted, + ); + if (decl.value) |decl_value| { + value = value.joinWithComma(Expr.assign(binding, decl_value, p.allocator), p.allocator); + any_initializers = true; + } else if (mode == .for_in_or_for_of) { + value = value.joinWithComma(binding, p.allocator); + } + } + + if (std.meta.activeTag(value.data) == .e_missing or !any_initializers) { + return .{ .ok = true }; + } + + return .{ .stmt = p.s(S.SExpr{ .value = value }, value.loc), .ok = true }; + } + + // fn maybeInlineMacroObject(p: *P, decl: *G.Decl, macro: Expr) void { + // if (decl.value == null) return; + // switch (decl.binding.data) { + // .b_identifier => |ident| { + // if (macro.get(p.loadNameFromRef(ident.ref))) |val| { + // decl + // } + // } + // } + // } + // if (comptime allow_macros) { + // if (p.macro_call_count and data.decls[i].value != null and + // data.decls[i].value.?.data == .e_object and data.decls[i].value.?.data.e_object.was_originally_macro) + // { + // p.maybeInlineMacroObject(&data.decls[i], data.decls[i].value.?); + // } + // } + + // EDot nodes represent a property access. This function may return an + // expression to replace the property access with. It assumes that the + // target of the EDot expression has already been visited. + fn maybeRewritePropertyAccess( + p: *P, + loc: logger.Loc, + target: js_ast.Expr, + name: string, + name_loc: logger.Loc, + is_call_target: bool, + ) ?Expr { + switch (target.data) { + .e_identifier => |id| { + // Rewrite "module.require()" to "require()" for Webpack compatibility. + // See https://github.com/webpack/webpack/pull/7750 for more info. + // This also makes correctness a little easier. + if (is_call_target and id.ref.eql(p.module_ref) and strings.eqlComptime(name, "require")) { + p.ignoreUsage(p.module_ref); + p.recordUsage(p.require_ref); + return p.e(E.Identifier{ .ref = p.require_ref }, name_loc); + } + + // If this is a known enum value, inline the value of the enum + if (is_typescript_enabled) { + if (p.known_enum_values.get(id.ref)) |enum_value_map| { + if (enum_value_map.get(name)) |enum_value| { + return p.e(E.Number{ .value = enum_value }, loc); + } + } + } + }, + .e_string => |str| { + // minify "long-string".length to 11 + if (strings.eqlComptime(name, "length")) { + return p.e(E.Number{ .value = @intToFloat(f64, str.len()) }, loc); + } + }, + else => {}, + } + + return null; + } + + pub fn ignoreUsage(p: *P, ref: Ref) void { + if (!p.is_control_flow_dead) { + p.symbols.items[ref.innerIndex()].use_count_estimate -|= 1; + var use = p.symbol_uses.get(ref) orelse p.panic("Expected symbol_uses to exist {s}\n{s}", .{ ref, p.symbol_uses }); + use.count_estimate -|= 1; + if (use.count_estimate == 0) { + _ = p.symbol_uses.swapRemove(ref); + } else { + p.symbol_uses.putAssumeCapacity(ref, use); + } + } + + // Don't roll back the "tsUseCounts" increment. This must be counted even if + // the value is ignored because that's what the TypeScript compiler does. + } + + fn visitAndAppendStmt(p: *P, stmts: *ListManaged(Stmt), stmt: *Stmt) !void { + switch (stmt.data) { + // These don't contain anything to traverse + + .s_debugger, .s_empty, .s_comment => {}, + .s_type_script => { + + // Erase TypeScript constructs from the output completely + return; + }, + .s_directive => { + + // if p.isStrictMode() && s.LegacyOctalLoc.Start > 0 { + // p.markStrictModeFeature(legacyOctalEscape, p.source.RangeOfLegacyOctalEscape(s.LegacyOctalLoc), "") + // } + return; + }, + .s_import => |data| { + try p.recordDeclaredSymbol(data.namespace_ref); + + if (data.default_name) |default_name| { + try p.recordDeclaredSymbol(default_name.ref.?); + } + + if (data.items.len > 0) { + for (data.items) |*item| { + try p.recordDeclaredSymbol(item.name.ref.?); + } + } + }, + .s_export_clause => |data| { + // "export {foo}" + var end: usize = 0; + var any_replaced = false; + if (p.options.features.replace_exports.count() > 0) { + for (data.items) |*item| { + const name = p.loadNameFromRef(item.name.ref.?); + + const symbol = try p.findSymbol(item.alias_loc, name); + const ref = symbol.ref; + + if (p.options.features.replace_exports.getPtr(name)) |entry| { + if (entry.* != .replace) p.ignoreUsage(symbol.ref); + _ = p.injectReplacementExport(stmts, symbol.ref, stmt.loc, entry); + any_replaced = true; + continue; + } + + if (p.symbols.items[ref.innerIndex()].kind == .unbound) { + // Silently strip exports of non-local symbols in TypeScript, since + // those likely correspond to type-only exports. But report exports of + // non-local symbols as errors in JavaScript. + if (!is_typescript_enabled) { + const r = js_lexer.rangeOfIdentifier(p.source, item.name.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "\"{s}\" is not declared in this file", .{name}); + } + continue; + } + + item.name.ref = ref; + data.items[end] = item.*; + end += 1; + } + } else { + for (data.items) |*item| { + const name = p.loadNameFromRef(item.name.ref.?); + const symbol = try p.findSymbol(item.alias_loc, name); + const ref = symbol.ref; + + if (p.symbols.items[ref.innerIndex()].kind == .unbound) { + // Silently strip exports of non-local symbols in TypeScript, since + // those likely correspond to type-only exports. But report exports of + // non-local symbols as errors in JavaScript. + if (!is_typescript_enabled) { + const r = js_lexer.rangeOfIdentifier(p.source, item.name.loc); + try p.log.addRangeErrorFmt(p.source, r, p.allocator, "\"{s}\" is not declared in this file", .{name}); + continue; + } + continue; + } + + item.name.ref = ref; + data.items[end] = item.*; + end += 1; + } + } + + const remove_for_tree_shaking = any_replaced and end == 0 and data.items.len > 0 and p.options.tree_shaking; + data.items.len = end; + + if (remove_for_tree_shaking) { + return; + } + }, + .s_export_from => |data| { + // When HMR is enabled, we need to transform this into + // import {foo} from "./foo"; + // export {foo}; + + // From: + // export {foo as default} from './foo'; + // To: + // import {default as foo} from './foo'; + // export {foo}; + + // "export {foo} from 'path'" + const name = p.loadNameFromRef(data.namespace_ref); + + data.namespace_ref = try p.newSymbol(.other, name); + try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.recordDeclaredSymbol(data.namespace_ref); + + if (p.options.features.replace_exports.count() > 0) { + var j: usize = 0; + // This is a re-export and the symbols created here are used to reference + for (data.items) |item| { + const old_ref = item.name.ref.?; + + if (p.options.features.replace_exports.count() > 0) { + if (p.options.features.replace_exports.getPtr(item.alias)) |entry| { + _ = p.injectReplacementExport(stmts, old_ref, logger.Loc.Empty, entry); + + continue; + } + } + + const _name = p.loadNameFromRef(old_ref); + + const ref = try p.newSymbol(.other, _name); + try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.recordDeclaredSymbol(data.namespace_ref); + data.items[j] = item; + data.items[j].name.ref = ref; + j += 1; + } + + data.items.len = j; + + if (j == 0 and data.items.len > 0) { + return; + } + } else { + + // This is a re-export and the symbols created here are used to reference + for (data.items) |*item| { + const _name = p.loadNameFromRef(item.name.ref.?); + const ref = try p.newSymbol(.other, _name); + try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.recordDeclaredSymbol(data.namespace_ref); + item.name.ref = ref; + } + } + }, + .s_export_star => |data| { + + // "export * from 'path'" + const name = p.loadNameFromRef(data.namespace_ref); + data.namespace_ref = try p.newSymbol(.other, name); + try p.current_scope.generated.append(p.allocator, data.namespace_ref); + try p.recordDeclaredSymbol(data.namespace_ref); + + // "export * as ns from 'path'" + if (data.alias) |alias| { + if (p.options.features.replace_exports.count() > 0) { + if (p.options.features.replace_exports.getPtr(alias.original_name)) |entry| { + _ = p.injectReplacementExport(stmts, p.declareSymbol(.other, logger.Loc.Empty, alias.original_name) catch unreachable, logger.Loc.Empty, entry); + return; + } + } + // "import * as ns from 'path'" + // "export {ns}" + + // jarred: For now, just always do this transform. + // because Safari doesn't support it and I've seen cases where this breaks + + p.recordUsage(data.namespace_ref); + try stmts.ensureTotalCapacity(stmts.items.len + 2); + stmts.appendAssumeCapacity(p.s(S.Import{ .namespace_ref = data.namespace_ref, .star_name_loc = alias.loc, .import_record_index = data.import_record_index }, stmt.loc)); + + var items = try List(js_ast.ClauseItem).initCapacity(p.allocator, 1); + items.appendAssumeCapacity(js_ast.ClauseItem{ .alias = alias.original_name, .original_name = alias.original_name, .alias_loc = alias.loc, .name = LocRef{ .loc = alias.loc, .ref = data.namespace_ref } }); + stmts.appendAssumeCapacity(p.s(S.ExportClause{ .items = items.toOwnedSlice(p.allocator), .is_single_line = true }, stmt.loc)); + return; + } + }, + .s_export_default => |data| { + if (data.default_name.ref) |ref| { + try p.recordDeclaredSymbol(ref); + } + + var mark_for_replace: bool = false; + + const orig_dead = p.is_control_flow_dead; + if (p.options.features.replace_exports.count() > 0) { + if (p.options.features.replace_exports.getPtr("default")) |entry| { + p.is_control_flow_dead = entry.* != .replace; + mark_for_replace = true; + } + } + + defer { + p.is_control_flow_dead = orig_dead; + } + + switch (data.value) { + .expr => |expr| { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(expr); + + data.value.expr = p.visitExpr(expr); + + if (p.is_control_flow_dead) { + return; + } + + // Optionally preserve the name + + data.value.expr = p.maybeKeepExprSymbolName(data.value.expr, js_ast.ClauseItem.default_alias, was_anonymous_named_expr); + + // Discard type-only export default statements + if (is_typescript_enabled) { + switch (data.value.expr.data) { + .e_identifier => |ident| { + if (!ident.ref.isSourceContentsSlice()) { + const symbol = p.symbols.items[ident.ref.innerIndex()]; + if (symbol.kind == .unbound) { + if (p.local_type_names.get(symbol.original_name)) |local_type| { + if (local_type) { + return; + } + } + } + } + }, + else => {}, + } + } + + if (mark_for_replace) { + var entry = p.options.features.replace_exports.getPtr("default").?; + if (entry.* == .replace) { + data.value.expr = entry.replace; + } else { + _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); + return; + } + } + + // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); + if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(expr.loc); + export_default_args[1] = data.value.expr; + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc)) catch unreachable; + return; + } + }, + + .stmt => |s2| { + switch (s2.data) { + .s_function => |func| { + var name: string = ""; + const had_name = func.func.name != null; + if (func.func.name) |func_loc| { + name = p.loadNameFromRef(func_loc.ref.?); + } else { + func.func.name = data.default_name; + name = js_ast.ClauseItem.default_alias; + } + + func.func = p.visitFunc(func.func, func.func.open_parens_loc); + + if (p.is_control_flow_dead) { + return; + } + + if (mark_for_replace) { + var entry = p.options.features.replace_exports.getPtr("default").?; + if (entry.* == .replace) { + data.value = .{ .expr = entry.replace }; + } else { + _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); + return; + } + + // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); + if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(data.value.expr.loc); + export_default_args[1] = data.value.expr; + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; + return; + } + } else if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(s2.loc); + + if (had_name) { + export_default_args[1] = p.e(E.Identifier{ .ref = func.func.name.?.ref.? }, s2.loc); + stmts.ensureUnusedCapacity(2) catch unreachable; + + stmts.appendAssumeCapacity(s2); + } else { + export_default_args[1] = p.e(E.Function{ .func = func.func }, s2.loc); + } + + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(s2.loc, "__exportDefault", export_default_args) }, s2.loc)) catch unreachable; + return; + } + + stmts.append(stmt.*) catch unreachable; + + // if (func.func.name != null and func.func.name.?.ref != null) { + // stmts.append(p.keepStmtSymbolName(func.func.name.?.loc, func.func.name.?.ref.?, name)) catch unreachable; + // } + // prevent doubling export default function name + return; + }, + .s_class => |class| { + // TODO: https://github.com/Jarred-Sumner/bun/issues/51 + _ = p.visitClass(s2.loc, &class.class); + + if (p.is_control_flow_dead) + return; + + if (mark_for_replace) { + var entry = p.options.features.replace_exports.getPtr("default").?; + if (entry.* == .replace) { + data.value = .{ .expr = entry.replace }; + } else { + _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); + return; + } + + // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); + if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(data.value.expr.loc); + export_default_args[1] = data.value.expr; + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; + return; + } + } else if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(s2.loc); + + const class_name_ref = brk: { + if (class.class.class_name) |class_name_ref| { + if (class_name_ref.ref) |ref| { + break :brk ref; + } + } + break :brk null; + }; + if (class_name_ref) |ref| { + stmts.ensureUnusedCapacity(2) catch unreachable; + stmts.appendAssumeCapacity(s2); + export_default_args[1] = p.e(E.Identifier{ .ref = ref }, s2.loc); + } else { + export_default_args[1] = p.e(class.class, s2.loc); + } + + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(s2.loc, "__exportDefault", export_default_args) }, s2.loc)) catch unreachable; + return; + } + + stmts.append(stmt.*) catch unreachable; + return; + }, + else => {}, + } + }, + } + }, + .s_export_equals => |data| { + if (p.options.enable_bundling) { + var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; + export_default_args[0] = p.@"module.exports"(stmt.loc); + export_default_args[1] = data.value; + + stmts.append(p.s(S.SExpr{ .value = p.callRuntime(stmt.loc, "__exportDefault", export_default_args) }, stmt.loc)) catch unreachable; + return; + } + + // "module.exports = value" + stmts.append( + Expr.assignStmt( + p.@"module.exports"( + stmt.loc, + ), + p.visitExpr(data.value), + p.allocator, + ), + ) catch unreachable; + p.recordUsage(p.module_ref); + }, + .s_break => |data| { + if (data.label) |*label| { + const name = p.loadNameFromRef(label.ref orelse p.panic("Expected label to have a ref", .{})); + const res = p.findLabelSymbol(label.loc, name); + if (res.found) { + label.ref = res.ref; + } else { + data.label = null; + } + } else if (!p.fn_or_arrow_data_visit.is_inside_loop and !p.fn_or_arrow_data_visit.is_inside_switch) { + const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); + p.log.addRangeError(p.source, r, "Cannot use \"break\" here") catch unreachable; + } + }, + .s_continue => |data| { + if (data.label) |*label| { + const name = p.loadNameFromRef(label.ref orelse p.panic("Expected continue label to have a ref", .{})); + const res = p.findLabelSymbol(label.loc, name); + label.ref = res.ref; + if (res.found and !res.is_loop) { + const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot \"continue\" to label {s}", .{name}) catch unreachable; + } + } else if (!p.fn_or_arrow_data_visit.is_inside_loop) { + const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); + p.log.addRangeError(p.source, r, "Cannot use \"continue\" here") catch unreachable; + } + }, + .s_label => |data| { + p.pushScopeForVisitPass(.label, stmt.loc) catch unreachable; + const name = p.loadNameFromRef(data.name.ref.?); + const ref = p.newSymbol(.label, name) catch unreachable; + data.name.ref = ref; + p.current_scope.label_ref = ref; + switch (data.stmt.data) { + .s_for, .s_for_in, .s_for_of, .s_while, .s_do_while => { + p.current_scope.label_stmt_is_loop = true; + }, + else => {}, + } + + data.stmt = p.visitSingleStmt(data.stmt, StmtsKind.none); + p.popScope(); + }, + .s_local => |data| { + const decls_len = if (!(data.is_export and p.options.features.replace_exports.entries.len > 0)) + p.visitDecls(data.decls, false) + else + p.visitDecls(data.decls, true); + + const is_now_dead = data.decls.len > 0 and decls_len == 0; + if (is_now_dead) { + return; + } + + data.decls.len = decls_len; + + // Handle being exported inside a namespace + if (data.is_export and p.enclosing_namespace_arg_ref != null) { + for (data.decls) |*d| { + if (d.value) |val| { + p.recordUsage((p.enclosing_namespace_arg_ref orelse unreachable)); + // TODO: is it necessary to lowerAssign? why does esbuild do it _most_ of the time? + stmts.append(p.s(S.SExpr{ + .value = Expr.assign(Binding.toExpr(&d.binding, p.to_expr_wrapper_namespace), val, p.allocator), + }, stmt.loc)) catch unreachable; + } + } + + return; + } + + // We must relocate vars in order to safely handle removing if/else depending on NODE_ENV. + // Edgecase: + // `export var` is skipped because it's unnecessary. That *should* be a noop, but it loses the `is_export` flag if we're in HMR. + if (data.kind == .k_var and !data.is_export) { + const relocated = p.maybeRelocateVarsToTopLevel(data.decls, .normal); + if (relocated.ok) { + if (relocated.stmt) |new_stmt| { + stmts.append(new_stmt) catch unreachable; + } + + return; + } + } + }, + .s_expr => |data| { + p.stmt_expr_value = data.value.data; + data.value = p.visitExpr(data.value); + // simplify unused + data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse data.value.toEmpty(); + }, + .s_throw => |data| { + data.value = p.visitExpr(data.value); + }, + .s_return => |data| { + + // Forbid top-level return inside modules with ECMAScript-style exports + if (p.fn_or_arrow_data_visit.is_outside_fn_or_arrow) { + const where = where: { + if (p.es6_export_keyword.len > 0) { + break :where p.es6_export_keyword; + } else if (p.top_level_await_keyword.len > 0) { + break :where p.top_level_await_keyword; + } else { + break :where logger.Range.None; + } + }; + + if (where.len > 0) { + p.log.addRangeError(p.source, where, "Top-level return cannot be used inside an ECMAScript module") catch unreachable; + } + } + + if (data.value) |val| { + data.value = p.visitExpr(val); + + // "return undefined;" can safely just always be "return;" + if (data.value != null and @as(Expr.Tag, data.value.?.data) == .e_undefined) { + // Returning undefined is implicit + data.value = null; + } + } + }, + .s_block => |data| { + { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + + // Pass the "is loop body" status on to the direct children of a block used + // as a loop body. This is used to enable optimizations specific to the + // topmost scope in a loop body block. + const kind = if (std.meta.eql(p.loop_body, stmt.data)) StmtsKind.loop_body else StmtsKind.none; + var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.stmts); + p.visitStmts(&_stmts, kind) catch unreachable; + data.stmts = _stmts.toOwnedSlice(); + p.popScope(); + } + + // // trim empty statements + if (data.stmts.len == 0) { + stmts.append(Stmt{ .data = Prefill.Data.SEmpty, .loc = stmt.loc }) catch unreachable; + return; + } else if (data.stmts.len == 1 and !statementCaresAboutScope(data.stmts[0])) { + // Unwrap blocks containing a single statement + stmts.append(data.stmts[0]) catch unreachable; + return; + } + stmts.append(stmt.*) catch unreachable; + return; + }, + .s_with => |data| { + // using with is forbidden in strict mode + // we largely only deal with strict mode + // however, old code should still technically transpile + // we do not attempt to preserve all the semantics of with + data.value = p.visitExpr(data.value); + // This stmt should be a block + if (comptime Environment.allow_assert) assert(data.body.data == .s_block); + data.body = p.visitSingleStmt(data.body, StmtsKind.none); + }, + .s_while => |data| { + data.test_ = p.visitExpr(data.test_); + data.body = p.visitLoopBody(data.body); + + data.test_ = SideEffects.simplifyBoolean(p, data.test_); + const result = SideEffects.toBoolean(data.test_.data); + if (result.ok and result.side_effects == .no_side_effects) { + data.test_ = p.e(E.Boolean{ .value = result.value }, data.test_.loc); + } + }, + .s_do_while => |data| { + data.body = p.visitLoopBody(data.body); + data.test_ = p.visitExpr(data.test_); + + data.test_ = SideEffects.simplifyBoolean(p, data.test_); + }, + .s_if => |data| { + data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(data.test_)); + + const effects = SideEffects.toBoolean(data.test_.data); + if (effects.ok and !effects.value) { + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + data.yes = p.visitSingleStmt(data.yes, StmtsKind.none); + p.is_control_flow_dead = old; + } else { + data.yes = p.visitSingleStmt(data.yes, StmtsKind.none); + } + + // The "else" clause is optional + if (data.no) |no| { + if (effects.ok and effects.value) { + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + defer p.is_control_flow_dead = old; + data.no = p.visitSingleStmt(no, .none); + } else { + data.no = p.visitSingleStmt(no, .none); + } + + // Trim unnecessary "else" clauses + if (data.no != null and @as(Stmt.Tag, data.no.?.data) == .s_empty) { + data.no = null; + } + } + + if (effects.ok) { + if (effects.value) { + if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) { + if (effects.side_effects == .could_have_side_effects) { + // Keep the condition if it could have side effects (but is still known to be truthy) + if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + } + } + + return try p.appendIfBodyPreservingScope(stmts, data.yes); + } else { + // We have to keep the "no" branch + } + } else { + // The test is falsy + if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) { + if (effects.side_effects == .could_have_side_effects) { + // Keep the condition if it could have side effects (but is still known to be truthy) + if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { + stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; + } + } + + // if (false) { + // } + if (data.no == null) { + return; + } + + return try p.appendIfBodyPreservingScope(stmts, data.no.?); + } + } + } + }, + .s_for => |data| { + { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + + if (data.init) |initst| { + data.init = p.visitForLoopInit(initst, false); + } + + if (data.test_) |test_| { + data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_)); + + const result = SideEffects.toBoolean(data.test_.?.data); + if (result.ok and result.value and result.side_effects == .no_side_effects) { + data.test_ = null; + } + } + + if (data.update) |update| { + data.update = p.visitExpr(update); + } + + data.body = p.visitLoopBody(data.body); + + // Potentially relocate "var" declarations to the top level. Note that this + // must be done inside the scope of the for loop or they won't be relocated. + if (data.init) |init_| { + if (init_.data == .s_local and init_.data.s_local.kind == .k_var) { + const relocate = p.maybeRelocateVarsToTopLevel(init_.data.s_local.decls, .normal); + if (relocate.stmt) |relocated| { + data.init = relocated; + } + } + } + p.popScope(); + } + }, + .s_for_in => |data| { + { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + defer p.popScope(); + _ = p.visitForLoopInit(data.init, true); + data.value = p.visitExpr(data.value); + data.body = p.visitLoopBody(data.body); + + if (data.init.data == .s_local and data.init.data.s_local.kind == .k_var) { + const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls, RelocateVars.Mode.for_in_or_for_of); + if (relocate.stmt) |relocated_stmt| { + data.init = relocated_stmt; + } + } + } + }, + .s_for_of => |data| { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + defer p.popScope(); + _ = p.visitForLoopInit(data.init, true); + data.value = p.visitExpr(data.value); + data.body = p.visitLoopBody(data.body); + + if (data.init.data == .s_local and data.init.data.s_local.kind == .k_var) { + const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls, RelocateVars.Mode.for_in_or_for_of); + if (relocate.stmt) |relocated_stmt| { + data.init = relocated_stmt; + } + } + }, + .s_try => |data| { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + { + var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.body); + p.fn_or_arrow_data_visit.try_body_count += 1; + p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; + p.fn_or_arrow_data_visit.try_body_count -= 1; + data.body = _stmts.toOwnedSlice(); + } + p.popScope(); + + if (data.catch_) |*catch_| { + p.pushScopeForVisitPass(.block, catch_.loc) catch unreachable; + { + if (catch_.binding != null) { + p.visitBinding(catch_.binding.?, null); + } + var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, catch_.body); + p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; + catch_.body = _stmts.toOwnedSlice(); + } + p.popScope(); + } + + if (data.finally) |*finally| { + p.pushScopeForVisitPass(.block, finally.loc) catch unreachable; + { + var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, finally.stmts); + p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; + finally.stmts = _stmts.toOwnedSlice(); + } + p.popScope(); + } + }, + .s_switch => |data| { + data.test_ = p.visitExpr(data.test_); + { + p.pushScopeForVisitPass(.block, data.body_loc) catch unreachable; + defer p.popScope(); + var old_is_inside_Swsitch = p.fn_or_arrow_data_visit.is_inside_switch; + p.fn_or_arrow_data_visit.is_inside_switch = true; + defer p.fn_or_arrow_data_visit.is_inside_switch = old_is_inside_Swsitch; + var i: usize = 0; + while (i < data.cases.len) : (i += 1) { + const case = data.cases[i]; + if (case.value) |val| { + data.cases[i].value = p.visitExpr(val); + // TODO: error messages + // Check("case", *c.Value, c.Value.Loc) + // p.warnAboutTypeofAndString(s.Test, *c.Value) + } + var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, case.body); + p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; + data.cases[i].body = _stmts.toOwnedSlice(); + } + } + // TODO: duplicate case checker + + }, + .s_function => |data| { + // We mark it as dead, but the value may not actually be dead + // We just want to be sure to not increment the usage counts for anything in the function + const mark_as_dead = data.func.flags.contains(.is_export) and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?); + const original_is_dead = p.is_control_flow_dead; + + if (mark_as_dead) { + p.is_control_flow_dead = true; + } + defer { + if (mark_as_dead) { + p.is_control_flow_dead = original_is_dead; + } + } + + data.func = p.visitFunc(data.func, data.func.open_parens_loc); + + // Handle exporting this function from a namespace + if (data.func.flags.contains(.is_export) and p.enclosing_namespace_arg_ref != null) { + data.func.flags.remove(.is_export); + + const enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref orelse unreachable; + stmts.ensureUnusedCapacity(3) catch unreachable; + stmts.appendAssumeCapacity(stmt.*); + stmts.appendAssumeCapacity(Expr.assignStmt(p.e(E.Dot{ + .target = p.e(E.Identifier{ .ref = enclosing_namespace_arg_ref }, stmt.loc), + .name = p.loadNameFromRef(data.func.name.?.ref.?), + .name_loc = data.func.name.?.loc, + }, stmt.loc), p.e(E.Identifier{ .ref = data.func.name.?.ref.? }, data.func.name.?.loc), p.allocator)); + } else if (!mark_as_dead) { + stmts.append(stmt.*) catch unreachable; + } else if (mark_as_dead) { + const name = data.func.name.?.ref.?; + if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(name))) |replacement| { + _ = p.injectReplacementExport(stmts, name, data.func.name.?.loc, replacement); + } + } + + // stmts.appendAssumeCapacity( + // // i wonder if this will crash + // p.keepStmtSymbolName( + // data.func.name.?.loc, + // data.func.name.?.ref.?, + // p.symbols.items[data.func.name.?.ref.?.innerIndex()].original_name, + // ), + // ); + return; + }, + .s_class => |data| { + const mark_as_dead = data.is_export and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?); + const original_is_dead = p.is_control_flow_dead; + + if (mark_as_dead) { + p.is_control_flow_dead = true; + } + defer { + if (mark_as_dead) { + p.is_control_flow_dead = original_is_dead; + } + } + + const shadow_ref = p.visitClass(stmt.loc, &data.class); + + // Remove the export flag inside a namespace + const was_export_inside_namespace = data.is_export and p.enclosing_namespace_arg_ref != null; + if (was_export_inside_namespace) { + data.is_export = false; + } + + const lowered = p.lowerClass(js_ast.StmtOrExpr{ .stmt = stmt.* }, shadow_ref); + + if (!mark_as_dead or was_export_inside_namespace) + // Lower class field syntax for browsers that don't support it + stmts.appendSlice(lowered) catch unreachable + else { + const ref = data.class.class_name.?.ref.?; + if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(ref))) |replacement| { + if (p.injectReplacementExport(stmts, ref, data.class.class_name.?.loc, replacement)) { + p.is_control_flow_dead = original_is_dead; + } + } + } + + // Handle exporting this class from a namespace + if (was_export_inside_namespace) { + stmts.appendAssumeCapacity(Expr.assignStmt(p.e(E.Dot{ + .target = p.e(E.Identifier{ .ref = p.enclosing_namespace_arg_ref.? }, stmt.loc), + .name = p.symbols.items[data.class.class_name.?.ref.?.innerIndex()].original_name, + .name_loc = data.class.class_name.?.loc, + }, stmt.loc), p.e(E.Identifier{ .ref = data.class.class_name.?.ref.? }, data.class.class_name.?.loc), p.allocator)); + } + + return; + }, + .s_enum => |data| { + p.recordDeclaredSymbol(data.name.ref.?) catch unreachable; + p.pushScopeForVisitPass(.entry, stmt.loc) catch unreachable; + defer p.popScope(); + p.recordDeclaredSymbol(data.arg) catch unreachable; + + const allocator = p.allocator; + // Scan ahead for any variables inside this namespace. This must be done + // ahead of time before visiting any statements inside the namespace + // because we may end up visiting the uses before the declarations. + // We need to convert the uses into property accesses on the namespace. + for (data.values) |value| { + if (!value.ref.isNull()) { + p.is_exported_inside_namespace.put(allocator, value.ref, data.arg) catch unreachable; + } + } + + // Values without initializers are initialized to one more than the + // previous value if the previous value is numeric. Otherwise values + // without initializers are initialized to undefined. + var next_numeric_value: f64 = 0.0; + var has_numeric_value = true; + + var value_exprs = ListManaged(Expr).initCapacity(allocator, data.values.len) catch unreachable; + + // Track values so they can be used by constant folding. We need to follow + // links here in case the enum was merged with a preceding namespace + var values_so_far = _hash_map.StringHashMapUnmanaged(f64){}; + + p.known_enum_values.put(allocator, data.name.ref orelse p.panic("Expected data.name.ref", .{}), values_so_far) catch unreachable; + p.known_enum_values.put(allocator, data.arg, values_so_far) catch unreachable; + + // We normally don't fold numeric constants because they might increase code + // size, but it's important to fold numeric constants inside enums since + // that's what the TypeScript compiler does. + const old_should_fold_numeric_constants = p.should_fold_numeric_constants; + p.should_fold_numeric_constants = true; + for (data.values) |*enum_value| { + // gotta allocate here so it lives after this function stack frame goes poof + const name = enum_value.name; + var assign_target: Expr = Expr{ .loc = logger.Loc.Empty, .data = Prefill.Data.EMissing }; + var has_string_value = false; + + if (enum_value.value != null) { + enum_value.value = p.visitExpr(enum_value.value.?); + switch (enum_value.value.?.data) { + .e_number => |num| { + + // prob never allocates in practice + values_so_far.put(allocator, name.string(allocator) catch unreachable, num.value) catch unreachable; + has_numeric_value = true; + next_numeric_value = num.value + 1.0; + }, + .e_string => { + has_string_value = true; + }, + else => {}, + } + } else if (has_numeric_value) { + enum_value.value = p.e(E.Number{ .value = next_numeric_value }, enum_value.loc); + values_so_far.put(allocator, name.string(allocator) catch unreachable, next_numeric_value) catch unreachable; + next_numeric_value += 1; + } else { + enum_value.value = p.e(E.Undefined{}, enum_value.loc); + } + // "Enum['Name'] = value" + assign_target = Expr.assign(p.e(E.Index{ + .target = p.e( + E.Identifier{ .ref = data.arg }, + enum_value.loc, + ), + .index = p.e( + enum_value.name, + enum_value.loc, + ), + }, enum_value.loc), enum_value.value orelse unreachable, allocator); + + p.recordUsage(data.arg); + + // String-valued enums do not form a two-way map + if (has_string_value) { + value_exprs.append(assign_target) catch unreachable; + } else { + // "Enum[assignTarget] = 'Name'" + value_exprs.append( + Expr.assign( + p.e(E.Index{ + .target = p.e( + E.Identifier{ .ref = data.arg }, + enum_value.loc, + ), + .index = assign_target, + }, enum_value.loc), + p.e(enum_value.name, enum_value.loc), + allocator, + ), + ) catch unreachable; + } + p.recordUsage(data.arg); + } + + p.should_fold_numeric_constants = old_should_fold_numeric_constants; + + var value_stmts = ListManaged(Stmt).initCapacity(allocator, value_exprs.items.len) catch unreachable; + // Generate statements from expressions + for (value_exprs.items) |expr| { + value_stmts.appendAssumeCapacity(p.s(S.SExpr{ .value = expr }, expr.loc)); + } + value_exprs.deinit(); + try p.generateClosureForTypeScriptNamespaceOrEnum( + stmts, + stmt.loc, + data.is_export, + data.name.loc, + data.name.ref.?, + data.arg, + value_stmts.toOwnedSlice(), + ); + return; + }, + .s_namespace => |data| { + p.recordDeclaredSymbol(data.name.ref.?) catch unreachable; + + // Scan ahead for any variables inside this namespace. This must be done + // ahead of time before visiting any statements inside the namespace + // because we may end up visiting the uses before the declarations. + // We need to convert the uses into property accesses on the namespace. + for (data.stmts) |child_stmt| { + switch (child_stmt.data) { + .s_local => |local| { + if (local.is_export) { + p.markExportedDeclsInsideNamespace(data.arg, local.decls); + } + }, + else => {}, + } + } + + var prepend_temp_refs = PrependTempRefsOpts{ .kind = StmtsKind.fn_body }; + var prepend_list = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.stmts); + + const old_enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref; + p.enclosing_namespace_arg_ref = data.arg; + p.pushScopeForVisitPass(.entry, stmt.loc) catch unreachable; + p.recordDeclaredSymbol(data.arg) catch unreachable; + p.visitStmtsAndPrependTempRefs(&prepend_list, &prepend_temp_refs) catch unreachable; + p.popScope(); + p.enclosing_namespace_arg_ref = old_enclosing_namespace_arg_ref; + + try p.generateClosureForTypeScriptNamespaceOrEnum( + stmts, + stmt.loc, + data.is_export, + data.name.loc, + data.name.ref.?, + data.arg, + prepend_list.items, + ); + return; + }, + else => { + notimpl(); + }, + } + + // if we get this far, it stays + try stmts.append(stmt.*); + } + + fn isExportToEliminate(p: *P, ref: Ref) bool { + const symbol_name = p.loadNameFromRef(ref); + return p.options.features.replace_exports.contains(symbol_name); + } + + fn visitDecls(p: *P, decls: []G.Decl, comptime is_possibly_decl_to_remove: bool) usize { + var i: usize = 0; + const count = decls.len; + var j: usize = 0; + var out_decls = decls; + while (i < count) : (i += 1) { + p.visitBinding(decls[i].binding, null); + + if (decls[i].value != null) { + var val = decls[i].value.?; + const was_anonymous_named_expr = p.isAnonymousNamedExpr(val); + var replacement: ?*const RuntimeFeatures.ReplaceableExport = null; + + const prev_macro_call_count = p.macro_call_count; + const orig_dead = p.is_control_flow_dead; + if (comptime is_possibly_decl_to_remove) { + if (decls[i].binding.data == .b_identifier) { + if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decls[i].binding.data.b_identifier.ref))) |replacer| { + replacement = replacer; + if (replacer.* != .replace) { + p.is_control_flow_dead = true; + } + } + } + } + + decls[i].value = p.visitExpr(val); + + if (comptime is_possibly_decl_to_remove) { + p.is_control_flow_dead = orig_dead; + } + if (comptime is_possibly_decl_to_remove) { + if (decls[i].binding.data == .b_identifier) { + if (replacement) |ptr| { + if (!p.replaceDeclAndPossiblyRemove(&decls[i], ptr)) { + continue; + } + } + } + } + + p.visitDecl( + &decls[i], + was_anonymous_named_expr, + if (comptime allow_macros) + prev_macro_call_count != p.macro_call_count + else + false, + ); + } else if (comptime is_possibly_decl_to_remove) { + if (decls[i].binding.data == .b_identifier) { + if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decls[i].binding.data.b_identifier.ref))) |ptr| { + if (!p.replaceDeclAndPossiblyRemove(&decls[i], ptr)) { + p.visitDecl( + &decls[i], + false, + false, + ); + } else { + continue; + } + } + } + } + + if (comptime is_possibly_decl_to_remove) { + out_decls[j] = decls[i]; + j += 1; + } + } + + if (comptime is_possibly_decl_to_remove) { + return j; + } + + return decls.len; + } + + fn injectReplacementExport(p: *P, stmts: *StmtList, name_ref: Ref, loc: logger.Loc, replacement: *const RuntimeFeatures.ReplaceableExport) bool { + switch (replacement.*) { + .delete => return false, + .replace => |value| { + const count = stmts.items.len; + var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; + + decls[0] = .{ .binding = p.b(B.Identifier{ .ref = name_ref }, loc), .value = value }; + var local = p.s( + S.Local{ + .is_export = true, + .decls = decls, + }, + loc, + ); + p.visitAndAppendStmt(stmts, &local) catch unreachable; + return count != stmts.items.len; + }, + .inject => |with| { + const count = stmts.items.len; + var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; + decls[0] = .{ + .binding = p.b( + B.Identifier{ .ref = p.declareSymbol(.other, loc, with.name) catch unreachable }, + loc, + ), + .value = with.value, + }; + + var local = p.s( + S.Local{ + .is_export = true, + .decls = decls, + }, + loc, + ); + p.visitAndAppendStmt(stmts, &local) catch unreachable; + return count != stmts.items.len; + }, + } + } + + fn replaceDeclAndPossiblyRemove(p: *P, decl: *G.Decl, replacement: *const RuntimeFeatures.ReplaceableExport) bool { + switch (replacement.*) { + .delete => return false, + .replace => |value| { + decl.*.value = p.visitExpr(value); + return true; + }, + .inject => |with| { + decl.* = .{ + .binding = p.b( + B.Identifier{ .ref = p.declareSymbol(.other, decl.binding.loc, with.name) catch unreachable }, + decl.binding.loc, + ), + .value = p.visitExpr(Expr{ .data = with.value.data, .loc = if (decl.value != null) decl.value.?.loc else decl.binding.loc }), + }; + return true; + }, + } + } + + fn visitBindingAndExprForMacro(p: *P, binding: Binding, expr: Expr) void { + switch (binding.data) { + .b_object => |bound_object| { + if (expr.data == .e_object and + expr.data.e_object.was_originally_macro) + { + var object = expr.data.e_object; + for (bound_object.properties) |property| { + if (property.flags.contains(.is_spread)) return; + } + var output_properties = object.properties.slice(); + var end: u32 = 0; + for (bound_object.properties) |property| { + if (property.key.asString(p.allocator)) |name| { + if (object.asProperty(name)) |query| { + switch (query.expr.data) { + .e_object, .e_array => p.visitBindingAndExprForMacro(property.value, query.expr), + else => {}, + } + output_properties[end] = output_properties[query.i]; + end += 1; + } + } + } + + object.properties.len = end; + } + }, + .b_array => |bound_array| { + if (expr.data == .e_array and + expr.data.e_array.was_originally_macro and !bound_array.has_spread) + { + var array = expr.data.e_array; + + array.items.len = @minimum(array.items.len, @truncate(u32, bound_array.items.len)); + var slice = array.items.slice(); + for (bound_array.items[0..array.items.len]) |item, item_i| { + const child_expr = slice[item_i]; + if (item.binding.data == .b_missing) { + slice[item_i] = p.e(E.Missing{}, expr.loc); + continue; + } + + p.visitBindingAndExprForMacro(item.binding, child_expr); + } + } + }, + else => {}, + } + } + + fn visitDecl(p: *P, decl: *Decl, was_anonymous_named_expr: bool, could_be_macro: bool) void { + // Optionally preserve the name + switch (decl.binding.data) { + .b_identifier => |id| { + decl.value = p.maybeKeepExprSymbolName( + decl.value.?, + p.symbols.items[id.ref.innerIndex()].original_name, + was_anonymous_named_expr, + ); + }, + .b_object, .b_array => { + if (comptime allow_macros) { + if (could_be_macro and decl.value != null) { + p.visitBindingAndExprForMacro(decl.binding, decl.value.?); + } + } + }, + else => {}, + } + } + + pub fn markExportedDeclsInsideNamespace(p: *P, ns_ref: Ref, decls: []G.Decl) void { + for (decls) |decl| { + p.markExportedBindingInsideNamespace(ns_ref, decl.binding); + } + } + + pub fn appendIfBodyPreservingScope(p: *P, stmts: *ListManaged(Stmt), body: Stmt) !void { + switch (body.data) { + .s_block => |block| { + var keep_block = false; + for (block.stmts) |stmt| { + if (statementCaresAboutScope(stmt)) { + keep_block = true; + break; + } + } + + if (!keep_block and block.stmts.len > 0) { + try stmts.appendSlice(block.stmts); + return; + } + }, + else => {}, + } + + if (statementCaresAboutScope(body)) { + var block_stmts = try p.allocator.alloc(Stmt, 1); + block_stmts[0] = body; + try stmts.append(p.s(S.Block{ .stmts = block_stmts }, body.loc)); + return; + } + + try stmts.append(body); + return; + } + + fn markExportedBindingInsideNamespace(p: *P, ref: Ref, binding: BindingNodeIndex) void { + switch (binding.data) { + .b_missing => {}, + .b_identifier => |ident| { + p.is_exported_inside_namespace.put(p.allocator, ident.ref, ref) catch unreachable; + }, + .b_array => |array| { + for (array.items) |item| { + p.markExportedBindingInsideNamespace(ref, item.binding); + } + }, + .b_object => |obj| { + for (obj.properties) |item| { + p.markExportedBindingInsideNamespace(ref, item.value); + } + }, + else => { + Global.panic("Unexpected binding type in namespace. This is a bug. {s}", .{binding}); + }, + } + } + + fn generateClosureForTypeScriptNamespaceOrEnum( + p: *P, + stmts: *ListManaged(Stmt), + stmt_loc: logger.Loc, + is_export: bool, + name_loc: logger.Loc, + _name_ref: Ref, + arg_ref: Ref, + stmts_inside_closure: []Stmt, + ) anyerror!void { + var name_ref = _name_ref; + // Follow the link chain in case symbols were merged + var symbol: Symbol = p.symbols.items[name_ref.innerIndex()]; + while (symbol.hasLink()) { + const link = symbol.link; + name_ref = link; + symbol = p.symbols.items[name_ref.innerIndex()]; + } + const allocator = p.allocator; + + // Make sure to only emit a variable once for a given namespace, since there + // can be multiple namespace blocks for the same namespace + if (symbol.kind == .ts_namespace or symbol.kind == .ts_enum and !p.emitted_namespace_vars.contains(name_ref)) { + p.emitted_namespace_vars.put(allocator, name_ref, .{}) catch unreachable; + + var decls = allocator.alloc(G.Decl, 1) catch unreachable; + decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = name_ref }, name_loc) }; + + if (p.enclosing_namespace_arg_ref == null) { + // Top-level namespace + stmts.append( + p.s( + S.Local{ + .kind = .k_var, + .decls = decls, + .is_export = is_export, + }, + stmt_loc, + ), + ) catch unreachable; + } else { + // Nested namespace + stmts.append( + p.s( + S.Local{ + .kind = .k_let, + .decls = decls, + .is_export = is_export, + }, + stmt_loc, + ), + ) catch unreachable; + } + } + + var arg_expr: Expr = undefined; + + if (is_export and p.enclosing_namespace_arg_ref != null) { + const namespace = p.enclosing_namespace_arg_ref.?; + // "name = enclosing.name || (enclosing.name = {})" + const name = p.symbols.items[name_ref.innerIndex()].original_name; + arg_expr = Expr.assign( + Expr.initIdentifier(name_ref, name_loc), + p.e( + E.Binary{ + .op = .bin_logical_or, + .left = p.e( + E.Dot{ + .target = Expr.initIdentifier(namespace, name_loc), + .name = name, + .name_loc = name_loc, + }, + name_loc, + ), + .right = Expr.assign( + p.e( + E.Dot{ + .target = Expr.initIdentifier(namespace, name_loc), + .name = name, + .name_loc = name_loc, + }, + name_loc, + ), + p.e(E.Object{}, name_loc), + allocator, + ), + }, + name_loc, + ), + allocator, + ); + p.recordUsage(namespace); + p.recordUsage(namespace); + p.recordUsage(name_ref); + } else { + // "name || (name = {})" + arg_expr = p.e(E.Binary{ + .op = .bin_logical_or, + .left = Expr.initIdentifier(name_ref, name_loc), + .right = Expr.assign( + Expr.initIdentifier(name_ref, name_loc), + p.e( + E.Object{}, + name_loc, + ), + allocator, + ), + }, name_loc); + p.recordUsage(name_ref); + p.recordUsage(name_ref); + } + + var func_args = allocator.alloc(G.Arg, 1) catch unreachable; + func_args[0] = .{ .binding = p.b(B.Identifier{ .ref = arg_ref }, name_loc) }; + var args_list = allocator.alloc(ExprNodeIndex, 1) catch unreachable; + args_list[0] = arg_expr; + const func = G.Fn{ + .args = func_args, + .name = null, + .open_parens_loc = stmt_loc, + .body = G.FnBody{ + .loc = stmt_loc, + .stmts = try allocator.dupe(StmtNodeIndex, stmts_inside_closure), + }, + }; + const target = p.e( + E.Function{ + .func = func, + }, + stmt_loc, + ); + + const call = p.e( + E.Call{ + .target = target, + .args = ExprNodeList.init(args_list), + }, + stmt_loc, + ); + + const closure = p.s( + S.SExpr{ + .value = call, + }, + stmt_loc, + ); + + stmts.append(closure) catch unreachable; + } + + // TODO: https://github.com/Jarred-Sumner/bun/issues/51 + fn lowerClass( + p: *P, + stmtorexpr: js_ast.StmtOrExpr, + // ref + _: Ref, + ) []Stmt { + switch (stmtorexpr) { + .stmt => |stmt| { + var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + stmts[0] = stmt; + return stmts; + }, + .expr => |expr| { + var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + stmts[0] = p.s(S.SExpr{ .value = expr }, expr.loc); + return stmts; + }, + } + } + + fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt { + switch (stmt.data) { + .s_expr => |st| { + const assign_target = if (is_in_or_of) js_ast.AssignTarget.replace else js_ast.AssignTarget.none; + p.stmt_expr_value = st.value.data; + st.value = p.visitExprInOut(st.value, ExprIn{ .assign_target = assign_target }); + }, + .s_local => |st| { + for (st.decls) |*dec| { + p.visitBinding(dec.binding, null); + if (dec.value) |val| { + dec.value = p.visitExpr(val); + } + } + // st.kind = .k_var; + // s.Decls = p.lowerObjectRestInDecls(s.Decls) + // s.Kind = p.selectLocalKind(s.Kind) + }, + else => { + p.panic("Unexpected stmt in visitForLoopInit: {s}", .{stmt}); + }, + } + + return stmt; + } + + fn wrapIdentifierNamespace( + p: *P, + loc: logger.Loc, + ref: Ref, + ) Expr { + const enclosing_ref = p.enclosing_namespace_arg_ref.?; + p.recordUsage(enclosing_ref); + + return p.e(E.Dot{ + .target = Expr.initIdentifier(enclosing_ref, loc), + .name = p.symbols.items[ref.innerIndex()].original_name, + .name_loc = loc, + }, loc); + } + + fn wrapIdentifierHoisting( + p: *P, + loc: logger.Loc, + ref: Ref, + ) Expr { + p.relocated_top_level_vars.append(p.allocator, LocRef{ .loc = loc, .ref = ref }) catch unreachable; + var _ref = ref; + p.recordUsage(_ref); + return Expr.initIdentifier(_ref, loc); + } + + fn isAnonymousNamedExpr(_: *P, expr: ExprNodeIndex) bool { + switch (expr.data) { + .e_arrow => { + return true; + }, + .e_function => |func| { + return func.func.name == null; + }, + .e_class => |class| { + return class.class_name == null; + }, + else => { + return false; + }, + } + } + + fn valueForDefine(p: *P, loc: logger.Loc, assign_target: js_ast.AssignTarget, is_delete_target: bool, define_data: *const DefineData) Expr { + switch (define_data.value) { + .e_identifier => { + return p.handleIdentifier( + loc, + define_data.value.e_identifier, + define_data.original_name.?, + IdentifierOpts{ + .assign_target = assign_target, + .is_delete_target = is_delete_target, + .was_originally_identifier = true, + }, + ); + }, + .e_string => |str| { + return p.e(str, loc); + }, + else => {}, + } + + return Expr{ + .data = define_data.value, + .loc = loc, + }; + } + + // This function is recursive + // But it shouldn't be that long + fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { + switch (expr.data) { + .e_dot => |ex| { + if (parts.len > 1) { + if (ex.optional_chain != null) { + return false; + } + + // Intermediates must be dot expressions + const last = parts.len - 1; + const is_tail_match = strings.eql(parts[last], ex.name); + return is_tail_match and p.isDotDefineMatch(ex.target, parts[0..last]); + } + }, + .e_import_meta => { + return parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"); + }, + // Note: this behavior differs from esbuild + // esbuild does not try to match index accessors + // we do, but only if it's a UTF8 string + // the intent is to handle people using this form instead of E.Dot. So we really only want to do this if the accessor can also be an identifier + .e_index => |index| { + if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) { + if (index.optional_chain != null) { + return false; + } + + const last = parts.len - 1; + const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator)); + return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]); + } + }, + .e_identifier => |ex| { + + // The last expression must be an identifier + if (parts.len == 1) { + const name = p.loadNameFromRef(ex.ref); + if (!strings.eql(name, parts[0])) { + return false; + } + + const result = p.findSymbol(expr.loc, name) catch return false; + + // We must not be in a "with" statement scope + if (result.is_inside_with_scope) { + return false; + } + + // The last symbol must be unbound + return p.symbols.items[result.ref.innerIndex()].kind == .unbound; + } + }, + else => {}, + } + + return false; + } + + fn visitBinding(p: *P, binding: BindingNodeIndex, duplicate_arg_check: ?*StringVoidMap) void { + switch (binding.data) { + .b_missing => {}, + .b_identifier => |bind| { + p.recordDeclaredSymbol(bind.ref) catch unreachable; + const name = p.symbols.items[bind.ref.innerIndex()].original_name; + if (isEvalOrArguments(name)) { + p.markStrictModeFeature(.eval_or_arguments, js_lexer.rangeOfIdentifier(p.source, binding.loc), name) catch unreachable; + } + if (duplicate_arg_check) |dup| { + if (dup.getOrPutContains(name)) { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, binding.loc), + p.allocator, + "\"{s}\" cannot be bound multiple times in the same parameter list", + .{name}, + ) catch unreachable; + } + } + }, + .b_array => |bind| { + for (bind.items) |*item| { + p.visitBinding(item.binding, duplicate_arg_check); + if (item.default_value) |default_value| { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); + item.default_value = p.visitExpr(default_value); + + switch (item.binding.data) { + .b_identifier => |bind_| { + item.default_value = p.maybeKeepExprSymbolName( + item.default_value orelse unreachable, + p.symbols.items[bind_.ref.innerIndex()].original_name, + was_anonymous_named_expr, + ); + }, + else => {}, + } + } + } + }, + .b_object => |bind| { + for (bind.properties) |*property| { + if (!property.flags.contains(.is_spread)) { + property.key = p.visitExpr(property.key); + } + + p.visitBinding(property.value, duplicate_arg_check); + if (property.default_value) |default_value| { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); + property.default_value = p.visitExpr(default_value); + + switch (property.value.data) { + .b_identifier => |bind_| { + property.default_value = p.maybeKeepExprSymbolName( + property.default_value orelse unreachable, + p.symbols.items[bind_.ref.innerIndex()].original_name, + was_anonymous_named_expr, + ); + }, + else => {}, + } + } + } + }, + else => { + p.panic("Unexpected binding {s}", .{binding}); + }, + } + } + + fn visitLoopBody(p: *P, stmt: StmtNodeIndex) StmtNodeIndex { + const old_is_inside_loop = p.fn_or_arrow_data_visit.is_inside_loop; + p.fn_or_arrow_data_visit.is_inside_loop = true; + p.loop_body = stmt.data; + const res = p.visitSingleStmt(stmt, .loop_body); + p.fn_or_arrow_data_visit.is_inside_loop = old_is_inside_loop; + return res; + } + + fn visitSingleStmt(p: *P, stmt: Stmt, kind: StmtsKind) Stmt { + const has_if_scope = switch (stmt.data) { + .s_function => stmt.data.s_function.func.flags.contains(.has_if_scope), + else => false, + }; + + // Introduce a fake block scope for function declarations inside if statements + if (has_if_scope) { + p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; + } + + var stmts = ListManaged(Stmt).initCapacity(p.allocator, 1) catch unreachable; + stmts.append(stmt) catch unreachable; + p.visitStmts(&stmts, kind) catch unreachable; + + if (has_if_scope) { + p.popScope(); + } + + return p.stmtsToSingleStmt(stmt.loc, stmts.toOwnedSlice()); + } + + // One statement could potentially expand to several statements + fn stmtsToSingleStmt(p: *P, loc: logger.Loc, stmts: []Stmt) Stmt { + if (stmts.len == 0) { + return Stmt{ .data = Prefill.Data.SEmpty, .loc = loc }; + } + + if (stmts.len == 1 and std.meta.activeTag(stmts[0].data) != .s_local or (std.meta.activeTag(stmts[0].data) == .s_local and stmts[0].data.s_local.kind == S.Local.Kind.k_var)) { + // "let" and "const" must be put in a block when in a single-statement context + return stmts[0]; + } + + return p.s(S.Block{ .stmts = stmts }, loc); + } + + fn findLabelSymbol(p: *P, loc: logger.Loc, name: string) FindLabelSymbolResult { + var res = FindLabelSymbolResult{ .ref = Ref.None, .is_loop = false }; + + var _scope: ?*Scope = p.current_scope; + + while (_scope != null and !_scope.?.kindStopsHoisting()) : (_scope = _scope.?.parent.?) { + const scope = _scope orelse unreachable; + const label_ref = scope.label_ref orelse continue; + if (scope.kind == .label and strings.eql(name, p.symbols.items[label_ref.innerIndex()].original_name)) { + // Track how many times we've referenced this symbol + p.recordUsage(label_ref); + res.ref = label_ref; + res.is_loop = scope.label_stmt_is_loop; + res.found = true; + return res; + } + } + + const r = js_lexer.rangeOfIdentifier(p.source, loc); + p.log.addRangeErrorFmt(p.source, r, p.allocator, "There is no containing label named \"{s}\"", .{name}) catch unreachable; + + // Allocate an "unbound" symbol + var ref = p.newSymbol(.unbound, name) catch unreachable; + + // Track how many times we've referenced this symbol + p.recordUsage(ref); + + return res; + } + + fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class) Ref { + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + + class.ts_decorators = p.visitTSDecorators(class.ts_decorators); + + if (class.class_name) |name| { + p.recordDeclaredSymbol(name.ref.?) catch unreachable; + } + + p.pushScopeForVisitPass(.class_name, name_scope_loc) catch unreachable; + const old_enclosing_class_keyword = p.enclosing_class_keyword; + p.enclosing_class_keyword = class.class_keyword; + p.current_scope.recursiveSetStrictMode(.implicit_strict_mode_class); + var class_name_ref: Ref = if (class.class_name != null) + class.class_name.?.ref.? + else + p.newSymbol(.other, "this") catch unreachable; + + var shadow_ref = Ref.None; + + if (!class_name_ref.eql(Ref.None)) { + // are not allowed to assign to this symbol (it throws a TypeError). + const name = p.symbols.items[class_name_ref.innerIndex()].original_name; + var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; + std.mem.copy(u8, identifier[1..identifier.len], name); + identifier[0] = '_'; + shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; + p.recordDeclaredSymbol(shadow_ref) catch unreachable; + if (class.class_name) |class_name| { + p.current_scope.members.put(p.allocator, identifier, Scope.Member{ .loc = class_name.loc, .ref = shadow_ref }) catch unreachable; + } + } + + if (class.extends) |extends| { + class.extends = p.visitExpr(extends); + } + + p.pushScopeForVisitPass(.class_body, class.body_loc) catch unreachable; + defer { + p.popScope(); + p.enclosing_class_keyword = old_enclosing_class_keyword; + } + + var i: usize = 0; + var constructor_function: ?*E.Function = null; + while (i < class.properties.len) : (i += 1) { + var property = &class.properties[i]; + + if (property.kind == .class_static_block) { + var old_fn_or_arrow_data = p.fn_or_arrow_data_visit; + var old_fn_only_data = p.fn_only_data_visit; + p.fn_or_arrow_data_visit = .{}; + p.fn_only_data_visit = .{ .is_this_nested = true, .is_new_target_allowed = true }; + + p.pushScopeForVisitPass(.class_static_init, property.class_static_block.?.loc) catch unreachable; + + // Make it an error to use "arguments" in a static class block + p.current_scope.forbid_arguments = true; + + var list = property.class_static_block.?.stmts.listManaged(p.allocator); + p.visitStmts(&list, .fn_body) catch unreachable; + property.class_static_block.?.stmts = js_ast.BabyList(Stmt).fromList(list); + p.popScope(); + + p.fn_or_arrow_data_visit = old_fn_or_arrow_data; + p.fn_only_data_visit = old_fn_only_data; + + continue; + } + property.ts_decorators = p.visitTSDecorators(property.ts_decorators); + const is_private = if (property.key != null) @as(Expr.Tag, property.key.?.data) == .e_private_identifier else false; + + // Special-case EPrivateIdentifier to allow it here + + if (is_private) { + p.recordDeclaredSymbol(property.key.?.data.e_private_identifier.ref) catch unreachable; + } else if (property.key) |key| { + class.properties[i].key = p.visitExpr(key); + } + + // Make it an error to use "arguments" in a class body + p.current_scope.forbid_arguments = true; + defer p.current_scope.forbid_arguments = false; + + // The value of "this" is shadowed inside property values + const old_is_this_captured = p.fn_only_data_visit.is_this_nested; + const old_this = p.fn_only_data_visit.this_class_static_ref; + p.fn_only_data_visit.is_this_nested = true; + p.fn_only_data_visit.is_new_target_allowed = true; + p.fn_only_data_visit.this_class_static_ref = null; + defer p.fn_only_data_visit.is_this_nested = old_is_this_captured; + defer p.fn_only_data_visit.this_class_static_ref = old_this; + + // We need to explicitly assign the name to the property initializer if it + // will be transformed such that it is no longer an inline initializer. + + var constructor_function_: ?*E.Function = null; + + var name_to_keep: ?string = null; + if (is_private) {} else if (!property.flags.contains(.is_method) and !property.flags.contains(.is_computed)) { + if (property.key) |key| { + if (@as(Expr.Tag, key.data) == .e_string) { + name_to_keep = key.data.e_string.string(p.allocator) catch unreachable; + } + } + } else if (property.flags.contains(.is_method)) { + if (comptime is_typescript_enabled) { + if (property.value.?.data == .e_function and property.key.?.data == .e_string and + property.key.?.data.e_string.eqlComptime("constructor")) + { + constructor_function_ = property.value.?.data.e_function; + constructor_function = constructor_function_; + } + } + } + + if (property.value) |val| { + if (name_to_keep) |name| { + const was_anon = p.isAnonymousNamedExpr(val); + property.value = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); + } else { + property.value = p.visitExpr(val); + } + + if (comptime is_typescript_enabled) { + if (constructor_function_ != null and property.value != null and property.value.?.data == .e_function) { + constructor_function = property.value.?.data.e_function; + } + } + } + + if (property.initializer) |val| { + // if (property.flags.is_static and ) + if (name_to_keep) |name| { + const was_anon = p.isAnonymousNamedExpr(val); + property.initializer = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); + } else { + property.initializer = p.visitExpr(val); + } + } + } + + // note: our version assumes useDefineForClassFields is true + if (comptime is_typescript_enabled) { + if (constructor_function) |constructor| { + var to_add: usize = 0; + for (constructor.func.args) |arg| { + to_add += @boolToInt(arg.is_typescript_ctor_field and arg.binding.data == .b_identifier); + } + + if (to_add > 0) { + // to match typescript behavior, we also must prepend to the class body + var stmts = std.ArrayList(Stmt).fromOwnedSlice(p.allocator, constructor.func.body.stmts); + stmts.ensureUnusedCapacity(to_add) catch unreachable; + var class_body = std.ArrayList(G.Property).fromOwnedSlice(p.allocator, class.properties); + class_body.ensureUnusedCapacity(to_add) catch unreachable; + var j: usize = 0; + + for (constructor.func.args) |arg| { + if (arg.is_typescript_ctor_field) { + switch (arg.binding.data) { + .b_identifier => |id| { + const name = p.symbols.items[id.ref.innerIndex()].original_name; + const ident = p.e(E.Identifier{ .ref = id.ref }, arg.binding.loc); + stmts.appendAssumeCapacity( + Expr.assignStmt( + p.e(E.Dot{ + .target = p.e(E.This{}, arg.binding.loc), + .name = name, + .name_loc = arg.binding.loc, + }, arg.binding.loc), + ident, + p.allocator, + ), + ); + // O(N) + class_body.items.len += 1; + std.mem.copyBackwards(G.Property, class_body.items[j + 1 .. class_body.items.len], class_body.items[j .. class_body.items.len - 1]); + class_body.items[j] = G.Property{ .key = ident }; + j += 1; + }, + else => {}, + } + } + } + + class.properties = class_body.toOwnedSlice(); + constructor.func.body.stmts = stmts.toOwnedSlice(); + } + } + } + + if (!shadow_ref.eql(Ref.None)) { + if (p.symbols.items[shadow_ref.innerIndex()].use_count_estimate == 0) { + // Don't generate a shadowing name if one isn't needed + shadow_ref = Ref.None; + } else if (class.class_name) |_| { + // If there was originally no class name but something inside needed one + // (e.g. there was a static property initializer that referenced "this"), + // store our generated name so the class expression ends up with a name. + class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; + p.current_scope.generated.append(p.allocator, class_name_ref) catch unreachable; + p.recordDeclaredSymbol(class_name_ref) catch unreachable; + } + } + + return shadow_ref; + } + + fn keepStmtSymbolName(p: *P, loc: logger.Loc, ref: Ref, name: string) Stmt { + p.expr_list.ensureUnusedCapacity(2) catch unreachable; + const start = p.expr_list.items.len; + p.expr_list.appendAssumeCapacity(p.e(E.Identifier{ + .ref = ref, + }, loc)); + p.expr_list.appendAssumeCapacity(p.e(E.String{ .data = name }, loc)); + return p.s(S.SExpr{ + // I believe that this is a spot we can do $RefreshReg$(name) + .value = p.callRuntime(loc, "__name", p.expr_list.items[start..p.expr_list.items.len]), + + // Make sure tree shaking removes this if the function is never used + .does_not_affect_tree_shaking = true, + }, loc); + } + + pub fn callRuntime(p: *P, loc: logger.Loc, comptime name: string, args: []Expr) Expr { + var ref: Ref = undefined; + p.has_called_runtime = true; + + if (!p.runtime_imports.contains(name)) { + ref = brk: { + if (comptime strings.eqlComptime(name, "__require")) { + p.runtime_imports.__require = GeneratedSymbol{ + .backup = declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, StaticSymbolName.List.__require.backup, true) catch unreachable, + .primary = p.require_ref, + .ref = declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, StaticSymbolName.List.__require.internal, true) catch unreachable, + }; + break :brk p.runtime_imports.__require.?.ref; + } + const generated_symbol = p.declareGeneratedSymbol(.other, name) catch unreachable; + p.runtime_imports.put(name, generated_symbol); + break :brk generated_symbol.ref; + }; + + p.module_scope.generated.append(p.allocator, ref) catch unreachable; + } else { + ref = p.runtime_imports.at(name).?; + } + + p.recordUsage(ref); + return p.e(E.Call{ + .target = p.e(E.Identifier{ + .ref = ref, + }, loc), + .args = ExprNodeList.init(args), + }, loc); + } + + // Try separating the list for appending, so that it's not a pointer. + fn visitStmts(p: *P, stmts: *ListManaged(Stmt), _: StmtsKind) !void { + if (only_scan_imports_and_do_not_visit) { + @compileError("only_scan_imports_and_do_not_visit must not run this."); + } + + // Save the current control-flow liveness. This represents if we are + // currently inside an "if (false) { ... }" block. + var old_is_control_flow_dead = p.is_control_flow_dead; + defer p.is_control_flow_dead = old_is_control_flow_dead; + + // visit all statements first + var visited = try ListManaged(Stmt).initCapacity(p.allocator, stmts.items.len); + var before = ListManaged(Stmt).init(p.allocator); + var after = ListManaged(Stmt).init(p.allocator); + + if (p.current_scope == p.module_scope) { + p.macro.prepend_stmts = &before; + } + + defer before.deinit(); + defer visited.deinit(); + defer after.deinit(); + + for (stmts.items) |*stmt| { + const list = list_getter: { + switch (stmt.data) { + .s_export_equals => { + // TypeScript "export = value;" becomes "module.exports = value;". This + // must happen at the end after everything is parsed because TypeScript + // moves this statement to the end when it generates code. + break :list_getter &after; + }, + .s_function => |data| { + // Manually hoist block-level function declarations to preserve semantics. + // This is only done for function declarations that are not generators + // or async functions, since this is a backwards-compatibility hack from + // Annex B of the JavaScript standard. + if (!p.current_scope.kindStopsHoisting() and p.symbols.items[data.func.name.?.ref.?.innerIndex()].kind == .hoisted_function) { + break :list_getter &before; + } + }, + else => {}, + } + break :list_getter &visited; + }; + try p.visitAndAppendStmt(list, stmt); + } + + var visited_count = visited.items.len; + if (p.is_control_flow_dead) { + var end: usize = 0; + for (visited.items) |item| { + if (!SideEffects.shouldKeepStmtInDeadControlFlow(item, p.allocator)) { + continue; + } + + visited.items[end] = item; + end += 1; + } + visited_count = end; + } + + const total_size = visited_count + before.items.len + after.items.len; + + if (total_size != stmts.items.len) { + try stmts.resize(total_size); + } + + var i: usize = 0; + + for (before.items) |item| { + stmts.items[i] = item; + i += 1; + } + + const visited_slice = visited.items[0..visited_count]; + for (visited_slice) |item| { + stmts.items[i] = item; + i += 1; + } + + for (after.items) |item| { + stmts.items[i] = item; + i += 1; + } + } + + fn extractDeclsForBinding(binding: Binding, decls: *ListManaged(G.Decl)) !void { + switch (binding.data) { + .b_property, .b_missing => {}, + .b_identifier => { + try decls.append(G.Decl{ .binding = binding }); + }, + .b_array => |arr| { + for (arr.items) |item| { + extractDeclsForBinding(item.binding, decls) catch unreachable; + } + }, + .b_object => |obj| { + for (obj.properties) |prop| { + extractDeclsForBinding(prop.value, decls) catch unreachable; + } + }, + } + } + + pub inline fn @"module.exports"(p: *P, loc: logger.Loc) Expr { + return p.e(E.Dot{ .name = exports_string_name, .name_loc = loc, .target = p.e(E.Identifier{ .ref = p.module_ref }, loc) }, loc); + } + + // This assumes that the open parenthesis has already been parsed by the caller + pub fn parseParenExpr(p: *P, loc: logger.Loc, level: Level, opts: ParenExprOpts) anyerror!Expr { + var items_list = ListManaged(Expr).init(p.allocator); + var errors = DeferredErrors{}; + var arrowArgErrors = DeferredArrowArgErrors{}; + var spread_range = logger.Range{}; + var type_colon_range = logger.Range{}; + var comma_after_spread: ?logger.Loc = null; + + // Push a scope assuming this is an arrow function. It may not be, in which + // case we'll need to roll this change back. This has to be done ahead of + // parsing the arguments instead of later on when we hit the "=>" token and + // we know it's an arrow function because the arguments may have default + // values that introduce new scopes and declare new symbols. If this is an + // arrow function, then those new scopes will need to be parented under the + // scope of the arrow function itself. + const scope_index = try p.pushScopeForParsePass(.function_args, loc); + + // Allow "in" inside parentheses + var oldAllowIn = p.allow_in; + p.allow_in = true; + + // Forbid "await" and "yield", but only for arrow functions + var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); + p.fn_or_arrow_data_parse.arrow_arg_errors = arrowArgErrors; + p.fn_or_arrow_data_parse.track_arrow_arg_errors = true; + + // Scan over the comma-separated arguments or expressions + while (p.lexer.token != .t_close_paren) { + const is_spread = p.lexer.token == .t_dot_dot_dot; + + if (is_spread) { + spread_range = p.lexer.range(); + // p.markSyntaxFeature() + try p.lexer.next(); + } + + // We don't know yet whether these are arguments or expressions, so parse + p.latest_arrow_arg_loc = p.lexer.loc(); + + var item = try p.parseExprOrBindings(.comma, &errors); + + if (is_spread) { + item = p.e(E.Spread{ .value = item }, loc); + } + + // Skip over types + if (is_typescript_enabled and p.lexer.token == .t_colon) { + type_colon_range = p.lexer.range(); + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); + } + + // There may be a "=" after the type (but not after an "as" cast) + if (is_typescript_enabled and p.lexer.token == .t_equals and !p.forbid_suffix_after_as_loc.eql(p.lexer.loc())) { + try p.lexer.next(); + item = Expr.assign(item, try p.parseExpr(.comma), p.allocator); + } + + items_list.append(item) catch unreachable; + + if (p.lexer.token != .t_comma) { + break; + } + + // Spread arguments must come last. If there's a spread argument followed + if (is_spread) { + comma_after_spread = p.lexer.loc(); + } + + // Eat the comma token + try p.lexer.next(); + } + var items = items_list.items; + + // The parenthetical construct must end with a close parenthesis + try p.lexer.expect(.t_close_paren); + + // Restore "in" operator status before we parse the arrow function body + p.allow_in = oldAllowIn; + + // Also restore "await" and "yield" expression errors + p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); + + // Are these arguments to an arrow function? + if (p.lexer.token == .t_equals_greater_than or opts.force_arrow_fn or (is_typescript_enabled and p.lexer.token == .t_colon)) { + // Arrow functions are not allowed inside certain expressions + if (level.gt(.assign)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + var invalidLog = LocList.init(p.allocator); + var args = ListManaged(G.Arg).init(p.allocator); + + if (opts.is_async) { + // markl,oweredsyntaxpoksdpokasd + } + + // First, try converting the expressions to bindings + for (items) |_, i| { + var is_spread = false; + switch (items[i].data) { + .e_spread => |v| { + is_spread = true; + items[i] = v.value; + }, + else => {}, + } + + var item = items[i]; + const tuple = p.convertExprToBindingAndInitializer(&item, &invalidLog, is_spread); + // double allocations + args.append(G.Arg{ + .binding = tuple.binding orelse Binding{ .data = Prefill.Data.BMissing, .loc = item.loc }, + .default = tuple.expr, + }) catch unreachable; + } + + // Avoid parsing TypeScript code like "a ? (1 + 2) : (3 + 4)" as an arrow + // function. The ":" after the ")" may be a return type annotation, so we + // attempt to convert the expressions to bindings first before deciding + // whether this is an arrow function, and only pick an arrow function if + // there were no conversion errors. + if (p.lexer.token == .t_equals_greater_than or ((comptime is_typescript_enabled) and + invalidLog.items.len == 0 and + p.trySkipTypeScriptArrowReturnTypeWithBacktracking()) or + opts.force_arrow_fn) + { + p.maybeCommaSpreadError(comma_after_spread); + p.logArrowArgErrors(&arrowArgErrors); + + // Now that we've decided we're an arrow function, report binding pattern + // conversion errors + if (invalidLog.items.len > 0) { + for (invalidLog.items) |_loc| { + _loc.addError( + p.log, + p.source, + ); + } + } + var arrow_data = FnOrArrowDataParse{ + .allow_await = if (opts.is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, + }; + var arrow = try p.parseArrowBody(args.items, &arrow_data); + arrow.is_async = opts.is_async; + arrow.has_rest_arg = spread_range.len > 0; + p.popScope(); + return p.e(arrow, loc); + } + } + + // If we get here, it's not an arrow function so undo the pushing of the + // scope we did earlier. This needs to flatten any child scopes into the + // parent scope as if the scope was never pushed in the first place. + p.popAndFlattenScope(scope_index); + + // If this isn't an arrow function, then types aren't allowed + if (type_colon_range.len > 0) { + try p.log.addRangeError(p.source, type_colon_range, "Unexpected \":\""); + return error.SyntaxError; + } + + // Are these arguments for a call to a function named "async"? + if (opts.is_async) { + p.logExprErrors(&errors); + const async_expr = p.e(E.Identifier{ .ref = try p.storeNameInRef("async") }, loc); + return p.e(E.Call{ .target = async_expr, .args = ExprNodeList.init(items) }, loc); + } + + // Is this a chain of expressions and comma operators? + if (items.len > 0) { + p.logExprErrors(&errors); + if (spread_range.len > 0) { + try p.log.addRangeError(p.source, type_colon_range, "Unexpected \"...\""); + return error.SyntaxError; + } + + var value = Expr.joinAllWithComma(items, p.allocator); + p.markExprAsParenthesized(&value); + return value; + } + + // Indicate that we expected an arrow function + try p.lexer.expected(.t_equals_greater_than); + return error.SyntaxError; + } + + // This code is tricky. + // - Doing it incorrectly will cause segfaults. + // - Doing it correctly drastically affects runtime performance while parsing larger files + // The key is in how we remove scopes from the list + // If we do an orderedRemove, it gets very slow. + // swapRemove is fast. But a little more dangerous. + // Instead, we just tombstone it. + pub fn popAndFlattenScope(p: *P, scope_index: usize) void { + // Move up to the parent scope + var to_flatten = p.current_scope; + var parent = to_flatten.parent.?; + p.current_scope = parent; + + // Erase this scope from the order. This will shift over the indices of all + // the scopes that were created after us. However, we shouldn't have to + // worry about other code with outstanding scope indices for these scopes. + // These scopes were all created in between this scope's push and pop + // operations, so they should all be child scopes and should all be popped + // by the time we get here. + p.scopes_in_order.items[scope_index] = null; + // Remove the last child from the parent scope + const last = parent.children.items.len - 1; + if (comptime Environment.allow_assert) assert(parent.children.items[last] == to_flatten); + _ = parent.children.popOrNull(); + + for (to_flatten.children.items) |item| { + item.parent = parent; + parent.children.append(p.allocator, item) catch unreachable; + } + } + + fn maybeCommaSpreadError(p: *P, _comma_after_spread: ?logger.Loc) void { + const comma_after_spread = _comma_after_spread orelse return; + if (comma_after_spread.start == -1) return; + + p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable; + } + + pub fn toAST(p: *P, _parts: []js_ast.Part, exports_kind: js_ast.ExportsKind, commonjs_wrapper_expr: ?Expr) !js_ast.Ast { + const allocator = p.allocator; + var parts = _parts; + // Insert an import statement for any runtime imports we generated + + if (p.options.tree_shaking and p.options.features.trim_unused_imports) { + p.treeShake(&parts, false); + } + + var parts_end: usize = 0; + // Handle import paths after the whole file has been visited because we need + // symbol usage counts to be able to remove unused type-only imports in + // TypeScript code. + while (true) { + var kept_import_equals = false; + var removed_import_equals = false; + + var i: usize = 0; + // Potentially remove some statements, then filter out parts to remove any + // with no statements + while (i < parts.len) : (i += 1) { + var part = parts[i]; + p.import_records_for_current_part.shrinkRetainingCapacity(0); + p.declared_symbols.shrinkRetainingCapacity(0); + + var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != null); + kept_import_equals = kept_import_equals or result.kept_import_equals; + removed_import_equals = removed_import_equals or result.removed_import_equals; + part.import_record_indices = part.import_record_indices; + part.declared_symbols = p.declared_symbols.toOwnedSlice(allocator); + part.stmts = result.stmts; + if (part.stmts.len > 0) { + if (p.module_scope.contains_direct_eval and part.declared_symbols.len > 0) { + // If this file contains a direct call to "eval()", all parts that + // declare top-level symbols must be kept since the eval'd code may + // reference those symbols. + part.can_be_removed_if_unused = false; + } + parts[parts_end] = part; + parts_end += 1; + } + } + + // We need to iterate multiple times if an import-equals statement was + // removed and there are more import-equals statements that may be removed + if (!kept_import_equals or !removed_import_equals) { + break; + } + } + + parts = parts[0..parts_end]; + + // Do a second pass for exported items now that imported items are filled out + for (parts) |part| { + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_export_clause => |clause| { + for (clause.items) |item| { + if (p.named_imports.getEntry(item.name.ref.?)) |_import| { + _import.value_ptr.is_exported = true; + } + } + }, + else => {}, + } + } + } + + if (p.options.tree_shaking) { + p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_bundling); + } + + if (commonjs_wrapper_expr) |commonjs_wrapper| { + var part = &parts[parts.len - 1]; + + var require_function_args = allocator.alloc(Arg, 2) catch unreachable; + + var imports_count: u32 = 0; + // We have to also move export from, since we will preserve those + var exports_from_count: u32 = 0; + + // Two passes. First pass just counts. + for (parts[parts.len - 1].stmts) |stmt| { + imports_count += switch (stmt.data) { + .s_import => @as(u32, 1), + else => @as(u32, 0), + }; + + exports_from_count += switch (stmt.data) { + .s_export_star, .s_export_from => @as(u32, 1), + else => @as(u32, 0), + }; + } + + var new_stmts_list = allocator.alloc(Stmt, exports_from_count + imports_count + 1) catch unreachable; + var imports_list = new_stmts_list[0..imports_count]; + + var exports_list = if (exports_from_count > 0) new_stmts_list[imports_list.len + 1 ..] else &[_]Stmt{}; + + require_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }; + require_function_args[1] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; + + var imports_list_i: u32 = 0; + var exports_list_i: u32 = 0; + + for (part.stmts) |_, i| { + switch (part.stmts[i].data) { + .s_import => { + imports_list[imports_list_i] = part.stmts[i]; + part.stmts[i] = Stmt.empty(); + part.stmts[i].loc = imports_list[imports_list_i].loc; + imports_list_i += 1; + }, + + .s_export_star, .s_export_from => { + exports_list[exports_list_i] = part.stmts[i]; + part.stmts[i] = Stmt.empty(); + part.stmts[i].loc = exports_list[exports_list_i].loc; + exports_list_i += 1; + }, + else => {}, + } + } + + commonjs_wrapper.data.e_call.args.ptr[0] = p.e( + E.Function{ .func = G.Fn{ + .name = null, + .open_parens_loc = logger.Loc.Empty, + .args = require_function_args, + .body = .{ .loc = logger.Loc.Empty, .stmts = parts[parts.len - 1].stmts }, + .flags = Flags.Function.init(.{ .is_export = true }), + } }, + logger.Loc.Empty, + ); + var sourcefile_name = p.source.path.pretty; + if (strings.lastIndexOf(sourcefile_name, "node_modules")) |node_modules_i| { + // 1 for the separator + const end = node_modules_i + 1 + "node_modules".len; + // If you were to name your file "node_modules.js" it shouldn't appear as ".js" + if (end < sourcefile_name.len) { + sourcefile_name = sourcefile_name[end..]; + } + } + commonjs_wrapper.data.e_call.args.ptr[1] = p.e(E.String{ .data = sourcefile_name }, logger.Loc.Empty); + + new_stmts_list[imports_list.len] = p.s( + S.ExportDefault{ + .value = .{ + .expr = commonjs_wrapper, + }, + .default_name = LocRef{ .ref = null, .loc = logger.Loc.Empty }, + }, + logger.Loc.Empty, + ); + part.stmts = new_stmts_list; + } else if (p.options.features.hot_module_reloading and p.options.features.allow_runtime) { + var named_exports_count: usize = p.named_exports.count(); + const named_imports: js_ast.Ast.NamedImports = p.named_imports; + + // To transform to something HMR'able, we must: + // 1. Wrap the top level code in an IIFE + // 2. Move imports to the top of the file (preserving the order) + // 3. Remove export clauses (done during ImportScanner) + // 4. Move export * from and export from to the bottom of the file (or the top, it doesn't matter I don't think) + // 5. Export everything as getters in our HMR module + // 6. Call the HMRModule's exportAll function like so: + // __hmrModule.exportAll({ + // exportAlias: () => identifier, + // exportAlias: () => identifier, + // }); + // This has the unfortunate property of making property accesses of exports slower at runtime. + // But, I'm not sure there's a way to use regular properties without breaking stuff. + var imports_count: usize = 0; + // We have to also move export from, since we will preserve those + var exports_from_count: usize = 0; + // Two passes. First pass just counts. + for (parts[parts.len - 1].stmts) |stmt| { + imports_count += switch (stmt.data) { + .s_import => @as(usize, 1), + else => @as(usize, 0), + }; + exports_from_count += switch (stmt.data) { + .s_export_star, .s_export_from => @as(usize, 1), + else => @as(usize, 0), + }; + } + var part = &parts[parts.len - 1]; + + const end_iife_stmts_count = part.stmts.len - imports_count - exports_from_count + 1; + // Why 7? + // 1. HMRClient.activate(${isDebug}); + // 2. var __hmrModule = new HMMRModule(id, file_path), __exports = __hmrModule.exports; + // 3. (__hmrModule.load = function() { + // ${...end_iffe_stmts_count - 1} + // ${end_iffe_stmts_count} + // __hmrModule.exportAll({exportAlias: () => identifier}) <-- ${named_exports_count} + // (); + // 4. var __hmrExport_exportName = __hmrModule.exports.exportName, + // 5. export { __hmrExport_exportName as blah, ... } + // 6. __hmrModule.onSetExports = (newExports) => { + // $named_exports_count __hmrExport_exportName = newExports.exportName; <-- ${named_exports_count} + // } + + // if there are no exports: + // - there shouldn't be an export statement + // - we don't need the S.Local for wrapping the exports + // We still call exportAll just with an empty object. + const has_any_exports = named_exports_count > 0; + + const toplevel_stmts_count = 3 + (@intCast(usize, @boolToInt(has_any_exports)) * 2); + var _stmts = allocator.alloc( + Stmt, + end_iife_stmts_count + toplevel_stmts_count + (named_exports_count * 2) + imports_count + exports_from_count, + ) catch unreachable; + // Normally, we'd have to grow that inner function's stmts list by one + // But we can avoid that by just making them all use this same array. + var curr_stmts = _stmts; + + // in debug: crash in the printer due to undefined memory + // in release: print ";" instead. + // this should never happen regardless, but i'm just being cautious here. + if (comptime !Environment.isDebug) { + std.mem.set(Stmt, _stmts, Stmt.empty()); + } + + // Second pass: move any imports from the part's stmts array to the new stmts + var imports_list = curr_stmts[0..imports_count]; + curr_stmts = curr_stmts[imports_list.len..]; + var toplevel_stmts = curr_stmts[0..toplevel_stmts_count]; + curr_stmts = curr_stmts[toplevel_stmts.len..]; + var exports_from = curr_stmts[0..exports_from_count]; + curr_stmts = curr_stmts[exports_from.len..]; + // This is used for onSetExports + var update_function_stmts = curr_stmts[0..named_exports_count]; + curr_stmts = curr_stmts[update_function_stmts.len..]; + var export_all_function_body_stmts = curr_stmts[0..named_exports_count]; + curr_stmts = curr_stmts[export_all_function_body_stmts.len..]; + // This is the original part statements + 1 + var part_stmts = curr_stmts; + if (comptime Environment.allow_assert) assert(part_stmts.len == end_iife_stmts_count); + var part_stmts_i: usize = 0; + + var import_list_i: usize = 0; + var export_list_i: usize = 0; + + // We must always copy it into the new stmts array + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_import => { + imports_list[import_list_i] = stmt; + import_list_i += 1; + }, + .s_export_star, .s_export_from => { + exports_from[export_list_i] = stmt; + export_list_i += 1; + }, + else => { + part_stmts[part_stmts_i] = stmt; + part_stmts_i += 1; + }, + } + } + + const new_call_args_count: usize = if (p.options.features.react_fast_refresh) 3 else 2; + var call_args = try allocator.alloc(Expr, new_call_args_count + 1); + var new_call_args = call_args[0..new_call_args_count]; + var hmr_module_ident = p.e(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty); + + new_call_args[0] = p.e(E.Number{ .value = @intToFloat(f64, p.options.filepath_hash_for_hmr) }, logger.Loc.Empty); + // This helps us provide better error messages + new_call_args[1] = p.e(E.String{ .data = p.source.path.pretty }, logger.Loc.Empty); + if (p.options.features.react_fast_refresh) { + new_call_args[2] = p.e(E.Identifier{ .ref = p.jsx_refresh_runtime.ref }, logger.Loc.Empty); + } + + var toplevel_stmts_i: u8 = 0; + + var decls = try allocator.alloc(G.Decl, 2 + named_exports_count); + var first_decl = decls[0..2]; + // We cannot rely on import.meta.url because if we import it within a blob: url, it will be nonsensical + // var __hmrModule = new HMRModule(123123124, "/index.js"), __exports = __hmrModule.exports; + const hmr_import_module_ = if (p.options.features.react_fast_refresh) + p.runtime_imports.__FastRefreshModule.? + else + p.runtime_imports.__HMRModule.?; + + const hmr_import_ref = hmr_import_module_.ref; + first_decl[0] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), + .value = p.e(E.New{ + .args = ExprNodeList.init(new_call_args), + .target = p.e( + E.Identifier{ + .ref = hmr_import_ref, + }, + logger.Loc.Empty, + ), + .close_parens_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + }; + first_decl[1] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), + .value = p.e(E.Dot{ + .target = p.e(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), + .name = "exports", + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + }; + + var export_clauses = try allocator.alloc(js_ast.ClauseItem, named_exports_count); + var named_export_i: usize = 0; + var named_exports_iter = p.named_exports.iterator(); + var export_properties = try allocator.alloc(G.Property, named_exports_count); + + var export_name_string_length: usize = 0; + while (named_exports_iter.next()) |named_export| { + export_name_string_length += named_export.key_ptr.len + "$$hmr_".len; + } + + var export_name_string_all = try allocator.alloc(u8, export_name_string_length); + var export_name_string_remainder = export_name_string_all; + var hmr_module_exports_dot = p.e( + E.Dot{ + .target = hmr_module_ident, + .name = "exports", + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ); + var exports_decls = decls[first_decl.len..]; + named_exports_iter = p.named_exports.iterator(); + var update_function_args = try allocator.alloc(G.Arg, 1); + var exports_ident = p.e(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty); + update_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; + + while (named_exports_iter.next()) |named_export| { + const named_export_value = named_export.value_ptr.*; + + // Do not try to HMR export {foo} from 'bar'; + if (named_imports.get(named_export_value.ref)) |named_import| { + if (named_import.is_exported) continue; + } + + const named_export_symbol: Symbol = p.symbols.items[named_export_value.ref.innerIndex()]; + + var export_name_string = export_name_string_remainder[0 .. named_export.key_ptr.len + "$$hmr_".len]; + export_name_string_remainder = export_name_string_remainder[export_name_string.len..]; + std.mem.copy(u8, export_name_string, "$$hmr_"); + std.mem.copy(u8, export_name_string["$$hmr_".len..], named_export.key_ptr.*); + + var name_ref = try p.declareSymbol(.other, logger.Loc.Empty, export_name_string); + + var body_stmts = export_all_function_body_stmts[named_export_i .. named_export_i + 1]; + body_stmts[0] = p.s( + // was this originally a named import? + // preserve the identifier + S.Return{ .value = if (named_export_symbol.namespace_alias != null) + p.e(E.ImportIdentifier{ + .ref = named_export_value.ref, + .was_originally_identifier = true, + }, logger.Loc.Empty) + else + p.e(E.Identifier{ + .ref = named_export_value.ref, + }, logger.Loc.Empty) }, + logger.Loc.Empty, + ); + export_clauses[named_export_i] = js_ast.ClauseItem{ + .original_name = "", + .alias = named_export.key_ptr.*, + .alias_loc = named_export_value.alias_loc, + .name = .{ .ref = name_ref, .loc = logger.Loc.Empty }, + }; + + var decl_value = p.e( + E.Dot{ .target = hmr_module_exports_dot, .name = named_export.key_ptr.*, .name_loc = logger.Loc.Empty }, + logger.Loc.Empty, + ); + exports_decls[named_export_i] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = name_ref }, logger.Loc.Empty), + .value = decl_value, + }; + + update_function_stmts[named_export_i] = Expr.assignStmt( + p.e( + E.Identifier{ .ref = name_ref }, + logger.Loc.Empty, + ), + p.e(E.Dot{ + .target = exports_ident, + .name = named_export.key_ptr.*, + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + allocator, + ); + + export_properties[named_export_i] = G.Property{ + .key = p.e(E.String{ .data = named_export.key_ptr.* }, logger.Loc.Empty), + .value = p.e( + E.Arrow{ + .args = &[_]G.Arg{}, + .body = .{ + .stmts = body_stmts, + .loc = logger.Loc.Empty, + }, + .prefer_expr = true, + }, + logger.Loc.Empty, + ), + }; + named_export_i += 1; + } + var export_all_args = call_args[new_call_args.len..]; + export_all_args[0] = p.e( + E.Object{ .properties = Property.List.init(export_properties[0..named_export_i]) }, + logger.Loc.Empty, + ); + + part_stmts[part_stmts.len - 1] = p.s( + S.SExpr{ + .value = p.e( + E.Call{ + .target = p.e( + E.Dot{ + .target = hmr_module_ident, + .name = "exportAll", + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(export_all_args), + }, + logger.Loc.Empty, + ), + }, + logger.Loc.Empty, + ); + + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Local{ + .decls = first_decl, + }, + logger.Loc.Empty, + ); + + toplevel_stmts_i += 1; + + const is_async = !p.top_level_await_keyword.isEmpty(); + + var func = p.e( + E.Function{ + .func = .{ + .body = .{ .loc = logger.Loc.Empty, .stmts = part_stmts[0 .. part_stmts_i + 1] }, + .name = null, + .open_parens_loc = logger.Loc.Empty, + .flags = Flags.Function.init(.{ + .print_as_iife = true, + .is_async = is_async, + }), + }, + }, + logger.Loc.Empty, + ); + + const call_load = p.e( + E.Call{ + .target = Expr.assign( + p.e( + E.Dot{ + .name = "_load", + .target = hmr_module_ident, + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + func, + allocator, + ), + }, + logger.Loc.Empty, + ); + // (__hmrModule._load = function())() + toplevel_stmts[toplevel_stmts_i] = p.s( + S.SExpr{ + .value = if (is_async) + p.e(E.Await{ .value = call_load }, logger.Loc.Empty) + else + call_load, + }, + logger.Loc.Empty, + ); + + toplevel_stmts_i += 1; + + if (has_any_exports) { + if (named_export_i > 0) { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Local{ + .decls = exports_decls[0..named_export_i], + }, + logger.Loc.Empty, + ); + } else { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Empty{}, + logger.Loc.Empty, + ); + } + + toplevel_stmts_i += 1; + } + + toplevel_stmts[toplevel_stmts_i] = p.s( + S.SExpr{ + .value = Expr.assign( + p.e( + E.Dot{ + .name = "_update", + .target = hmr_module_ident, + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + p.e( + E.Function{ + .func = .{ + .body = .{ .loc = logger.Loc.Empty, .stmts = if (named_export_i > 0) update_function_stmts[0..named_export_i] else &.{} }, + .name = null, + .args = update_function_args, + .open_parens_loc = logger.Loc.Empty, + }, + }, + logger.Loc.Empty, + ), + allocator, + ), + }, + logger.Loc.Empty, + ); + toplevel_stmts_i += 1; + if (has_any_exports) { + if (named_export_i > 0) { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.ExportClause{ + .items = export_clauses[0..named_export_i], + }, + logger.Loc.Empty, + ); + } else { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Empty{}, + logger.Loc.Empty, + ); + } + } + + part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; + } else if (p.options.features.hot_module_reloading) {} + + { + + // Each part tracks the other parts it depends on within this file + // var local_dependencies = AutoHashMap(u32, u32).init(p.allocator); + + // while (i < parts.len) : (i += 1) { + // const part = parts[i]; + // if (part.symbol_uses.count() > 0) { + // var iter = part.symbol_uses.iterator(); + // var dependencies = List(js_ast.Dependency).init(p.allocator); + // while (iter.next()) |entry| { + // const ref = entry.key; + + // if (p.top_level_symbol_to_parts.get(ref)) |tlstp| { + // for (tlstp.items) |other_part_index| { + // if (!local_dependencies.contains(other_part_index) or other_part_index != i) { + // try local_dependencies.put(other_part_index, @intCast(u32, i)); + // try dependencies.append(js_ast.Dependency{ + // .source_index = p.source.index, + // .part_index = other_part_index, + // }); + // } + // } + // } + + // // Also map from imports to parts that use them + // // TODO: will appending to this list like this be a perf issue? + // if (p.named_imports.getEntry(ref)) |named_import_entry| { + // const named_import = named_import_entry.value; + // var buf = try p.allocator.alloc(u32, named_import.local_parts_with_uses.len + 1); + // if (named_import.local_parts_with_uses.len > 0) { + // std.mem.copy(u32, buf, named_import.local_parts_with_uses); + // } + // buf[buf.len - 1] = @intCast(u32, i); + // named_import_entry.value.local_parts_with_uses = buf; + // } + // } + // } + // } + } + + return js_ast.Ast{ + .runtime_imports = p.runtime_imports, + .parts = parts, + .module_scope = p.module_scope.*, + .symbols = p.symbols.items, + .exports_ref = p.exports_ref, + .wrapper_ref = null, + .module_ref = p.module_ref, + .import_records = p.import_records.items, + .export_star_import_records = p.export_star_import_records.items, + .approximate_newline_count = p.lexer.approximate_newline_count, + .exports_kind = exports_kind, + .named_imports = p.named_imports, + .named_exports = p.named_exports, + .import_keyword = p.es6_import_keyword, + .export_keyword = p.es6_export_keyword, + .require_ref = if (p.runtime_imports.__require != null) + p.runtime_imports.__require.?.ref + else + p.require_ref, + + .uses_module_ref = (p.symbols.items[p.module_ref.innerIndex()].use_count_estimate > 0), + .uses_exports_ref = (p.symbols.items[p.exports_ref.innerIndex()].use_count_estimate > 0), + .uses_require_ref = if (p.runtime_imports.__require != null) + (p.symbols.items[p.runtime_imports.__require.?.ref.innerIndex()].use_count_estimate > 0) + else + false, + // .top_Level_await_keyword = p.top_level_await_keyword, + }; + } + + pub fn init( + allocator: Allocator, + log: *logger.Log, + source: *const logger.Source, + define: *Define, + lexer: js_lexer.Lexer, + opts: Parser.Options, + this: *P, + ) !void { + var scope_order = try ScopeOrderList.initCapacity(allocator, 1); + var scope = try allocator.create(Scope); + scope.* = Scope{ + .members = @TypeOf(scope.members){}, + .children = @TypeOf(scope.children){}, + .generated = @TypeOf(scope.generated){}, + .kind = .entry, + .label_ref = null, + .parent = null, + }; + + scope_order.appendAssumeCapacity(ScopeOrder{ .loc = locModuleScope, .scope = scope }); + this.* = P{ + .cjs_import_stmts = @TypeOf(this.cjs_import_stmts).init(allocator), + // This must default to true or else parsing "in" won't work right. + // It will fail for the case in the "in-keyword.js" file + .allow_in = true, + + .call_target = nullExprData, + .delete_target = nullExprData, + .stmt_expr_value = nullExprData, + .expr_list = .{}, + .loop_body = nullStmtData, + .define = define, + .import_records = undefined, + .named_imports = undefined, + .named_exports = js_ast.Ast.NamedExports.init(allocator), + .log = log, + .allocator = allocator, + .options = opts, + .then_catch_chain = ThenCatchChain{ .next_target = nullExprData }, + .to_expr_wrapper_namespace = undefined, + .to_expr_wrapper_hoisted = undefined, + .import_transposer = undefined, + .require_transposer = undefined, + .require_resolve_transposer = undefined, + .source = source, + .macro = MacroState.init(allocator), + .current_scope = scope, + .module_scope = scope, + .scopes_in_order = scope_order, + .needs_jsx_import = if (comptime only_scan_imports_and_do_not_visit) false else NeedsJSXType{}, + .lexer = lexer, + }; + + this.symbols = std.ArrayList(Symbol).init(allocator); + + if (comptime !only_scan_imports_and_do_not_visit) { + this.import_records = @TypeOf(this.import_records).init(allocator); + this.named_imports = NamedImportsType.init(allocator); + } + + this.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(this); + this.to_expr_wrapper_hoisted = Binding2ExprWrapper.Hoisted.init(this); + this.import_transposer = @TypeOf(this.import_transposer).init(this); + this.require_transposer = @TypeOf(this.require_transposer).init(this); + this.require_resolve_transposer = @TypeOf(this.require_resolve_transposer).init(this); + + if (opts.features.top_level_await) { + this.fn_or_arrow_data_parse.allow_await = .allow_expr; + this.fn_or_arrow_data_parse.is_top_level = true; + } + } + }; +} + +// Doing this seems to yield a 1% performance improvement parsing larger files +// ❯ hyperfine "../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable" "../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable" --min-runs=500 +// Benchmark #1: ../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable +// Time (mean ± σ): 25.1 ms ± 1.1 ms [User: 20.4 ms, System: 3.1 ms] +// Range (min … max): 23.5 ms … 31.7 ms 500 runs + +// Benchmark #2: ../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable +// Time (mean ± σ): 25.6 ms ± 1.3 ms [User: 20.9 ms, System: 3.1 ms] +// Range (min … max): 24.1 ms … 39.7 ms 500 runs +// '../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' ran +// 1.02 ± 0.07 times faster than '../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' +const JavaScriptParser = NewParser(.{}); +const JSXParser = NewParser(.{ .jsx = .react }); +const TSXParser = NewParser(.{ .jsx = .react, .typescript = true }); +const TypeScriptParser = NewParser(.{ .typescript = true }); +const SolidJSXParser = NewParser(.{ .jsx = .solid }); +const SolidTSXParser = NewParser(.{ .jsx = .solid, .typescript = true }); + +const JSParserMacro = NewParser(.{ + .jsx = .macro, +}); +const TSParserMacro = NewParser(.{ + .jsx = .macro, + .typescript = true, +}); + +const JavaScriptImportScanner = NewParser(.{ .scan_only = true }); +const JSXImportScanner = NewParser(.{ .jsx = .react, .scan_only = true }); +const TSXImportScanner = NewParser(.{ .jsx = .react, .typescript = true, .scan_only = true }); +const TypeScriptImportScanner = NewParser(.{ .typescript = true, .scan_only = true }); + +// The "await" and "yield" expressions are never allowed in argument lists but +// may or may not be allowed otherwise depending on the details of the enclosing +// function or module. This needs to be handled when parsing an arrow function +// argument list because we don't know if these expressions are not allowed until +// we reach the "=>" token (or discover the absence of one). +// +// Specifically, for await: +// +// // This is ok +// async function foo() { (x = await y) } +// +// // This is an error +// async function foo() { (x = await y) => {} } +// +// And for yield: +// +// // This is ok +// function* foo() { (x = yield y) } +// +// // This is an error +// function* foo() { (x = yield y) => {} } +// +const DeferredArrowArgErrors = struct { + invalid_expr_await: logger.Range = logger.Range.None, + invalid_expr_yield: logger.Range = logger.Range.None, +}; diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig deleted file mode 100644 index a5d4a74d2..000000000 --- a/src/js_parser/js_parser.zig +++ /dev/null @@ -1,18211 +0,0 @@ -pub const std = @import("std"); -pub const logger = @import("../logger.zig"); -pub const js_lexer = @import("../js_lexer.zig"); -pub const importRecord = @import("../import_record.zig"); -pub const js_ast = @import("../js_ast.zig"); -pub const options = @import("../options.zig"); -pub const js_printer = @import("../js_printer.zig"); -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 _hash_map = @import("../hash_map.zig"); -const bun = @import("../global.zig"); -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 ComptimeStringMap = @import("../comptime_string_map.zig").ComptimeStringMap; - -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 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; - -pub const StringHashMap = _hash_map.StringHashMap; -pub const AutoHashMap = _hash_map.AutoHashMap; -const StringHashMapUnamanged = _hash_map.StringHashMapUnamanged; -const ObjectPool = @import("../pool.zig").ObjectPool; -const NodeFallbackModules = @import("../node_fallbacks.zig"); -// 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: 1 -// 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, -); - -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()) { - lhs.data.e_string.push(rhs.data.e_string); - return lhs; - } - }, - .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, - 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| { - ex.yes = self.maybeTransposeIf(ex.yes, state); - ex.no = self.maybeTransposeIf(ex.no, state); - return arg; - }, - 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 - // <div - // <button - // <Hello-:Button - if (strings.containsComptime(name, "-:") or (p.lexer.token != .t_dot and name[0] >= 'a' and name[0] <= 'z')) { - return JSXTag{ - .data = Data{ .tag = p.e(E.String{ - .data = name, - }, loc) }, - .range = tag_range, - }; - } - - // Otherwise, this is an identifier - // <Button> - var tag = p.e(E.Identifier{ .ref = try p.storeNameInRef(name) }, loc); - - // Parse a member expression chain - // <Button.Red> - while (p.lexer.token == .t_dot) { - try p.lexer.nextInsideJSXElement(); - const member_range = p.lexer.range(); - const member = p.lexer.identifier; - try p.lexer.expectInsideJSXElement(.t_identifier); - - if (strings.indexOfChar(member, '-')) |index| { - try p.log.addError(p.source, logger.Loc{ .start = member_range.loc.start + @intCast(i32, index) }, "Unexpected \"-\""); - return error.SyntaxError; - } - - var _name = try p.allocator.alloc(u8, name.len + 1 + member.len); - std.mem.copy(u8, _name, name); - _name[name.len] = '.'; - std.mem.copy(u8, _name[name.len + 1 .. _name.len], member); - name = _name; - tag_range.len = member_range.loc.start + member_range.len - tag_range.loc.start; - tag = p.e(E.Dot{ .target = tag, .name = member, .name_loc = member_range.loc }, loc); - } - - return JSXTag{ .data = Data{ .tag = tag }, .range = tag_range, .name = name }; - } -}; - -pub const TypeScript = struct { - // This function is taken from the official TypeScript compiler source code: - // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts - pub fn canFollowTypeArgumentsInExpression(token: js_lexer.T) bool { - switch (token) { - // These are the only tokens can legally follow a type argument list. So we - // definitely want to treat them as type arg lists. - .t_open_paren, // foo<x>( - .t_no_substitution_template_literal, // foo<T> `...` - // foo<T> `...${100}...` - .t_template_head, - => { - return true; - }, - // These cases can't legally follow a type arg list. However, they're not - // legal expressions either. The user is probably in the middle of a - // generic type. So treat it as such. - .t_dot, // foo<x>. - .t_close_paren, // foo<x>) - .t_close_bracket, // foo<x>] - .t_colon, // foo<x>: - .t_semicolon, // foo<x>; - .t_question, // foo<x>? - .t_equals_equals, // foo<x> == - .t_equals_equals_equals, // foo<x> === - .t_exclamation_equals, // foo<x> != - .t_exclamation_equals_equals, // foo<x> !== - .t_ampersand_ampersand, // foo<x> && - .t_bar_bar, // foo<x> || - .t_question_question, // foo<x> ?? - .t_caret, // foo<x> ^ - .t_ampersand, // foo<x> & - .t_bar, // foo<x> | - .t_close_brace, // foo<x> } - .t_end_of_file, // foo<x> - => { - return true; - }, - - // We don't want to treat these as type arguments. Otherwise we'll parse - // this as an invocation expression. Instead, we want to parse out the - // expression in isolation from the type arguments. - .t_comma, // foo<x>, - .t_open_brace, // foo<x> { - => { - return false; - }, - else => { - // Anything else treat as an expression - return false; - }, - } - } - pub const Identifier = struct { - pub const StmtIdentifier = enum { - s_type, - - s_namespace, - - s_abstract, - - s_module, - - s_interface, - - s_declare, - }; - pub fn forStr(str: string) ?StmtIdentifier { - switch (str.len) { - "type".len => return if (strings.eqlComptimeIgnoreLen(str, "type")) - .s_type - else - null, - "interface".len => { - if (strings.eqlComptime(str, "interface")) { - return .s_interface; - } else if (strings.eqlComptime(str, "namespace")) { - return .s_namespace; - } else { - return null; - } - }, - "abstract".len => { - if (strings.eqlComptime(str, "abstract")) { - return .s_abstract; - } else { - return null; - } - }, - "declare".len => { - if (strings.eqlComptime(str, "declare")) { - return .s_declare; - } else { - return null; - } - }, - "module".len => { - if (strings.eqlComptime(str, "module")) { - return .s_module; - } else { - return null; - } - }, - else => return null, - } - } - pub const IMap = ComptimeStringMap(Kind, .{ - .{ "unique", .unique }, - .{ "abstract", .abstract }, - .{ "asserts", .asserts }, - .{ "keyof", .prefix }, - .{ "readonly", .prefix }, - .{ "infer", .prefix }, - .{ "any", .primitive }, - .{ "never", .primitive }, - .{ "unknown", .primitive }, - .{ "undefined", .primitive }, - .{ "object", .primitive }, - .{ "number", .primitive }, - .{ "string", .primitive }, - .{ "boolean", .primitive }, - .{ "bigint", .primitive }, - .{ "symbol", .primitive }, - }); - pub const Kind = enum { - normal, - unique, - abstract, - asserts, - prefix, - primitive, - }; - }; - - pub const SkipTypeOptions = struct { - is_return_type: bool = false, - }; -}; - -// We must prevent collisions from generated names. -// We want to avoid adding a pass over all the symbols in the file. -// To do that: -// For every generated symbol, we reserve two backup symbol names -// If any usages of the preferred ref, we swap original_name with the backup -// If any usages of the backup ref, we swap original_name with the internal -// We *assume* the internal name is never used. -// In practice, it is possible. But, the internal names are so crazy long you'd have to be deliberately trying to use them. -const GeneratedSymbol = @import("../runtime.zig").Runtime.GeneratedSymbol; - -pub const ImportScanner = struct { - stmts: []Stmt = &([_]Stmt{}), - - kept_import_equals: bool = false, - removed_import_equals: bool = false, - pub fn scan(comptime P: type, p: *P, stmts: []Stmt, will_transform_to_common_js: bool) !ImportScanner { - var scanner = ImportScanner{}; - var stmts_end: usize = 0; - const allocator = p.allocator; - const is_typescript_enabled: bool = comptime P.parser_features.typescript; - - for (stmts) |_stmt| { - // zls needs the hint, it seems. - var stmt: Stmt = _stmt; - switch (stmt.data) { - .s_import => |st__| { - var st = st__.*; - defer { - st__.* = st; - } - - var record: *ImportRecord = &p.import_records.items[st.import_record_index]; - - if (record.path.isMacro()) { - record.is_unused = true; - record.path.is_disabled = true; - continue; - } - - // The official TypeScript compiler always removes unused imported - // symbols. However, we deliberately deviate from the official - // TypeScript compiler's behavior doing this in a specific scenario: - // we are not bundling, symbol renaming is off, and the tsconfig.json - // "importsNotUsedAsValues" setting is present and is not set to - // "remove". - // - // This exists to support the use case of compiling partial modules for - // compile-to-JavaScript languages such as Svelte. These languages try - // to reference imports in ways that are impossible for esbuild to know - // about when esbuild is only given a partial module to compile. Here - // is an example of some Svelte code that might use esbuild to convert - // TypeScript to JavaScript: - // - // <script lang="ts"> - // import Counter from './Counter.svelte'; - // export let name: string = 'world'; - // </script> - // <main> - // <h1>Hello {name}!</h1> - // <Counter /> - // </main> - // - // Tools that use esbuild to compile TypeScript code inside a Svelte - // file like this only give esbuild the contents of the <script> tag. - // These tools work around this missing import problem when using the - // official TypeScript compiler by hacking the TypeScript AST to - // remove the "unused import" flags. This isn't possible in esbuild - // because esbuild deliberately does not expose an AST manipulation - // API for performance reasons. - // - // We deviate from the TypeScript compiler's behavior in this specific - // case because doing so is useful for these compile-to-JavaScript - // languages and is benign in other cases. The rationale is as follows: - // - // * If "importsNotUsedAsValues" is absent or set to "remove", then - // we don't know if these imports are values or types. It's not - // safe to keep them because if they are types, the missing imports - // will cause run-time failures because there will be no matching - // exports. It's only safe keep imports if "importsNotUsedAsValues" - // is set to "preserve" or "error" because then we can assume that - // none of the imports are types (since the TypeScript compiler - // would generate an error in that case). - // - // * If we're bundling, then we know we aren't being used to compile - // a partial module. The parser is seeing the entire code for the - // module so it's safe to remove unused imports. And also we don't - // want the linker to generate errors about missing imports if the - // imported file is also in the bundle. - // - // * If identifier minification is enabled, then using esbuild as a - // partial-module transform library wouldn't work anyway because - // the names wouldn't match. And that means we're minifying so the - // user is expecting the output to be as small as possible. So we - // should omit unused imports. - // - var did_remove_star_loc = false; - const keep_unused_imports = !p.options.features.trim_unused_imports; - - // TypeScript always trims unused imports. This is important for - // correctness since some imports might be fake (only in the type - // system and used for type-only imports). - if (!keep_unused_imports) { - var found_imports = false; - var is_unused_in_typescript = true; - - if (st.default_name) |default_name| { - found_imports = true; - const symbol = p.symbols.items[default_name.ref.?.innerIndex()]; - - // TypeScript has a separate definition of unused - if (is_typescript_enabled and p.ts_use_counts.items[default_name.ref.?.innerIndex()] != 0) { - is_unused_in_typescript = false; - } - - // Remove the symbol if it's never used outside a dead code region - if (symbol.use_count_estimate == 0) { - st.default_name = null; - } - } - - // Remove the star import if it's unused - if (st.star_name_loc) |_| { - found_imports = true; - const symbol = p.symbols.items[st.namespace_ref.innerIndex()]; - - // TypeScript has a separate definition of unused - if (is_typescript_enabled and p.ts_use_counts.items[st.namespace_ref.innerIndex()] != 0) { - is_unused_in_typescript = false; - } - - // Remove the symbol if it's never used outside a dead code region - if (symbol.use_count_estimate == 0) { - // Make sure we don't remove this if it was used for a property - // access while bundling - var has_any = false; - - if (p.import_items_for_namespace.get(st.namespace_ref)) |entry| { - if (entry.count() > 0) { - has_any = true; - break; - } - } - - if (!has_any) { - st.star_name_loc = null; - did_remove_star_loc = true; - } - } - } - - // Remove items if they are unused - if (st.items.len > 0) { - found_imports = true; - var items_end: usize = 0; - var i: usize = 0; - while (i < st.items.len) : (i += 1) { - const item = st.items[i]; - const ref = item.name.ref.?; - const symbol: Symbol = p.symbols.items[ref.innerIndex()]; - - // TypeScript has a separate definition of unused - if (is_typescript_enabled and p.ts_use_counts.items[ref.innerIndex()] != 0) { - is_unused_in_typescript = false; - } - - // Remove the symbol if it's never used outside a dead code region - if (symbol.use_count_estimate != 0) { - st.items[items_end] = item; - items_end += 1; - } - } - - st.items = st.items[0..items_end]; - } - - // -- Original Comment -- - // Omit this statement if we're parsing TypeScript and all imports are - // unused. Note that this is distinct from the case where there were - // no imports at all (e.g. "import 'foo'"). In that case we want to keep - // the statement because the user is clearly trying to import the module - // for side effects. - // - // This culling is important for correctness when parsing TypeScript - // because a) the TypeScript compiler does ths and we want to match it - // and b) this may be a fake module that only exists in the type system - // and doesn't actually exist in reality. - // - // We do not want to do this culling in JavaScript though because the - // module may have side effects even if all imports are unused. - // -- Original Comment -- - - // jarred: I think, in this project, we want this behavior, even in JavaScript. - // I think this would be a big performance improvement. - // The less you import, the less code you transpile. - // Side-effect imports are nearly always done through identifier-less imports - // e.g. `import 'fancy-stylesheet-thing/style.css';` - // This is a breaking change though. We can make it an option with some guardrail - // so maybe if it errors, it shows a suggestion "retry without trimming unused imports" - if ((is_typescript_enabled and found_imports and is_unused_in_typescript and !p.options.preserve_unused_imports_ts) or - (!is_typescript_enabled and p.options.features.trim_unused_imports and found_imports and st.star_name_loc == null and st.items.len == 0 and st.default_name == null)) - { - // internal imports are presumed to be always used - // require statements cannot be stripped - if (!record.is_internal and !record.was_originally_require) { - record.is_unused = true; - continue; - } - } - } - - const namespace_ref = st.namespace_ref; - const convert_star_to_clause = !p.options.enable_bundling and !p.options.can_import_from_bundle and p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0; - - if (convert_star_to_clause and !keep_unused_imports) { - st.star_name_loc = null; - } - - record.contains_default_alias = record.contains_default_alias or st.default_name != null; - - const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse - ImportItemForNamespaceMap.init(allocator); - - // ESM requires live bindings - // CommonJS does not require live bindings - // We load ESM in browsers & in Bun.js - // We have to simulate live bindings for cases where the code is bundled - // We do not know at this stage whether or not the import statement is bundled - // This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace - for (st.items) |item| { - const is_default = strings.eqlComptime(item.alias, "default"); - record.contains_default_alias = record.contains_default_alias or is_default; - - const name: LocRef = item.name; - const name_ref = name.ref.?; - - try p.named_imports.put(name_ref, js_ast.NamedImport{ - .alias = item.alias, - .alias_loc = name.loc, - .namespace_ref = namespace_ref, - .import_record_index = st.import_record_index, - }); - - // Make sure the printer prints this as a property access - var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()]; - - symbol.namespace_alias = G.NamespaceAlias{ - .namespace_ref = namespace_ref, - .alias = item.alias, - .import_record_index = st.import_record_index, - .was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name), - }; - } - - try p.import_records_for_current_part.append(allocator, st.import_record_index); - - if (st.star_name_loc != null) { - record.contains_import_star = true; - } - - if (record.was_originally_require) { - var symbol = &p.symbols.items[namespace_ref.innerIndex()]; - symbol.namespace_alias = G.NamespaceAlias{ - .namespace_ref = namespace_ref, - .alias = "", - .import_record_index = st.import_record_index, - .was_originally_property_access = false, - }; - } - }, - - .s_function => |st| { - if (st.func.flags.contains(.is_export)) { - if (st.func.name) |name| { - const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name; - try p.recordExport(name.loc, original_name, name.ref.?); - - if (p.options.features.hot_module_reloading) { - st.func.flags.remove(.is_export); - } - } else { - try p.log.addRangeError(p.source, logger.Range{ .loc = st.func.open_parens_loc, .len = 2 }, "Exported functions must have a name"); - } - } - }, - .s_class => |st| { - if (st.is_export) { - if (st.class.class_name) |name| { - try p.recordExport(name.loc, p.symbols.items[name.ref.?.innerIndex()].original_name, name.ref.?); - - if (p.options.features.hot_module_reloading) { - st.is_export = false; - } - } else { - try p.log.addRangeError(p.source, logger.Range{ .loc = st.class.body_loc, .len = 0 }, "Exported classes must have a name"); - } - } - }, - .s_local => |st| { - if (st.is_export) { - for (st.decls) |decl| { - p.recordExportedBinding(decl.binding); - } - } - - // Remove unused import-equals statements, since those likely - // correspond to types instead of values - if (st.was_ts_import_equals and !st.is_export and st.decls.len > 0) { - var decl = st.decls[0]; - - // Skip to the underlying reference - var value = decl.value; - if (decl.value) |val| { - while (true) { - if (@as(Expr.Tag, val.data) == .e_dot) { - value = val.data.e_dot.target; - } else { - break; - } - } - } - - // Is this an identifier reference and not a require() call? - if (value) |val| { - if (@as(Expr.Tag, val.data) == .e_identifier) { - // Is this import statement unused? - if (@as(Binding.Tag, decl.binding.data) == .b_identifier and p.symbols.items[decl.binding.data.b_identifier.ref.innerIndex()].use_count_estimate == 0) { - p.ignoreUsage(val.data.e_identifier.ref); - - scanner.removed_import_equals = true; - continue; - } else { - scanner.kept_import_equals = true; - } - } - } - } - - // We must do this at the end to not mess up import = - if (p.options.features.hot_module_reloading and st.is_export) { - st.is_export = false; - } - }, - .s_export_default => |st| { - // This is defer'd so that we still record export default for identifiers - defer { - if (st.default_name.ref) |ref| { - p.recordExport(st.default_name.loc, "default", ref) catch {}; - } - } - - // Rewrite this export to be: - // exports.default = - // But only if it's anonymous - if (p.options.features.hot_module_reloading) { - - // export default can be: - // - an expression - // - a function - // - a class - // it cannot be a declaration! - // we want to avoid adding a new name - // but we must remove the export default clause. - transform_export_default_when_its_anonymous: { - switch (st.value) { - .expr => |ex| { - switch (ex.data) { - .e_identifier => { - continue; - }, - .e_import_identifier => |import_ident| { - st.default_name.ref = import_ident.ref; - continue; - }, - .e_function => |func| { - if (func.func.name) |name_ref| { - if (name_ref.ref != null) { - stmt = p.s(S.Function{ .func = func.func }, ex.loc); - st.default_name.ref = name_ref.ref.?; - break :transform_export_default_when_its_anonymous; - } - } - }, - .e_class => |class| { - if (class.class_name) |name_ref| { - if (name_ref.ref != null) { - stmt = p.s( - S.Class{ - .class = class.*, - }, - ex.loc, - ); - st.default_name.ref = name_ref.ref.?; - break :transform_export_default_when_its_anonymous; - } - } - }, - else => {}, - } - var decls = try allocator.alloc(G.Decl, 1); - decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), .value = ex }; - - stmt = p.s(S.Local{ - .decls = decls, - .kind = S.Local.Kind.k_var, - .is_export = false, - }, ex.loc); - }, - .stmt => |class_or_func| { - switch (class_or_func.data) { - .s_function => |func| { - if (func.func.name) |name_ref| { - if (name_ref.ref != null) { - stmt = class_or_func; - st.default_name.ref = name_ref.ref.?; - break :transform_export_default_when_its_anonymous; - } - } - - var decls = try allocator.alloc(G.Decl, 1); - decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), .value = p.e(E.Function{ .func = func.func }, stmt.loc) }; - - stmt = p.s(S.Local{ - .decls = decls, - .kind = S.Local.Kind.k_var, - .is_export = false, - }, stmt.loc); - }, - .s_class => |class| { - if (class.class.class_name) |name_ref| { - if (name_ref.ref != null) { - stmt = class_or_func; - st.default_name.ref = name_ref.ref.?; - break :transform_export_default_when_its_anonymous; - } - } - - var decls = try allocator.alloc(G.Decl, 1); - decls[0] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), - .value = p.e(E.Class{ - .class_keyword = class.class.class_keyword, - .ts_decorators = class.class.ts_decorators, - .class_name = class.class.class_name, - .extends = class.class.extends, - .body_loc = class.class.body_loc, - .properties = class.class.properties, - .close_brace_loc = class.class.close_brace_loc, - }, stmt.loc), - }; - - stmt = p.s(S.Local{ - .decls = decls, - .kind = S.Local.Kind.k_var, - .is_export = false, - }, stmt.loc); - }, - else => unreachable, - } - }, - } - } - } else if (will_transform_to_common_js) { - const expr: js_ast.Expr = switch (st.value) { - .expr => |exp| exp, - .stmt => |s2| brk2: { - switch (s2.data) { - .s_function => |func| { - break :brk2 p.e(E.Function{ .func = func.func }, s2.loc); - }, - .s_class => |class| { - break :brk2 p.e(class.class, s2.loc); - }, - else => unreachable, - } - }, - }; - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(expr.loc); - export_default_args[1] = expr; - stmt = p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc); - } - }, - .s_export_clause => |st| { - for (st.items) |item| { - try p.recordExport(item.alias_loc, item.alias, item.name.ref.?); - } - - // export clauses simply disappear when we have HMR on, we use NamedExports to regenerate it at the end - if (p.options.features.hot_module_reloading) { - continue; - } - }, - .s_export_star => |st| { - try p.import_records_for_current_part.append(allocator, st.import_record_index); - - if (st.alias) |alias| { - // "export * as ns from 'path'" - try p.named_imports.put(st.namespace_ref, js_ast.NamedImport{ - .alias = null, - .alias_is_star = true, - .alias_loc = alias.loc, - .namespace_ref = Ref.None, - .import_record_index = st.import_record_index, - .is_exported = true, - }); - try p.recordExport(alias.loc, alias.original_name, st.namespace_ref); - } else { - // "export * from 'path'" - try p.export_star_import_records.append(allocator, st.import_record_index); - } - }, - .s_export_from => |st| { - try p.import_records_for_current_part.append(allocator, st.import_record_index); - - for (st.items) |item| { - const ref = item.name.ref orelse p.panic("Expected export from item to have a name {s}", .{st}); - // Note that the imported alias is not item.Alias, which is the - // exported alias. This is somewhat confusing because each - // SExportFrom statement is basically SImport + SExportClause in one. - try p.named_imports.put(ref, js_ast.NamedImport{ - .alias_is_star = false, - .alias = item.original_name, - .alias_loc = item.name.loc, - .namespace_ref = st.namespace_ref, - .import_record_index = st.import_record_index, - .is_exported = true, - }); - try p.recordExport(item.name.loc, item.alias, ref); - } - }, - else => {}, - } - - stmts[stmts_end] = stmt; - stmts_end += 1; - } - scanner.stmts = stmts[0..stmts_end]; - return scanner; - } -}; - -const StaticSymbolName = struct { - internal: string, - primary: string, - backup: string, - - pub const List = struct { - fn NewStaticSymbol(comptime basename: string) StaticSymbolName { - return comptime StaticSymbolName{ - .internal = basename ++ "_" ++ std.fmt.comptimePrint("{x}", .{std.hash.Wyhash.hash(0, basename)}), - .primary = basename, - .backup = "_" ++ basename ++ "$", - }; - } - - fn NewStaticSymbolWithBackup(comptime basename: string, comptime backup: string) StaticSymbolName { - return comptime StaticSymbolName{ - .internal = basename ++ "_" ++ std.fmt.comptimePrint("{x}", .{std.hash.Wyhash.hash(0, basename)}), - .primary = basename, - .backup = backup, - }; - } - - pub const jsx = NewStaticSymbol("jsx"); - pub const jsxs = NewStaticSymbol("jsxs"); - pub const ImportSource = NewStaticSymbol("JSX"); - pub const ClassicImportSource = NewStaticSymbol("JSXClassic"); - pub const jsxFilename = NewStaticSymbolWithBackup("fileName", "jsxFileName"); - pub const Factory = NewStaticSymbol("jsxEl"); - pub const Refresher = NewStaticSymbol("FastRefresh"); - pub const Fragment = NewStaticSymbol("JSXFrag"); - - pub const __name = NewStaticSymbol("__name"); - pub const __toModule = NewStaticSymbol("__toModule"); - pub const __require = NewStaticSymbol("require"); - pub const __cJS2eSM = NewStaticSymbol("__cJS2eSM"); - pub const __export = NewStaticSymbol("__export"); - pub const __reExport = NewStaticSymbol("__reExport"); - pub const __load = NewStaticSymbol("__load"); - pub const @"$$lzy" = NewStaticSymbol("$$lzy"); - pub const __HMRModule = NewStaticSymbol("HMR"); - pub const __HMRClient = NewStaticSymbol("Bun"); - pub const __FastRefreshModule = NewStaticSymbol("FastHMR"); - pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh"); - - pub const @"$$m" = NewStaticSymbol("$$m"); - - pub const __exportValue = NewStaticSymbol("__exportValue"); - pub const __exportDefault = NewStaticSymbol("__exportDefault"); - pub const hmr = NewStaticSymbol("hmr"); - - pub const insert = NewStaticSymbol("insert"); - pub const template = NewStaticSymbol("template"); - pub const wrap = NewStaticSymbol("wrap"); - pub const createComponent = NewStaticSymbol("createComponent"); - pub const setAttribute = NewStaticSymbol("setAttribute"); - pub const effect = NewStaticSymbol("effect"); - pub const delegateEvents = NewStaticSymbol("delegateEvents"); - pub const Solid = NewStaticSymbol("Solid"); - }; -}; - -pub const SideEffects = enum(u1) { - could_have_side_effects, - no_side_effects, - - pub const Result = struct { - side_effects: SideEffects, - ok: bool = false, - value: bool = false, - }; - - pub fn canChangeStrictToLoose(lhs: Expr.Data, rhs: Expr.Data) bool { - const left = lhs.knownPrimitive(); - const right = rhs.knownPrimitive(); - return left == right and left != .unknown and left != .mixed; - } - - pub fn simplifyBoolean(p: anytype, expr: Expr) Expr { - switch (expr.data) { - .e_unary => |e| { - if (e.op == .un_not) { - // "!!a" => "a" - if (e.value.data == .e_unary and e.value.data.e_unary.op == .un_not) { - return simplifyBoolean(p, e.value.data.e_unary.value); - } - - e.value = simplifyBoolean(p, e.value); - } - }, - .e_binary => |e| { - switch (e.op) { - .bin_logical_and => { - const effects = SideEffects.toBoolean(e.right.data); - if (effects.ok and effects.value and effects.side_effects == .no_side_effects) { - // "if (anything && truthyNoSideEffects)" => "if (anything)" - return e.left; - } - }, - .bin_logical_or => { - const effects = SideEffects.toBoolean(e.right.data); - if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) { - // "if (anything || falsyNoSideEffects)" => "if (anything)" - return e.left; - } - }, - else => {}, - } - }, - else => {}, - } - - return expr; - } - - pub const toNumber = Expr.Data.toNumber; - pub const typeof = Expr.Data.toTypeof; - - pub fn isPrimitiveToReorder(data: Expr.Data) bool { - switch (data) { - .e_null, .e_undefined, .e_string, .e_boolean, .e_number, .e_big_int => { - return true; - }, - else => { - return false; - }, - } - } - - pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr { - switch (expr.data) { - .e_null, .e_undefined, .e_missing, .e_boolean, .e_number, .e_big_int, .e_string, .e_this, .e_reg_exp, .e_function, .e_arrow, .e_import_meta => { - return null; - }, - - .e_dot => |dot| { - if (dot.can_be_removed_if_unused) { - return null; - } - }, - .e_identifier => |ident| { - if (ident.must_keep_due_to_with_stmt) { - return expr; - } - - if (ident.can_be_removed_if_unused or p.symbols.items[ident.ref.innerIndex()].kind != .unbound) { - return null; - } - }, - .e_if => |__if__| { - __if__.yes = simpifyUnusedExpr(p, __if__.yes) orelse __if__.yes.toEmpty(); - __if__.no = simpifyUnusedExpr(p, __if__.no) orelse __if__.no.toEmpty(); - - // "foo() ? 1 : 2" => "foo()" - if (__if__.yes.isEmpty() and __if__.no.isEmpty()) { - return simpifyUnusedExpr(p, __if__.test_); - } - - // "foo() ? 1 : bar()" => "foo() || bar()" - if (__if__.yes.isEmpty()) { - return Expr.joinWithLeftAssociativeOp( - .bin_logical_or, - __if__.test_, - __if__.no, - p.allocator, - ); - } - - // "foo() ? bar() : 2" => "foo() && bar()" - if (__if__.no.isEmpty()) { - return Expr.joinWithLeftAssociativeOp( - .bin_logical_and, - __if__.test_, - __if__.yes, - p.allocator, - ); - } - }, - .e_unary => |un| { - // These operators must not have any type conversions that can execute code - // such as "toString" or "valueOf". They must also never throw any exceptions. - switch (un.op) { - .un_void, .un_not => { - return simpifyUnusedExpr(p, un.value); - }, - .un_typeof => { - // "typeof x" must not be transformed into if "x" since doing so could - // cause an exception to be thrown. Instead we can just remove it since - // "typeof x" is special-cased in the standard to never throw. - if (std.meta.activeTag(un.value.data) == .e_identifier) { - return null; - } - - return simpifyUnusedExpr(p, un.value); - }, - - else => {}, - } - }, - - .e_call => |call| { - - // A call that has been marked "__PURE__" can be removed if all arguments - // can be removed. The annotation causes us to ignore the target. - if (call.can_be_unwrapped_if_unused) { - if (call.args.len > 0) { - return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, simpifyUnusedExpr, p.allocator); - } - } - }, - - .e_binary => |bin| { - switch (bin.op) { - // These operators must not have any type conversions that can execute code - // such as "toString" or "valueOf". They must also never throw any exceptions. - .bin_strict_eq, .bin_strict_ne, .bin_comma => { - return Expr.joinWithComma( - simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), - simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), - p.allocator, - ); - }, - - // We can simplify "==" and "!=" even though they can call "toString" and/or - // "valueOf" if we can statically determine that the types of both sides are - // primitives. In that case there won't be any chance for user-defined - // "toString" and/or "valueOf" to be called. - .bin_loose_eq, - .bin_loose_ne, - => { - if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) { - return Expr.joinWithComma(simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), p.allocator); - } - }, - - .bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => { - bin.right = simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(); - // Preserve short-circuit behavior: the left expression is only unused if - // the right expression can be completely removed. Otherwise, the left - // expression is important for the branch. - - if (bin.right.isEmpty()) - return simpifyUnusedExpr(p, bin.left); - }, - - else => {}, - } - }, - - .e_object => { - // Arrays with "..." spread expressions can't be unwrapped because the - // "..." triggers code evaluation via iterators. In that case, just trim - // the other items instead and leave the array expression there. - - var properties_slice = expr.data.e_object.properties.slice(); - var end: usize = 0; - var any_computed = false; - for (properties_slice) |spread| { - end = 0; - any_computed = any_computed or spread.flags.contains(.is_computed); - if (spread.kind == .spread) { - // Spread properties must always be evaluated - for (properties_slice) |prop_| { - var prop = prop_; - if (prop_.kind != .spread) { - if (prop.value != null) { - if (simpifyUnusedExpr(p, prop.value.?)) |value| { - prop.value = value; - } else if (!prop.flags.contains(.is_computed)) { - continue; - } else { - prop.value = p.e(E.Number{ .value = 0.0 }, prop.value.?.loc); - } - } - } - - properties_slice[end] = prop_; - end += 1; - } - - properties_slice = properties_slice[0..end]; - expr.data.e_object.properties = G.Property.List.init(properties_slice); - return expr; - } - } - - if (any_computed) { - // Otherwise, the object can be completely removed. We only need to keep any - // object properties with side effects. Apply this simplification recursively. - // for (properties_slice) |prop| { - // if (prop.flags.is_computed) { - // // Make sure "ToString" is still evaluated on the key - - // } - // } - - // keep this for now because we need better test coverage to do this correctly - return expr; - } - - return null; - }, - .e_array => { - var items = expr.data.e_array.items.slice(); - - for (items) |item| { - if (item.data == .e_spread) { - var end: usize = 0; - for (items) |item__| { - var item_ = item__; - if (item_.data != .e_missing) { - items[end] = item_; - end += 1; - } - - expr.data.e_array.items = ExprNodeList.init(items[0..end]); - return expr; - } - } - } - - // Otherwise, the array can be completely removed. We only need to keep any - // array items with side effects. Apply this simplification recursively. - return Expr.joinAllWithCommaCallback( - items, - @TypeOf(p), - p, - simpifyUnusedExpr, - p.allocator, - ); - }, - - .e_new => |call| { - // A constructor call that has been marked "__PURE__" can be removed if all arguments - // can be removed. The annotation causes us to ignore the target. - if (call.can_be_unwrapped_if_unused) { - if (call.args.len > 0) { - return Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, simpifyUnusedExpr, p.allocator); - } - } - }, - else => {}, - } - - return expr; - } - - fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void { - switch (binding.data) { - .b_identifier => { - decls.append(.{ .binding = binding }) catch unreachable; - }, - .b_array => |array| { - for (array.items) |item| { - findIdentifiers(item.binding, decls); - } - }, - .b_object => |obj| { - for (obj.properties) |item| { - findIdentifiers(item.value, decls); - } - }, - else => {}, - } - } - - // If this is in a dead branch, then we want to trim as much dead code as we - // can. Everything can be trimmed except for hoisted declarations ("var" and - // "function"), which affect the parent scope. For example: - // - // function foo() { - // if (false) { var x; } - // x = 1; - // } - // - // We can't trim the entire branch as dead or calling foo() will incorrectly - // assign to a global variable instead. - pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool { - switch (stmt.data) { - // Omit these statements entirely - .s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false, - - .s_local => |local| { - if (local.kind != .k_var) { - // Omit these statements entirely - return false; - } - - // Omit everything except the identifiers - - // common case: single var foo = blah, don't need to allocate - if (local.decls.len == 1 and local.decls[0].binding.data == .b_identifier) { - const prev = local.decls[0]; - stmt.data.s_local.decls[0] = G.Decl{ .binding = prev.binding }; - return true; - } - - var decls = std.ArrayList(G.Decl).initCapacity(allocator, local.decls.len) catch unreachable; - for (local.decls) |decl| { - findIdentifiers(decl.binding, &decls); - } - - local.decls = decls.toOwnedSlice(); - return true; - }, - - .s_block => |block| { - for (block.stmts) |child| { - if (shouldKeepStmtInDeadControlFlow(child, allocator)) { - return true; - } - } - - return false; - }, - - .s_if => |_if_| { - if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) { - return true; - } - - const no = _if_.no orelse return false; - - return shouldKeepStmtInDeadControlFlow(no, allocator); - }, - - .s_while => { - return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator); - }, - - .s_do_while => { - return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator); - }, - - .s_for => |__for__| { - if (__for__.init) |init_| { - if (shouldKeepStmtInDeadControlFlow(init_, allocator)) { - return true; - } - } - - return shouldKeepStmtInDeadControlFlow(__for__.body, allocator); - }, - - .s_for_in => |__for__| { - return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); - }, - - .s_for_of => |__for__| { - return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); - }, - - .s_label => |label| { - return shouldKeepStmtInDeadControlFlow(label.stmt, allocator); - }, - else => return true, - } - } - - // Returns true if this expression is known to result in a primitive value (i.e. - // null, undefined, boolean, number, bigint, or string), even if the expression - // cannot be removed due to side effects. - pub fn isPrimitiveWithSideEffects(data: Expr.Data) bool { - switch (data) { - .e_null, .e_undefined, .e_boolean, .e_number, .e_big_int, .e_string => { - return true; - }, - .e_unary => |e| { - switch (e.op) { - // number or bigint - .un_pos, - .un_neg, - .un_cpl, - .un_pre_dec, - .un_pre_inc, - .un_post_dec, - .un_post_inc, - // boolean - .un_not, - .un_delete, - // undefined - .un_void, - // string - .un_typeof, - => { - return true; - }, - else => {}, - } - }, - .e_binary => |e| { - switch (e.op) { - // boolean - .bin_lt, - .bin_le, - .bin_gt, - .bin_ge, - .bin_in, - .bin_instanceof, - .bin_loose_eq, - .bin_loose_ne, - .bin_strict_eq, - .bin_strict_ne, - // string, number, or bigint - .bin_add, - .bin_add_assign, - // number or bigint - .bin_sub, - .bin_mul, - .bin_div, - .bin_rem, - .bin_pow, - .bin_sub_assign, - .bin_mul_assign, - .bin_div_assign, - .bin_rem_assign, - .bin_pow_assign, - .bin_shl, - .bin_shr, - .bin_u_shr, - .bin_shl_assign, - .bin_shr_assign, - .bin_u_shr_assign, - .bin_bitwise_or, - .bin_bitwise_and, - .bin_bitwise_xor, - .bin_bitwise_or_assign, - .bin_bitwise_and_assign, - .bin_bitwise_xor_assign, - => { - return true; - }, - - // These always return one of the arguments unmodified - .bin_logical_and, - .bin_logical_or, - .bin_nullish_coalescing, - .bin_logical_and_assign, - .bin_logical_or_assign, - .bin_nullish_coalescing_assign, - => { - return isPrimitiveWithSideEffects(e.left.data) and isPrimitiveWithSideEffects(e.right.data); - }, - .bin_comma => { - return isPrimitiveWithSideEffects(e.right.data); - }, - else => {}, - } - }, - .e_if => |e| { - return isPrimitiveWithSideEffects(e.yes.data) and isPrimitiveWithSideEffects(e.no.data); - }, - else => {}, - } - return false; - } - - pub const toTypeOf = Expr.Data.typeof; - - pub fn toNullOrUndefined(exp: Expr.Data) Result { - switch (exp) { - // Never null or undefined - .e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => { - return Result{ .value = false, .side_effects = SideEffects.no_side_effects, .ok = true }; - }, - - .e_object, .e_array, .e_class => { - return Result{ .value = false, .side_effects = .could_have_side_effects, .ok = true }; - }, - - // always anull or undefined - .e_null, .e_undefined => { - return Result{ .value = true, .side_effects = .no_side_effects, .ok = true }; - }, - - .e_unary => |e| { - switch (e.op) { - // Always number or bigint - .un_pos, - .un_neg, - .un_cpl, - .un_pre_dec, - .un_pre_inc, - .un_post_dec, - .un_post_inc, - - // Always boolean - .un_not, - .un_typeof, - .un_delete, - => { - return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects }; - }, - - // Always undefined - .un_void => { - return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true }; - }, - - else => {}, - } - }, - - .e_binary => |e| { - switch (e.op) { - // always string or number or bigint - .bin_add, - .bin_add_assign, - // always number or bigint - .bin_sub, - .bin_mul, - .bin_div, - .bin_rem, - .bin_pow, - .bin_sub_assign, - .bin_mul_assign, - .bin_div_assign, - .bin_rem_assign, - .bin_pow_assign, - .bin_shl, - .bin_shr, - .bin_u_shr, - .bin_shl_assign, - .bin_shr_assign, - .bin_u_shr_assign, - .bin_bitwise_or, - .bin_bitwise_and, - .bin_bitwise_xor, - .bin_bitwise_or_assign, - .bin_bitwise_and_assign, - .bin_bitwise_xor_assign, - // always boolean - .bin_lt, - .bin_le, - .bin_gt, - .bin_ge, - .bin_in, - .bin_instanceof, - .bin_loose_eq, - .bin_loose_ne, - .bin_strict_eq, - .bin_strict_ne, - => { - return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects }; - }, - - .bin_comma => { - const res = toNullOrUndefined(e.right.data); - if (res.ok) { - return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects }; - } - }, - else => {}, - } - }, - else => {}, - } - - return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects }; - } - - pub fn toBoolean(exp: Expr.Data) Result { - switch (exp) { - .e_null, .e_undefined => { - return Result{ .ok = true, .value = false, .side_effects = .no_side_effects }; - }, - .e_boolean => |e| { - return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects }; - }, - .e_number => |e| { - return Result{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects }; - }, - .e_big_int => |e| { - return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects }; - }, - .e_string => |e| { - return Result{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects }; - }, - .e_function, .e_arrow, .e_reg_exp => { - return Result{ .ok = true, .value = true, .side_effects = .no_side_effects }; - }, - .e_object, .e_array, .e_class => { - return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; - }, - .e_unary => |e_| { - switch (e_.op) { - .un_void => { - return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects }; - }, - .un_typeof => { - // Never an empty string - - return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; - }, - .un_not => { - var result = toBoolean(e_.value.data); - if (result.ok) { - result.value = !result.value; - return result; - } - }, - else => {}, - } - }, - .e_binary => |e_| { - switch (e_.op) { - .bin_logical_or => { - // "anything || truthy" is truthy - const result = toBoolean(e_.right.data); - if (result.value and result.ok) { - return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; - } - }, - .bin_logical_and => { - // "anything && falsy" is falsy - const result = toBoolean(e_.right.data); - if (!result.value and result.ok) { - return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects }; - } - }, - .bin_comma => { - // "anything, truthy/falsy" is truthy/falsy - var result = toBoolean(e_.right.data); - if (result.ok) { - result.side_effects = .could_have_side_effects; - return result; - } - }, - else => {}, - } - }, - else => {}, - } - - return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects }; - } -}; - -const ExprOrLetStmt = struct { - stmt_or_expr: js_ast.StmtOrExpr, - decls: []G.Decl = &([_]G.Decl{}), -}; - -const FunctionKind = enum { stmt, expr }; - -const AsyncPrefixExpression = enum(u2) { - none, - is_yield, - is_async, - is_await, - - const map = ComptimeStringMap(AsyncPrefixExpression, .{ - .{ "yield", .is_yield }, - .{ "await", .is_await }, - .{ "async", .is_async }, - }); - - pub fn find(ident: string) AsyncPrefixExpression { - return map.get(ident) orelse .none; - } -}; - -const IdentifierOpts = struct { - assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, - is_delete_target: bool = false, - was_originally_identifier: bool = false, -}; - -fn statementCaresAboutScope(stmt: Stmt) bool { - switch (stmt.data) { - .s_block, - .s_empty, - .s_debugger, - .s_expr, - .s_if, - .s_for, - .s_for_in, - .s_for_of, - .s_do_while, - .s_while, - .s_with, - .s_try, - .s_switch, - .s_return, - .s_throw, - .s_break, - .s_continue, - .s_directive, - => { - return false; - }, - // This is technically incorrect. - // var does not care about the scope - // However, we are choosing _not_ to relocate vars to the top level - - .s_local => |local| { - return local.kind != .k_var; - }, - else => { - return true; - }, - } -} - -const ExprIn = struct { - // This tells us if there are optional chain expressions (EDot, EIndex, or - // ECall) that are chained on to this expression. Because of the way the AST - // works, chaining expressions on to this expression means they are our - // parent expressions. - // - // Some examples: - // - // a?.b.c // EDot - // a?.b[c] // EIndex - // a?.b() // ECall - // - // Note that this is false if our parent is a node with a OptionalChain - // value of OptionalChainStart. That means it's the start of a new chain, so - // it's not considered part of this one. - // - // Some examples: - // - // a?.b?.c // EDot - // a?.b?.[c] // EIndex - // a?.b?.() // ECall - // - // Also note that this is false if our parent is a node with a OptionalChain - // value of OptionalChainNone. That means it's outside parentheses, which - // means it's no longer part of the chain. - // - // Some examples: - // - // (a?.b).c // EDot - // (a?.b)[c] // EIndex - // (a?.b)() // ECall - // - has_chain_parent: bool = false, - - // If our parent is an ECall node with an OptionalChain value of - // OptionalChainStart, then we will need to store the value for the "this" of - // that call somewhere if the current expression is an optional chain that - // ends in a property access. That's because the value for "this" will be - // used twice: once for the inner optional chain and once for the outer - // optional chain. - // - // Example: - // - // // Original - // a?.b?.(); - // - // // Lowered - // var _a; - // (_a = a == null ? void 0 : a.b) == null ? void 0 : _a.call(a); - // - // In the example above we need to store "a" as the value for "this" so we - // can substitute it back in when we call "_a" if "_a" is indeed present. - // See also "thisArgFunc" and "thisArgWrapFunc" in "exprOut". - store_this_arg_for_parent_optional_chain: bool = false, - - // Certain substitutions of identifiers are disallowed for assignment targets. - // For example, we shouldn't transform "undefined = 1" into "void 0 = 1". This - // isn't something real-world code would do but it matters for conformance - // tests. - assign_target: js_ast.AssignTarget = js_ast.AssignTarget.none, -}; - -const ExprOut = struct { - // True if the child node is an optional chain node (EDot, EIndex, or ECall - // with an IsOptionalChain value of true) - child_contains_optional_chain: bool = false, -}; - -const Tup = std.meta.Tuple; - -// This function exists to tie all of these checks together in one place -// This can sometimes show up on benchmarks as a small thing. -fn isEvalOrArguments(name: string) bool { - return strings.eqlComptime(name, "eval") or strings.eqlComptime(name, "arguments"); -} - -const PrependTempRefsOpts = struct { - fn_body_loc: ?logger.Loc = null, - kind: StmtsKind = StmtsKind.none, -}; - -pub const StmtsKind = enum { - none, - loop_body, - fn_body, -}; - -fn notimpl() noreturn { - Global.panic("Not implemented yet!!", .{}); -} - -const ExprBindingTuple = struct { - expr: ?ExprNodeIndex = null, - binding: ?Binding = null, -}; - -const TempRef = struct { - ref: Ref, - value: ?Expr = null, -}; - -const ImportNamespaceCallOrConstruct = struct { - ref: Ref, - is_construct: bool = false, -}; - -const ThenCatchChain = struct { - next_target: js_ast.Expr.Data, - has_multiple_args: bool = false, - has_catch: bool = false, -}; - -const ParsedPath = struct { loc: logger.Loc, text: string }; - -const StrictModeFeature = enum { - with_statement, - delete_bare_name, - for_in_var_init, - eval_or_arguments, - reserved_word, - legacy_octal_literal, - legacy_octal_escape, - if_else_function_stmt, -}; - -const Map = _hash_map.AutoHashMapUnmanaged; - -const List = std.ArrayListUnmanaged; -const ListManaged = std.ArrayList; -const InvalidLoc = struct { - loc: logger.Loc, - kind: Tag = Tag.unknown, - - pub const Tag = enum { - spread, - parenthese, - getter, - setter, - method, - unknown, - }; - - pub fn addError(loc: InvalidLoc, log: *logger.Log, source: *const logger.Source) void { - @setCold(true); - const text = switch (loc.kind) { - .spread => "Unexpected trailing comma after rest element", - .parenthese => "Unexpected parentheses in binding pattern", - .getter => "Unexpected getter in binding pattern", - .setter => "Unexpected setter in binding pattern", - .method => "Unexpected method in binding pattern", - .unknown => "Invalid binding pattern", - }; - log.addError(source, loc.loc, text) catch unreachable; - } -}; -const LocList = ListManaged(InvalidLoc); -const StmtList = ListManaged(Stmt); - -// This hash table is used every time we parse function args -// Rather than allocating a new hash table each time, we can just reuse the previous allocation - -const StringVoidMap = struct { - allocator: Allocator, - map: std.StringHashMapUnmanaged(void) = std.StringHashMapUnmanaged(void){}, - - /// Returns true if the map already contained the given key. - pub fn getOrPutContains(this: *StringVoidMap, key: string) bool { - const entry = this.map.getOrPut(this.allocator, key) catch unreachable; - return entry.found_existing; - } - - pub fn contains(this: *StringVoidMap, key: string) bool { - return this.map.contains(key); - } - - fn init(allocator: Allocator) anyerror!StringVoidMap { - return StringVoidMap{ .allocator = allocator }; - } - - pub fn reset(this: *StringVoidMap) void { - // We must reset or the hash table will contain invalid pointers - this.map.clearRetainingCapacity(); - } - - pub inline fn get(allocator: Allocator) *Node { - return Pool.get(allocator); - } - - pub inline fn release(node: *Node) void { - Pool.release(node); - } - - pub const Pool = ObjectPool(StringVoidMap, init, true, 32); - pub const Node = Pool.Node; -}; -const RefCtx = @import("../ast/base.zig").RefCtx; -const SymbolUseMap = std.HashMapUnmanaged(Ref, js_ast.Symbol.Use, RefCtx, 80); -const StringBoolMap = std.StringHashMapUnmanaged(bool); -const RefMap = std.HashMapUnmanaged(Ref, void, RefCtx, 80); -const RefArrayMap = std.ArrayHashMapUnmanaged(Ref, void, @import("../ast/base.zig").RefHashCtx, false); - -const RefRefMap = std.HashMapUnmanaged(Ref, Ref, RefCtx, 80); -const ImportRecord = importRecord.ImportRecord; -const Flags = js_ast.Flags; -const ScopeOrder = struct { - loc: logger.Loc, - scope: *js_ast.Scope, -}; - -const ParenExprOpts = struct { - async_range: logger.Range = logger.Range.None, - is_async: bool = false, - force_arrow_fn: bool = false, -}; - -const AwaitOrYield = enum(u3) { - allow_ident, - allow_expr, - forbid_all, -}; - -// This is function-specific information used during parsing. It is saved and -// restored on the call stack around code that parses nested functions and -// arrow expressions. -const FnOrArrowDataParse = struct { - async_range: logger.Range = logger.Range.None, - allow_await: AwaitOrYield = AwaitOrYield.allow_ident, - allow_yield: AwaitOrYield = AwaitOrYield.allow_ident, - allow_super_call: bool = false, - allow_super_property: bool = false, - is_top_level: bool = false, - is_constructor: bool = false, - is_typescript_declare: bool = false, - - is_return_disallowed: bool = false, - is_this_disallowed: bool = false, - - has_async_range: bool = false, - arrow_arg_errors: DeferredArrowArgErrors = DeferredArrowArgErrors{}, - track_arrow_arg_errors: bool = false, - - // In TypeScript, forward declarations of functions have no bodies - allow_missing_body_for_type_script: bool = false, - - // Allow TypeScript decorators in function arguments - allow_ts_decorators: bool = false, - - pub fn i() FnOrArrowDataParse { - return FnOrArrowDataParse{ .allow_await = AwaitOrYield.forbid_all }; - } -}; - -// This is function-specific information used during visiting. It is saved and -// restored on the call stack around code that parses nested functions and -// arrow expressions. -const FnOrArrowDataVisit = struct { - // super_index_ref: ?*Ref = null, - - is_arrow: bool = false, - is_async: bool = false, - is_inside_loop: bool = false, - is_inside_switch: bool = false, - is_outside_fn_or_arrow: bool = false, - - // This is used to silence unresolvable imports due to "require" calls inside - // a try/catch statement. The assumption is that the try/catch statement is - // there to handle the case where the reference to "require" crashes. - try_body_count: i32 = 0, -}; - -// This is function-specific information used during visiting. It is saved and -// restored on the call stack around code that parses nested functions (but not -// nested arrow functions). -const FnOnlyDataVisit = struct { - // This is a reference to the magic "arguments" variable that exists inside - // functions in JavaScript. It will be non-nil inside functions and nil - // otherwise. - arguments_ref: ?Ref = null, - - // Arrow functions don't capture the value of "this" and "arguments". Instead, - // the values are inherited from the surrounding context. If arrow functions - // are turned into regular functions due to lowering, we will need to generate - // local variables to capture these values so they are preserved correctly. - this_capture_ref: ?Ref = null, - arguments_capture_ref: ?Ref = null, - - // Inside a static class property initializer, "this" expressions should be - // replaced with the class name. - this_class_static_ref: ?Ref = null, - - // If we're inside an async arrow function and async functions are not - // supported, then we will have to convert that arrow function to a generator - // function. That means references to "arguments" inside the arrow function - // will have to reference a captured variable instead of the real variable. - is_inside_async_arrow_fn: bool = false, - - // If false, disallow "new.target" expressions. We disallow all "new.target" - // expressions at the top-level of the file (i.e. not inside a function or - // a class field). Technically since CommonJS files are wrapped in a function - // you can use "new.target" in node as an alias for "undefined" but we don't - // support that. - is_new_target_allowed: bool = false, - - // If false, the value for "this" is the top-level module scope "this" value. - // That means it's "undefined" for ECMAScript modules and "exports" for - // CommonJS modules. We track this information so that we can substitute the - // correct value for these top-level "this" references at compile time instead - // of passing the "this" expression through to the output and leaving the - // interpretation up to the run-time behavior of the generated code. - // - // If true, the value for "this" is nested inside something (either a function - // or a class declaration). That means the top-level module scope "this" value - // has been shadowed and is now inaccessible. - is_this_nested: bool = false, -}; - -// Due to ES6 destructuring patterns, there are many cases where it's -// impossible to distinguish between an array or object literal and a -// destructuring assignment until we hit the "=" operator later on. -// This object defers errors about being in one state or the other -// until we discover which state we're in. -const DeferredErrors = struct { - // These are errors for expressions - invalid_expr_default_value: ?logger.Range = null, - invalid_expr_after_question: ?logger.Range = null, - array_spread_feature: ?logger.Range = null, - - pub fn isEmpty(self: *DeferredErrors) bool { - return self.invalid_expr_default_value == null and self.invalid_expr_after_question == null and self.array_spread_feature == null; - } - - pub fn mergeInto(self: *DeferredErrors, to: *DeferredErrors) void { - to.invalid_expr_default_value = self.invalid_expr_default_value orelse to.invalid_expr_default_value; - to.invalid_expr_after_question = self.invalid_expr_after_question orelse to.invalid_expr_after_question; - to.array_spread_feature = self.array_spread_feature orelse to.array_spread_feature; - } - - const None = DeferredErrors{ - .invalid_expr_default_value = null, - .invalid_expr_after_question = null, - .array_spread_feature = null, - }; -}; - -const ImportClause = struct { - items: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), - is_single_line: bool = false, - had_type_only_imports: bool = false, -}; - -const ModuleType = enum { esm }; - -const PropertyOpts = struct { - async_range: logger.Range = logger.Range.None, - declare_range: logger.Range = logger.Range.None, - is_async: bool = false, - is_generator: bool = false, - - // Class-related options - is_static: bool = false, - is_class: bool = false, - class_has_extends: bool = false, - allow_ts_decorators: bool = false, - ts_decorators: []Expr = &[_]Expr{}, -}; - -pub const ScanPassResult = struct { - pub const ParsePassSymbolUse = struct { ref: Ref, used: bool = false, import_record_index: u32 }; - pub const NamespaceCounter = struct { count: u16, import_record_index: u32 }; - pub const ParsePassSymbolUsageMap = std.StringArrayHashMap(ParsePassSymbolUse); - import_records: ListManaged(ImportRecord), - named_imports: js_ast.Ast.NamedImports, - used_symbols: ParsePassSymbolUsageMap, - import_records_to_keep: ListManaged(u32), - approximate_newline_count: usize = 0, - - pub fn init(allocator: Allocator) ScanPassResult { - return .{ - .import_records = ListManaged(ImportRecord).init(allocator), - .named_imports = js_ast.Ast.NamedImports.init(allocator), - .used_symbols = ParsePassSymbolUsageMap.init(allocator), - .import_records_to_keep = ListManaged(u32).init(allocator), - .approximate_newline_count = 0, - }; - } - - pub fn reset(scan_pass: *ScanPassResult) void { - scan_pass.named_imports.clearRetainingCapacity(); - scan_pass.import_records.shrinkRetainingCapacity(0); - scan_pass.used_symbols.clearRetainingCapacity(); - scan_pass.approximate_newline_count = 0; - } -}; - -fn MacroContextType() type { - if (comptime Environment.isWasm) { - return ?*anyopaque; - } - - return js_ast.Macro.MacroContext; -} - -pub const Parser = struct { - options: Options, - lexer: js_lexer.Lexer, - log: *logger.Log, - source: *const logger.Source, - define: *Define, - allocator: Allocator, - - pub const Options = struct { - jsx: options.JSX.Pragma, - can_import_from_bundle: bool = false, - ts: bool = false, - keep_names: bool = true, - omit_runtime_for_tests: bool = false, - ignore_dce_annotations: bool = false, - preserve_unused_imports_ts: bool = false, - use_define_for_class_fields: bool = false, - suppress_warnings_about_weird_code: bool = true, - filepath_hash_for_hmr: u32 = 0, - features: RuntimeFeatures = RuntimeFeatures{}, - - tree_shaking: bool = false, - - macro_context: *MacroContextType() = undefined, - - warn_about_unbundled_modules: bool = true, - - // Used when bundling node_modules - enable_bundling: bool = false, - transform_require_to_import: bool = true, - - moduleType: ModuleType = ModuleType.esm, - - pub fn init(jsx: options.JSX.Pragma, loader: options.Loader) Options { - var opts = Options{ - .ts = loader.isTypeScript(), - - .jsx = jsx, - }; - opts.jsx.parse = loader.isJSX(); - return opts; - } - }; - - pub fn scanImports(self: *Parser, scan_pass: *ScanPassResult) !void { - if (self.options.ts and self.options.jsx.parse) { - return try self._scanImports(TSXImportScanner, scan_pass); - } else if (self.options.ts) { - return try self._scanImports(TypeScriptImportScanner, scan_pass); - } else if (self.options.jsx.parse) { - return try self._scanImports(JSXImportScanner, scan_pass); - } else { - return try self._scanImports(JavaScriptImportScanner, scan_pass); - } - } - - fn _scanImports(self: *Parser, comptime ParserType: type, scan_pass: *ScanPassResult) !void { - var p: ParserType = undefined; - - try ParserType.init(self.allocator, self.log, self.source, self.define, self.lexer, self.options, &p); - p.import_records = &scan_pass.import_records; - p.named_imports = &scan_pass.named_imports; - - // The problem with our scan pass approach is type-only imports. - // We don't have accurate symbol counts. - // So we don't have a good way to distuingish between a type-only import and not. - if (comptime ParserType.parser_features.typescript) { - p.parse_pass_symbol_uses = &scan_pass.used_symbols; - } - - // Parse the file in the first pass, but do not bind symbols - var opts = ParseStatementOptions{ .is_module_scope = true }; - - // Parsing seems to take around 2x as much time as visiting. - // Which makes sense. - // June 4: "Parsing took: 18028000" - // June 4: "Rest of this took: 8003000" - _ = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts); - - // - if (comptime ParserType.parser_features.typescript) { - for (scan_pass.import_records.items) |*import_record| { - // Mark everything as unused - // Except: - // - export * as ns from 'foo'; - // - export * from 'foo'; - // - import 'foo'; - // - import("foo") - // - require("foo") - import_record.is_unused = import_record.is_unused or - (import_record.kind == .stmt and - !import_record.was_originally_bare_import and - !import_record.calls_run_time_re_export_fn); - } - - var iter = scan_pass.used_symbols.iterator(); - while (iter.next()) |entry| { - const val = entry.value_ptr; - if (val.used) { - scan_pass.import_records.items[val.import_record_index].is_unused = false; - } - } - } - - // Symbol use counts are unavailable - // So we say "did we parse any JSX?" - // if yes, just automatically add the import so that .bun knows to include the file. - if (self.options.jsx.parse and p.needs_jsx_import) { - _ = p.addImportRecord( - .require, - logger.Loc{ .start = 0 }, - p.options.jsx.import_source, - ); - // Ensure we have both classic and automatic - // This is to handle cases where they use fragments in the automatic runtime - _ = p.addImportRecord( - .require, - logger.Loc{ .start = 0 }, - p.options.jsx.classic_import_source, - ); - } - - scan_pass.approximate_newline_count = p.lexer.approximate_newline_count; - } - - pub fn parse(self: *Parser) !js_ast.Result { - if (comptime Environment.isWasm) { - self.options.ts = true; - self.options.jsx.parse = true; - // if (self.options.features.is_macro_runtime) { - // return try self._parse(TSParserMacro); - // } - - return try self._parse(TSXParser); - } - - if (self.options.ts and self.options.features.is_macro_runtime) return try self._parse(TSParserMacro); - if (!self.options.ts and self.options.features.is_macro_runtime) return try self._parse(JSParserMacro); - - if (self.options.ts and self.options.jsx.parse) { - return if (self.options.jsx.runtime != .solid) try self._parse(TSXParser) else try self._parse(SolidTSXParser); - } else if (self.options.ts) { - return try self._parse(TypeScriptParser); - } else if (self.options.jsx.parse) { - return if (self.options.jsx.runtime != .solid) try self._parse(JSXParser) else try self._parse(SolidJSXParser); - } else { - return try self._parse(JavaScriptParser); - } - } - - fn _parse(self: *Parser, comptime ParserType: type) !js_ast.Result { - var p: ParserType = undefined; - try ParserType.init(self.allocator, self.log, self.source, self.define, self.lexer, self.options, &p); - p.should_fold_numeric_constants = self.options.features.should_fold_numeric_constants; - defer p.lexer.deinit(); - var result: js_ast.Result = undefined; - - // defer { - // if (p.allocated_names_pool) |pool| { - // pool.data = p.allocated_names; - // pool.release(); - // p.allocated_names_pool = null; - // } - // } - - // Consume a leading hashbang comment - var hashbang: string = ""; - if (p.lexer.token == .t_hashbang) { - hashbang = p.lexer.identifier; - try p.lexer.next(); - } - - // Parse the file in the first pass, but do not bind symbols - var opts = ParseStatementOptions{ .is_module_scope = true }; - - // Parsing seems to take around 2x as much time as visiting. - // Which makes sense. - // June 4: "Parsing took: 18028000" - // June 4: "Rest of this took: 8003000" - const stmts = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts); - - try p.prepareForVisitPass(); - - // ESM is always strict mode. I don't think we need this. - // // Strip off a leading "use strict" directive when not bundling - // var directive = ""; - - // Insert a variable for "import.meta" at the top of the file if it was used. - // We don't need to worry about "use strict" directives because this only - // happens when bundling, in which case we are flatting the module scopes of - // all modules together anyway so such directives are meaningless. - // if (!p.import_meta_ref.isSourceIndexNull()) { - // // heap so it lives beyond this function call - // var decls = try p.allocator.alloc(G.Decl, 1); - // decls[0] = Decl{ .binding = p.b(B.Identifier{ - // .ref = p.import_meta_ref, - // }, logger.Loc.Empty), .value = p.e(E.Object{}, logger.Loc.Empty) }; - // var importMetaStatement = p.s(S.Local{ - // .kind = .k_const, - // .decls = decls, - // }, logger.Loc.Empty); - // } - - var before = ListManaged(js_ast.Part).init(p.allocator); - var after = ListManaged(js_ast.Part).init(p.allocator); - var parts = ListManaged(js_ast.Part).init(p.allocator); - - if (!p.options.tree_shaking) { - try p.appendPart(&parts, stmts); - } else { - // When tree shaking is enabled, each top-level statement is potentially a separate part. - for (stmts) |stmt| { - // switch (stmt.data) { - - // } - switch (stmt.data) { - .s_local => |local| { - if (local.decls.len > 1) { - for (local.decls) |decl| { - var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); - sliced.items.len = 1; - var _local = local.*; - var list = try ListManaged(G.Decl).initCapacity(p.allocator, 1); - list.items.len = 1; - list.items[0] = decl; - _local.decls = list.items; - sliced.items[0] = p.s(_local, stmt.loc); - try p.appendPart(&parts, sliced.items); - } - } else { - var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); - sliced.items.len = 1; - sliced.items[0] = stmt; - try p.appendPart(&parts, sliced.items); - } - }, - else => { - var sliced = try ListManaged(Stmt).initCapacity(p.allocator, 1); - sliced.items.len = 1; - sliced.items[0] = stmt; - try p.appendPart(&parts, sliced.items); - }, - } - } - } - - const uses_dirname = p.symbols.items[p.dirname_ref.innerIndex()].use_count_estimate > 0; - const uses_dynamic_require = p.options.features.dynamic_require and p.symbols.items[p.require_ref.innerIndex()].use_count_estimate > 0; - const uses_filename = p.symbols.items[p.filename_ref.innerIndex()].use_count_estimate > 0; - - if (uses_dynamic_require) { - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); - var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; - var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - var exprs = p.allocator.alloc(Expr, 1) catch unreachable; - exprs[0] = p.e(E.ImportMeta{}, logger.Loc.Empty); - // var require = import.meta.require.bind(import.meta) - decls[0] = .{ - .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), - .value = p.e( - E.Call{ - .target = p.e( - E.Dot{ - .target = p.e( - E.Dot{ - .target = p.e(E.ImportMeta{}, logger.Loc.Empty), - .name = "require", - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - .name = "bind", - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - .args = ExprNodeList.init(exprs), - }, - logger.Loc.Empty, - ), - }; - - declared_symbols[0] = .{ .ref = p.require_ref, .is_top_level = true }; - - part_stmts[0] = p.s(S.Local{ - .kind = .k_var, - .decls = decls, - }, logger.Loc.Empty); - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .tag = .dirname_filename, - }) catch unreachable; - } - - if (uses_dirname or uses_filename) { - const count = @as(usize, @boolToInt(uses_dirname)) + @as(usize, @boolToInt(uses_filename)); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, count); - var decls = p.allocator.alloc(G.Decl, count) catch unreachable; - if (uses_dirname) { - decls[0] = .{ - .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), - .value = p.e( - // TODO: test UTF-8 file paths - E.String.init(p.source.path.name.dir), - logger.Loc.Empty, - ), - }; - declared_symbols[0] = .{ .ref = p.dirname_ref, .is_top_level = true }; - } - if (uses_filename) { - decls[@as(usize, @boolToInt(uses_dirname))] = .{ - .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), - .value = p.e( - E.String.init(p.source.path.text), - logger.Loc.Empty, - ), - }; - declared_symbols[@as(usize, @boolToInt(uses_dirname))] = .{ .ref = p.filename_ref, .is_top_level = true }; - } - - // TODO: DeclaredSymbol - var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - part_stmts[0] = p.s(S.Local{ - .kind = .k_var, - .decls = decls, - }, logger.Loc.Empty); - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .tag = .dirname_filename, - }) catch unreachable; - } - - var did_import_fast_refresh = false; - - // Analyze cross-part dependencies for tree shaking and code splitting - var exports_kind = js_ast.ExportsKind.none; - const uses_exports_ref = p.symbols.items[p.exports_ref.innerIndex()].use_count_estimate > 0; - const uses_module_ref = p.symbols.items[p.module_ref.innerIndex()].use_count_estimate > 0; - - var wrapper_expr: ?Expr = null; - - if ((p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0) and !uses_exports_ref) { - exports_kind = .esm; - } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { - exports_kind = .cjs; - if (p.options.transform_require_to_import) { - var args = p.allocator.alloc(Expr, 2) catch unreachable; - - if (p.runtime_imports.__exportDefault == null and p.has_export_default) { - p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); - p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); - } - - wrapper_expr = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args); - p.resolveGeneratedSymbol(&p.runtime_imports.__cJS2eSM.?); - - // Disable HMR if we're wrapping it in CommonJS - // It's technically possible to support this. - // But we need to cut scope for the v0. - p.options.features.hot_module_reloading = false; - p.options.features.react_fast_refresh = false; - p.runtime_imports.__HMRModule = null; - p.runtime_imports.__FastRefreshModule = null; - p.runtime_imports.__FastRefreshRuntime = null; - p.runtime_imports.__HMRClient = null; - } - } else { - exports_kind = .esm; - } - - // Auto-import JSX - if (ParserType.jsx_transform_type == .react) { - const jsx_filename_symbol = p.symbols.items[p.jsx_filename.ref.innerIndex()]; - - { - const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; - const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; - const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; - const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; - - // Currently, React (and most node_modules) ship a CJS version or a UMD version - // but we should assume that it'll pretty much always be CJS - // Given that, we can't directly call import {jsxDEV} from 'react'; - // Instead, we must call require("react").default.jsxDEV - // So a jsx_symbol usage means a jsx_factory_symbol usage - // This is kind of a broken way of doing it because it wouldn't work if it was more than one level deep - if (FeatureFlags.jsx_runtime_is_cjs) { - if (jsx_symbol.use_count_estimate > 0 or jsx_static_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_automatic.ref); - } - - if (jsx_fragment_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_classic.ref); - } - - if (jsx_factory_symbol.use_count_estimate > 0) { - p.recordUsage(p.jsx_classic.ref); - } - } - } - - p.resolveStaticJSXSymbols(); - - if (p.options.features.auto_import_jsx) { - const jsx_classic_symbol = p.symbols.items[p.jsx_classic.ref.innerIndex()]; - const jsx_automatic_symbol = p.symbols.items[p.jsx_automatic.ref.innerIndex()]; - - // JSX auto-imports - // The classic runtime is a different import than the main import - // There are cases where you can use both JSX runtimes in the same file. - // 1. If you use a spread operator like this: <div foo bar key="foo" {...props} baz /> - // 2. If you use a React.Fragment - // So we have to support both. - if (jsx_classic_symbol.use_count_estimate > 0 or jsx_automatic_symbol.use_count_estimate > 0) { - // These must unfortunately be copied - // p.symbols may grow during this scope - // if it grows, the previous pointers are invalidated - const jsx_symbol = p.symbols.items[p.jsx_runtime.ref.innerIndex()]; - const jsx_static_symbol = p.symbols.items[p.jsxs_runtime.ref.innerIndex()]; - const jsx_fragment_symbol = p.symbols.items[p.jsx_fragment.ref.innerIndex()]; - const jsx_factory_symbol = p.symbols.items[p.jsx_factory.ref.innerIndex()]; - - const classic_namespace_ref = p.jsx_classic.ref; - const automatic_namespace_ref = p.jsx_automatic.ref; - - const decls_count: u32 = - @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) * 2 + - @intCast(u32, @boolToInt(jsx_static_symbol.use_count_estimate > 0)) * 2 + - @intCast(u32, @boolToInt(jsx_factory_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_filename_symbol.use_count_estimate > 0)); - - const imports_count = - @intCast(u32, @boolToInt(jsx_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_classic_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(jsx_fragment_symbol.use_count_estimate > 0)) + - @intCast(u32, @boolToInt(p.options.features.react_fast_refresh)) + - @intCast(u32, @boolToInt(jsx_static_symbol.use_count_estimate > 0)); - const stmts_count = imports_count + 1; - const symbols_count: u32 = imports_count + decls_count; - const loc = logger.Loc{ .start = 0 }; - - // Preallocate everything we'll need here - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, symbols_count); - var decls = try p.allocator.alloc(G.Decl, decls_count); - var jsx_part_stmts = try p.allocator.alloc(Stmt, stmts_count); - // Use the same array for storing the require call target of potentially both JSX runtimes - var require_call_args_base = p.allocator.alloc(Expr, if (p.options.can_import_from_bundle) 0 else imports_count) catch unreachable; - var import_records = try p.allocator.alloc(u32, imports_count); - - var decl_i: usize = 0; - var declared_symbols_i: usize = 0; - var import_record_i: usize = 0; - var require_call_args_i: usize = 0; - var stmt_i: usize = 0; - - if (jsx_symbol.use_count_estimate > 0 or jsx_static_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = automatic_namespace_ref, .is_top_level = true }; - declared_symbols_i += 1; - - const automatic_identifier = p.e(E.ImportIdentifier{ .ref = automatic_namespace_ref }, loc); - const dot_call_target = brk: { - if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { - break :brk automatic_identifier; - } else { - require_call_args_base[require_call_args_i] = automatic_identifier; - require_call_args_i += 1; - break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]); - } - }; - - if (jsx_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; - - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_runtime.ref, - }, - loc, - ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.jsx, - .name_loc = loc, - .can_be_removed_if_unused = true, - }, - loc, - ), - }; - decl_i += 1; - } - - if (jsx_static_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsxs_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; - - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsxs_runtime.ref, - }, - loc, - ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.jsx_static, - .name_loc = loc, - .can_be_removed_if_unused = true, - }, - loc, - ), - }; - - decl_i += 1; - } - - if (jsx_filename_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_filename.ref, .is_top_level = true }; - declared_symbols_i += 1; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_filename.ref, - }, - loc, - ), - .value = p.e(E.String{ .data = p.source.path.pretty }, loc), - }; - decl_i += 1; - } - - // We do not mark this as .require becuase we are already wrapping it manually. - const import_record_id = p.addImportRecord(.internal, loc, p.options.jsx.import_source); - p.import_records.items[import_record_id].tag = .jsx_import; - // When everything is CommonJS - // We import JSX like this: - // var {jsxDev} = require("react/jsx-dev") - - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = automatic_namespace_ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - - stmt_i += 1; - p.named_imports.put( - automatic_namespace_ref, - js_ast.NamedImport{ - .alias = jsx_automatic_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = automatic_namespace_ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, automatic_namespace_ref, .{}) catch unreachable; - import_records[import_record_i] = import_record_id; - import_record_i += 1; - } - - if (jsx_classic_symbol.use_count_estimate > 0) { - const classic_identifier = p.e(E.ImportIdentifier{ .ref = classic_namespace_ref }, loc); - - const dot_call_target = brk: { - // var react = $aopaSD123(); - - if (p.options.can_import_from_bundle or p.options.enable_bundling or !p.options.features.allow_runtime) { - break :brk classic_identifier; - } else { - const require_call_args_start = require_call_args_i; - require_call_args_base[require_call_args_i] = classic_identifier; - require_call_args_i += 1; - break :brk p.callUnbundledRequire(require_call_args_base[require_call_args_start..][0..1]); - } - }; - - if (jsx_factory_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_factory.ref, .is_top_level = true }; - declared_symbols_i += 1; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_factory.ref, - }, - loc, - ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.factory[p.options.jsx.factory.len - 1], - .name_loc = loc, - .can_be_removed_if_unused = true, - }, - loc, - ), - }; - decl_i += 1; - } - - if (jsx_fragment_symbol.use_count_estimate > 0) { - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_fragment.ref, .is_top_level = true }; - declared_symbols_i += 1; - decls[decl_i] = G.Decl{ - .binding = p.b( - B.Identifier{ - .ref = p.jsx_fragment.ref, - }, - loc, - ), - .value = p.e( - E.Dot{ - .target = dot_call_target, - .name = p.options.jsx.fragment[p.options.jsx.fragment.len - 1], - .name_loc = loc, - .can_be_removed_if_unused = true, - }, - loc, - ), - }; - decl_i += 1; - } - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.classic_import_source); - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = classic_namespace_ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - p.import_records.items[import_record_id].tag = .jsx_classic; - stmt_i += 1; - p.named_imports.put( - classic_namespace_ref, - js_ast.NamedImport{ - .alias = jsx_classic_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = classic_namespace_ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, classic_namespace_ref, .{}) catch unreachable; - import_records[import_record_i] = import_record_id; - declared_symbols[declared_symbols_i] = .{ .ref = classic_namespace_ref, .is_top_level = true }; - declared_symbols_i += 1; - } - - if (p.options.features.react_fast_refresh) { - defer did_import_fast_refresh = true; - p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); - if (!p.options.jsx.use_embedded_refresh_runtime) { - const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; - - declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; - declared_symbols_i += 1; - - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); - p.import_records.items[import_record_id].tag = .react_refresh; - jsx_part_stmts[stmt_i] = p.s(S.Import{ - .namespace_ref = p.jsx_refresh_runtime.ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - - stmt_i += 1; - p.named_imports.put( - p.jsx_refresh_runtime.ref, - js_ast.NamedImport{ - .alias = refresh_runtime_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = p.jsx_refresh_runtime.ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable; - import_records[import_record_i] = import_record_id; - } - p.recordUsage(p.jsx_refresh_runtime.ref); - } - - jsx_part_stmts[stmt_i] = p.s(S.Local{ .kind = .k_var, .decls = decls[0..decl_i] }, loc); - stmt_i += 1; - - before.append(js_ast.Part{ - .stmts = jsx_part_stmts[0..stmt_i], - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .jsx_import, - }) catch unreachable; - } - } - - if (!did_import_fast_refresh and p.options.features.react_fast_refresh) { - p.resolveGeneratedSymbol(&p.jsx_refresh_runtime); - p.recordUsage(p.jsx_refresh_runtime.ref); - - if (!p.options.jsx.use_embedded_refresh_runtime) { - if (comptime Environment.allow_assert) - assert(!p.options.enable_bundling); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, 1); - const loc = logger.Loc.Empty; - const import_record_id = p.addImportRecord(.require, loc, p.options.jsx.refresh_runtime); - p.import_records.items[import_record_id].tag = .react_refresh; - - var import_stmt = p.s(S.Import{ - .namespace_ref = p.jsx_refresh_runtime.ref, - .star_name_loc = loc, - .is_single_line = true, - .import_record_index = import_record_id, - }, loc); - - const refresh_runtime_symbol: *const Symbol = &p.symbols.items[p.jsx_refresh_runtime.ref.innerIndex()]; - - p.named_imports.put( - p.jsx_refresh_runtime.ref, - js_ast.NamedImport{ - .alias = refresh_runtime_symbol.original_name, - .alias_is_star = true, - .alias_loc = loc, - .namespace_ref = p.jsx_refresh_runtime.ref, - .import_record_index = import_record_id, - }, - ) catch unreachable; - p.is_import_item.put(p.allocator, p.jsx_refresh_runtime.ref, .{}) catch unreachable; - var import_records = try p.allocator.alloc(@TypeOf(import_record_id), 1); - import_records[0] = import_record_id; - declared_symbols[0] = .{ .ref = p.jsx_refresh_runtime.ref, .is_top_level = true }; - var part_stmts = try p.allocator.alloc(Stmt, 1); - part_stmts[0] = import_stmt; - - before.append(js_ast.Part{ - .stmts = part_stmts, - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .react_fast_refresh, - }) catch unreachable; - } - } - } else if (comptime ParserType.jsx_transform_type == .solid) { - p.resolveGeneratedSymbol(&p.solid.wrap); - p.resolveGeneratedSymbol(&p.solid.insert); - p.resolveGeneratedSymbol(&p.solid.template); - p.resolveGeneratedSymbol(&p.solid.delegateEvents); - p.resolveGeneratedSymbol(&p.solid.createComponent); - p.resolveGeneratedSymbol(&p.solid.setAttribute); - p.resolveGeneratedSymbol(&p.solid.effect); - p.resolveGeneratedSymbol(&p.solid.namespace); - - const import_count = - @as(usize, @boolToInt(p.symbols.items[p.solid.wrap.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.insert.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.template.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.delegateEvents.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.createComponent.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.setAttribute.ref.innerIndex()].use_count_estimate > 0)) + - @as(usize, @boolToInt(p.symbols.items[p.solid.effect.ref.innerIndex()].use_count_estimate > 0)); - var import_items = try p.allocator.alloc(js_ast.ClauseItem, import_count); - - // 1. Inject the part containing template declarations and Solid's import statement - var stmts_to_inject = p.allocator.alloc(Stmt, @as(usize, @boolToInt(p.solid.template_decls.items.len > 0)) + @as(usize, @boolToInt(import_count > 0))) catch unreachable; - var j: usize = 0; - const order = .{ - "createComponent", - "delegateEvents", - "effect", - "insert", - "setAttribute", - "template", - "wrap", - }; - - try p.named_imports.ensureUnusedCapacity(import_count); - try p.is_import_item.ensureUnusedCapacity(p.allocator, @intCast(u32, import_count)); - - if (import_count > 0) { - const import_record_id = p.addImportRecord(.stmt, logger.Loc.Empty, p.options.jsx.import_source); - var declared_symbols = p.allocator.alloc(js_ast.DeclaredSymbol, p.solid.template_decls.items.len) catch unreachable; - - inline for (order) |field_name| { - const ref = @field(p.solid, field_name).ref; - if (p.symbols.items[ref.innerIndex()].use_count_estimate > 0) { - import_items[j] = js_ast.ClauseItem{ - .alias = field_name, - .name = .{ .loc = logger.Loc.Empty, .ref = ref }, - .alias_loc = logger.Loc.Empty, - .original_name = "", - }; - - p.named_imports.putAssumeCapacity( - ref, - js_ast.NamedImport{ - .alias = p.symbols.items[ref.innerIndex()].original_name, - .alias_is_star = false, - .alias_loc = logger.Loc.Empty, - .namespace_ref = p.solid.namespace.ref, - .import_record_index = import_record_id, - }, - ); - p.is_import_item.putAssumeCapacity(ref, .{}); - j += 1; - } - } - - p.import_records.items[import_record_id].tag = .jsx_import; - stmts_to_inject[0] = p.s( - S.Import{ - .namespace_ref = p.solid.namespace.ref, - .star_name_loc = null, - .is_single_line = true, - .import_record_index = import_record_id, - .items = import_items, - }, - logger.Loc.Empty, - ); - if (p.solid.template_decls.items.len > 0) { - for (p.solid.template_decls.items) |_, i| { - declared_symbols[i] = js_ast.DeclaredSymbol{ - .ref = p.solid.template_decls.items[i].binding.data.b_identifier.ref, - .is_top_level = true, - }; - } - stmts_to_inject[1] = p.s( - S.Local{ - .decls = p.solid.template_decls.items, - }, - logger.Loc.Empty, - ); - } - var import_record_ids = p.allocator.alloc(u32, 1) catch unreachable; - import_record_ids[0] = import_record_id; - - before.append(js_ast.Part{ - .stmts = stmts_to_inject, - .declared_symbols = declared_symbols, - .import_record_indices = import_record_ids, - .tag = .jsx_import, - }) catch unreachable; - } - } - - if (p.options.enable_bundling) p.resolveBundlingSymbols(); - - var runtime_imports_iter = p.runtime_imports.iter(); - - const has_cjs_imports = p.cjs_import_stmts.items.len > 0 and p.options.transform_require_to_import; - - p.resolveCommonJSSymbols(); - - // - don't import runtime if we're bundling, it's already included - // - when HMR is enabled, we always need to import the runtime for HMRClient and HMRModule. - // - when HMR is not enabled, we only need any runtime imports if we're importing require() - if (p.options.features.allow_runtime and - !p.options.enable_bundling and - (p.has_called_runtime or p.options.features.hot_module_reloading or has_cjs_imports)) - { - const before_start = before.items.len; - if (p.options.features.hot_module_reloading) { - p.resolveHMRSymbols(); - - if (runtime_imports_iter.next()) |entry| { - std.debug.assert(entry.key == 0); - - // HMRClient.activate(true) - var args_list: []Expr = if (Environment.isDebug) &Prefill.HotModuleReloading.DebugEnabledArgs else &Prefill.HotModuleReloading.DebugDisabled; - - var hmr_module_class_ident = p.e(E.Identifier{ .ref = p.runtime_imports.__HMRClient.?.ref }, logger.Loc.Empty); - const imports = [_]u16{entry.key}; - // TODO: remove these unnecessary allocations - p.generateImportStmt( - RuntimeImports.Name, - &imports, - &before, - p.runtime_imports, - p.s( - S.SExpr{ - .value = p.e(E.Call{ - .target = p.e(E.Dot{ - .target = hmr_module_class_ident, - .name = "activate", - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - .args = ExprNodeList.init(args_list), - }, logger.Loc.Empty), - }, - logger.Loc.Empty, - ), - "import_", - true, - ) catch unreachable; - } - } - - while (runtime_imports_iter.next()) |entry| { - const imports = [_]u16{entry.key}; - // TODO: remove these unnecessary allocations - p.generateImportStmt( - RuntimeImports.Name, - &imports, - &before, - p.runtime_imports, - null, - "import_", - true, - ) catch unreachable; - } - // If we import JSX, we might call require. - // We need to import require before importing JSX. - // But a runtime import may not be necessary until we import JSX. - // So we have to swap it after the fact, instead of just moving this above the JSX import. - if (before_start > 0) { - var j: usize = 0; - while (j < before_start) : (j += 1) { - std.mem.swap(js_ast.Part, &before.items[j], &before.items[before.items.len - j - 1]); - } - } - } - - if (has_cjs_imports) { - var import_records = try p.allocator.alloc(u32, p.cjs_import_stmts.items.len); - var declared_symbols = try p.allocator.alloc(js_ast.DeclaredSymbol, p.cjs_import_stmts.items.len); - - for (p.cjs_import_stmts.items) |entry, i| { - const import_statement: *S.Import = entry.data.s_import; - import_records[i] = import_statement.import_record_index; - declared_symbols[i] = .{ - .ref = import_statement.namespace_ref, - .is_top_level = true, - }; - } - - before.append(js_ast.Part{ - .stmts = p.cjs_import_stmts.items, - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .cjs_imports, - }) catch unreachable; - } - - var parts_slice: []js_ast.Part = &([_]js_ast.Part{}); - - if (before.items.len > 0 or after.items.len > 0) { - const before_len = before.items.len; - const after_len = after.items.len; - const parts_len = parts.items.len; - - var _parts = try p.allocator.alloc( - js_ast.Part, - before_len + - after_len + - parts_len, - ); - - var remaining_parts = _parts; - if (before_len > 0) { - const parts_to_copy = before.items; - std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); - remaining_parts = remaining_parts[parts_to_copy.len..]; - } - - if (parts_len > 0) { - const parts_to_copy = parts.items; - std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); - remaining_parts = remaining_parts[parts_to_copy.len..]; - } - - if (after_len > 0) { - const parts_to_copy = after.items; - std.mem.copy(js_ast.Part, remaining_parts, parts_to_copy); - } - - parts_slice = _parts; - } else { - after.deinit(); - before.deinit(); - parts_slice = parts.items; - } - - // Pop the module scope to apply the "ContainsDirectEval" rules - // p.popScope(); - - result.ast = try p.toAST(parts_slice, exports_kind, wrapper_expr); - result.ok = true; - - return result; - } - - pub fn init(_options: Options, log: *logger.Log, source: *const logger.Source, define: *Define, allocator: Allocator) !Parser { - const lexer = try js_lexer.Lexer.init(log, source.*, allocator); - return Parser{ - .options = _options, - .allocator = allocator, - .lexer = lexer, - .define = define, - .source = source, - .log = log, - }; - } -}; - -const FindLabelSymbolResult = struct { ref: Ref, is_loop: bool, found: bool = false }; - -const FindSymbolResult = struct { - ref: Ref, - declare_loc: ?logger.Loc = null, - is_inside_with_scope: bool = false, -}; -const ExportClauseResult = struct { - clauses: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}), - is_single_line: bool = false, - had_type_only_exports: bool = false, -}; - -const DeferredTsDecorators = struct { - values: []js_ast.Expr, - - // If this turns out to be a "declare class" statement, we need to undo the - // scopes that were potentially pushed while parsing the decorator arguments. - scope_index: usize, -}; - -const LexicalDecl = enum(u8) { forbid, allow_all, allow_fn_inside_if, allow_fn_inside_label }; - -const ParseClassOptions = struct { - ts_decorators: []Expr = &[_]Expr{}, - allow_ts_decorators: bool = false, - is_type_script_declare: bool = false, -}; - -const ParseStatementOptions = struct { - ts_decorators: ?DeferredTsDecorators = null, - lexical_decl: LexicalDecl = .forbid, - is_module_scope: bool = false, - is_namespace_scope: bool = false, - is_export: bool = false, - is_name_optional: bool = false, // For "export default" pseudo-statements, - is_typescript_declare: bool = false, - - pub fn hasDecorators(self: *ParseStatementOptions) bool { - const decs = self.ts_decorators orelse return false; - return decs.values.len > 0; - } -}; - -var e_missing_data = E.Missing{}; -var s_missing = S.Empty{}; -var nullExprData = Expr.Data{ .e_missing = e_missing_data }; -var nullStmtData = Stmt.Data{ .s_empty = s_missing }; -pub const Prefill = struct { - pub const HotModuleReloading = struct { - pub var DebugEnabledArgs = [_]Expr{ - Expr{ .data = .{ .e_boolean = E.Boolean{ .value = true } }, .loc = logger.Loc.Empty }, - }; - pub var DebugDisabled = [_]Expr{ - Expr{ .data = .{ .e_boolean = E.Boolean{ .value = false } }, .loc = logger.Loc.Empty }, - }; - pub var ActivateString = E.String{ - .data = "activate", - }; - pub var ActivateIndex = E.Index{ - .index = .{ - .data = .{ - .e_string = &ActivateString, - }, - .loc = logger.Loc.Empty, - }, - .target = undefined, - }; - }; - pub const StringLiteral = struct { - pub var Key = [3]u8{ 'k', 'e', 'y' }; - pub var Children = [_]u8{ 'c', 'h', 'i', 'l', 'd', 'r', 'e', 'n' }; - pub var Filename = [_]u8{ 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' }; - pub var LineNumber = [_]u8{ 'l', 'i', 'n', 'e', 'N', 'u', 'm', 'b', 'e', 'r' }; - pub var ColumnNumber = [_]u8{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' }; - }; - pub const Value = struct { - pub const EThis = E.This{}; - pub const Zero = E.Number{ .value = 0.0 }; - }; - pub const String = struct { - pub var Key = E.String{ .data = &Prefill.StringLiteral.Key }; - pub var Children = E.String{ .data = &Prefill.StringLiteral.Children }; - pub var Filename = E.String{ .data = &Prefill.StringLiteral.Filename }; - pub var LineNumber = E.String{ .data = &Prefill.StringLiteral.LineNumber }; - pub var ColumnNumber = E.String{ .data = &Prefill.StringLiteral.ColumnNumber }; - }; - pub const Data = struct { - pub var BMissing = B{ .b_missing = BMissing_ }; - pub var BMissing_ = B.Missing{}; - - pub var EMissing = Expr.Data{ .e_missing = EMissing_ }; - pub var EMissing_ = E.Missing{}; - - pub var SEmpty = Stmt.Data{ .s_empty = SEmpty_ }; - pub var SEmpty_ = S.Empty{}; - - pub var Filename = Expr.Data{ .e_string = &Prefill.String.Filename }; - pub var LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber }; - pub var ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber }; - pub const This = Expr.Data{ .e_this = E.This{} }; - pub const Zero = Expr.Data{ .e_number = Value.Zero }; - }; - pub const Runtime = struct { - pub var JSXFilename = "__jsxFilename"; - pub var MarkAsModule = "__markAsModule"; - pub var CommonJS = "__commonJS"; - pub var ReExport = "__reExport"; - pub var ToModule = "__toModule"; - const JSXShortname = "jsx"; - }; -}; - -var keyExprData = Expr.Data{ .e_string = &Prefill.String.Key }; -var jsxChildrenKeyData = Expr.Data{ .e_string = &Prefill.String.Children }; -var nullExprValueData = E.Null{}; -var falseExprValueData = E.Boolean{ .value = false }; -var nullValueExpr = Expr.Data{ .e_null = nullExprValueData }; -var falseValueExpr = Expr.Data{ .e_boolean = E.Boolean{ .value = false } }; - -pub const ImportOrRequireScanResults = struct { - import_records: List(ImportRecord), -}; - -const JSXTransformType = enum { - none, - react, - macro, - solid, -}; - -const SolidJS = struct { - namespace: GeneratedSymbol = undefined, - wrap: GeneratedSymbol = undefined, - insert: GeneratedSymbol = undefined, - template: GeneratedSymbol = undefined, - delegateEvents: GeneratedSymbol = undefined, - createComponent: GeneratedSymbol = undefined, - setAttribute: GeneratedSymbol = undefined, - effect: GeneratedSymbol = undefined, - - events_to_delegate: Events.Bitset = .{}, - template_decls: std.ArrayListUnmanaged(G.Decl) = .{}, - is_in_jsx_component: bool = false, - - pub const Stack = struct { - component_body: std.ArrayListUnmanaged(Stmt) = .{}, - component_body_decls: std.ArrayListUnmanaged(G.Decl) = .{}, - last_template_id: E.Identifier = .{}, - last_element_id: E.Identifier = .{}, - temporary_scope: Scope = Scope{ - .kind = .function_body, - .parent = null, - }, - prev_scope: ?*Scope = null, - node_count: u32 = 0, - - current_template_string: MutableString = .{ - .allocator = undefined, - .list = .{}, - }, - buffered_writer: MutableString.BufferedWriter = undefined, - - element_counter: u32 = 0, - }; - - pub fn generateElementName(this: *SolidJS, allocator: std.mem.Allocator) string { - if (this.component_body_decls.items.len <= prefilled_element_names.len) { - return prefilled_element_names[this.component_body_decls.items.len]; - } - return std.fmt.allocPrint(allocator, "_el${d}", .{this.component_body_decls.items.len}) catch unreachable; - } - - pub fn generateTemplateName(this: *SolidJS, allocator: std.mem.Allocator) string { - if (this.template_decls.items.len <= prefilled_template_names.len) { - return prefilled_template_names[this.template_decls.items.len]; - } - return std.fmt.allocPrint(allocator, "_tmpl${d}", .{this.template_decls.items.len}) catch unreachable; - } - - pub fn generateElement(solid: *SolidJS, p: anytype, template_expression: Expr, value_loc: logger.Loc) !E.Identifier { - var name = solid.generateElementName(p.allocator); - - var prev_scope = p.current_scope; - p.current_scope = &solid.temporary_scope; - const ref = p.declareSymbolMaybeGenerated(.import, value_loc, name, true) catch unreachable; - p.current_scope = prev_scope; - const element = .{ .ref = ref }; - var decl_value: Expr = undefined; - switch (solid.component_body_decls.items.len) { - 0 => { - decl_value = p.e( - E.Call{ - .target = p.e( - E.Dot{ - .name = "cloneNode", - .name_loc = value_loc, - .target = template_expression, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - template_expression.loc, - ), - .args = ExprNodeList.init(true_args), - .can_be_unwrapped_if_unused = true, - }, - value_loc, - ); - p.recordUsage(template_expression.data.e_identifier.ref); - }, - 1 => { - const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref }; - decl_value = p.e( - E.Dot{ - .target = .{ - .data = .{ .e_identifier = ident }, - .loc = value_loc, - }, - .name = "firstChild", - .name_loc = template_expression.loc, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - value_loc, - ); - p.recordUsage(ident.ref); - }, - else => { - const ident = E.Identifier{ .ref = solid.component_body_decls.items[solid.component_body_decls.items.len - 1].binding.data.b_identifier.ref }; - decl_value = p.e(E.Dot{ - .target = .{ - .data = .{ .e_identifier = ident }, - .loc = value_loc, - }, - .name_loc = template_expression.loc, - .name = "nextSibling", - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, value_loc); - p.recordUsage(ident.ref); - }, - } - try solid.component_body_decls.append( - p.allocator, - G.Decl{ .binding = p.b(B.Identifier{ .ref = ref }, template_expression.loc), .value = decl_value }, - ); - return element; - } - - pub const Events = enum { - Click, - Change, - Input, - Submit, - KeyDown, - KeyUp, - KeyPress, - MouseDown, - MouseUp, - MouseMove, - MouseEnter, - MouseLeave, - MouseOver, - MouseOut, - Focus, - Blur, - Scroll, - Wheel, - TouchStart, - TouchMove, - TouchEnd, - TouchCancel, - PointerDown, - PointerUp, - PointerMove, - PointerCancel, - PointerEnter, - PointerLeave, - PointerOver, - PointerOut, - GotPointerCapture, - LostPointerCapture, - Select, - ContextMenu, - DragStart, - Drag, - DragEnd, - DragEnter, - DragLeave, - DragOver, - Drop, - Copy, - Cut, - Paste, - CompositionStart, - CompositionUpdate, - CompositionEnd, - - pub const Bitset = std.enums.EnumSet(Events); - }; - - const prefilled_element_names = [_]string{ - "_el", - "_el$1", - "_el$2", - "_el$3", - "_el$4", - "_el$5", - "_el$6", - "_el$7", - "_el$8", - "_el$9", - "_el$10", - "_el$11", - "_el$12", - "_el$13", - "_el$14", - "_el$15", - "_el$16", - "_el$17", - "_el$18", - "_el$19", - "_el$20", - "_el$21", - }; - const prefilled_template_names = [_]string{ - "_tmpl", - "_tmpl$1", - "_tmpl$2", - "_tmpl$3", - "_tmpl$4", - "_tmpl$5", - "_tmpl$6", - "_tmpl$7", - "_tmpl$8", - "_tmpl$9", - "_tmpl$10", - "_tmpl$11", - "_tmpl$12", - "_tmpl$13", - "_tmpl$14", - "_tmpl$15", - "_tmpl$16", - "_tmpl$17", - "_tmpl$18", - "_tmpl$19", - "_tmpl$20", - "_tmpl$21", - }; -}; - -fn GetSolidJSSymbols(comptime jsx: JSXTransformType) type { - if (jsx != .solid) - return void; - - return SolidJS; -} -const ParserFeatures = struct { - typescript: bool = false, - jsx: JSXTransformType = JSXTransformType.none, - scan_only: bool = false, - - // *** How React Fast Refresh works *** - // - // Implmenetations: - // [0]: https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js - // [1]: https://github.com/swc-project/swc/blob/master/ecmascript/transforms/react/src/refresh/mod.rs - // - // Additional reading: - // - https://github.com/facebook/react/issues/16604#issuecomment-528663101 - // - https://github.com/facebook/react/blob/master/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js - // - // From reading[0] and Dan Abramov's comment, there are really five parts. - // 1. At the top of the file: - // 1. Declare a $RefreshReg$ if it doesn't exist - // - This really just does "RefreshRuntime.register(ComponentIdentifier, ComponentIdentifier.name);" - // 2. Run "var _s${componentIndex} = $RefreshSig$()" to generate a function for updating react refresh scoped to the component. So it's one per *component*. - // - This really just does "RefreshRuntime.createSignatureFunctionForTransform();" - // 2. Register all React components[2] defined in the module scope by calling the equivalent of $RefreshReg$(ComponentIdentifier, "ComponentName") - // 3. For each registered component: - // 1. Call "_s()" to mark the first render of this component for "react-refresh/runtime". Call this at the start of the React component's function body - // 2. Track every call expression to a hook[3] inside the component, including: - // - Identifier of the hook function - // - Arguments passed - // 3. For each hook's call expression, generate a signature key which is - // - The hook's identifier ref - // - The S.Decl ("VariableDeclarator")'s source - // "var [foo, bar] = useFooBar();" - // ^--------^ This region, I think. Judging from this line: https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js#L407 - // - For the "useState" hook, also hash the source of the first argument if it exists e.g. useState(foo => true); - // - For the "useReducer" hook, also hash the source of the second argument if it exists e.g. useReducer({}, () => ({})); - // 4. If the hook component is not builtin and is defined inside a component, always reset the component state - // - See this test: https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js#L909 - // 4. From the signature key generated in 3., call one of the following: - // - _s(ComponentIdentifier, hash(signature)); - // - _s(ComponentIdentifier, hash(signature), true /* forceReset */); - // - _s(ComponentIdentifier, hash(signature), false /* forceReset */, () => [customHook1, customHook2, customHook3]); - // Note: This step is only strictly required on rebuild. - // 5. if (isReactComponentBoundary(exports)) enqueueUpdateAndHandleErrors(); - // **** FAQ **** - // [2]: Q: From a parser's perspective, what's a component? - // A: typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z -- https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/ReactFreshBabelPlugin.js#L42-L44 - // [3]: Q: From a parser's perspective, what's a hook? - // A: /^use[A-Z]/ -- https://github.com/facebook/react/blob/568dc3532e25b30eee5072de08503b1bbc4f065d/packages/react-refresh/src/ReactFreshBabelPlugin.js#L390 - // - // - // - // react_fast_refresh: bool = false, -}; - -// Our implementation diverges somewhat from the official implementation -// Specifically, we use a subclass of HMRModule - FastRefreshModule -// Instead of creating a globally-scoped -const FastRefresh = struct {}; - -const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef); - -pub const MacroState = struct { - refs: MacroRefs, - prepend_stmts: *ListManaged(Stmt) = undefined, - imports: std.AutoArrayHashMap(i32, Ref), - - pub fn init(allocator: Allocator) MacroState { - return MacroState{ - .refs = MacroRefs.init(allocator), - .prepend_stmts = undefined, - .imports = std.AutoArrayHashMap(i32, Ref).init(allocator), - }; - } -}; - -// workaround for https://github.com/ziglang/zig/issues/10903 -fn NewParser( - comptime parser_features: ParserFeatures, -) type { - return NewParser_( - parser_features.typescript, - parser_features.jsx, - parser_features.scan_only, - ); -} -fn NewParser_( - comptime parser_feature__typescript: bool, - comptime parser_feature__jsx: JSXTransformType, - comptime parser_feature__scan_only: bool, -) type { - const js_parser_features: ParserFeatures = .{ - .typescript = parser_feature__typescript, - .jsx = parser_feature__jsx, - .scan_only = parser_feature__scan_only, - }; - - // P is for Parser! - return struct { - const js_parser_jsx = if (FeatureFlags.force_macro) JSXTransformType.macro else js_parser_features.jsx; - const is_typescript_enabled = js_parser_features.typescript; - const is_jsx_enabled = js_parser_jsx != .none; - const only_scan_imports_and_do_not_visit = js_parser_features.scan_only; - const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord); - const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports; - const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void; - const track_symbol_usage_during_parse_pass = only_scan_imports_and_do_not_visit and is_typescript_enabled; - const ParsePassSymbolUsageType = if (track_symbol_usage_during_parse_pass) *ScanPassResult.ParsePassSymbolUsageMap else void; - - pub const parser_features: ParserFeatures = js_parser_features; - const P = @This(); - pub const jsx_transform_type: JSXTransformType = js_parser_jsx; - const allow_macros = FeatureFlags.is_macro_enabled and jsx_transform_type != .macro; - const MacroCallCountType = if (allow_macros) u32 else u0; - macro: MacroState = undefined, - allocator: Allocator, - options: Parser.Options, - log: *logger.Log, - define: *Define, - source: *const logger.Source, - lexer: js_lexer.Lexer, - allow_in: bool = false, - allow_private_identifiers: bool = false, - - has_top_level_return: bool = false, - latest_return_had_semicolon: bool = false, - has_import_meta: bool = false, - has_es_module_syntax: bool = false, - top_level_await_keyword: logger.Range = logger.Range.None, - fn_or_arrow_data_parse: FnOrArrowDataParse = FnOrArrowDataParse{}, - fn_or_arrow_data_visit: FnOrArrowDataVisit = FnOrArrowDataVisit{}, - fn_only_data_visit: FnOnlyDataVisit = FnOnlyDataVisit{}, - allocated_names: List(string) = .{}, - // allocated_names: ListManaged(string) = ListManaged(string).init(bun.default_allocator), - // allocated_names_pool: ?*AllocatedNamesPool.Node = null, - latest_arrow_arg_loc: logger.Loc = logger.Loc.Empty, - forbid_suffix_after_as_loc: logger.Loc = logger.Loc.Empty, - current_scope: *js_ast.Scope = undefined, - scopes_for_current_part: List(*js_ast.Scope) = .{}, - symbols: ListManaged(js_ast.Symbol) = undefined, - ts_use_counts: List(u32) = .{}, - exports_ref: Ref = Ref.None, - require_ref: Ref = Ref.None, - module_ref: Ref = Ref.None, - filename_ref: Ref = Ref.None, - dirname_ref: Ref = Ref.None, - import_meta_ref: Ref = Ref.None, - promise_ref: ?Ref = null, - scopes_in_order_visitor_index: usize = 0, - has_classic_runtime_warned: bool = false, - macro_call_count: MacroCallCountType = 0, - - /// Used for transforming export default -> module.exports - has_export_default: bool = false, - - hmr_module: GeneratedSymbol = GeneratedSymbol{ .primary = Ref.None, .backup = Ref.None, .ref = Ref.None }, - - has_called_runtime: bool = false, - - cjs_import_stmts: std.ArrayList(Stmt), - - injected_define_symbols: List(Ref) = .{}, - symbol_uses: js_ast.Part.SymbolUseMap = .{}, - declared_symbols: List(js_ast.DeclaredSymbol) = .{}, - declared_symbols_for_reuse: List(js_ast.DeclaredSymbol) = .{}, - runtime_imports: RuntimeImports = RuntimeImports{}, - - parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined, - // duplicate_case_checker: void, - // non_bmp_identifiers: StringBoolMap, - // legacy_octal_literals: void, - // legacy_octal_literals: map[js_ast.E]logger.Range, - - // For lowering private methods - // weak_map_ref: ?Ref, - // weak_set_ref: ?Ref, - // private_getters: RefRefMap, - // private_setters: RefRefMap, - - // These are for TypeScript - should_fold_numeric_constants: bool = false, - emitted_namespace_vars: RefMap = RefMap{}, - is_exported_inside_namespace: RefRefMap = .{}, - known_enum_values: Map(Ref, _hash_map.StringHashMapUnmanaged(f64)) = .{}, - local_type_names: StringBoolMap = StringBoolMap{}, - - // This is the reference to the generated function argument for the namespace, - // which is different than the reference to the namespace itself: - // - // namespace ns { - // } - // - // The code above is transformed into something like this: - // - // var ns1; - // (function(ns2) { - // })(ns1 or (ns1 = {})); - // - // This variable is "ns2" not "ns1". It is only used during the second - // "visit" pass. - enclosing_namespace_arg_ref: ?Ref = null, - - jsx_filename: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsx_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsx_factory: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsx_fragment: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsx_automatic: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsxs_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - jsx_classic: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - // only applicable when is_react_fast_refresh_enabled - jsx_refresh_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, - - solid: GetSolidJSSymbols(jsx_transform_type) = if (jsx_transform_type == JSXTransformType.solid) SolidJS{} else void{}, - - bun_jsx_ref: Ref = Ref.None, - - // Imports (both ES6 and CommonJS) are tracked at the top level - import_records: ImportRecordList, - import_records_for_current_part: List(u32) = .{}, - export_star_import_records: List(u32) = .{}, - - // These are for handling ES6 imports and exports - es6_import_keyword: logger.Range = logger.Range.None, - es6_export_keyword: logger.Range = logger.Range.None, - enclosing_class_keyword: logger.Range = logger.Range.None, - import_items_for_namespace: std.AutoHashMapUnmanaged(Ref, ImportItemForNamespaceMap) = .{}, - is_import_item: RefMap = .{}, - named_imports: NamedImportsType, - named_exports: js_ast.Ast.NamedExports, - import_namespace_cc_map: Map(ImportNamespaceCallOrConstruct, bool) = .{}, - - // When we're only scanning the imports - // If they're using the automatic JSX runtime - // We won't know that we need to import JSX robustly because we don't track - // symbol counts. Instead, we ask: - // "Did we parse anything that looked like JSX"? - // If yes, then automatically add the JSX import. - needs_jsx_import: NeedsJSXType, - - // The parser does two passes and we need to pass the scope tree information - // from the first pass to the second pass. That's done by tracking the calls - // to pushScopeForParsePass() and popScope() during the first pass in - // scopesInOrder. - // - // Then, when the second pass calls pushScopeForVisitPass() and popScope(), - // we consume entries from scopesInOrder and make sure they are in the same - // order. This way the second pass can efficiently use the same scope tree - // as the first pass without having to attach the scope tree to the AST. - // - // We need to split this into two passes because the pass that declares the - // symbols must be separate from the pass that binds identifiers to declared - // symbols to handle declaring a hoisted "var" symbol in a nested scope and - // binding a name to it in a parent or sibling scope. - scopes_in_order: ScopeOrderList = .{}, - - // These properties are for the visit pass, which runs after the parse pass. - // The visit pass binds identifiers to declared symbols, does constant - // folding, substitutes compile-time variable definitions, and lowers certain - // syntactic constructs as appropriate. - stmt_expr_value: Expr.Data, - call_target: Expr.Data, - delete_target: Expr.Data, - loop_body: Stmt.Data, - module_scope: *js_ast.Scope = undefined, - is_control_flow_dead: bool = false, - - // Inside a TypeScript namespace, an "export declare" statement can be used - // to cause a namespace to be emitted even though it has no other observable - // effect. This flag is used to implement this feature. - // - // Specifically, namespaces should be generated for all of the following - // namespaces below except for "f", which should not be generated: - // - // namespace a { export declare const a } - // namespace b { export declare let [[b]] } - // namespace c { export declare function c() } - // namespace d { export declare class d {} } - // namespace e { export declare enum e {} } - // namespace f { export declare namespace f {} } - // - // The TypeScript compiler compiles this into the following code (notice "f" - // is missing): - // - // var a; (function (a_1) {})(a or (a = {})); - // var b; (function (b_1) {})(b or (b = {})); - // var c; (function (c_1) {})(c or (c = {})); - // var d; (function (d_1) {})(d or (d = {})); - // var e; (function (e_1) {})(e or (e = {})); - // - // Note that this should not be implemented by declaring symbols for "export - // declare" statements because the TypeScript compiler doesn't generate any - // code for these statements, so these statements are actually references to - // global variables. There is one exception, which is that local variables - // *should* be declared as symbols because they are replaced with. This seems - // like very arbitrary behavior but it's what the TypeScript compiler does, - // so we try to match it. - // - // Specifically, in the following code below "a" and "b" should be declared - // and should be substituted with "ns.a" and "ns.b" but the other symbols - // shouldn't. References to the other symbols actually refer to global - // variables instead of to symbols that are exported from the namespace. - // This is the case as of TypeScript 4.3. I assume this is a TypeScript bug: - // - // namespace ns { - // export declare const a - // export declare let [[b]] - // export declare function c() - // export declare class d { } - // export declare enum e { } - // console.log(a, b, c, d, e) - // } - // - // The TypeScript compiler compiles this into the following code: - // - // var ns; - // (function (ns) { - // console.log(ns.a, ns.b, c, d, e); - // })(ns or (ns = {})); - // - // Relevant issue: https://github.com/evanw/esbuild/issues/1158 - has_non_local_export_declare_inside_namespace: bool = false, - - // This helps recognize the "await import()" pattern. When this is present, - // warnings about non-string import paths will be omitted inside try blocks. - await_target: ?js_ast.Expr.Data = null, - - to_expr_wrapper_namespace: Binding2ExprWrapper.Namespace, - to_expr_wrapper_hoisted: Binding2ExprWrapper.Hoisted, - - // This helps recognize the "import().catch()" pattern. We also try to avoid - // warning about this just like the "try { await import() }" pattern. - then_catch_chain: ThenCatchChain, - - // Temporary variables used for lowering - temp_refs_to_declare: List(TempRef) = .{}, - temp_ref_count: i32 = 0, - - // When bundling, hoisted top-level local variables declared with "var" in - // nested scopes are moved up to be declared in the top-level scope instead. - // The old "var" statements are turned into regular assignments instead. This - // makes it easier to quickly scan the top-level statements for "var" locals - // with the guarantee that all will be found. - relocated_top_level_vars: List(js_ast.LocRef) = .{}, - - // ArrowFunction is a special case in the grammar. Although it appears to be - // a PrimaryExpression, it's actually an AssignmentExpression. This means if - // a AssignmentExpression ends up producing an ArrowFunction then nothing can - // come after it other than the comma operator, since the comma operator is - // the only thing above AssignmentExpression under the Expression rule: - // - // AssignmentExpression: - // ArrowFunction - // ConditionalExpression - // LeftHandSideExpression = AssignmentExpression - // LeftHandSideExpression AssignmentOperator AssignmentExpression - // - // Expression: - // AssignmentExpression - // Expression , AssignmentExpression - // - after_arrow_body_loc: logger.Loc = logger.Loc.Empty, - import_transposer: ImportTransposer, - require_transposer: RequireTransposer, - require_resolve_transposer: RequireResolveTransposer, - - // This is a general place to put lots of Expr objects - expr_list: List(Expr) = .{}, - - scope_order_to_visit: []ScopeOrder = &([_]ScopeOrder{}), - - import_refs_to_always_trim_if_unused: RefArrayMap = .{}, - - pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr { - // The argument must be a string - if (@as(Expr.Tag, arg.data) == .e_string) { - // Ignore calls to import() if the control flow is provably dead here. - // We don't want to spend time scanning the required files if they will - // never be used. - if (p.is_control_flow_dead) { - return p.e(E.Null{}, arg.loc); - } - - const import_record_index = p.addImportRecord(.dynamic, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable); - p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target; - p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; - return p.e(E.Import{ - .expr = arg, - .import_record_index = Ref.toInt(import_record_index), - // .leading_interior_comments = arg.getString(). - }, state.loc); - } - - if (p.options.warn_about_unbundled_modules) { - // Use a debug log so people can see this if they want to - const r = js_lexer.rangeOfIdentifier(p.source, state.loc); - p.log.addRangeDebug(p.source, r, "This \"import\" expression cannot be bundled because the argument is not a string literal") catch unreachable; - } - - return p.e(E.Import{ - .expr = arg, - .import_record_index = Ref.None.sourceIndex(), - }, state.loc); - } - - pub fn transposeRequireResolve(p: *P, arg: Expr, state: anytype) Expr { - // The argument must be a string - if (@as(Expr.Tag, arg.data) == .e_string) { - // Ignore calls to import() if the control flow is provably dead here. - // We don't want to spend time scanning the required files if they will - // never be used. - if (p.is_control_flow_dead) { - return p.e(E.Null{}, arg.loc); - } - - const import_record_index = p.addImportRecord(.require, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable); - p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; - p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; - return p.e(E.RequireOrRequireResolve{ - .import_record_index = Ref.toInt(import_record_index), - // .leading_interior_comments = arg.getString(). - }, arg.loc); - } - - if (p.options.warn_about_unbundled_modules) { - // Use a debug log so people can see this if they want to - const r = js_lexer.rangeOfIdentifier(p.source, arg.loc); - p.log.addRangeDebug(p.source, r, "This \"require.resolve\" expression cannot be bundled because the argument is not a string literal") catch unreachable; - } - - return state; - } - - pub fn transposeRequire(p: *P, arg: Expr, _: anytype) Expr { - switch (arg.data) { - .e_string => |str| { - - // Ignore calls to require() if the control flow is provably dead here. - // We don't want to spend time scanning the required files if they will - // never be used. - if (p.is_control_flow_dead) { - return Expr{ .data = nullExprData, .loc = arg.loc }; - } - - const pathname = str.string(p.allocator) catch unreachable; - - // When we know that we support dynamically requiring this file type - // we can avoid eager loading it - // instead, we can just use the require() function directly. - if (p.options.features.dynamic_require and - !p.options.enable_bundling and - (strings.endsWithComptime(pathname, ".json") or - // strings.endsWithComptime(pathname, ".toml") or - strings.endsWithComptime(pathname, ".node"))) - { - p.ignoreUsage(p.require_ref); - var args = p.allocator.alloc(Expr, 1) catch unreachable; - args[0] = arg; - - return p.e( - E.Call{ - .target = p.e( - E.Dot{ - .target = p.e(E.ImportMeta{}, arg.loc), - .name = "require", - .name_loc = arg.loc, - }, - arg.loc, - ), - .args = js_ast.ExprNodeList.init(args), - .close_paren_loc = arg.loc, - }, - arg.loc, - ); - } - - const import_record_index = p.addImportRecord(.require, arg.loc, pathname); - p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0; - p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable; - - if (!p.options.transform_require_to_import) { - return p.e(E.Require{ .import_record_index = import_record_index }, arg.loc); - } - - p.import_records.items[import_record_index].was_originally_require = true; - p.import_records.items[import_record_index].contains_import_star = true; - - const symbol_name = p.import_records.items[import_record_index].path.name.nonUniqueNameString(p.allocator); - const cjs_import_name = std.fmt.allocPrint( - p.allocator, - "{s}_{x}_{d}", - .{ - symbol_name, - @truncate( - u16, - std.hash.Wyhash.hash( - 0, - p.import_records.items[import_record_index].path.text, - ), - ), - p.cjs_import_stmts.items.len, - }, - ) catch unreachable; - - const namespace_ref = p.declareSymbol(.hoisted, arg.loc, cjs_import_name) catch unreachable; - - p.cjs_import_stmts.append( - p.s( - S.Import{ - .namespace_ref = namespace_ref, - .star_name_loc = arg.loc, - .is_single_line = true, - .import_record_index = import_record_index, - }, - arg.loc, - ), - ) catch unreachable; - - const args = p.allocator.alloc(Expr, 1) catch unreachable; - args[0] = p.e( - E.ImportIdentifier{ - .ref = namespace_ref, - }, - arg.loc, - ); - - p.ignoreUsage(p.require_ref); - - // require(import_object_assign) - return p.callRuntime(arg.loc, "__require", args); - }, - else => {}, - } - - return arg; - } - - fn isBindingUsed(p: *P, binding: Binding, default_export_ref: Ref) bool { - switch (binding.data) { - .b_identifier => |ident| { - if (default_export_ref.eql(ident.ref)) return true; - if (p.named_imports.contains(ident.ref)) - return true; - - for (p.named_exports.values()) |named_export| { - if (named_export.ref.eql(ident.ref)) - return true; - } - - const symbol: *const Symbol = &p.symbols.items[ident.ref.innerIndex()]; - return symbol.use_count_estimate > 0; - }, - .b_array => |array| { - for (array.items) |item| { - if (isBindingUsed(p, item.binding, default_export_ref)) { - return true; - } - } - - return false; - }, - .b_object => |obj| { - for (obj.properties) |prop| { - if (isBindingUsed(p, prop.value, default_export_ref)) { - return true; - } - } - - return false; - }, - .b_property => |prop| { - return p.isBindingUsed(prop.value, default_export_ref); - }, - - .b_missing => return false, - } - } - - pub fn treeShake(p: *P, parts: *[]js_ast.Part, merge: bool) void { - var parts_: []js_ast.Part = parts.*; - defer { - if (merge and parts_.len > 1) { - var first_none_part: usize = parts_.len; - var stmts_count: usize = 0; - for (parts_) |part, i| { - if (part.tag == .none) { - stmts_count += part.stmts.len; - first_none_part = @minimum(i, first_none_part); - } - } - - if (first_none_part < parts_.len) { - var stmts_list = p.allocator.alloc(Stmt, stmts_count) catch unreachable; - var stmts_remain = stmts_list; - - for (parts_) |part| { - if (part.tag == .none) { - std.mem.copy(Stmt, stmts_remain, part.stmts); - stmts_remain = stmts_remain[part.stmts.len..]; - } - } - - parts_[first_none_part].stmts = stmts_list; - - parts_ = parts_[0 .. first_none_part + 1]; - } - } - - parts.* = parts_; - } - while (parts_.len > 1) { - var parts_end: usize = 0; - var last_end = parts_.len; - var default_export_ref = Ref.None; - if (p.named_exports.get("default")) |named| { - default_export_ref = named.ref; - } - - for (parts_) |part| { - const is_dead = part.can_be_removed_if_unused and can_remove_part: { - for (part.stmts) |stmt| { - switch (stmt.data) { - .s_local => |local| { - if (local.is_export) break :can_remove_part false; - for (local.decls) |decl| { - if (isBindingUsed(p, decl.binding, default_export_ref)) - break :can_remove_part false; - } - }, - .s_if => |if_statement| { - const result = SideEffects.toBoolean(if_statement.test_.data); - if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { - break :can_remove_part false; - } - }, - .s_while => |while_statement| { - const result = SideEffects.toBoolean(while_statement.test_.data); - if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { - break :can_remove_part false; - } - }, - .s_for => |for_statement| { - if (for_statement.test_) |expr| { - const result = SideEffects.toBoolean(expr.data); - if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { - break :can_remove_part false; - } - } - }, - .s_function => |func| { - if (func.func.flags.contains(.is_export)) break :can_remove_part false; - if (func.func.name) |name| { - const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()]; - - if (name.ref.?.eql(default_export_ref) or - symbol.use_count_estimate > 0 or - p.named_exports.contains(symbol.original_name) or - p.named_imports.contains(name.ref.?) or - p.is_import_item.get(name.ref.?) != null) - { - break :can_remove_part false; - } - } - }, - .s_import, - .s_export_clause, - .s_export_from, - .s_export_default, - => break :can_remove_part false, - - .s_class => |class| { - if (class.is_export) break :can_remove_part false; - if (class.class.class_name) |name| { - const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()]; - - if (name.ref.?.eql(default_export_ref) or - symbol.use_count_estimate > 0 or - p.named_exports.contains(symbol.original_name) or - p.named_imports.contains(name.ref.?) or - p.is_import_item.get(name.ref.?) != null) - { - break :can_remove_part false; - } - } - }, - - else => break :can_remove_part false, - } - } - break :can_remove_part true; - }; - - if (is_dead) { - p.clearSymbolUsagesFromDeadPart(part); - - continue; - } - - parts_[parts_end] = part; - parts_end += 1; - } - - parts_.len = parts_end; - if (last_end == parts_.len) { - break; - } - } - } - - const ImportTransposer = ExpressionTransposer(P, P.transposeImport); - const RequireTransposer = ExpressionTransposer(P, P.transposeRequire); - const RequireResolveTransposer = ExpressionTransposer(P, P.transposeRequireResolve); - - const Binding2ExprWrapper = struct { - pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace); - pub const Hoisted = Binding.ToExpr(P, P.wrapIdentifierHoisting); - }; - - fn clearSymbolUsagesFromDeadPart(p: *P, part: js_ast.Part) void { - var symbol_use_refs = part.symbol_uses.keys(); - var symbol_use_values = part.symbol_uses.values(); - var symbols = p.symbols.items; - - for (symbol_use_refs) |ref, i| { - symbols[ref.innerIndex()].use_count_estimate -|= symbol_use_values[i].count_estimate; - } - - for (part.declared_symbols) |declared| { - symbols[declared.ref.innerIndex()].use_count_estimate = 0; - // } - } - } - - pub fn s(_: *P, t: anytype, loc: logger.Loc) Stmt { - const Type = @TypeOf(t); - comptime { - if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) { - @compileError("Attempted to use TypeScript syntax in a non-TypeScript environment"); - } - } - - if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) { - unreachable; - } - - // Output.print("\nStmt: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start }); - if (@typeInfo(Type) == .Pointer) { - // ExportFrom normally becomes import records during the visiting pass - // However, we skip the visiting pass in this mode - // So we must generate a minimum version of it here. - if (comptime only_scan_imports_and_do_not_visit) { - // if (@TypeOf(t) == *S.ExportFrom) { - // switch (call.target.data) { - // .e_identifier => |ident| { - // // is this a require("something") - // if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args[0].data) == .e_string) { - // _ = p.addImportRecord(.require, loc, call.args[0].data.e_string.string(p.allocator) catch unreachable); - // } - // }, - // else => {}, - // } - // } - } - - return Stmt.init(std.meta.Child(Type), t, loc); - } else { - return Stmt.alloc(Type, t, loc); - } - } - - pub fn e(p: *P, t: anytype, loc: logger.Loc) Expr { - const Type = @TypeOf(t); - - comptime { - if (jsx_transform_type == .none) { - if (Type == E.JSXElement or Type == *E.JSXElement) { - @compileError("JSXElement is not supported in this environment"); - } - } - } - - // Output.print("\nExpr: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start }); - if (@typeInfo(Type) == .Pointer) { - if (comptime only_scan_imports_and_do_not_visit) { - if (Type == *E.Call) { - const call: *E.Call = t; - switch (call.target.data) { - .e_identifier => |ident| { - // is this a require("something") - if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args.ptr[0].data) == .e_string) { - _ = p.addImportRecord(.require, loc, call.args.first_().data.e_string.string(p.allocator) catch unreachable); - } - }, - else => {}, - } - } - } - return Expr.init(std.meta.Child(Type), t.*, loc); - } else { - if (comptime only_scan_imports_and_do_not_visit) { - if (Type == E.Call) { - const call: E.Call = t; - switch (call.target.data) { - .e_identifier => |ident| { - // is this a require("something") - if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args.ptr[0].data) == .e_string) { - _ = p.addImportRecord(.require, loc, call.args.first_().data.e_string.string(p.allocator) catch unreachable); - } - }, - else => {}, - } - } - } - return Expr.init(Type, t, loc); - } - } - - pub fn b(p: *P, t: anytype, loc: logger.Loc) Binding { - if (@typeInfo(@TypeOf(t)) == .Pointer) { - return Binding.init(t, loc); - } else { - return Binding.alloc(p.allocator, t, loc); - } - } - - pub fn deinit(parser: *P) void { - parser.allocated_names.deinit(); - parser.scopes_for_current_part.deinit(); - parser.symbols.deinit(); - parser.ts_use_counts.deinit(); - parser.declared_symbols.deinit(); - parser.known_enum_values.deinit(); - parser.import_records.deinit(); - parser.import_records_for_current_part.deinit(); - parser.export_star_import_records.deinit(); - parser.import_items_for_namespace.deinit(); - parser.named_imports.deinit(); - parser.import_namespace_cc_map.deinit(); - parser.scopes_in_order.deinit(); - parser.temp_refs_to_declare.deinit(); - parser.relocated_top_level_vars.deinit(); - } - - pub fn findSymbol(p: *P, loc: logger.Loc, name: string) !FindSymbolResult { - var declare_loc: logger.Loc = undefined; - var is_inside_with_scope = false; - // This function can show up in profiling. - // That's part of why we do this. - // Instead of rehashing `name` for every scope, we do it just once. - const hash = @TypeOf(p.module_scope.members).getHash(name); - const allocator = p.allocator; - - const ref: Ref = brk: { - var _scope: ?*Scope = p.current_scope; - - var did_forbid_argumen = false; - - while (_scope) |scope| : (_scope = _scope.?.parent) { - - // Track if we're inside a "with" statement body - if (scope.kind == .with) { - is_inside_with_scope = true; - } - - // Forbid referencing "arguments" inside class bodies - if (scope.forbid_arguments and !did_forbid_argumen and strings.eqlComptime(name, "arguments")) { - const r = js_lexer.rangeOfIdentifier(p.source, loc); - p.log.addRangeErrorFmt(p.source, r, allocator, "Cannot access \"{s}\" here", .{name}) catch unreachable; - did_forbid_argumen = true; - } - - // Is the symbol a member of this scope? - if (scope.members.getWithHash(name, hash)) |member| { - declare_loc = member.loc; - break :brk member.ref; - } - } - - // Allocate an "unbound" symbol - p.checkForNonBMPCodePoint(loc, name); - const _ref = p.newSymbol(.unbound, name) catch unreachable; - declare_loc = loc; - p.module_scope.members.putWithHash(allocator, name, hash, js_ast.Scope.Member{ .ref = _ref, .loc = logger.Loc.Empty }) catch unreachable; - - break :brk _ref; - }; - - // If we had to pass through a "with" statement body to get to the symbol - // declaration, then this reference could potentially also refer to a - // property on the target object of the "with" statement. We must not rename - // it or we risk changing the behavior of the code. - if (is_inside_with_scope) { - p.symbols.items[ref.innerIndex()].must_not_be_renamed = true; - } - - // Track how many times we've referenced this symbol - p.recordUsage(ref); - - return FindSymbolResult{ - .ref = ref, - .declare_loc = declare_loc, - .is_inside_with_scope = is_inside_with_scope, - }; - } - - pub fn recordExportedBinding(p: *P, binding: Binding) void { - switch (binding.data) { - .b_missing => {}, - .b_identifier => |ident| { - p.recordExport(binding.loc, p.symbols.items[ident.ref.innerIndex()].original_name, ident.ref) catch unreachable; - }, - .b_array => |array| { - for (array.items) |prop| { - p.recordExportedBinding(prop.binding); - } - }, - .b_object => |obj| { - for (obj.properties) |prop| { - p.recordExportedBinding(prop.value); - } - }, - else => { - p.panic("Unexpected binding export type {s}", .{binding}); - }, - } - } - - // If we're auto-importing JSX and it's bundled, we use the bundled version - // This means we need to transform from require(react) to react() - // unless we're building inside of bun, then it's just normal commonjs - pub inline fn callUnbundledRequire(p: *P, require_args: []Expr) Expr { - return p.callRuntime(require_args[0].loc, "__require", require_args); - } - - pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void { - if (p.named_exports.get(alias)) |name| { - // Duplicate exports are an error - var notes = try p.allocator.alloc(logger.Data, 1); - notes[0] = logger.Data{ - .text = try std.fmt.allocPrint(p.allocator, "\"{s}\" was originally exported here", .{alias}), - .location = logger.Location.init_or_nil(p.source, js_lexer.rangeOfIdentifier(p.source, name.alias_loc)), - }; - try p.log.addRangeErrorFmtWithNotes( - p.source, - js_lexer.rangeOfIdentifier(p.source, loc), - p.allocator, - notes, - "Multiple exports with the same name \"{s}\"", - .{std.mem.trim(u8, alias, "\"'")}, - ); - } else { - try p.named_exports.put(alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref }); - } - } - - pub fn recordUsage(p: *P, ref: Ref) void { - // The use count stored in the symbol is used for generating symbol names - // during minification. These counts shouldn't include references inside dead - // code regions since those will be culled. - if (!p.is_control_flow_dead) { - if (comptime Environment.allow_assert) assert(p.symbols.items.len > ref.innerIndex()); - p.symbols.items[ref.innerIndex()].use_count_estimate += 1; - var result = p.symbol_uses.getOrPut(p.allocator, ref) catch unreachable; - if (!result.found_existing) { - result.value_ptr.* = Symbol.Use{ .count_estimate = 1 }; - } else { - result.value_ptr.count_estimate += 1; - } - } - - // The correctness of TypeScript-to-JavaScript conversion relies on accurate - // symbol use counts for the whole file, including dead code regions. This is - // tracked separately in a parser-only data structure. - if (is_typescript_enabled) { - p.ts_use_counts.items[ref.innerIndex()] += 1; - } - } - - fn logArrowArgErrors(p: *P, errors: *DeferredArrowArgErrors) void { - if (errors.invalid_expr_await.len > 0) { - var r = errors.invalid_expr_await; - p.log.addRangeError(p.source, r, "Cannot use an \"await\" expression here") catch unreachable; - } - - if (errors.invalid_expr_yield.len > 0) { - var r = errors.invalid_expr_yield; - p.log.addRangeError(p.source, r, "Cannot use a \"yield\" expression here") catch unreachable; - } - } - - fn keyNameForError(p: *P, key: js_ast.Expr) string { - switch (key.data) { - .e_string => { - return key.data.e_string.string(p.allocator) catch unreachable; - }, - .e_private_identifier => |private| { - return p.loadNameFromRef(private.ref); - // return p.loadNameFromRef() - }, - else => { - return "property"; - }, - } - } - - pub fn handleIdentifier(p: *P, loc: logger.Loc, ident: E.Identifier, _original_name: ?string, opts: IdentifierOpts) Expr { - const ref = ident.ref; - - if ((opts.assign_target != .none or opts.is_delete_target) and p.symbols.items[ref.innerIndex()].kind == .import) { - // Create an error for assigning to an import namespace - const r = js_lexer.rangeOfIdentifier(p.source, loc); - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot assign to import \"{s}\"", .{ - p.symbols.items[ref.innerIndex()].original_name, - }) catch unreachable; - } - - // Substitute an EImportIdentifier now if this is an import item - if (p.is_import_item.contains(ref)) { - return p.e( - E.ImportIdentifier{ .ref = ref, .was_originally_identifier = opts.was_originally_identifier }, - loc, - ); - } - - // Substitute a namespace export reference now if appropriate - if (is_typescript_enabled) { - if (p.is_exported_inside_namespace.get(ref)) |ns_ref| { - const name = p.symbols.items[ref.innerIndex()].original_name; - - // If this is a known enum value, inline the value of the enum - if (p.known_enum_values.get(ns_ref)) |enum_values| { - if (enum_values.get(name)) |number| { - return p.e(E.Number{ .value = number }, loc); - } - } - - // Otherwise, create a property access on the namespace - p.recordUsage(ns_ref); - - return p.e(E.Dot{ .target = p.e(E.Identifier{ .ref = ns_ref }, loc), .name = name, .name_loc = loc }, loc); - } - } - - if (_original_name) |original_name| { - const result = p.findSymbol(loc, original_name) catch unreachable; - var _ident = ident; - _ident.ref = result.ref; - return p.e(_ident, loc); - } - - return p.e(ident, loc); - } - - pub fn generateImportStmt( - p: *P, - import_path: string, - imports: anytype, - parts: *ListManaged(js_ast.Part), - symbols: anytype, - additional_stmt: ?Stmt, - comptime suffix: string, - comptime is_internal: bool, - ) !void { - const allocator = p.allocator; - const import_record_i = p.addImportRecordByRange(.stmt, logger.Range.None, import_path); - var import_record: *ImportRecord = &p.import_records.items[import_record_i]; - import_record.path.namespace = "runtime"; - import_record.is_internal = is_internal; - var import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator); - var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + suffix.len); - var clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len); - var stmts = try allocator.alloc(Stmt, 1 + if (additional_stmt != null) @as(usize, 1) else @as(usize, 0)); - var declared_symbols = try allocator.alloc(js_ast.DeclaredSymbol, imports.len); - std.mem.copy(u8, namespace_identifier[0..suffix.len], suffix); - std.mem.copy( - u8, - namespace_identifier[suffix.len..namespace_identifier.len], - import_path_identifier[0..import_path_identifier.len], - ); - - const namespace_ref = try p.newSymbol(.other, namespace_identifier); - try p.module_scope.generated.append(allocator, namespace_ref); - for (imports) |alias, i| { - const ref = symbols.get(alias) orelse unreachable; - const alias_name = if (@TypeOf(symbols) == RuntimeImports) RuntimeImports.all[alias] else alias; - clause_items[i] = js_ast.ClauseItem{ - .alias = alias_name, - .original_name = alias_name, - .alias_loc = logger.Loc{}, - .name = LocRef{ .ref = ref, .loc = logger.Loc{} }, - }; - declared_symbols[i] = js_ast.DeclaredSymbol{ .ref = ref, .is_top_level = true }; - try p.is_import_item.put(allocator, ref, .{}); - try p.named_imports.put(ref, js_ast.NamedImport{ - .alias = alias_name, - .alias_loc = logger.Loc{}, - .namespace_ref = null, - .import_record_index = import_record_i, - }); - } - - stmts[0] = p.s(S.Import{ - .namespace_ref = namespace_ref, - .items = clause_items, - .import_record_index = import_record_i, - }, logger.Loc{}); - if (additional_stmt) |add| { - stmts[1] = add; - } - - var import_records = try allocator.alloc(@TypeOf(import_record_i), 1); - import_records[0] = import_record_i; - - // Append a single import to the end of the file (ES6 imports are hoisted - // so we don't need to worry about where the import statement goes) - parts.append(js_ast.Part{ - .stmts = stmts, - .declared_symbols = declared_symbols, - .import_record_indices = import_records, - .tag = .runtime, - }) catch unreachable; - } - - pub fn prepareForVisitPass(p: *P) !void { - { - var count: usize = 0; - for (p.scopes_in_order.items) |item| { - if (item != null) { - count += 1; - } - } - var i: usize = 0; - p.scope_order_to_visit = try p.allocator.alloc(ScopeOrder, p.scopes_in_order.items.len); - for (p.scopes_in_order.items) |item| { - if (item) |_item| { - p.scope_order_to_visit[i] = _item; - i += 1; - } - } - } - - try p.pushScopeForVisitPass(js_ast.Scope.Kind.entry, locModuleScope); - p.fn_or_arrow_data_visit.is_outside_fn_or_arrow = true; - p.module_scope = p.current_scope; - p.has_es_module_syntax = p.es6_import_keyword.len > 0 or p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0; - - // ECMAScript modules are always interpreted as strict mode. This has to be - // done before "hoistSymbols" because strict mode can alter hoisting (!). - if (p.es6_import_keyword.len > 0) { - p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_import); - } else if (p.es6_export_keyword.len > 0) { - p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_export); - } else if (p.top_level_await_keyword.len > 0) { - p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_top_level_await); - } - - p.hoistSymbols(p.module_scope); - - var generated_symbols_count: u32 = 3; - - if (p.options.enable_bundling) { - generated_symbols_count += 4; - } - - if (p.options.features.hot_module_reloading) { - generated_symbols_count += 3; - - if (p.options.features.react_fast_refresh) { - generated_symbols_count += 1; - } - } - - if (is_jsx_enabled) { - generated_symbols_count += 7; - - if (p.options.jsx.development) generated_symbols_count += 1; - } - - try p.module_scope.generated.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3); - try p.module_scope.members.ensureCapacity(p.allocator, generated_symbols_count * 3 + p.module_scope.members.count()); - - p.exports_ref = try p.declareCommonJSSymbol(.hoisted, "exports"); - p.module_ref = try p.declareCommonJSSymbol(.hoisted, "module"); - p.require_ref = try p.declareCommonJSSymbol(.unbound, "require"); - p.dirname_ref = try p.declareCommonJSSymbol(.unbound, "__dirname"); - p.filename_ref = try p.declareCommonJSSymbol(.unbound, "__filename"); - - if (p.options.enable_bundling) { - p.runtime_imports.__reExport = try p.declareGeneratedSymbol(.other, "__reExport"); - p.runtime_imports.@"$$m" = try p.declareGeneratedSymbol(.other, "$$m"); - - p.runtime_imports.@"$$lzy" = try p.declareGeneratedSymbol(.other, "$$lzy"); - - p.runtime_imports.__export = try p.declareGeneratedSymbol(.other, "__export"); - p.runtime_imports.__exportValue = try p.declareGeneratedSymbol(.other, "__exportValue"); - p.runtime_imports.__exportDefault = try p.declareGeneratedSymbol(.other, "__exportDefault"); - } - - if (p.options.features.hot_module_reloading) { - p.hmr_module = try p.declareGeneratedSymbol(.other, "hmr"); - if (p.options.features.react_fast_refresh) { - if (p.options.jsx.use_embedded_refresh_runtime) { - p.runtime_imports.__FastRefreshRuntime = try p.declareGeneratedSymbol(.other, "__FastRefreshRuntime"); - p.recordUsage(p.runtime_imports.__FastRefreshRuntime.?.ref); - p.jsx_refresh_runtime = p.runtime_imports.__FastRefreshRuntime.?; - } else { - p.jsx_refresh_runtime = try p.declareGeneratedSymbol(.other, "Refresher"); - } - - p.runtime_imports.__FastRefreshModule = try p.declareGeneratedSymbol(.other, "__FastRefreshModule"); - p.recordUsage(p.runtime_imports.__FastRefreshModule.?.ref); - } else { - p.runtime_imports.__HMRModule = try p.declareGeneratedSymbol(.other, "__HMRModule"); - p.recordUsage(p.runtime_imports.__HMRModule.?.ref); - } - - p.runtime_imports.__HMRClient = try p.declareGeneratedSymbol(.other, "__HMRClient"); - p.recordUsage(p.hmr_module.ref); - p.recordUsage(p.runtime_imports.__HMRClient.?.ref); - } - - switch (comptime jsx_transform_type) { - .react => { - if (p.options.jsx.development) { - p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") catch unreachable; - } - p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; - p.jsx_runtime = p.declareGeneratedSymbol(.other, "jsx") catch unreachable; - p.jsxs_runtime = p.declareGeneratedSymbol(.other, "jsxs") catch unreachable; - p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable; - - if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) { - p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable; - } - - if (p.options.jsx.import_source.len > 0) { - p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable; - } - }, - .solid => { - p.solid.insert = p.declareGeneratedSymbol(.other, "insert") catch unreachable; - p.solid.template = p.declareGeneratedSymbol(.other, "template") catch unreachable; - p.solid.wrap = p.declareGeneratedSymbol(.other, "wrap") catch unreachable; - p.solid.namespace = p.declareGeneratedSymbol(.other, "Solid") catch unreachable; - p.solid.delegateEvents = p.declareGeneratedSymbol(.other, "delegateEvents") catch unreachable; - p.solid.createComponent = p.declareGeneratedSymbol(.other, "createComponent") catch unreachable; - p.solid.setAttribute = p.declareGeneratedSymbol(.other, "setAttribute") catch unreachable; - p.solid.effect = p.declareGeneratedSymbol(.other, "effect") catch unreachable; - p.solid.current_template_string = MutableString.initEmpty(p.allocator); - p.solid.buffered_writer = p.solid.current_template_string.bufferedWriter(); - }, - .macro => { - p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable; - BunJSX.bun_jsx_identifier = E.Identifier{ - .ref = p.bun_jsx_ref, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }; - p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; - }, - else => {}, - } - } - - // This won't work for adversarial cases - pub fn resolveGeneratedSymbol(p: *P, generated_symbol: *GeneratedSymbol) void { - if (generated_symbol.ref.isNull()) return; - - if (p.symbols.items[generated_symbol.primary.innerIndex()].use_count_estimate == 0 and - p.symbols.items[generated_symbol.primary.innerIndex()].link.isNull()) - { - p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.primary.innerIndex()].original_name; - return; - } - - if (p.symbols.items[generated_symbol.backup.innerIndex()].use_count_estimate == 0 and - p.symbols.items[generated_symbol.backup.innerIndex()].link.isNull()) - { - p.symbols.items[generated_symbol.ref.innerIndex()].original_name = p.symbols.items[generated_symbol.backup.innerIndex()].original_name; - return; - } - } - - pub fn resolveCommonJSSymbols(p: *P) void { - if (p.runtime_imports.__require) |*require| { - p.resolveGeneratedSymbol(require); - } - } - - pub fn resolveBundlingSymbols(p: *P) void { - p.recordUsage(p.runtime_imports.@"$$m".?.ref); - - p.resolveGeneratedSymbol(&p.runtime_imports.__reExport.?); - p.resolveGeneratedSymbol(&p.runtime_imports.@"$$m".?); - p.resolveGeneratedSymbol(&p.runtime_imports.@"$$lzy".?); - p.resolveGeneratedSymbol(&p.runtime_imports.__export.?); - p.resolveGeneratedSymbol(&p.runtime_imports.__exportValue.?); - p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); - } - - pub fn resolveHMRSymbols(p: *P) void { - p.resolveGeneratedSymbol(&p.hmr_module); - if (p.runtime_imports.__FastRefreshModule != null) { - p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshModule.?); - if (p.options.jsx.use_embedded_refresh_runtime) - p.resolveGeneratedSymbol(&p.runtime_imports.__FastRefreshRuntime.?); - } - if (p.runtime_imports.__HMRModule != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRModule.?); - if (p.runtime_imports.__HMRClient != null) p.resolveGeneratedSymbol(&p.runtime_imports.__HMRClient.?); - } - - pub fn resolveStaticJSXSymbols(p: *P) void { - p.resolveGeneratedSymbol(&p.jsx_runtime); - p.resolveGeneratedSymbol(&p.jsxs_runtime); - p.resolveGeneratedSymbol(&p.jsx_factory); - p.resolveGeneratedSymbol(&p.jsx_fragment); - p.resolveGeneratedSymbol(&p.jsx_classic); - p.resolveGeneratedSymbol(&p.jsx_automatic); - p.resolveGeneratedSymbol(&p.jsx_filename); - } - - fn hoistSymbols(p: *P, scope: *js_ast.Scope) void { - if (!scope.kindStopsHoisting()) { - var iter = scope.members.iterator(); - const allocator = p.allocator; - nextMember: while (iter.next()) |res| { - var symbol = &p.symbols.items[res.value.ref.innerIndex()]; - if (!symbol.isHoisted()) { - continue :nextMember; - } - - // Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope - var __scope = scope.parent; - - var hash: u64 = undefined; - if (__scope) |_scope| { - hash = @TypeOf(_scope.members).getHash(symbol.original_name); - } - - while (__scope) |_scope| { - // Variable declarations hoisted past a "with" statement may actually end - // up overwriting a property on the target of the "with" statement instead - // of initializing the variable. We must not rename them or we risk - // causing a behavior change. - // - // var obj = { foo: 1 } - // with (obj) { var foo = 2 } - // assert(foo === undefined) - // assert(obj.foo === 2) - // - if (_scope.kind == .with) { - symbol.must_not_be_renamed = true; - } - - if (_scope.members.getEntryWithHash(symbol.original_name, hash)) |existing_member_entry| { - const existing_member = &existing_member_entry.value; - const existing_symbol: *const Symbol = &p.symbols.items[existing_member.ref.innerIndex()]; - - // We can hoist the symbol from the child scope into the symbol in - // this scope if: - // - // - The symbol is unbound (i.e. a global variable access) - // - The symbol is also another hoisted variable - // - The symbol is a function of any kind and we're in a function or module scope - // - // Is this unbound (i.e. a global access) or also hoisted? - if (existing_symbol.kind == .unbound or existing_symbol.kind == .hoisted or - (Symbol.isKindFunction(existing_symbol.kind) and (_scope.kind == .entry or _scope.kind == .function_body))) - { - // Silently merge this symbol into the existing symbol - symbol.link = existing_member.ref; - continue :nextMember; - } - - // Otherwise if this isn't a catch identifier, it's a collision - if (existing_symbol.kind != .catch_identifier) { - - // An identifier binding from a catch statement and a function - // declaration can both silently shadow another hoisted symbol - if (symbol.kind != .catch_identifier and symbol.kind != .hoisted_function) { - const r = js_lexer.rangeOfIdentifier(p.source, res.value.loc); - var notes = allocator.alloc(logger.Data, 1) catch unreachable; - notes[0] = - logger.rangeData( - p.source, - r, - std.fmt.allocPrint( - allocator, - "{s} was originally declared here", - .{existing_symbol.original_name}, - ) catch unreachable, - ); - - p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, existing_member_entry.value.loc), allocator, notes, "{s} has already been declared", .{symbol.original_name}) catch unreachable; - } - - continue :nextMember; - } - } - - if (_scope.kindStopsHoisting()) { - _scope.members.putWithHash(allocator, symbol.original_name, hash, res.value) catch unreachable; - break; - } - __scope = _scope.parent; - } - } - } - - for (scope.children.items) |_, i| { - p.hoistSymbols(scope.children.items[i]); - } - } - - inline fn nextScopeInOrderForVisitPass(p: *P) ScopeOrder { - const head = p.scope_order_to_visit[0]; - p.scope_order_to_visit = p.scope_order_to_visit[1..p.scope_order_to_visit.len]; - return head; - } - - fn pushScopeForVisitPass(p: *P, kind: js_ast.Scope.Kind, loc: logger.Loc) !void { - // Output.print("\n+Loc: {d}\n", .{loc.start}); - // for (p.scopes_in_order.items[p.scopes_in_order_visitor_index..p.scopes_in_order.items.len]) |scope_order, i| { - // if (scope_order) |ord| { - // Output.print("Scope ({d}, {d})\n", .{ @enumToInt(ord.scope.kind), ord.loc.start }); - // } - // } - const order = p.nextScopeInOrderForVisitPass(); - - // Sanity-check that the scopes generated by the first and second passes match - if (order.loc.start != loc.start or order.scope.kind != kind) { - p.panic("Expected scope ({s}, {d}) in {s}, found scope ({s}, {d})", .{ kind, loc.start, p.source.path.pretty, order.scope.kind, order.loc.start }); - } - - p.current_scope = order.scope; - - try p.scopes_for_current_part.append(p.allocator, order.scope); - } - - fn pushScopeForParsePass(p: *P, comptime kind: js_ast.Scope.Kind, loc: logger.Loc) !usize { - var parent: *Scope = p.current_scope; - const allocator = p.allocator; - var scope = try allocator.create(Scope); - - scope.* = Scope{ - .kind = kind, - .label_ref = null, - .parent = parent, - .generated = .{}, - }; - - try parent.children.append(allocator, scope); - scope.strict_mode = parent.strict_mode; - - p.current_scope = scope; - - if (comptime !Environment.isRelease) { - // Enforce that scope locations are strictly increasing to help catch bugs - // where the pushed scopes are mistmatched between the first and second passes - if (p.scopes_in_order.items.len > 0) { - var last_i = p.scopes_in_order.items.len - 1; - while (p.scopes_in_order.items[last_i] == null and last_i > 0) { - last_i -= 1; - } - - if (p.scopes_in_order.items[last_i]) |prev_scope| { - if (prev_scope.loc.start >= loc.start) { - p.panic("Scope location {d} must be greater than {d}", .{ loc.start, prev_scope.loc.start }); - } - } - } - } - - // Copy down function arguments into the function body scope. That way we get - // errors if a statement in the function body tries to re-declare any of the - // arguments. - if (comptime kind == js_ast.Scope.Kind.function_body) { - if (comptime Environment.allow_assert) - assert(parent.kind == js_ast.Scope.Kind.function_args); - - var iter = scope.parent.?.members.iterator(); - while (iter.next()) |entry| { - // // Don't copy down the optional function expression name. Re-declaring - // // the name of a function expression is allowed. - const adjacent_kind = p.symbols.items[entry.value.ref.innerIndex()].kind; - if (adjacent_kind != .hoisted_function) { - try scope.members.put(allocator, entry.key, entry.value); - } - } - } - - // Remember the length in case we call popAndDiscardScope() later - const scope_index = p.scopes_in_order.items.len; - try p.scopes_in_order.append(allocator, ScopeOrder{ .loc = loc, .scope = scope }); - // Output.print("\nLoc: {d}\n", .{loc.start}); - return scope_index; - } - - // Note: do not write to "p.log" in this function. Any errors due to conversion - // from expression to binding should be written to "invalidLog" instead. That - // way we can potentially keep this as an expression if it turns out it's not - // needed as a binding after all. - fn convertExprToBinding(p: *P, expr: ExprNodeIndex, invalid_loc: *LocList) ?Binding { - switch (expr.data) { - .e_missing => { - return null; - }, - .e_identifier => |ex| { - return p.b(B.Identifier{ .ref = ex.ref }, expr.loc); - }, - .e_array => |ex| { - if (ex.comma_after_spread) |spread| { - invalid_loc.append(.{ - .loc = spread, - .kind = .spread, - }) catch unreachable; - } - - if (ex.is_parenthesized) { - invalid_loc.append(.{ - .loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc, - .kind = .parenthese, - }) catch unreachable; - } - - // p.markSyntaxFeature(Destructing) - var items = List(js_ast.ArrayBinding).initCapacity(p.allocator, ex.items.len) catch unreachable; - var is_spread = false; - for (ex.items.slice()) |_, i| { - var item = ex.items.ptr[i]; - if (item.data == .e_spread) { - is_spread = true; - item = item.data.e_spread.value; - } - const res = p.convertExprToBindingAndInitializer(&item, invalid_loc, is_spread); - - items.appendAssumeCapacity(js_ast.ArrayBinding{ - // It's valid for it to be missing - // An example: - // Promise.all(promises).then(([, len]) => true); - // ^ Binding is missing there - .binding = res.binding orelse p.b(B.Missing{}, item.loc), - .default_value = res.expr, - }); - } - - return p.b(B.Array{ - .items = items.items, - .has_spread = is_spread, - .is_single_line = ex.is_single_line, - }, expr.loc); - }, - .e_object => |ex| { - if (ex.comma_after_spread) |sp| { - invalid_loc.append(.{ .loc = sp, .kind = .spread }) catch unreachable; - } - - if (ex.is_parenthesized) { - invalid_loc.append(.{ .loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc, .kind = .parenthese }) catch unreachable; - } - // p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "{")) - - var properties = List(B.Property).initCapacity(p.allocator, ex.properties.len) catch unreachable; - for (ex.properties.slice()) |*item| { - if (item.flags.contains(.is_method) or item.kind == .get or item.kind == .set) { - invalid_loc.append(.{ - .loc = item.key.?.loc, - .kind = if (item.flags.contains(.is_method)) - InvalidLoc.Tag.method - else if (item.kind == .get) - InvalidLoc.Tag.getter - else - InvalidLoc.Tag.setter, - }) catch unreachable; - continue; - } - var value = &item.value.?; - const tup = p.convertExprToBindingAndInitializer(value, invalid_loc, false); - const initializer = tup.expr orelse item.initializer; - const is_spread = item.kind == .spread or item.flags.contains(.is_spread); - properties.appendAssumeCapacity(B.Property{ - .flags = Flags.Property.init(.{ - .is_spread = is_spread, - .is_computed = item.flags.contains(.is_computed), - }), - .key = item.key orelse p.e(E.Missing{}, expr.loc), - .value = tup.binding orelse p.b(B.Missing{}, expr.loc), - .default_value = initializer, - }); - } - - return p.b(B.Object{ - .properties = properties.items, - .is_single_line = ex.is_single_line, - }, expr.loc); - }, - else => { - invalid_loc.append(.{ .loc = expr.loc, .kind = .unknown }) catch unreachable; - return null; - }, - } - - return null; - } - - fn convertExprToBindingAndInitializer(p: *P, _expr: *ExprNodeIndex, invalid_log: *LocList, is_spread: bool) ExprBindingTuple { - var initializer: ?ExprNodeIndex = null; - var expr = _expr; - // zig syntax is sometimes painful - switch (expr.*.data) { - .e_binary => |bin| { - if (bin.op == .bin_assign) { - initializer = bin.right; - expr = &bin.left; - } - }, - else => {}, - } - - var bind = p.convertExprToBinding(expr.*, invalid_log); - if (initializer) |initial| { - const equalsRange = p.source.rangeOfOperatorBefore(initial.loc, "="); - if (is_spread) { - p.log.addRangeError(p.source, equalsRange, "A rest argument cannot have a default initializer") catch unreachable; - } else { - // p.markSyntaxFeature(); - } - } - return ExprBindingTuple{ .binding = bind, .expr = initializer }; - } - - fn forbidLexicalDecl(p: *P, loc: logger.Loc) !void { - try p.log.addError(p.source, loc, "Cannot use a declaration in a single-statement context"); - } - - /// If we attempt to parse TypeScript syntax outside of a TypeScript file - /// make it a compile error - inline fn markTypeScriptOnly(_: *const P) void { - if (comptime !is_typescript_enabled) { - @compileError("This function can only be used in TypeScript"); - } - - // explicitly mark it as unreachable in the hopes that the function doesn't exist at all - if (!is_typescript_enabled) { - unreachable; - } - } - - fn logExprErrors(p: *P, errors: *DeferredErrors) void { - if (errors.invalid_expr_default_value) |r| { - p.log.addRangeError( - p.source, - r, - "Unexpected \"=\"", - ) catch unreachable; - } - - if (errors.invalid_expr_after_question) |r| { - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Unexpected {s}", .{p.source.contents[r.loc.i()..r.endI()]}) catch unreachable; - } - - // if (errors.array_spread_feature) |err| { - // p.markSyntaxFeature(compat.ArraySpread, errors.arraySpreadFeature) - // } - } - - // This assumes the "function" token has already been parsed - - fn parseFnStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, asyncRange: ?logger.Range) !Stmt { - const is_generator = p.lexer.token == T.t_asterisk; - const is_async = asyncRange != null; - - if (is_generator) { - // p.markSyntaxFeature(compat.Generator, p.lexer.Range()) - try p.lexer.next(); - } else if (is_async) { - // p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) - } - - switch (opts.lexical_decl) { - .forbid => { - try p.forbidLexicalDecl(loc); - }, - - // Allow certain function statements in certain single-statement contexts - .allow_fn_inside_if, .allow_fn_inside_label => { - if (opts.is_typescript_declare or is_generator or is_async) { - try p.forbidLexicalDecl(loc); - } - }, - else => {}, - } - - var name: ?js_ast.LocRef = null; - var nameText: string = ""; - - // The name is optional for "export default function() {}" pseudo-statements - if (!opts.is_name_optional or p.lexer.token == T.t_identifier) { - var nameLoc = p.lexer.loc(); - nameText = p.lexer.identifier; - try p.lexer.expect(T.t_identifier); - // Difference - const ref = try p.newSymbol(Symbol.Kind.other, nameText); - name = js_ast.LocRef{ - .loc = nameLoc, - .ref = ref, - }; - } - - // Even anonymous functions can have TypeScript type parameters - if (is_typescript_enabled) { - try p.skipTypeScriptTypeParameters(); - } - - // Introduce a fake block scope for function declarations inside if statements - var ifStmtScopeIndex: usize = 0; - var hasIfScope = opts.lexical_decl == .allow_fn_inside_if; - if (hasIfScope) { - ifStmtScopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.block, loc); - } - - var scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc()); - var func = try p.parseFn(name, FnOrArrowDataParse{ - .async_range = asyncRange orelse logger.Range.None, - .has_async_range = asyncRange != null, - .allow_await = if (is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, - .allow_yield = if (is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, - .is_typescript_declare = opts.is_typescript_declare, - - // Only allow omitting the body if we're parsing TypeScript - .allow_missing_body_for_type_script = is_typescript_enabled, - }); - - if (comptime is_typescript_enabled) { - // Don't output anything if it's just a forward declaration of a function - if (opts.is_typescript_declare or func.flags.contains(.is_forward_declaration)) { - p.popAndDiscardScope(scopeIndex); - - // Balance the fake block scope introduced above - if (hasIfScope) { - p.popScope(); - } - - if (opts.is_typescript_declare and opts.is_namespace_scope and opts.is_export) { - p.has_non_local_export_declare_inside_namespace = true; - } - - return p.s(S.TypeScript{}, loc); - } - } - - p.popScope(); - - // Only declare the function after we know if it had a body or not. Otherwise - // TypeScript code such as this will double-declare the symbol: - // - // function foo(): void; - // function foo(): void {} - // - if (name) |*name_| { - const kind = if (is_generator or is_async) Symbol.Kind.generator_or_async_function else Symbol.Kind.hoisted_function; - name_.ref = try p.declareSymbol(kind, name_.loc, nameText); - func.name = name_.*; - } - - func.flags.setPresent(.has_if_scope, hasIfScope); - func.flags.setPresent(.is_export, opts.is_export); - - // Balance the fake block scope introduced above - if (hasIfScope) { - p.popScope(); - } - - return p.s(S.Function{ - .func = func, - }, func.open_parens_loc); - } - - fn popAndDiscardScope(p: *P, scope_index: usize) void { - // Move up to the parent scope - var to_discard = p.current_scope; - var parent = to_discard.parent orelse unreachable; - - p.current_scope = parent; - - // Truncate the scope order where we started to pretend we never saw this scope - p.scopes_in_order.shrinkRetainingCapacity(scope_index); - - var children = parent.children; - // Remove the last child from the parent scope - var last = children.items.len - 1; - if (children.items[last] != to_discard) { - p.panic("Internal error", .{}); - } - - _ = children.popOrNull(); - } - - fn parseFn(p: *P, name: ?js_ast.LocRef, opts: FnOrArrowDataParse) anyerror!G.Fn { - // if data.allowAwait and data.allowYield { - // p.markSyntaxFeature(compat.AsyncGenerator, data.asyncRange) - // } - - var func = G.Fn{ - .name = name, - - .flags = Flags.Function.init(.{ - .has_rest_arg = false, - .is_async = opts.allow_await == .allow_expr, - .is_generator = opts.allow_yield == .allow_expr, - }), - - .arguments_ref = null, - .open_parens_loc = p.lexer.loc(), - }; - try p.lexer.expect(T.t_open_paren); - - // Await and yield are not allowed in function arguments - var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); - - p.fn_or_arrow_data_parse.allow_await = if (opts.allow_await == .allow_expr) - AwaitOrYield.forbid_all - else - AwaitOrYield.allow_ident; - - p.fn_or_arrow_data_parse.allow_yield = if (opts.allow_yield == .allow_expr) - AwaitOrYield.forbid_all - else - AwaitOrYield.allow_ident; - - // If "super()" is allowed in the body, it's allowed in the arguments - p.fn_or_arrow_data_parse.allow_super_call = opts.allow_super_call; - p.fn_or_arrow_data_parse.allow_super_property = opts.allow_super_property; - - var args = List(G.Arg){}; - while (p.lexer.token != T.t_close_paren) { - // Skip over "this" type annotations - if (is_typescript_enabled and p.lexer.token == T.t_this) { - try p.lexer.next(); - if (p.lexer.token == T.t_colon) { - try p.lexer.next(); - try p.skipTypeScriptType(js_ast.Op.Level.lowest); - } - if (p.lexer.token != T.t_comma) { - break; - } - - try p.lexer.next(); - continue; - } - - var ts_decorators: []ExprNodeIndex = &([_]ExprNodeIndex{}); - if (opts.allow_ts_decorators) { - ts_decorators = try p.parseTypeScriptDecorators(); - } - - if (!func.flags.contains(.has_rest_arg) and p.lexer.token == T.t_dot_dot_dot) { - // p.markSyntaxFeature - try p.lexer.next(); - func.flags.insert(.has_rest_arg); - } - - var is_typescript_ctor_field = false; - var is_identifier = p.lexer.token == T.t_identifier; - var text = p.lexer.identifier; - var arg = try p.parseBinding(); - - if (comptime is_typescript_enabled) { - if (is_identifier and opts.is_constructor) { - // Skip over TypeScript accessibility modifiers, which turn this argument - // into a class field when used inside a class constructor. This is known - // as a "parameter property" in TypeScript. - while (true) { - switch (p.lexer.token) { - .t_identifier, .t_open_brace, .t_open_bracket => { - if (!js_lexer.TypeScriptAccessibilityModifier.has(text)) { - break; - } - - is_typescript_ctor_field = true; - - // TypeScript requires an identifier binding - if (p.lexer.token != .t_identifier) { - try p.lexer.expect(.t_identifier); - } - text = p.lexer.identifier; - - // Re-parse the binding (the current binding is the TypeScript keyword) - arg = try p.parseBinding(); - }, - else => { - break; - }, - } - } - } - - // "function foo(a?) {}" - if (p.lexer.token == .t_question) { - try p.lexer.next(); - } - - // "function foo(a: any) {}" - if (p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - } - - var parseStmtOpts = ParseStatementOptions{}; - p.declareBinding(.hoisted, &arg, &parseStmtOpts) catch unreachable; - - var default_value: ?ExprNodeIndex = null; - if (!func.flags.contains(.has_rest_arg) and p.lexer.token == .t_equals) { - // p.markSyntaxFeature - try p.lexer.next(); - default_value = try p.parseExpr(.comma); - } - - args.append(p.allocator, G.Arg{ - .ts_decorators = ExprNodeList.init(ts_decorators), - .binding = arg, - .default = default_value, - - // We need to track this because it affects code generation - .is_typescript_ctor_field = is_typescript_ctor_field, - }) catch unreachable; - - if (p.lexer.token != .t_comma) { - break; - } - - if (func.flags.contains(.has_rest_arg)) { - // JavaScript does not allow a comma after a rest argument - if (opts.is_typescript_declare) { - // TypeScript does allow a comma after a rest argument in a "declare" context - try p.lexer.next(); - } else { - try p.lexer.expect(.t_close_paren); - } - - break; - } - - try p.lexer.next(); - } - if (args.items.len > 0) { - func.args = args.items; - } - - // Reserve the special name "arguments" in this scope. This ensures that it - // shadows any variable called "arguments" in any parent scopes. But only do - // this if it wasn't already declared above because arguments are allowed to - // be called "arguments", in which case the real "arguments" is inaccessible. - if (!p.current_scope.members.contains("arguments")) { - func.arguments_ref = p.declareSymbolMaybeGenerated(.arguments, func.open_parens_loc, "arguments", true) catch unreachable; - p.symbols.items[func.arguments_ref.?.innerIndex()].must_not_be_renamed = true; - } - - try p.lexer.expect(.t_close_paren); - p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); - - // "function foo(): any {}" - if (is_typescript_enabled and p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypescriptReturnType(); - } - - // "function foo(): any;" - if (opts.allow_missing_body_for_type_script and p.lexer.token != .t_open_brace) { - try p.lexer.expectOrInsertSemicolon(); - func.flags.insert(.is_forward_declaration); - return func; - } - var tempOpts = opts; - func.body = try p.parseFnBody(&tempOpts); - - return func; - } - - // pub fn parseBinding(p: *P) - - pub inline fn skipTypescriptReturnType(p: *P) anyerror!void { - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_return_type = true }); - } - - pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex { - if (!is_typescript_enabled) { - return &([_]ExprNodeIndex{}); - } - - var decorators = ListManaged(ExprNodeIndex).init(p.allocator); - while (p.lexer.token == T.t_at) { - try p.lexer.next(); - - // Parse a new/call expression with "exprFlagTSDecorator" so we ignore - // EIndex expressions, since they may be part of a computed property: - // - // class Foo { - // @foo ['computed']() {} - // } - // - // This matches the behavior of the TypeScript compiler. - try decorators.append(try p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator)); - } - - return decorators.items; - } - - inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void { - p.markTypeScriptOnly(); - try p.skipTypeScriptTypeWithOpts(level, .{}); - } - - fn skipTypeScriptBinding(p: *P) anyerror!void { - p.markTypeScriptOnly(); - switch (p.lexer.token) { - .t_identifier, .t_this => { - try p.lexer.next(); - }, - .t_open_bracket => { - try p.lexer.next(); - - // "[, , a]" - - while (p.lexer.token == .t_comma) { - try p.lexer.next(); - } - // "[a, b]" - while (p.lexer.token != .t_close_bracket) { - try p.skipTypeScriptBinding(); - - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - - try p.lexer.expect(.t_close_bracket); - }, - .t_open_brace => { - try p.lexer.next(); - - while (p.lexer.token != .t_close_brace) { - var found_identifier = false; - - switch (p.lexer.token) { - .t_identifier => { - found_identifier = true; - try p.lexer.next(); - }, - - // "{1: y}" - // "{'x': y}" - .t_string_literal, .t_numeric_literal => { - try p.lexer.next(); - }, - - else => { - if (p.lexer.isIdentifierOrKeyword()) { - // "{if: x}" - try p.lexer.next(); - } else { - try p.lexer.unexpected(); - return error.Backtrack; - } - }, - } - - if (p.lexer.token == .t_colon or !found_identifier) { - try p.lexer.expect(.t_colon); - try p.skipTypeScriptBinding(); - } - - if (p.lexer.token != .t_comma) { - break; - } - - try p.lexer.next(); - } - - try p.lexer.expect(.t_close_brace); - }, - else => { - // try p.lexer.unexpected(); - return error.Backtrack; - }, - } - } - - fn skipTypescriptFnArgs(p: *P) anyerror!void { - p.markTypeScriptOnly(); - - try p.lexer.expect(.t_open_paren); - - while (p.lexer.token != .t_close_paren) { - // "(...a)" - if (p.lexer.token == .t_dot_dot_dot) { - try p.lexer.next(); - } - - try p.skipTypeScriptBinding(); - - // "(a?)" - if (p.lexer.token == .t_question) { - try p.lexer.next(); - } - - // "(a: any)" - if (p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - - // "(a, b)" - if (p.lexer.token != .t_comma) { - break; - } - - try p.lexer.next(); - } - - try p.lexer.expect(.t_close_paren); - } - - // This is a spot where the TypeScript grammar is highly ambiguous. Here are - // some cases that are valid: - // - // let x = (y: any): (() => {}) => { }; - // let x = (y: any): () => {} => { }; - // let x = (y: any): (y) => {} => { }; - // let x = (y: any): (y[]) => {}; - // let x = (y: any): (a | b) => {}; - // - // Here are some cases that aren't valid: - // - // let x = (y: any): (y) => {}; - // let x = (y: any): (y) => {return 0}; - // let x = (y: any): asserts y is (y) => {}; - // - fn skipTypeScriptParenOrFnType(p: *P) anyerror!void { - p.markTypeScriptOnly(); - - if (p.trySkipTypeScriptArrowArgsWithBacktracking()) { - try p.skipTypescriptReturnType(); - } else { - try p.lexer.expect(.t_open_paren); - try p.skipTypeScriptType(.lowest); - try p.lexer.expect(.t_close_paren); - } - } - - fn skipTypeScriptTypeWithOpts(p: *P, level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions) anyerror!void { - p.markTypeScriptOnly(); - - while (true) { - switch (p.lexer.token) { - .t_numeric_literal, - .t_big_integer_literal, - .t_string_literal, - .t_no_substitution_template_literal, - .t_true, - .t_false, - .t_null, - .t_void, - .t_const, - => { - try p.lexer.next(); - }, - - .t_this => { - try p.lexer.next(); - - // "function check(): this is boolean" - if (p.lexer.isContextualKeyword("is") and !p.lexer.has_newline_before) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - return; - } - }, - .t_minus => { - // "-123" - // "-123n" - try p.lexer.next(); - - if (p.lexer.token == .t_big_integer_literal) { - try p.lexer.next(); - } else { - try p.lexer.expect(.t_numeric_literal); - } - }, - .t_ampersand, .t_bar => { - // Support things like "type Foo = | A | B" and "type Foo = & A & B" - try p.lexer.next(); - continue; - }, - .t_import => { - // "import('fs')" - try p.lexer.next(); - try p.lexer.expect(.t_open_paren); - try p.lexer.expect(.t_string_literal); - try p.lexer.expect(.t_close_paren); - }, - .t_new => { - // "new () => Foo" - // "new <T>() => Foo<T>" - try p.lexer.next(); - try p.skipTypeScriptTypeParameters(); - try p.skipTypeScriptParenOrFnType(); - }, - .t_less_than => { - // "<T>() => Foo<T>" - try p.skipTypeScriptTypeParameters(); - try p.skipTypeScriptParenOrFnType(); - }, - .t_open_paren => { - // "(number | string)" - try p.skipTypeScriptParenOrFnType(); - }, - .t_identifier => { - const kind = TypeScript.Identifier.IMap.get(p.lexer.identifier) orelse .normal; - - if (kind == .prefix) { - try p.lexer.next(); - try p.skipTypeScriptType(.prefix); - break; - } - - var check_type_parameters = true; - - switch (kind) { - .unique => { - try p.lexer.next(); - - // "let foo: unique symbol" - - if (p.lexer.isContextualKeyword("symbol")) { - try p.lexer.next(); - break; - } - }, - .abstract => { - try p.lexer.next(); - - // "let foo: abstract new () => {}" added in TypeScript 4.2 - if (p.lexer.token == .t_new) { - continue; - } - }, - .asserts => { - try p.lexer.next(); - - // "function assert(x: boolean): asserts x" - // "function assert(x: boolean): asserts x is boolean" - - if (opts.is_return_type and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) { - try p.lexer.next(); - } - }, - .primitive => { - try p.lexer.next(); - check_type_parameters = false; - }, - else => { - try p.lexer.next(); - }, - } - - // "function assert(x: any): x is boolean" - - if (p.lexer.isContextualKeyword("is") and !p.lexer.has_newline_before) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - return; - } - - // "let foo: any \n <number>foo" must not become a single type - if (check_type_parameters and !p.lexer.has_newline_before) { - _ = try p.skipTypeScriptTypeArguments(false); - } - }, - .t_typeof => { - try p.lexer.next(); - if (p.lexer.token == .t_import) { - // "typeof import('fs')" - continue; - } else { - // "typeof x" - // "typeof x.y" - - while (true) { - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expected(.t_identifier); - } - - try p.lexer.next(); - if (p.lexer.token != .t_dot) { - break; - } - - try p.lexer.next(); - } - } - }, - .t_open_bracket => { - // "[number, string]" - // "[first: number, second: string]" - try p.lexer.next(); - - while (p.lexer.token != .t_close_bracket) { - if (p.lexer.token == .t_dot_dot_dot) { - try p.lexer.next(); - } - try p.skipTypeScriptType(.lowest); - if (p.lexer.token == .t_question) { - try p.lexer.next(); - } - if (p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - try p.lexer.expect(.t_close_bracket); - }, - .t_open_brace => { - try p.skipTypeScriptObjectType(); - }, - .t_template_head => { - // "`${'a' | 'b'}-${'c' | 'd'}`" - - while (true) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - try p.lexer.rescanCloseBraceAsTemplateToken(); - - if (p.lexer.token == .t_template_tail) { - try p.lexer.next(); - break; - } - } - }, - - else => { - try p.lexer.unexpected(); - return error.Backtrack; - }, - } - break; - } - - while (true) { - switch (p.lexer.token) { - .t_bar => { - if (level.gte(.bitwise_or)) { - return; - } - try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_or); - }, - .t_ampersand => { - if (level.gte(.bitwise_and)) { - return; - } - - try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_and); - }, - .t_exclamation => { - // A postfix "!" is allowed in JSDoc types in TypeScript, which are only - // present in comments. While it's not valid in a non-comment position, - // it's still parsed and turned into a soft error by the TypeScript - // compiler. It turns out parsing this is important for correctness for - // "as" casts because the "!" token must still be consumed. - if (p.lexer.has_newline_before) { - return; - } - - try p.lexer.next(); - }, - .t_dot => { - try p.lexer.next(); - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - try p.lexer.next(); - _ = try p.skipTypeScriptTypeArguments(false); - }, - .t_open_bracket => { - // "{ ['x']: string \n ['y']: string }" must not become a single type - if (p.lexer.has_newline_before) { - return; - } - try p.lexer.next(); - if (p.lexer.token != .t_close_bracket) { - try p.skipTypeScriptType(.lowest); - } - try p.lexer.expect(.t_close_bracket); - }, - .t_extends => { - // "{ x: number \n extends: boolean }" must not become a single type - if (p.lexer.has_newline_before or level.gte(.conditional)) { - return; - } - - try p.lexer.next(); - - // The type following "extends" is not permitted to be another conditional type - try p.skipTypeScriptType(.conditional); - try p.lexer.expect(.t_question); - try p.skipTypeScriptType(.lowest); - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); - }, - else => { - return; - }, - } - } - } - fn skipTypeScriptObjectType(p: *P) anyerror!void { - p.markTypeScriptOnly(); - - try p.lexer.expect(.t_open_brace); - - while (p.lexer.token != .t_close_brace) { - // "{ -readonly [K in keyof T]: T[K] }" - // "{ +readonly [K in keyof T]: T[K] }" - if (p.lexer.token == .t_plus or p.lexer.token == .t_minus) { - try p.lexer.next(); - } - - // Skip over modifiers and the property identifier - var found_key = false; - while (p.lexer.isIdentifierOrKeyword() or p.lexer.token == .t_string_literal or p.lexer.token == .t_numeric_literal) { - try p.lexer.next(); - found_key = true; - } - - if (p.lexer.token == .t_open_bracket) { - // Index signature or computed property - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - - // "{ [key: string]: number }" - // "{ readonly [K in keyof T]: T[K] }" - switch (p.lexer.token) { - .t_colon => { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - }, - .t_in => { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - if (p.lexer.isContextualKeyword("as")) { - // "{ [K in keyof T as `get-${K}`]: T[K] }" - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - }, - else => {}, - } - - try p.lexer.expect(.t_close_bracket); - - // "{ [K in keyof T]+?: T[K] }" - // "{ [K in keyof T]-?: T[K] }" - switch (p.lexer.token) { - .t_plus, .t_minus => { - try p.lexer.next(); - }, - else => {}, - } - - found_key = true; - } - - // "?" indicates an optional property - // "!" indicates an initialization assertion - if (found_key and (p.lexer.token == .t_question or p.lexer.token == .t_exclamation)) { - try p.lexer.next(); - } - - // Type parameters come right after the optional mark - try p.skipTypeScriptTypeParameters(); - - switch (p.lexer.token) { - .t_colon => { - // Regular property - if (!found_key) { - try p.lexer.expect(.t_identifier); - } - - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - }, - .t_open_paren => { - // Method signature - try p.skipTypescriptFnArgs(); - - if (p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypescriptReturnType(); - } - }, - else => { - if (!found_key) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - }, - } - switch (p.lexer.token) { - .t_close_brace => {}, - .t_comma, .t_semicolon => { - try p.lexer.next(); - }, - else => { - if (!p.lexer.has_newline_before) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - }, - } - } - try p.lexer.expect(.t_close_brace); - } - - fn processImportStatement(p: *P, stmt_: S.Import, path: ParsedPath, loc: logger.Loc, was_originally_bare_import: bool) anyerror!Stmt { - const is_macro = FeatureFlags.is_macro_enabled and js_ast.Macro.isMacroPath(path.text); - var stmt = stmt_; - if (is_macro) { - const id = p.addImportRecord(.stmt, path.loc, path.text); - p.import_records.items[id].path.namespace = js_ast.Macro.namespace; - p.import_records.items[id].is_unused = true; - - if (stmt.default_name) |name_loc| { - const name = p.loadNameFromRef(name_loc.ref.?); - const ref = try p.declareSymbol(.other, name_loc.loc, name); - try p.is_import_item.put(p.allocator, ref, .{}); - try p.macro.refs.put(ref, id); - } - - for (stmt.items) |item| { - const name = p.loadNameFromRef(item.name.ref.?); - const ref = try p.declareSymbol(.other, item.name.loc, name); - try p.is_import_item.put(p.allocator, ref, .{}); - try p.macro.refs.put(ref, id); - } - - return p.s(S.Empty{}, loc); - } - - const macro_remap = if ((comptime allow_macros) and !is_macro) - p.options.macro_context.getRemap(path.text) - else - null; - - stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text); - p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import; - - if (stmt.star_name_loc) |star| { - const name = p.loadNameFromRef(stmt.namespace_ref); - - stmt.namespace_ref = try p.declareSymbol(.import, star, name); - - if (comptime track_symbol_usage_during_parse_pass) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = stmt.namespace_ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; - } - } else { - var path_name = fs.PathName.init(strings.append(p.allocator, "import_", path.text) catch unreachable); - const name = try path_name.nonUniqueNameString(p.allocator); - stmt.namespace_ref = try p.newSymbol(.other, name); - var scope: *Scope = p.current_scope; - try scope.generated.append(p.allocator, stmt.namespace_ref); - } - - var item_refs = ImportItemForNamespaceMap.init(p.allocator); - const count_excluding_namespace = @intCast(u16, stmt.items.len) + - @intCast(u16, @boolToInt(stmt.default_name != null)); - - try item_refs.ensureUnusedCapacity(count_excluding_namespace); - // Even though we allocate ahead of time here - // we cannot use putAssumeCapacity because a symbol can have existing links - // those may write to this hash table, so this estimate may be innaccurate - try p.is_import_item.ensureUnusedCapacity(p.allocator, count_excluding_namespace); - var remap_count: u32 = 0; - // Link the default item to the namespace - if (stmt.default_name) |*name_loc| { - outer: { - const name = p.loadNameFromRef(name_loc.ref.?); - const ref = try p.declareSymbol(.import, name_loc.loc, name); - name_loc.ref = ref; - try p.is_import_item.put(p.allocator, ref, .{}); - - if (macro_remap) |*remap| { - if (remap.get("default")) |remapped_path| { - const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path); - try p.macro.refs.put(ref, new_import_id); - - p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace; - p.import_records.items[new_import_id].is_unused = true; - if (comptime only_scan_imports_and_do_not_visit) { - p.import_records.items[new_import_id].is_internal = true; - p.import_records.items[new_import_id].path.is_disabled = true; - } - stmt.default_name = null; - remap_count += 1; - break :outer; - } - } - - if (comptime track_symbol_usage_during_parse_pass) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; - } - - if (is_macro) { - try p.macro.refs.put(ref, stmt.import_record_index); - stmt.default_name = null; - break :outer; - } - - if (comptime ParsePassSymbolUsageType != void) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; - } - - item_refs.putAssumeCapacity(name, name_loc.*); - } - } - var i: usize = 0; - var end: usize = 0; - - while (i < stmt.items.len) : (i += 1) { - var item: js_ast.ClauseItem = stmt.items[i]; - const name = p.loadNameFromRef(item.name.ref orelse unreachable); - const ref = try p.declareSymbol(.import, item.name.loc, name); - item.name.ref = ref; - - try p.is_import_item.put(p.allocator, ref, .{}); - p.checkForNonBMPCodePoint(item.alias_loc, item.alias); - - if (macro_remap) |*remap| { - if (remap.get(item.alias)) |remapped_path| { - const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path); - try p.macro.refs.put(ref, new_import_id); - - p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace; - p.import_records.items[new_import_id].is_unused = true; - if (comptime only_scan_imports_and_do_not_visit) { - p.import_records.items[new_import_id].is_internal = true; - p.import_records.items[new_import_id].path.is_disabled = true; - } - remap_count += 1; - continue; - } - } - - if (comptime track_symbol_usage_during_parse_pass) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; - } - - item_refs.putAssumeCapacity(item.alias, item.name); - stmt.items[end] = item; - end += 1; - } - stmt.items = stmt.items[0..end]; - - // If we remapped the entire import away - // i.e. import {graphql} "react-relay" - - if (remap_count > 0 and stmt.items.len == 0 and stmt.default_name == null) { - p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace; - p.import_records.items[stmt.import_record_index].is_unused = true; - - if (comptime only_scan_imports_and_do_not_visit) { - p.import_records.items[stmt.import_record_index].path.is_disabled = true; - p.import_records.items[stmt.import_record_index].is_internal = true; - } - - return p.s(S.Empty{}, loc); - } else if (remap_count > 0) { - item_refs.shrinkAndFree(stmt.items.len + @as(usize, @boolToInt(stmt.default_name != null))); - } - - // Track the items for this namespace - try p.import_items_for_namespace.put(p.allocator, stmt.namespace_ref, item_refs); - return p.s(stmt, loc); - } - - // This is the type parameter declarations that go with other symbol - // declarations (class, function, type, etc.) - fn skipTypeScriptTypeParameters(p: *P) anyerror!void { - p.markTypeScriptOnly(); - - if (p.lexer.token == .t_less_than) { - try p.lexer.next(); - - while (true) { - try p.lexer.expect(.t_identifier); - // "class Foo<T extends number> {}" - if (p.lexer.token == .t_extends) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - // "class Foo<T = void> {}" - if (p.lexer.token == .t_equals) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - if (p.lexer.token == .t_greater_than) { - break; - } - } - try p.lexer.expectGreaterThan(false); - } - } - - fn createDefaultName(p: *P, loc: logger.Loc) !js_ast.LocRef { - var identifier = try std.fmt.allocPrint(p.allocator, "{s}_default", .{try p.source.path.name.nonUniqueNameString(p.allocator)}); - - const name = js_ast.LocRef{ .loc = loc, .ref = try p.newSymbol(Symbol.Kind.other, identifier) }; - - var scope = p.current_scope; - - try scope.generated.append(p.allocator, name.ref.?); - - return name; - } - - pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !Ref { - const inner_index = Ref.toInt(p.symbols.items.len); - try p.symbols.append(Symbol{ - .kind = kind, - .original_name = identifier, - }); - - if (is_typescript_enabled) { - try p.ts_use_counts.append(p.allocator, 0); - } - - return Ref.init(inner_index, Ref.toInt(p.source.index), false); - } - - fn parseLabelName(p: *P) !?js_ast.LocRef { - if (p.lexer.token != .t_identifier or p.lexer.has_newline_before) { - return null; - } - - const name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(p.lexer.identifier) }; - try p.lexer.next(); - return name; - } - - fn parseClassStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) !Stmt { - var name: ?js_ast.LocRef = null; - var class_keyword = p.lexer.range(); - if (p.lexer.token == .t_class) { - //marksyntaxfeature - try p.lexer.next(); - } else { - try p.lexer.expected(.t_class); - } - - var is_identifier = p.lexer.token == .t_identifier; - - if (!opts.is_name_optional or (is_identifier and (!is_typescript_enabled or !strings.eqlComptime(p.lexer.identifier, "interface")))) { - var name_loc = p.lexer.loc(); - var name_text = p.lexer.identifier; - try p.lexer.expect(.t_identifier); - - // We must return here - // or the lexer will crash loop! - // example: - // export class {} - if (!is_identifier) { - return error.SyntaxError; - } - - if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) { - try p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here"); - } - - name = LocRef{ .loc = name_loc, .ref = null }; - if (!opts.is_typescript_declare) { - (name orelse unreachable).ref = p.declareSymbol(.class, name_loc, name_text) catch unreachable; - } - } - - // Even anonymous classes can have TypeScript type parameters - if (is_typescript_enabled) { - try p.skipTypeScriptTypeParameters(); - } - var class_opts = ParseClassOptions{ - .allow_ts_decorators = true, - .is_type_script_declare = opts.is_typescript_declare, - }; - if (opts.ts_decorators) |dec| { - class_opts.ts_decorators = dec.values; - } - - const scope_index = p.pushScopeForParsePass(.class_name, loc) catch unreachable; - const class = try p.parseClass(class_keyword, name, class_opts); - - if (comptime is_typescript_enabled) { - if (opts.is_typescript_declare) { - p.popAndDiscardScope(scope_index); - if (opts.is_namespace_scope and opts.is_export) { - p.has_non_local_export_declare_inside_namespace = true; - } - - return p.s(S.TypeScript{}, loc); - } - } - - p.popScope(); - return p.s(S.Class{ - .class = class, - .is_export = opts.is_export, - }, loc); - } - - // For HMR, we must convert syntax like this: - // export function leftPad() { - // export const guy = GUY_FIERI_ASCII_ART; - // export class Bacon {} - // export default GuyFieriAsciiArt; - // export {Bacon}; - // export {Bacon as default}; - // to: - // var __hmr__module = new __hmr_HMRModule(file_id, import.meta); - // (__hmr__module._load = function() { - // __hmr__module.exports.leftPad = function () {}; - // __hmr__module.exports.npmProgressBar33 = true; - // __hmr__module.exports.Bacon = class {}; - // })(); - // export { __hmr__module.exports.leftPad as leftPad, __hmr__module.exports.npmProgressBar33 as npmProgressBar33, __hmr__module } - // - // - // - // At bottom of the file: - // - - // var __hmr__exports = new HMRModule({ - // leftPad: () => leftPad, - // npmProgressBar33 () => npmProgressBar33, - // default: () => GuyFieriAsciiArt, - // [__hmr_ModuleIDSymbol]: - //}); - // export { __hmr__exports.leftPad as leftPad, __hmr__ } - // - - // Then: - // if () { - // - // } - - // pub fn maybeRewriteExportSymbol(p: *P, ) - - fn defaultNameForExpr(p: *P, expr: Expr, loc: logger.Loc) LocRef { - switch (expr.data) { - .e_function => |func_container| { - if (func_container.func.name) |_name| { - if (_name.ref) |ref| { - return LocRef{ .loc = loc, .ref = ref }; - } - } - }, - .e_identifier => |ident| { - return LocRef{ .loc = loc, .ref = ident.ref }; - }, - .e_import_identifier => |ident| { - if (!allow_macros or (allow_macros and !p.macro.refs.contains(ident.ref))) { - return LocRef{ .loc = loc, .ref = ident.ref }; - } - }, - .e_class => |class| { - if (class.class_name) |_name| { - if (_name.ref) |ref| { - return LocRef{ .loc = loc, .ref = ref }; - } - } - }, - else => {}, - } - - return createDefaultName(p, loc) catch unreachable; - } - - fn parseStmt(p: *P, opts: *ParseStatementOptions) anyerror!Stmt { - var loc = p.lexer.loc(); - - switch (p.lexer.token) { - .t_semicolon => { - try p.lexer.next(); - return Stmt.empty(); - }, - - .t_export => { - var previousExportKeyword = p.es6_export_keyword; - if (opts.is_module_scope) { - p.es6_export_keyword = p.lexer.range(); - } else if (!opts.is_namespace_scope) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - try p.lexer.next(); - - // TypeScript decorators only work on class declarations - // "@decorator export class Foo {}" - // "@decorator export abstract class Foo {}" - // "@decorator export default class Foo {}" - // "@decorator export default abstract class Foo {}" - // "@decorator export declare class Foo {}" - // "@decorator export declare abstract class Foo {}" - if (opts.ts_decorators != null and p.lexer.token != js_lexer.T.t_class and - p.lexer.token != js_lexer.T.t_default and - !p.lexer.isContextualKeyword("abstract") and - !p.lexer.isContextualKeyword("declare")) - { - try p.lexer.expected(js_lexer.T.t_class); - } - - switch (p.lexer.token) { - T.t_class, T.t_const, T.t_function, T.t_var => { - opts.is_export = true; - return p.parseStmt(opts); - }, - - T.t_import => { - // "export import foo = bar" - if (is_typescript_enabled and (opts.is_module_scope or opts.is_namespace_scope)) { - opts.is_export = true; - return p.parseStmt(opts); - } - - try p.lexer.unexpected(); - return error.SyntaxError; - }, - - T.t_enum => { - if (!is_typescript_enabled) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - opts.is_export = true; - return p.parseStmt(opts); - }, - - T.t_identifier => { - if (p.lexer.isContextualKeyword("let")) { - opts.is_export = true; - return p.parseStmt(opts); - } - - if (comptime is_typescript_enabled) { - if (opts.is_typescript_declare and p.lexer.isContextualKeyword("as")) { - // "export as namespace ns;" - try p.lexer.next(); - try p.lexer.expectContextualKeyword("namespace"); - try p.lexer.expect(T.t_identifier); - try p.lexer.expectOrInsertSemicolon(); - - return p.s(S.TypeScript{}, loc); - } - } - - if (p.lexer.isContextualKeyword("async")) { - var asyncRange = p.lexer.range(); - try p.lexer.next(); - if (p.lexer.has_newline_before) { - try p.log.addRangeError(p.source, asyncRange, "Unexpected newline after \"async\""); - } - - try p.lexer.expect(T.t_function); - opts.is_export = true; - return try p.parseFnStmt(loc, opts, asyncRange); - } - - if (is_typescript_enabled) { - if (TypeScript.Identifier.forStr(p.lexer.identifier)) |ident| { - switch (ident) { - .s_type => { - // "export type foo = ..." - const type_range = p.lexer.range(); - try p.lexer.next(); - if (p.lexer.has_newline_before) { - try p.log.addErrorFmt(p.source, type_range.end(), p.allocator, "Unexpected newline after \"type\"", .{}); - return error.SynaxError; - } - var skipper = ParseStatementOptions{ .is_module_scope = opts.is_module_scope, .is_export = true }; - try p.skipTypeScriptTypeStmt(&skipper); - return p.s(S.TypeScript{}, loc); - }, - .s_namespace, .s_abstract, .s_module, .s_interface => { - // "export namespace Foo {}" - // "export abstract class Foo {}" - // "export module Foo {}" - // "export interface Foo {}" - opts.is_export = true; - return try p.parseStmt(opts); - }, - .s_declare => { - // "export declare class Foo {}" - opts.is_export = true; - opts.lexical_decl = .allow_all; - opts.is_typescript_declare = true; - return try p.parseStmt(opts); - }, - } - } - } - - try p.lexer.unexpected(); - return error.SyntaxError; - }, - - T.t_default => { - if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - var defaultLoc = p.lexer.loc(); - try p.lexer.next(); - - // TypeScript decorators only work on class declarations - // "@decorator export default class Foo {}" - // "@decorator export default abstract class Foo {}" - if (opts.ts_decorators != null and p.lexer.token != T.t_class and !p.lexer.isContextualKeyword("abstract")) { - try p.lexer.expected(T.t_class); - } - - if (p.lexer.isContextualKeyword("async")) { - var async_range = p.lexer.range(); - try p.lexer.next(); - var defaultName: js_ast.LocRef = undefined; - if (p.lexer.token == T.t_function and !p.lexer.has_newline_before) { - try p.lexer.next(); - var stmtOpts = ParseStatementOptions{ - .is_name_optional = true, - .lexical_decl = .allow_all, - }; - var stmt = try p.parseFnStmt(loc, &stmtOpts, async_range); - if (@as(Stmt.Tag, stmt.data) == .s_type_script) { - // This was just a type annotation - return stmt; - } - - if (stmt.data.s_function.func.name) |name| { - defaultName = js_ast.LocRef{ .loc = name.loc, .ref = name.ref }; - } else { - defaultName = try p.createDefaultName(defaultLoc); - } - var value = js_ast.StmtOrExpr{ .stmt = stmt }; - return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); - } - - defaultName = try createDefaultName(p, loc); - - const prefix_expr = try p.parseAsyncPrefixExpr(async_range, Level.comma); - var expr = try p.parseSuffix(prefix_expr, Level.comma, null, Expr.EFlags.none); - try p.lexer.expectOrInsertSemicolon(); - var value = js_ast.StmtOrExpr{ .expr = expr }; - p.has_export_default = true; - return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc); - } - - if (p.lexer.token == .t_function or p.lexer.token == .t_class or p.lexer.isContextualKeyword("interface")) { - var _opts = ParseStatementOptions{ - .ts_decorators = opts.ts_decorators, - .is_name_optional = true, - .lexical_decl = .allow_all, - }; - var stmt = try p.parseStmt(&_opts); - - const default_name: js_ast.LocRef = default_name_getter: { - switch (stmt.data) { - // This was just a type annotation - .s_type_script => { - return stmt; - }, - - .s_function => |func_container| { - if (func_container.func.name) |name| { - break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; - } else {} - }, - .s_class => |class| { - if (class.class.class_name) |name| { - break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; - } else {} - }, - else => {}, - } - - break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; - }; - p.has_export_default = true; - return p.s( - S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, - loc, - ); - } - - const is_identifier = p.lexer.token == .t_identifier; - const name = p.lexer.identifier; - var expr = try p.parseExpr(.comma); - - // Handle the default export of an abstract class in TypeScript - if (is_typescript_enabled and is_identifier and (p.lexer.token == .t_class or opts.ts_decorators != null) and strings.eqlComptime(name, "abstract")) { - switch (expr.data) { - .e_identifier => { - var stmtOpts = ParseStatementOptions{ - .ts_decorators = opts.ts_decorators, - .is_name_optional = true, - }; - const stmt: Stmt = try p.parseClassStmt(loc, &stmtOpts); - - // Use the statement name if present, since it's a better name - const default_name: js_ast.LocRef = default_name_getter: { - switch (stmt.data) { - // This was just a type annotation - .s_type_script => { - return stmt; - }, - - .s_function => |func_container| { - if (func_container.func.name) |_name| { - break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; - } else {} - }, - .s_class => |class| { - if (class.class.class_name) |_name| { - break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; - } else {} - }, - else => {}, - } - - break :default_name_getter createDefaultName(p, defaultLoc) catch unreachable; - }; - p.has_export_default = true; - return p.s(S.ExportDefault{ .default_name = default_name, .value = js_ast.StmtOrExpr{ .stmt = stmt } }, loc); - }, - else => { - p.panic("internal error: unexpected", .{}); - }, - } - } - - try p.lexer.expectOrInsertSemicolon(); - - // Use the expression name if present, since it's a better name - p.has_export_default = true; - return p.s( - S.ExportDefault{ - .default_name = p.defaultNameForExpr(expr, defaultLoc), - .value = js_ast.StmtOrExpr{ - .expr = expr, - }, - }, - loc, - ); - }, - T.t_asterisk => { - if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - try p.lexer.next(); - var namespace_ref: Ref = Ref.None; - var alias: ?js_ast.G.ExportStarAlias = null; - var path: ParsedPath = undefined; - - if (p.lexer.isContextualKeyword("as")) { - // "export * as ns from 'path'" - try p.lexer.next(); - const name = try p.parseClauseAlias("export"); - namespace_ref = try p.storeNameInRef(name); - alias = G.ExportStarAlias{ .loc = p.lexer.loc(), .original_name = name }; - try p.lexer.next(); - try p.lexer.expectContextualKeyword("from"); - path = try p.parsePath(); - } else { - // "export * from 'path'" - try p.lexer.expectContextualKeyword("from"); - path = try p.parsePath(); - const name = try fs.PathName.init(path.text).nonUniqueNameString(p.allocator); - namespace_ref = try p.storeNameInRef(name); - } - - var import_record_index = p.addImportRecord( - ImportKind.stmt, - path.loc, - path.text, - // TODO: import assertions - // path.assertions - ); - - if (comptime track_symbol_usage_during_parse_pass) { - // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; - } - - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.ExportStar{ - .namespace_ref = namespace_ref, - .alias = alias, - .import_record_index = import_record_index, - }, loc); - }, - T.t_open_brace => { - if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - const export_clause = try p.parseExportClause(); - if (p.lexer.isContextualKeyword("from")) { - try p.lexer.expectContextualKeyword("from"); - const parsedPath = try p.parsePath(); - - try p.lexer.expectOrInsertSemicolon(); - - if (comptime is_typescript_enabled) { - // export {type Foo} from 'bar'; - // -> - // nothing - // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA - if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { - return p.s(S.TypeScript{}, loc); - } - } - - const import_record_index = p.addImportRecord(.stmt, parsedPath.loc, parsedPath.text); - var path_name = fs.PathName.init(strings.append(p.allocator, "import_", parsedPath.text) catch unreachable); - const namespace_ref = p.storeNameInRef(path_name.nonUniqueNameString(p.allocator) catch unreachable) catch unreachable; - - if (comptime track_symbol_usage_during_parse_pass) { - // In the scan pass, we need _some_ way of knowing *not* to mark as unused - p.import_records.items[import_record_index].calls_run_time_re_export_fn = true; - } - - return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc); - } - try p.lexer.expectOrInsertSemicolon(); - - if (comptime is_typescript_enabled) { - // export {type Foo}; - // -> - // nothing - // https://www.typescriptlang.org/play?useDefineForClassFields=true&esModuleInterop=false&declaration=false&target=99&isolatedModules=false&ts=4.5.4#code/KYDwDg9gTgLgBDAnmYcDeAxCEC+cBmUEAtnAOQBGAhlGQNwBQQA - if (export_clause.clauses.len == 0 and export_clause.had_type_only_exports) { - return p.s(S.TypeScript{}, loc); - } - } - - return p.s(S.ExportClause{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line }, loc); - }, - T.t_equals => { - // "export = value;" - - p.es6_export_keyword = previousExportKeyword; // This wasn't an ESM export statement after all - if (is_typescript_enabled) { - try p.lexer.next(); - var value = try p.parseExpr(.lowest); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.ExportEquals{ .value = value }, loc); - } - try p.lexer.unexpected(); - return error.SyntaxError; - }, - else => { - try p.lexer.unexpected(); - return error.SyntaxError; - }, - } - }, - - .t_function => { - try p.lexer.next(); - return try p.parseFnStmt(loc, opts, null); - }, - .t_enum => { - if (!is_typescript_enabled) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - return p.parseTypescriptEnumStmt(loc, opts); - }, - .t_at => { - // Parse decorators before class statements, which are potentially exported - if (is_typescript_enabled) { - const scope_index = p.scopes_in_order.items.len; - const ts_decorators = try p.parseTypeScriptDecorators(); - - // If this turns out to be a "declare class" statement, we need to undo the - // scopes that were potentially pushed while parsing the decorator arguments. - // That can look like any one of the following: - // - // "@decorator declare class Foo {}" - // "@decorator declare abstract class Foo {}" - // "@decorator export declare class Foo {}" - // "@decorator export declare abstract class Foo {}" - // - opts.ts_decorators = DeferredTsDecorators{ - .values = ts_decorators, - .scope_index = scope_index, - }; - - // "@decorator class Foo {}" - // "@decorator abstract class Foo {}" - // "@decorator declare class Foo {}" - // "@decorator declare abstract class Foo {}" - // "@decorator export class Foo {}" - // "@decorator export abstract class Foo {}" - // "@decorator export declare class Foo {}" - // "@decorator export declare abstract class Foo {}" - // "@decorator export default class Foo {}" - // "@decorator export default abstract class Foo {}" - if (p.lexer.token != .t_class and p.lexer.token != .t_export and !p.lexer.isContextualKeyword("abstract") and !p.lexer.isContextualKeyword("declare")) { - try p.lexer.expected(.t_class); - } - - return p.parseStmt(opts); - } - // notimpl(); - - try p.lexer.unexpected(); - return error.SyntaxError; - }, - .t_class => { - if (opts.lexical_decl != .allow_all) { - try p.forbidLexicalDecl(loc); - } - - return try p.parseClassStmt(loc, opts); - }, - .t_var => { - try p.lexer.next(); - const decls = try p.parseAndDeclareDecls(.hoisted, opts); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.Local{ .kind = .k_var, .decls = decls, .is_export = opts.is_export }, loc); - }, - .t_const => { - if (opts.lexical_decl != .allow_all) { - try p.forbidLexicalDecl(loc); - } - // p.markSyntaxFeature(compat.Const, p.lexer.Range()) - - try p.lexer.next(); - - if (is_typescript_enabled and p.lexer.token == T.t_enum) { - return p.parseTypescriptEnumStmt(loc, opts); - } - - const decls = try p.parseAndDeclareDecls(.cconst, opts); - try p.lexer.expectOrInsertSemicolon(); - - if (!opts.is_typescript_declare) { - try p.requireInitializers(decls); - } - - // When HMR is enabled, replace all const/let exports with var - const kind = if (p.options.features.hot_module_reloading and opts.is_export) S.Local.Kind.k_var else S.Local.Kind.k_const; - return p.s(S.Local{ .kind = kind, .decls = decls, .is_export = opts.is_export }, loc); - }, - .t_if => { - try p.lexer.next(); - try p.lexer.expect(.t_open_paren); - const test_ = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - var stmtOpts = ParseStatementOptions{ - .lexical_decl = .allow_fn_inside_if, - }; - const yes = try p.parseStmt(&stmtOpts); - var no: ?Stmt = null; - if (p.lexer.token == .t_else) { - try p.lexer.next(); - stmtOpts = ParseStatementOptions{ - .lexical_decl = .allow_fn_inside_if, - }; - no = try p.parseStmt(&stmtOpts); - } - - return p.s(S.If{ - .test_ = test_, - .yes = yes, - .no = no, - }, loc); - }, - .t_do => { - try p.lexer.next(); - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - try p.lexer.expect(.t_while); - try p.lexer.expect(.t_open_paren); - const test_ = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - - // This is a weird corner case where automatic semicolon insertion applies - // even without a newline present - if (p.lexer.token == .t_semicolon) { - try p.lexer.next(); - } - return p.s(S.DoWhile{ .body = body, .test_ = test_ }, loc); - }, - .t_while => { - try p.lexer.next(); - - try p.lexer.expect(.t_open_paren); - const test_ = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - - return p.s(S.While{ - .body = body, - .test_ = test_, - }, loc); - }, - .t_with => { - try p.lexer.next(); - try p.lexer.expect(.t_open_paren); - const test_ = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - - const body_loc = p.lexer.loc(); - _ = try p.pushScopeForParsePass(.block, body_loc); - defer p.popScope(); - - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - - return p.s(S.With{ .body = body, .body_loc = body_loc, .value = test_ }, loc); - }, - .t_switch => { - try p.lexer.next(); - - try p.lexer.expect(.t_open_paren); - const test_ = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - - const body_loc = p.lexer.loc(); - _ = try p.pushScopeForParsePass(.block, body_loc); - defer p.popScope(); - - try p.lexer.expect(.t_open_brace); - var cases = ListManaged(js_ast.Case).init(p.allocator); - var foundDefault = false; - var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; - var value: ?js_ast.Expr = null; - while (p.lexer.token != .t_close_brace) { - var body = StmtList.init(p.allocator); - value = null; - if (p.lexer.token == .t_default) { - if (foundDefault) { - try p.log.addRangeError(p.source, p.lexer.range(), "Multiple default clauses are not allowed"); - return error.SyntaxError; - } - - foundDefault = true; - try p.lexer.next(); - try p.lexer.expect(.t_colon); - } else { - try p.lexer.expect(.t_case); - value = try p.parseExpr(.lowest); - try p.lexer.expect(.t_colon); - } - - caseBody: while (true) { - switch (p.lexer.token) { - .t_close_brace, .t_case, .t_default => { - break :caseBody; - }, - else => { - stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; - try body.append(try p.parseStmt(&stmtOpts)); - }, - } - } - try cases.append(js_ast.Case{ .value = value, .body = body.items, .loc = logger.Loc.Empty }); - } - try p.lexer.expect(.t_close_brace); - return p.s(S.Switch{ .test_ = test_, .body_loc = body_loc, .cases = cases.items }, loc); - }, - .t_try => { - try p.lexer.next(); - const body_loc = p.lexer.loc(); - try p.lexer.expect(.t_open_brace); - _ = try p.pushScopeForParsePass(.block, loc); - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); - p.popScope(); - try p.lexer.next(); - - var catch_: ?js_ast.Catch = null; - var finally: ?js_ast.Finally = null; - - if (p.lexer.token == .t_catch) { - const catch_loc = p.lexer.loc(); - _ = try p.pushScopeForParsePass(.block, catch_loc); - try p.lexer.next(); - var binding: ?js_ast.Binding = null; - - // The catch binding is optional, and can be omitted - // jarred: TIL! - if (p.lexer.token != .t_open_brace) { - try p.lexer.expect(.t_open_paren); - var value = try p.parseBinding(); - - // Skip over types - if (is_typescript_enabled and p.lexer.token == .t_colon) { - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); - } - - try p.lexer.expect(.t_close_paren); - - // Bare identifiers are a special case - var kind = Symbol.Kind.other; - switch (value.data) { - .b_identifier => { - kind = .catch_identifier; - }, - else => {}, - } - stmtOpts = ParseStatementOptions{}; - try p.declareBinding(kind, &value, &stmtOpts); - binding = value; - } - - try p.lexer.expect(.t_open_brace); - stmtOpts = ParseStatementOptions{}; - const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); - try p.lexer.next(); - catch_ = js_ast.Catch{ - .loc = catch_loc, - .binding = binding, - .body = stmts, - }; - p.popScope(); - } - - if (p.lexer.token == .t_finally or catch_ == null) { - const finally_loc = p.lexer.loc(); - _ = try p.pushScopeForParsePass(.block, finally_loc); - try p.lexer.expect(.t_finally); - try p.lexer.expect(.t_open_brace); - stmtOpts = ParseStatementOptions{}; - const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); - try p.lexer.next(); - finally = js_ast.Finally{ .loc = finally_loc, .stmts = stmts }; - p.popScope(); - } - - return p.s( - S.Try{ .body_loc = body_loc, .body = body, .catch_ = catch_, .finally = finally }, - loc, - ); - }, - .t_for => { - _ = try p.pushScopeForParsePass(.block, loc); - defer p.popScope(); - - try p.lexer.next(); - - // "for await (let x of y) {}" - var isForAwait = p.lexer.isContextualKeyword("await"); - if (isForAwait) { - const await_range = p.lexer.range(); - if (p.fn_or_arrow_data_parse.allow_await != .allow_expr) { - try p.log.addRangeError(p.source, await_range, "Cannot use \"await\" outside an async function"); - isForAwait = false; - } else { - // TODO: improve error handling here - // didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange) - if (p.fn_or_arrow_data_parse.is_top_level) { - p.top_level_await_keyword = await_range; - // p.markSyntaxFeature(compat.TopLevelAwait, awaitRange) - } - } - try p.lexer.next(); - } - - try p.lexer.expect(.t_open_paren); - - var init_: ?Stmt = null; - var test_: ?Expr = null; - var update: ?Expr = null; - - // "in" expressions aren't allowed here - p.allow_in = false; - - var bad_let_range: ?logger.Range = null; - if (p.lexer.isContextualKeyword("let")) { - bad_let_range = p.lexer.range(); - } - - var decls: []G.Decl = &([_]G.Decl{}); - var init_loc = p.lexer.loc(); - var is_var = false; - switch (p.lexer.token) { - // for (var ) - .t_var => { - is_var = true; - try p.lexer.next(); - var stmtOpts = ParseStatementOptions{}; - decls = try p.parseAndDeclareDecls(.hoisted, &stmtOpts); - init_ = p.s(S.Local{ .kind = .k_var, .decls = decls }, init_loc); - }, - // for (const ) - .t_const => { - try p.lexer.next(); - var stmtOpts = ParseStatementOptions{}; - decls = try p.parseAndDeclareDecls(.cconst, &stmtOpts); - init_ = p.s(S.Local{ .kind = .k_const, .decls = decls }, init_loc); - }, - // for (;) - .t_semicolon => {}, - else => { - var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; - - const res = try p.parseExprOrLetStmt(&stmtOpts); - switch (res.stmt_or_expr) { - .stmt => |stmt| { - bad_let_range = null; - init_ = stmt; - }, - .expr => |expr| { - init_ = p.s(S.SExpr{ - .value = expr, - }, init_loc); - }, - } - }, - } - - // "in" expressions are allowed again - p.allow_in = true; - - // Detect for-of loops - if (p.lexer.isContextualKeyword("of") or isForAwait) { - if (bad_let_range) |r| { - try p.log.addRangeError(p.source, r, "\"let\" must be wrapped in parentheses to be used as an expression here"); - return error.SyntaxError; - } - - if (isForAwait and !p.lexer.isContextualKeyword("of")) { - if (init_ != null) { - try p.lexer.expectedString("\"of\""); - } else { - try p.lexer.unexpected(); - return error.SyntaxError; - } - } - - try p.forbidInitializers(decls, "of", false); - try p.lexer.next(); - const value = try p.parseExpr(.comma); - try p.lexer.expect(.t_close_paren); - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - return p.s(S.ForOf{ .is_await = isForAwait, .init = init_ orelse unreachable, .value = value, .body = body }, loc); - } - - // Detect for-in loops - if (p.lexer.token == .t_in) { - try p.forbidInitializers(decls, "in", is_var); - try p.lexer.next(); - const value = try p.parseExpr(.lowest); - try p.lexer.expect(.t_close_paren); - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - return p.s(S.ForIn{ .init = init_ orelse unreachable, .value = value, .body = body }, loc); - } - - // Only require "const" statement initializers when we know we're a normal for loop - if (init_) |init_stmt| { - switch (init_stmt.data) { - .s_local => { - if (init_stmt.data.s_local.kind == .k_const) { - try p.requireInitializers(decls); - } - }, - else => {}, - } - } - - try p.lexer.expect(.t_semicolon); - if (p.lexer.token != .t_semicolon) { - test_ = try p.parseExpr(.lowest); - } - - try p.lexer.expect(.t_semicolon); - - if (p.lexer.token != .t_close_paren) { - update = try p.parseExpr(.lowest); - } - - try p.lexer.expect(.t_close_paren); - var stmtOpts = ParseStatementOptions{}; - const body = try p.parseStmt(&stmtOpts); - return p.s( - S.For{ .init = init_, .test_ = test_, .update = update, .body = body }, - loc, - ); - }, - .t_import => { - const previous_import_keyword = p.es6_import_keyword; - p.es6_import_keyword = p.lexer.range(); - try p.lexer.next(); - var stmt: S.Import = S.Import{ - .namespace_ref = Ref.None, - .import_record_index = std.math.maxInt(u32), - }; - var was_originally_bare_import = false; - - // "export import foo = bar" - if ((opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) and p.lexer.token != .t_identifier) { - try p.lexer.expected(.t_identifier); - } - - switch (p.lexer.token) { - // "import('path')" - // "import.meta" - .t_open_paren, .t_dot => { - p.es6_import_keyword = previous_import_keyword; // this wasn't an esm import statement after all - const expr = try p.parseSuffix(try p.parseImportExpr(loc, .lowest), .lowest, null, Expr.EFlags.none); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.SExpr{ - .value = expr, - }, loc); - }, - .t_string_literal, .t_no_substitution_template_literal => { - // "import 'path'" - if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - was_originally_bare_import = true; - }, - .t_asterisk => { - // "import * as ns from 'path'" - if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - try p.lexer.next(); - try p.lexer.expectContextualKeyword("as"); - stmt = S.Import{ - .namespace_ref = try p.storeNameInRef(p.lexer.identifier), - .star_name_loc = p.lexer.loc(), - .import_record_index = std.math.maxInt(u32), - }; - try p.lexer.expect(.t_identifier); - try p.lexer.expectContextualKeyword("from"); - }, - .t_open_brace => { - // "import {item1, item2} from 'path'" - if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - var importClause = try p.parseImportClause(); - if (comptime is_typescript_enabled) { - if (importClause.had_type_only_imports and importClause.items.len == 0) { - try p.lexer.expectContextualKeyword("from"); - _ = try p.parsePath(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.TypeScript{}, loc); - } - } - - stmt = S.Import{ - .namespace_ref = Ref.None, - .import_record_index = std.math.maxInt(u32), - .items = importClause.items, - .is_single_line = importClause.is_single_line, - }; - try p.lexer.expectContextualKeyword("from"); - }, - .t_identifier => { - // "import defaultItem from 'path'" - // "import foo = bar" - if (!opts.is_module_scope and (!opts.is_namespace_scope)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - var default_name = p.lexer.identifier; - stmt = S.Import{ .namespace_ref = Ref.None, .import_record_index = std.math.maxInt(u32), .default_name = LocRef{ - .loc = p.lexer.loc(), - .ref = try p.storeNameInRef(default_name), - } }; - try p.lexer.next(); - - if (comptime is_typescript_enabled) { - // Skip over type-only imports - if (strings.eqlComptime(default_name, "type")) { - switch (p.lexer.token) { - .t_identifier => { - if (!strings.eqlComptime(p.lexer.identifier, "from")) { - default_name = p.lexer.identifier; - stmt.default_name.?.loc = p.lexer.loc(); - try p.lexer.next(); - - if (p.lexer.token == .t_equals) { - // "import type foo = require('bar');" - // "import type foo = bar.baz;" - opts.is_typescript_declare = true; - return try p.parseTypeScriptImportEqualsStmt(loc, opts, stmt.default_name.?.loc, default_name); - } else { - // "import type foo from 'bar';" - try p.lexer.expectContextualKeyword("from"); - _ = try p.parsePath(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.TypeScript{}, loc); - } - } - }, - .t_asterisk => { - // "import type * as foo from 'bar';" - try p.lexer.next(); - try p.lexer.expectContextualKeyword("as"); - try p.lexer.expect(.t_identifier); - try p.lexer.expectContextualKeyword("from"); - _ = try p.parsePath(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.TypeScript{}, loc); - }, - - .t_open_brace => { - // "import type {foo} from 'bar';" - _ = try p.parseImportClause(); - try p.lexer.expectContextualKeyword("from"); - _ = try p.parsePath(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.TypeScript{}, loc); - }, - else => {}, - } - } - - // Parse TypeScript import assignment statements - if (p.lexer.token == .t_equals or opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) { - p.es6_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all; - return p.parseTypeScriptImportEqualsStmt(loc, opts, logger.Loc.Empty, default_name); - } - } - - if (p.lexer.token == .t_comma) { - try p.lexer.next(); - - switch (p.lexer.token) { - // "import defaultItem, * as ns from 'path'" - .t_asterisk => { - try p.lexer.next(); - try p.lexer.expectContextualKeyword("as"); - stmt.namespace_ref = try p.storeNameInRef(p.lexer.identifier); - stmt.star_name_loc = p.lexer.loc(); - try p.lexer.expect(.t_identifier); - }, - // "import defaultItem, {item1, item2} from 'path'" - .t_open_brace => { - const importClause = try p.parseImportClause(); - - stmt.items = importClause.items; - stmt.is_single_line = importClause.is_single_line; - }, - else => { - try p.lexer.unexpected(); - return error.SyntaxError; - }, - } - } - - try p.lexer.expectContextualKeyword("from"); - }, - else => { - try p.lexer.unexpected(); - return error.SyntaxError; - }, - } - - const path = try p.parsePath(); - try p.lexer.expectOrInsertSemicolon(); - - return try p.processImportStatement(stmt, path, loc, was_originally_bare_import); - }, - .t_break => { - try p.lexer.next(); - const name = try p.parseLabelName(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.Break{ .label = name }, loc); - }, - .t_continue => { - try p.lexer.next(); - const name = try p.parseLabelName(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.Continue{ .label = name }, loc); - }, - .t_return => { - if (p.fn_or_arrow_data_parse.is_return_disallowed) { - try p.log.addRangeError(p.source, p.lexer.range(), "A return statement cannot be used here"); - } - try p.lexer.next(); - var value: ?Expr = null; - if ((p.lexer.token != .t_semicolon and - !p.lexer.has_newline_before and - p.lexer.token != .t_close_brace and - p.lexer.token != .t_end_of_file)) - { - value = try p.parseExpr(.lowest); - } - p.latest_return_had_semicolon = p.lexer.token == .t_semicolon; - try p.lexer.expectOrInsertSemicolon(); - - return p.s(S.Return{ .value = value }, loc); - }, - .t_throw => { - try p.lexer.next(); - if (p.lexer.has_newline_before) { - try p.log.addError(p.source, logger.Loc{ - .start = loc.start + 5, - }, "Unexpected newline after \"throw\""); - return error.SyntaxError; - } - const expr = try p.parseExpr(.lowest); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.Throw{ .value = expr }, loc); - }, - .t_debugger => { - try p.lexer.next(); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.Debugger{}, loc); - }, - .t_open_brace => { - _ = try p.pushScopeForParsePass(.block, loc); - defer p.popScope(); - try p.lexer.next(); - var stmtOpts = ParseStatementOptions{}; - const stmts = try p.parseStmtsUpTo(.t_close_brace, &stmtOpts); - try p.lexer.next(); - return p.s(S.Block{ - .stmts = stmts, - }, loc); - }, - - else => { - const is_identifier = p.lexer.token == .t_identifier; - const name = p.lexer.identifier; - var emiss = E.Missing{}; - // Parse either an async function, an async expression, or a normal expression - var expr: Expr = Expr{ .loc = loc, .data = Expr.Data{ .e_missing = emiss } }; - if (is_identifier and strings.eqlComptime(p.lexer.raw(), "async")) { - var async_range = p.lexer.range(); - try p.lexer.next(); - if (p.lexer.token == .t_function and !p.lexer.has_newline_before) { - try p.lexer.next(); - - return try p.parseFnStmt(async_range.loc, opts, async_range); - } - - expr = try p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, .lowest), .lowest, null, Expr.EFlags.none); - } else { - const exprOrLet = try p.parseExprOrLetStmt(opts); - switch (exprOrLet.stmt_or_expr) { - .stmt => |stmt| { - try p.lexer.expectOrInsertSemicolon(); - return stmt; - }, - .expr => |_expr| { - expr = _expr; - }, - } - } - if (is_identifier) { - switch (expr.data) { - .e_identifier => |ident| { - if (p.lexer.token == .t_colon and !opts.hasDecorators()) { - _ = try p.pushScopeForParsePass(.label, loc); - defer p.popScope(); - - // Parse a labeled statement - try p.lexer.next(); - - const _name = LocRef{ .loc = expr.loc, .ref = ident.ref }; - var nestedOpts = ParseStatementOptions{}; - - switch (opts.lexical_decl) { - .allow_all, .allow_fn_inside_label => { - nestedOpts.lexical_decl = .allow_fn_inside_label; - }, - else => {}, - } - var stmt = try p.parseStmt(&nestedOpts); - return p.s(S.Label{ .name = _name, .stmt = stmt }, loc); - } - }, - else => {}, - } - - if (is_typescript_enabled) { - if (js_lexer.TypescriptStmtKeyword.List.get(name)) |ts_stmt| { - switch (ts_stmt) { - .ts_stmt_type => { - if (p.lexer.token == .t_identifier and !p.lexer.has_newline_before) { - // "type Foo = any" - var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; - try p.skipTypeScriptTypeStmt(&stmtOpts); - return p.s(S.TypeScript{}, loc); - } - }, - .ts_stmt_namespace, .ts_stmt_module => { - // "namespace Foo {}" - // "module Foo {}" - // "declare module 'fs' {}" - // "declare module 'fs';" - if (((opts.is_module_scope or opts.is_namespace_scope) and (p.lexer.token == .t_identifier or - (p.lexer.token == .t_string_literal and opts.is_typescript_declare)))) - { - return p.parseTypeScriptNamespaceStmt(loc, opts); - } - }, - .ts_stmt_interface => { - // "interface Foo {}" - var stmtOpts = ParseStatementOptions{ .is_module_scope = opts.is_module_scope }; - - try p.skipTypeScriptInterfaceStmt(&stmtOpts); - return p.s(S.TypeScript{}, loc); - }, - .ts_stmt_abstract => { - if (p.lexer.token == .t_class or opts.ts_decorators != null) { - return try p.parseClassStmt(loc, opts); - } - }, - .ts_stmt_global => { - // "declare module 'fs' { global { namespace NodeJS {} } }" - if (opts.is_namespace_scope and opts.is_typescript_declare and p.lexer.token == .t_open_brace) { - try p.lexer.next(); - _ = try p.parseStmtsUpTo(.t_close_brace, opts); - try p.lexer.next(); - return p.s(S.TypeScript{}, loc); - } - }, - .ts_stmt_declare => { - opts.lexical_decl = .allow_all; - opts.is_typescript_declare = true; - - // "@decorator declare class Foo {}" - // "@decorator declare abstract class Foo {}" - if (opts.ts_decorators != null and p.lexer.token != .t_class and !p.lexer.isContextualKeyword("abstract")) { - try p.lexer.expected(.t_class); - } - - // "declare global { ... }" - if (p.lexer.isContextualKeyword("global")) { - try p.lexer.next(); - try p.lexer.expect(.t_open_brace); - _ = try p.parseStmtsUpTo(.t_close_brace, opts); - try p.lexer.next(); - return p.s(S.TypeScript{}, loc); - } - - // "declare const x: any" - const stmt = try p.parseStmt(opts); - if (opts.ts_decorators) |decs| { - p.discardScopesUpTo(decs.scope_index); - } - - // Unlike almost all uses of "declare", statements that use - // "export declare" with "var/let/const" inside a namespace affect - // code generation. They cause any declared bindings to be - // considered exports of the namespace. Identifier references to - // those names must be converted into property accesses off the - // namespace object: - // - // namespace ns { - // export declare const x - // export function y() { return x } - // } - // - // (ns as any).x = 1 - // console.log(ns.y()) - // - // In this example, "return x" must be replaced with "return ns.x". - // This is handled by replacing each "export declare" statement - // inside a namespace with an "export var" statement containing all - // of the declared bindings. That "export var" statement will later - // cause identifiers to be transformed into property accesses. - if (opts.is_namespace_scope and opts.is_export) { - var decls: []G.Decl = &([_]G.Decl{}); - switch (stmt.data) { - .s_local => |local| { - var _decls = try ListManaged(G.Decl).initCapacity(p.allocator, local.decls.len); - for (local.decls) |decl| { - try extractDeclsForBinding(decl.binding, &_decls); - } - decls = _decls.items; - }, - else => {}, - } - - if (decls.len > 0) { - return p.s(S.Local{ - .kind = .k_var, - .is_export = true, - .decls = decls, - }, loc); - } - } - - return p.s(S.TypeScript{}, loc); - }, - } - } - } - } - // Output.print("\n\nmVALUE {s}:{s}\n", .{ expr, name }); - try p.lexer.expectOrInsertSemicolon(); - return p.s(S.SExpr{ .value = expr }, loc); - }, - } - - return js_ast.Stmt.empty(); - } - - fn discardScopesUpTo(p: *P, scope_index: usize) void { - // Remove any direct children from their parent - var scope = p.current_scope; - var children = scope.children; - - for (p.scopes_in_order.items[scope_index..]) |_child| { - const child = _child orelse continue; - - if (child.scope.parent == p.current_scope) { - var i: usize = children.items.len - 1; - while (i >= 0) { - if (children.items[i] == child.scope) { - _ = children.orderedRemove(i); - break; - } - i -= 1; - } - } - } - - // Truncate the scope order where we started to pretend we never saw this scope - p.scopes_in_order.shrinkRetainingCapacity(scope_index); - } - - fn skipTypeScriptTypeStmt(p: *P, opts: *ParseStatementOptions) anyerror!void { - if (opts.is_export and p.lexer.token == .t_open_brace) { - // "export type {foo}" - // "export type {foo} from 'bar'" - _ = try p.parseExportClause(); - if (p.lexer.isContextualKeyword("from")) { - try p.lexer.next(); - _ = try p.parsePath(); - } - try p.lexer.expectOrInsertSemicolon(); - return; - } - - const name = p.lexer.identifier; - try p.lexer.expect(.t_identifier); - - if (opts.is_module_scope) { - p.local_type_names.put(p.allocator, name, true) catch unreachable; - } - - try p.skipTypeScriptTypeParameters(); - try p.lexer.expect(.t_equals); - try p.skipTypeScriptType(.lowest); - try p.lexer.expectOrInsertSemicolon(); - } - - fn parseTypeScriptNamespaceStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt { - // "namespace foo {}"; - const name_loc = p.lexer.loc(); - const name_text = p.lexer.identifier; - try p.lexer.next(); - - var name = LocRef{ .loc = name_loc, .ref = null }; - const scope_index = try p.pushScopeForParsePass(.entry, loc); - - const old_has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace; - p.has_non_local_export_declare_inside_namespace = false; - - var stmts: ListManaged(Stmt) = ListManaged(Stmt).init(p.allocator); - - if (p.lexer.token == .t_dot) { - const dot_loc = p.lexer.loc(); - try p.lexer.next(); - - var _opts = ParseStatementOptions{ - .is_export = true, - .is_namespace_scope = true, - .is_typescript_declare = opts.is_typescript_declare, - }; - stmts.append(try p.parseTypeScriptNamespaceStmt(dot_loc, &_opts)) catch unreachable; - } else if (opts.is_typescript_declare and p.lexer.token != .t_open_brace) { - try p.lexer.expectOrInsertSemicolon(); - } else { - try p.lexer.expect(.t_open_brace); - var _opts = ParseStatementOptions{ - .is_namespace_scope = true, - .is_typescript_declare = opts.is_typescript_declare, - }; - stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, try p.parseStmtsUpTo(.t_close_brace, &_opts)); - try p.lexer.next(); - } - const has_non_local_export_declare_inside_namespace = p.has_non_local_export_declare_inside_namespace; - p.has_non_local_export_declare_inside_namespace = old_has_non_local_export_declare_inside_namespace; - - // Import assignments may be only used in type expressions, not value - // expressions. If this is the case, the TypeScript compiler removes - // them entirely from the output. That can cause the namespace itself - // to be considered empty and thus be removed. - var import_equal_count: usize = 0; - const _stmts: []Stmt = stmts.items; - for (_stmts) |stmt| { - switch (stmt.data) { - .s_local => |local| { - if (local.was_ts_import_equals and !local.is_export) { - import_equal_count += 1; - } - }, - else => {}, - } - } - - // TypeScript omits namespaces without values. These namespaces - // are only allowed to be used in type expressions. They are - // allowed to be exported, but can also only be used in type - // expressions when imported. So we shouldn't count them as a - // real export either. - // - // TypeScript also strangely counts namespaces containing only - // "export declare" statements as non-empty even though "declare" - // statements are only type annotations. We cannot omit the namespace - // in that case. See https://github.com/evanw/esbuild/issues/1158. - if ((stmts.items.len == import_equal_count and !has_non_local_export_declare_inside_namespace) or opts.is_typescript_declare) { - p.popAndDiscardScope(scope_index); - if (opts.is_module_scope) { - p.local_type_names.put(p.allocator, name_text, true) catch unreachable; - } - return p.s(S.TypeScript{}, loc); - } - - var arg_ref: ?Ref = null; - if (!opts.is_typescript_declare) { - // Avoid a collision with the namespace closure argument variable if the - // namespace exports a symbol with the same name as the namespace itself: - // - // namespace foo { - // export let foo = 123 - // console.log(foo) - // } - // - // TypeScript generates the following code in this case: - // - // var foo; - // (function (foo_1) { - // foo_1.foo = 123; - // console.log(foo_1.foo); - // })(foo || (foo = {})); - // - if (p.current_scope.members.contains(name_text)) { - // Add a "_" to make tests easier to read, since non-bundler tests don't - // run the renamer. For external-facing things the renamer will avoid - // collisions automatically so this isn't important for correctness. - arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; - p.current_scope.generated.append(p.allocator, arg_ref.?) catch unreachable; - } else { - arg_ref = p.newSymbol(.hoisted, name_text) catch unreachable; - } - } - p.popScope(); - - if (!opts.is_typescript_declare) { - name.ref = p.declareSymbol(.ts_namespace, name_loc, name_text) catch unreachable; - } - - return p.s( - S.Namespace{ .name = name, .arg = arg_ref orelse Ref.None, .stmts = stmts.items, .is_export = opts.is_export }, - loc, - ); - } - - fn skipTypeScriptInterfaceStmt(p: *P, opts: *ParseStatementOptions) !void { - const name = p.lexer.identifier; - try p.lexer.expect(.t_identifier); - - if (opts.is_module_scope) { - p.local_type_names.put(p.allocator, name, true) catch unreachable; - } - - try p.skipTypeScriptTypeParameters(); - - if (p.lexer.token == .t_extends) { - try p.lexer.next(); - - while (true) { - try p.skipTypeScriptType(.lowest); - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - } - - if (p.lexer.isContextualKeyword("implements")) { - try p.lexer.next(); - while (true) { - try p.skipTypeScriptType(.lowest); - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - } - - try p.skipTypeScriptObjectType(); - } - - // This assumes the caller has already parsed the "import" token - - fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) anyerror!Stmt { - try p.lexer.expect(.t_equals); - - const kind = S.Local.Kind.k_const; - const name = p.lexer.identifier; - var value = p.e(E.Identifier{ .ref = p.storeNameInRef(name) catch unreachable }, p.lexer.loc()); - try p.lexer.expect(.t_identifier); - - if (strings.eqlComptime(name, "require") and p.lexer.token == .t_open_paren) { - // "import ns = require('x')" - try p.lexer.next(); - const path = p.e(p.lexer.toEString(), p.lexer.loc()); - try p.lexer.expect(.t_string_literal); - try p.lexer.expect(.t_close_paren); - const args = try ExprNodeList.one(p.allocator, path); - value.data = .{ .e_call = Expr.Data.Store.All.append(E.Call, E.Call{ .target = value, .close_paren_loc = p.lexer.loc(), .args = args }) }; - } else { - // "import Foo = Bar" - // "import Foo = Bar.Baz" - while (p.lexer.token == .t_dot) { - try p.lexer.next(); - value.data = .{ .e_dot = Expr.Data.Store.All.append( - E.Dot, - E.Dot{ .target = value, .name = p.lexer.identifier, .name_loc = p.lexer.loc() }, - ) }; - try p.lexer.expect(.t_identifier); - } - } - - try p.lexer.expectOrInsertSemicolon(); - - if (opts.is_typescript_declare) { - // "import type foo = require('bar');" - // "import type foo = bar.baz;" - return p.s(S.TypeScript{}, loc); - } - - const ref = p.declareSymbol(.cconst, default_name_loc, default_name) catch unreachable; - var decls = p.allocator.alloc(Decl, 1) catch unreachable; - decls[0] = Decl{ - .binding = p.b(B.Identifier{ .ref = ref }, default_name_loc), - .value = value, - }; - return p.s(S.Local{ .kind = kind, .decls = decls, .is_export = opts.is_export, .was_ts_import_equals = true }, loc); - } - - fn parseClauseAlias(p: *P, kind: string) !string { - const loc = p.lexer.loc(); - - // The alias may now be a string (see https://github.com/tc39/ecma262/pull/2154) - if (p.lexer.token == .t_string_literal) { - if (p.lexer.string_literal_is_ascii) { - return p.lexer.string_literal_slice; - } else if (p.lexer.utf16ToStringWithValidation(p.lexer.string_literal)) |alias| { - return alias; - } else |_| { - const r = p.source.rangeOfString(loc); - // TODO: improve error message - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Invalid {s} alias because it contains an unpaired Unicode surrogate (like emoji)", .{kind}); - return p.source.textForRange(r); - } - } - - // The alias may be a keyword - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - - const alias = p.lexer.identifier; - p.checkForNonBMPCodePoint(loc, alias); - return alias; - } - - fn parseImportClause( - p: *P, - ) !ImportClause { - var items = ListManaged(js_ast.ClauseItem).init(p.allocator); - try p.lexer.expect(.t_open_brace); - var is_single_line = !p.lexer.has_newline_before; - // this variable should not exist if we're not in a typescript file - var had_type_only_imports = if (comptime is_typescript_enabled) - false - else - void{}; - - while (p.lexer.token != .t_close_brace) { - // The alias may be a keyword; - const isIdentifier = p.lexer.token == .t_identifier; - const alias_loc = p.lexer.loc(); - const alias = try p.parseClauseAlias("import"); - var name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(alias) }; - var original_name = alias; - try p.lexer.next(); - - const probably_type_only_import = if (comptime is_typescript_enabled) - strings.eqlComptime(alias, "type") and - p.lexer.token != .t_comma and - p.lexer.token != .t_close_brace - else - false; - - // "import { type xx } from 'mod'" - // "import { type xx as yy } from 'mod'" - // "import { type 'xx' as yy } from 'mod'" - // "import { type as } from 'mod'" - // "import { type as as } from 'mod'" - // "import { type as as as } from 'mod'" - if (probably_type_only_import) { - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - if (p.lexer.isContextualKeyword("as")) { - original_name = p.lexer.identifier; - name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) }; - try p.lexer.next(); - - if (p.lexer.token == .t_identifier) { - - // "import { type as as as } from 'mod'" - // "import { type as as foo } from 'mod'" - had_type_only_imports = true; - try p.lexer.next(); - } else { - // "import { type as as } from 'mod'" - - try items.append(.{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }); - } - } else if (p.lexer.token == .t_identifier) { - had_type_only_imports = true; - - // "import { type as xxx } from 'mod'" - original_name = p.lexer.identifier; - name = LocRef{ .loc = p.lexer.loc(), .ref = try p.storeNameInRef(original_name) }; - try p.lexer.expect(.t_identifier); - - if (isEvalOrArguments(original_name)) { - const r = p.source.rangeOfString(name.loc); - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use {s} as an identifier here", .{original_name}); - } - - try items.append(.{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }); - } - } else { - const is_identifier = p.lexer.token == .t_identifier; - - // "import { type xx } from 'mod'" - // "import { type xx as yy } from 'mod'" - // "import { type if as yy } from 'mod'" - // "import { type 'xx' as yy } from 'mod'" - _ = try p.parseClauseAlias("import"); - try p.lexer.next(); - - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - - try p.lexer.expect(.t_identifier); - } else if (!is_identifier) { - // An import where the name is a keyword must have an alias - try p.lexer.expectedString("\"as\""); - } - had_type_only_imports = true; - } - } else { - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - original_name = p.lexer.identifier; - name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(original_name) }; - try p.lexer.expect(.t_identifier); - } else if (!isIdentifier) { - // An import where the name is a keyword must have an alias - try p.lexer.expectedString("\"as\""); - } - - // Reject forbidden names - if (isEvalOrArguments(original_name)) { - const r = js_lexer.rangeOfIdentifier(p.source, name.loc); - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use \"{s}\" as an identifier here", .{original_name}); - } - - try items.append(js_ast.ClauseItem{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }); - } - - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - try p.lexer.next(); - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - try p.lexer.expect(.t_close_brace); - return ImportClause{ - .items = items.items, - .is_single_line = is_single_line, - .had_type_only_imports = if (comptime is_typescript_enabled) - had_type_only_imports - else - false, - }; - } - - fn forbidInitializers(p: *P, decls: []G.Decl, comptime loop_type: string, is_var: bool) !void { - switch (decls.len) { - 0 => {}, - 1 => { - if (decls[0].value) |value| { - if (is_var) { - - // This is a weird special case. Initializers are allowed in "var" - // statements with identifier bindings. - return; - } - - try p.log.addError(p.source, value.loc, comptime std.fmt.comptimePrint("for-{s} loop variables cannot have an initializer", .{loop_type})); - } - }, - else => { - try p.log.addError(p.source, decls[0].binding.loc, comptime std.fmt.comptimePrint("for-{s} loops must have a single declaration", .{loop_type})); - }, - } - } - - fn parseExprOrLetStmt(p: *P, opts: *ParseStatementOptions) !ExprOrLetStmt { - var let_range = p.lexer.range(); - var raw = p.lexer.raw(); - if (p.lexer.token != .t_identifier or !strings.eqlComptime(raw, "let")) { - // Output.print("HI", .{}); - return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = try p.parseExpr(.lowest) } }; - } - - try p.lexer.next(); - - switch (p.lexer.token) { - .t_identifier, .t_open_bracket, .t_open_brace => { - if (opts.lexical_decl == .allow_all or !p.lexer.has_newline_before or p.lexer.token == .t_open_bracket) { - if (opts.lexical_decl != .allow_all) { - try p.forbidLexicalDecl(let_range.loc); - } - - const decls = try p.parseAndDeclareDecls(.other, opts); - return ExprOrLetStmt{ - .stmt_or_expr = js_ast.StmtOrExpr{ - .stmt = p.s(S.Local{ - // Replace all "export let" with "export var" when HMR is enabled - .kind = if (opts.is_export and p.options.features.hot_module_reloading) .k_var else .k_let, - .decls = decls, - .is_export = opts.is_export, - }, let_range.loc), - }, - .decls = decls, - }; - } - }, - else => {}, - } - - const ref = p.storeNameInRef(raw) catch unreachable; - const expr = p.e(E.Identifier{ .ref = ref }, let_range.loc); - return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = try p.parseSuffix(expr, .lowest, null, Expr.EFlags.none) } }; - } - - fn requireInitializers(p: *P, decls: []G.Decl) !void { - for (decls) |decl| { - if (decl.value == null) { - switch (decl.binding.data) { - .b_identifier => |ident| { - const r = js_lexer.rangeOfIdentifier(p.source, decl.binding.loc); - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "The constant \"{s}\" must be initialized", .{p.symbols.items[ident.ref.innerIndex()].original_name}); - // return;/ - }, - else => { - try p.log.addError(p.source, decl.binding.loc, "This constant must be initialized"); - }, - } - } - } - } - - fn parseBinding(p: *P) anyerror!Binding { - var loc = p.lexer.loc(); - - switch (p.lexer.token) { - .t_identifier => { - const name = p.lexer.identifier; - if ((p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name, "await")) or (p.fn_or_arrow_data_parse.allow_yield != .allow_ident and strings.eqlComptime(name, "yield"))) { - // TODO: add fmt to addRangeError - p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"yield\" or \"await\" here.") catch unreachable; - } - - const ref = p.storeNameInRef(name) catch unreachable; - try p.lexer.next(); - return p.b(B.Identifier{ .ref = ref }, loc); - }, - .t_open_bracket => { - try p.lexer.next(); - var is_single_line = !p.lexer.has_newline_before; - var items = ListManaged(js_ast.ArrayBinding).init(p.allocator); - var has_spread = false; - - // "in" expressions are allowed - const old_allow_in = p.allow_in; - p.allow_in = true; - - while (p.lexer.token != .t_close_bracket) { - if (p.lexer.token == .t_comma) { - items.append(js_ast.ArrayBinding{ - .binding = Binding{ .data = Prefill.Data.BMissing, .loc = p.lexer.loc() }, - }) catch unreachable; - } else { - if (p.lexer.token == .t_dot_dot_dot) { - try p.lexer.next(); - has_spread = true; - - // This was a bug in the ES2015 spec that was fixed in ES2016 - if (p.lexer.token != .t_identifier) { - // p.markSyntaxFeature(compat.NestedRestBinding, p.lexer.Range()) - - } - } - - const binding = try p.parseBinding(); - - var default_value: ?Expr = null; - if (!has_spread and p.lexer.token == .t_equals) { - try p.lexer.next(); - default_value = try p.parseExpr(.comma); - } - - items.append(js_ast.ArrayBinding{ .binding = binding, .default_value = default_value }) catch unreachable; - - // Commas after spread elements are not allowed - if (has_spread and p.lexer.token == .t_comma) { - p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; - return error.SyntaxError; - } - } - - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.next(); - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - p.allow_in = old_allow_in; - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.expect(.t_close_bracket); - return p.b(B.Array{ - .items = items.items, - .has_spread = has_spread, - .is_single_line = is_single_line, - }, loc); - }, - .t_open_brace => { - // p.markSyntaxFeature(compat.Destructuring, p.lexer.Range()) - try p.lexer.next(); - var is_single_line = !p.lexer.has_newline_before; - var properties = ListManaged(js_ast.B.Property).init(p.allocator); - - // "in" expressions are allowed - const old_allow_in = p.allow_in; - p.allow_in = true; - - while (p.lexer.token != .t_close_brace) { - var property = try p.parsePropertyBinding(); - properties.append(property) catch unreachable; - - // Commas after spread elements are not allowed - if (property.flags.contains(.is_spread) and p.lexer.token == .t_comma) { - p.log.addRangeError(p.source, p.lexer.range(), "Unexpected \",\" after rest pattern") catch unreachable; - return error.SyntaxError; - } - - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.next(); - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - p.allow_in = old_allow_in; - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.expect(.t_close_brace); - - return p.b(B.Object{ - .properties = properties.items, - .is_single_line = is_single_line, - }, loc); - }, - else => {}, - } - - try p.lexer.expect(.t_identifier); - return Binding{ .loc = loc, .data = Prefill.Data.BMissing }; - } - - pub fn parsePropertyBinding(p: *P) anyerror!B.Property { - var key: js_ast.Expr = Expr{ .loc = logger.Loc.Empty, .data = Prefill.Data.EMissing }; - var is_computed = false; - - switch (p.lexer.token) { - .t_dot_dot_dot => { - try p.lexer.next(); - const value = p.b( - B.Identifier{ - .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable, - }, - p.lexer.loc(), - ); - try p.lexer.expect(.t_identifier); - return B.Property{ - .key = p.e(E.Missing{}, p.lexer.loc()), - - .flags = Flags.Property.init(.{ .is_spread = true }), - .value = value, - }; - }, - .t_numeric_literal => { - key = p.e(E.Number{ - .value = p.lexer.number, - }, p.lexer.loc()); - // check for legacy octal literal - try p.lexer.next(); - }, - .t_string_literal => { - key = try p.parseStringLiteral(); - }, - .t_big_integer_literal => { - key = p.e(E.BigInt{ - .value = p.lexer.identifier, - }, p.lexer.loc()); - // p.markSyntaxFeature(compat.BigInt, p.lexer.Range()) - try p.lexer.next(); - }, - .t_open_bracket => { - is_computed = true; - try p.lexer.next(); - key = try p.parseExpr(.comma); - try p.lexer.expect(.t_close_bracket); - }, - else => { - const name = p.lexer.identifier; - const loc = p.lexer.loc(); - - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - - try p.lexer.next(); - - key = p.e(E.String{ .data = name }, loc); - - if (p.lexer.token != .t_colon and p.lexer.token != .t_open_paren) { - const ref = p.storeNameInRef(name) catch unreachable; - const value = p.b(B.Identifier{ .ref = ref }, loc); - var default_value: ?Expr = null; - if (p.lexer.token == .t_equals) { - try p.lexer.next(); - default_value = try p.parseExpr(.comma); - } - - return B.Property{ - .key = key, - .value = value, - .default_value = default_value, - }; - } - }, - } - - try p.lexer.expect(.t_colon); - const value = try p.parseBinding(); - - var default_value: ?Expr = null; - if (p.lexer.token == .t_equals) { - try p.lexer.next(); - default_value = try p.parseExpr(.comma); - } - - return B.Property{ - .flags = Flags.Property.init(.{ - .is_computed = is_computed, - }), - .key = key, - .value = value, - .default_value = default_value, - }; - } - - fn parseAndDeclareDecls(p: *P, kind: Symbol.Kind, opts: *ParseStatementOptions) anyerror![]G.Decl { - var decls = ListManaged(G.Decl).init(p.allocator); - - while (true) { - // Forbid "let let" and "const let" but not "var let" - if ((kind == .other or kind == .cconst) and p.lexer.isContextualKeyword("let")) { - p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"let\" as an identifier here") catch unreachable; - } - - var value: ?js_ast.Expr = null; - var local = try p.parseBinding(); - p.declareBinding(kind, &local, opts) catch unreachable; - - // Skip over types - if (comptime is_typescript_enabled) { - // "let foo!" - var is_definite_assignment_assertion = p.lexer.token == .t_exclamation; - if (is_definite_assignment_assertion) { - try p.lexer.next(); - } - - // "let foo: number" - if (is_definite_assignment_assertion or p.lexer.token == .t_colon) { - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); - } - - // If we end with a .t_close_paren, that's a bug. It means we aren't following the last parenthese - - if (comptime Environment.allow_assert) - assert(p.lexer.token != .t_close_paren); - } - - if (p.lexer.token == .t_equals) { - try p.lexer.next(); - value = try p.parseExpr(.comma); - } - - decls.append(G.Decl{ - .binding = local, - .value = value, - }) catch unreachable; - - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - - return decls.items; - } - - pub fn parseTypescriptEnumStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt { - try p.lexer.expect(.t_enum); - const name_loc = p.lexer.loc(); - const name_text = p.lexer.identifier; - try p.lexer.expect(.t_identifier); - var name = LocRef{ .loc = name_loc, .ref = Ref.None }; - var arg_ref = Ref.None; - if (!opts.is_typescript_declare) { - name.ref = try p.declareSymbol(.ts_enum, name_loc, name_text); - _ = try p.pushScopeForParsePass(.entry, loc); - } - - try p.lexer.expect(.t_open_brace); - - var values = std.ArrayList(js_ast.EnumValue).init(p.allocator); - while (p.lexer.token != .t_close_brace) { - var value = js_ast.EnumValue{ .loc = p.lexer.loc(), .ref = Ref.None, .name = undefined, .value = null }; - var needs_symbol = false; - - // Parse the name - if (p.lexer.token == .t_string_literal) { - value.name = p.lexer.toEString(); - } else if (p.lexer.isIdentifierOrKeyword()) { - value.name = E.String{ .data = p.lexer.identifier }; - needs_symbol = true; - } else { - try p.lexer.expect(.t_identifier); - } - try p.lexer.next(); - - // Identifiers can be referenced by other values - - if (!opts.is_typescript_declare and needs_symbol) { - value.ref = try p.declareSymbol(.other, value.loc, try value.name.string(p.allocator)); - } - - // Parse the initializer - if (p.lexer.token == .t_equals) { - try p.lexer.next(); - value.value = try p.parseExpr(.comma); - } - - values.append(value) catch unreachable; - - if (p.lexer.token != .t_comma and p.lexer.token != .t_semicolon) { - break; - } - - try p.lexer.next(); - } - - if (!opts.is_typescript_declare) { - // Avoid a collision with the enum closure argument variable if the - // enum exports a symbol with the same name as the enum itself: - // - // enum foo { - // foo = 123, - // bar = foo, - // } - // - // TypeScript generates the following code in this case: - // - // var foo; - // (function (foo) { - // foo[foo["foo"] = 123] = "foo"; - // foo[foo["bar"] = 123] = "bar"; - // })(foo || (foo = {})); - // - // Whereas in this case: - // - // enum foo { - // bar = foo as any, - // } - // - // TypeScript generates the following code: - // - // var foo; - // (function (foo) { - // foo[foo["bar"] = foo] = "bar"; - // })(foo || (foo = {})); - // - if (p.current_scope.members.contains(name_text)) { - // Add a "_" to make tests easier to read, since non-bundler tests don't - // run the renamer. For external-facing things the renamer will avoid - // collisions automatically so this isn't important for correctness. - arg_ref = p.newSymbol(.hoisted, strings.cat(p.allocator, "_", name_text) catch unreachable) catch unreachable; - p.current_scope.generated.append(p.allocator, arg_ref) catch unreachable; - } else { - arg_ref = p.declareSymbol(.hoisted, name_loc, name_text) catch unreachable; - } - - p.popScope(); - } - - try p.lexer.expect(.t_close_brace); - - if (opts.is_typescript_declare) { - if (opts.is_namespace_scope and opts.is_export) { - p.has_non_local_export_declare_inside_namespace = true; - } - - return p.s(S.TypeScript{}, loc); - } - - return p.s(S.Enum{ - .name = name, - .arg = arg_ref, - .values = values.toOwnedSlice(), - .is_export = opts.is_export, - }, loc); - } - - fn parseExportClause(p: *P) !ExportClauseResult { - var items = ListManaged(js_ast.ClauseItem).initCapacity(p.allocator, 1) catch unreachable; - try p.lexer.expect(.t_open_brace); - var is_single_line = !p.lexer.has_newline_before; - var first_non_identifier_loc = logger.Loc{ .start = 0 }; - var had_type_only_exports = false; - - while (p.lexer.token != .t_close_brace) { - var alias = try p.parseClauseAlias("export"); - var alias_loc = p.lexer.loc(); - - const name = LocRef{ - .loc = alias_loc, - .ref = p.storeNameInRef(alias) catch unreachable, - }; - const original_name = alias; - - // The name can actually be a keyword if we're really an "export from" - // statement. However, we won't know until later. Allow keywords as - // identifiers for now and throw an error later if there's no "from". - // - // // This is fine - // export { default } from 'path' - // - // // This is a syntax error - // export { default } - // - if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) { - first_non_identifier_loc = p.lexer.loc(); - } - try p.lexer.next(); - - if (comptime is_typescript_enabled) { - if (strings.eqlComptime(alias, "type") and p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - - if (p.lexer.isContextualKeyword("as")) { - alias = try p.parseClauseAlias("export"); - alias_loc = p.lexer.loc(); - try p.lexer.next(); - - if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { - // "export { type as as as }" - // "export { type as as foo }" - // "export { type as as 'foo' }" - _ = p.parseClauseAlias("export") catch ""; - had_type_only_exports = true; - try p.lexer.next(); - } else { - // "export { type as as }" - items.append(js_ast.ClauseItem{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }) catch unreachable; - } - } else if (p.lexer.token != .t_comma and p.lexer.token != .t_close_brace) { - // "export { type as xxx }" - // "export { type as 'xxx' }" - alias = try p.parseClauseAlias("export"); - alias_loc = p.lexer.loc(); - try p.lexer.next(); - - items.append(js_ast.ClauseItem{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }) catch unreachable; - } else { - had_type_only_exports = true; - } - } else { - // The name can actually be a keyword if we're really an "export from" - // statement. However, we won't know until later. Allow keywords as - // identifiers for now and throw an error later if there's no "from". - // - // // This is fine - // export { default } from 'path' - // - // // This is a syntax error - // export { default } - // - if (p.lexer.token != .t_identifier and first_non_identifier_loc.start == 0) { - first_non_identifier_loc = p.lexer.loc(); - } - - // "export { type xx }" - // "export { type xx as yy }" - // "export { type xx as if }" - // "export { type default } from 'path'" - // "export { type default as if } from 'path'" - // "export { type xx as 'yy' }" - // "export { type 'xx' } from 'mod'" - _ = p.parseClauseAlias("export") catch ""; - try p.lexer.next(); - - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - _ = p.parseClauseAlias("export") catch ""; - try p.lexer.next(); - } - - had_type_only_exports = true; - } - } else { - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - alias = try p.parseClauseAlias("export"); - alias_loc = p.lexer.loc(); - - try p.lexer.next(); - } - - items.append(js_ast.ClauseItem{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }) catch unreachable; - } - } else { - if (p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - alias = try p.parseClauseAlias("export"); - alias_loc = p.lexer.loc(); - - try p.lexer.next(); - } - - items.append(js_ast.ClauseItem{ - .alias = alias, - .alias_loc = alias_loc, - .name = name, - .original_name = original_name, - }) catch unreachable; - } - - // we're done if there's no comma - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.next(); - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - try p.lexer.expect(.t_close_brace); - - // Throw an error here if we found a keyword earlier and this isn't an - // "export from" statement after all - if (first_non_identifier_loc.start != 0 and !p.lexer.isContextualKeyword("from")) { - const r = js_lexer.rangeOfIdentifier(p.source, first_non_identifier_loc); - try p.lexer.addRangeError(r, "Expected identifier but found \"{s}\"", .{p.source.textForRange(r)}, true); - return error.SyntaxError; - } - - return ExportClauseResult{ - .clauses = items.items, - .is_single_line = is_single_line, - .had_type_only_exports = had_type_only_exports, - }; - } - - pub fn parsePath(p: *P) !ParsedPath { - var path = ParsedPath{ - .loc = p.lexer.loc(), - .text = p.lexer.string_literal_slice, - }; - - if (p.lexer.token == .t_no_substitution_template_literal) { - try p.lexer.next(); - } else { - try p.lexer.expect(.t_string_literal); - } - - // For now, we silently strip import assertions - if (!p.lexer.has_newline_before and p.lexer.isContextualKeyword("assert")) { - try p.lexer.next(); - try p.lexer.expect(.t_open_brace); - - while (p.lexer.token != .t_close_brace) { - // Parse the key - if (p.lexer.isIdentifierOrKeyword()) {} else if (p.lexer.token == .t_string_literal) {} else { - try p.lexer.expect(.t_identifier); - } - - try p.lexer.next(); - try p.lexer.expect(.t_colon); - - try p.lexer.expect(.t_string_literal); - - if (p.lexer.token != .t_comma) { - break; - } - - try p.lexer.next(); - } - - try p.lexer.expect(.t_close_brace); - } - - return path; - } - - // TODO: - pub fn checkForNonBMPCodePoint(_: *P, _: logger.Loc, _: string) void {} - - fn parseStmtsUpTo(p: *P, eend: js_lexer.T, _opts: *ParseStatementOptions) ![]Stmt { - var opts = _opts.*; - var stmts = StmtList.init(p.allocator); - - var returnWithoutSemicolonStart: i32 = -1; - opts.lexical_decl = .allow_all; - var isDirectivePrologue = true; - - while (true) { - for (p.lexer.comments_to_preserve_before.items) |comment| { - try stmts.append(p.s(S.Comment{ - .text = comment.text, - }, p.lexer.loc())); - } - p.lexer.comments_to_preserve_before.shrinkRetainingCapacity(0); - - if (p.lexer.token == eend) { - break; - } - - var current_opts = opts; - var stmt = try p.parseStmt(¤t_opts); - - // Skip TypeScript types entirely - if (is_typescript_enabled) { - switch (stmt.data) { - .s_type_script => { - continue; - }, - else => {}, - } - } - - // Parse one or more directives at the beginning - if (isDirectivePrologue) { - isDirectivePrologue = false; - switch (stmt.data) { - .s_expr => |expr| { - switch (expr.value.data) { - .e_string => |str| { - if (!str.prefer_template) { - isDirectivePrologue = true; - - if (str.eqlComptime("use strict")) { - // Track "use strict" directives - p.current_scope.strict_mode = .explicit_strict_mode; - } else if (str.eqlComptime("use asm")) { - stmt.data = Prefill.Data.SEmpty; - } - } - }, - else => {}, - } - }, - else => {}, - } - } - - try stmts.append(stmt); - - // Warn about ASI and return statements. Here's an example of code with - // this problem: https://github.com/rollup/rollup/issues/3729 - if (!p.options.suppress_warnings_about_weird_code) { - var needsCheck = true; - switch (stmt.data) { - .s_return => |ret| { - if (ret.value == null and !p.latest_return_had_semicolon) { - returnWithoutSemicolonStart = stmt.loc.start; - needsCheck = false; - } - }, - else => {}, - } - - if (needsCheck and returnWithoutSemicolonStart != -1) { - switch (stmt.data) { - .s_expr => { - try p.log.addWarning( - p.source, - logger.Loc{ .start = returnWithoutSemicolonStart + 6 }, - "The following expression is not returned because of an automatically-inserted semicolon", - ); - }, - else => {}, - } - - returnWithoutSemicolonStart = -1; - } - } - } - - return stmts.toOwnedSlice(); - } - - fn markStrictModeFeature(p: *P, feature: StrictModeFeature, r: logger.Range, detail: string) !void { - const can_be_transformed = feature == StrictModeFeature.for_in_var_init; - const text = switch (feature) { - .with_statement => "With statements", - .delete_bare_name => "\"delete\" of a bare identifier", - .for_in_var_init => "Variable initializers within for-in loops", - .eval_or_arguments => try std.fmt.allocPrint(p.allocator, "Declarations with the name {s}", .{detail}), - .reserved_word => try std.fmt.allocPrint(p.allocator, "\"{s}\" is a reserved word and", .{detail}), - .legacy_octal_literal => "Legacy octal literals", - .legacy_octal_escape => "Legacy octal escape sequences", - .if_else_function_stmt => "Function declarations inside if statements", - // else => { - // text = "This feature"; - // }, - }; - - var scope = p.current_scope; - if (p.isStrictMode()) { - var why: string = ""; - var where: logger.Range = logger.Range.None; - switch (scope.strict_mode) { - .implicit_strict_mode_import => { - where = p.es6_import_keyword; - }, - .implicit_strict_mode_export => { - where = p.es6_export_keyword; - }, - .implicit_strict_mode_top_level_await => { - where = p.top_level_await_keyword; - }, - .implicit_strict_mode_class => { - why = "All code inside a class is implicitly in strict mode"; - where = p.enclosing_class_keyword; - }, - else => {}, - } - if (why.len == 0) { - why = try std.fmt.allocPrint(p.allocator, "This file is implicitly in strict mode because of the \"{s}\" keyword here", .{p.source.textForRange(where)}); - } - - try p.log.addRangeErrorWithNotes(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used in strict mode", .{text}), &([_]logger.Data{logger.rangeData(p.source, where, why)})); - } else if (!can_be_transformed and p.isStrictModeOutputFormat()) { - try p.log.addRangeError(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used with esm due to strict mode", .{text})); - } - } - - pub inline fn isStrictMode(p: *P) bool { - return p.current_scope.strict_mode != .sloppy_mode; - } - - pub inline fn isStrictModeOutputFormat(_: *P) bool { - return true; - } - - pub fn declareCommonJSSymbol(p: *P, comptime kind: Symbol.Kind, comptime name: string) !Ref { - const name_hash = comptime @TypeOf(p.module_scope.members).getHash(name); - const member = p.module_scope.members.getWithHash(name, name_hash); - - // If the code declared this symbol using "var name", then this is actually - // not a collision. For example, node will let you do this: - // - // var exports; - // module.exports.foo = 123; - // console.log(exports.foo); - // - // This works because node's implementation of CommonJS wraps the entire - // source file like this: - // - // (function(require, exports, module, __filename, __dirname) { - // var exports; - // module.exports.foo = 123; - // console.log(exports.foo); - // }) - // - // Both the "exports" argument and "var exports" are hoisted variables, so - // they don't collide. - if (member) |_member| { - if (p.symbols.items[_member.ref.innerIndex()].kind == .hoisted and kind == .hoisted and !p.has_es_module_syntax) { - return _member.ref; - } - } - - // Create a new symbol if we didn't merge with an existing one above - const ref = try p.newSymbol(kind, name); - - if (member == null) { - try p.module_scope.members.putWithHash(p.allocator, name, name_hash, Scope.Member{ .ref = ref, .loc = logger.Loc.Empty }); - return ref; - } - - // If the variable was declared, then it shadows this symbol. The code in - // this module will be unable to reference this symbol. However, we must - // still add the symbol to the scope so it gets minified (automatically- - // generated code may still reference the symbol). - try p.module_scope.generated.append(p.allocator, ref); - return ref; - } - - fn declareGeneratedSymbol(p: *P, kind: Symbol.Kind, comptime name: string) !GeneratedSymbol { - const static = @field(StaticSymbolName.List, name); - return GeneratedSymbol{ - .backup = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.backup, true), - .primary = try declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static.primary, true), - .ref = try declareSymbolMaybeGenerated(p, kind, logger.Loc.Empty, static.internal, true), - }; - } - - fn declareSymbol(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string) !Ref { - return try @call(.{ .modifier = .always_inline }, declareSymbolMaybeGenerated, .{ p, kind, loc, name, false }); - } - - fn declareSymbolMaybeGenerated(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string, comptime is_generated: bool) !Ref { - // p.checkForNonBMPCodePoint(loc, name) - - if (comptime !is_generated) { - - // Forbid declaring a symbol with a reserved word in strict mode - if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) { - try p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, loc), name); - } - } - - // Allocate a new symbol - var ref = try p.newSymbol(kind, name); - - const scope = p.current_scope; - var entry = try scope.members.getOrPut(p.allocator, name); - if (entry.found_existing) { - const existing = entry.entry.value; - var symbol: *Symbol = &p.symbols.items[existing.ref.innerIndex()]; - - if (comptime !is_generated) { - switch (scope.canMergeSymbols(symbol.kind, kind, is_typescript_enabled)) { - .forbidden => { - var notes = try p.allocator.alloc(logger.Data, 1); - notes[0] = - logger.rangeData( - p.source, - js_lexer.rangeOfIdentifier(p.source, existing.loc), - std.fmt.allocPrint( - p.allocator, - "{s} was originally declared here", - .{symbol.original_name}, - ) catch unreachable, - ); - - p.log.addRangeErrorFmtWithNotes( - p.source, - js_lexer.rangeOfIdentifier(p.source, loc), - p.allocator, - notes, - "\"{s}\" has already been declared", - .{symbol.original_name}, - ) catch unreachable; - - return existing.ref; - }, - .keep_existing => { - ref = existing.ref; - }, - .replace_with_new => { - symbol.link = ref; - }, - .become_private_get_set_pair => { - ref = existing.ref; - symbol.kind = .private_get_set_pair; - }, - .become_private_static_get_set_pair => { - ref = existing.ref; - symbol.kind = .private_static_get_set_pair; - }, - - .overwrite_with_new => {}, - // else => unreachable, - } - } else { - // Ensure that EImportIdentifier is created for the symbol in handleIdentifier - if (symbol.kind == .import and kind != .import) { - try p.is_import_item.put(p.allocator, ref, .{}); - } - - p.symbols.items[ref.innerIndex()].link = existing.ref; - } - } - - entry.entry.value = js_ast.Scope.Member{ .ref = ref, .loc = loc }; - if (comptime is_generated) { - try p.module_scope.generated.append(p.allocator, ref); - } - return ref; - } - - fn validateFunctionName(p: *P, func: G.Fn, kind: FunctionKind) void { - if (func.name) |name| { - const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name; - - if (func.flags.contains(.is_async) and strings.eqlComptime(original_name, "await")) { - p.log.addRangeError( - p.source, - js_lexer.rangeOfIdentifier(p.source, name.loc), - "An async function cannot be named \"await\"", - ) catch unreachable; - } else if (kind == .expr and func.flags.contains(.is_generator) and strings.eqlComptime(original_name, "yield")) { - p.log.addRangeError( - p.source, - js_lexer.rangeOfIdentifier(p.source, name.loc), - "An generator function expression cannot be named \"yield\"", - ) catch unreachable; - } - } - } - - fn parseFnExpr(p: *P, loc: logger.Loc, is_async: bool, async_range: logger.Range) !Expr { - try p.lexer.next(); - const is_generator = p.lexer.token == T.t_asterisk; - if (is_generator) { - // p.markSyntaxFeature() - try p.lexer.next(); - } else if (is_async) { - // p.markLoweredSyntaxFeature(compat.AsyncAwait, asyncRange, compat.Generator) - } - - var name: ?js_ast.LocRef = null; - - _ = p.pushScopeForParsePass(.function_args, loc) catch unreachable; - - // The name is optional - if (p.lexer.token == .t_identifier) { - const text = p.lexer.identifier; - - // Don't declare the name "arguments" since it's shadowed and inaccessible - name = js_ast.LocRef{ - .loc = p.lexer.loc(), - .ref = if (text.len > 0 and !strings.eqlComptime(text, "arguments")) - try p.declareSymbol(.hoisted_function, p.lexer.loc(), text) - else - try p.newSymbol(.hoisted_function, text), - }; - - try p.lexer.next(); - } - - // Even anonymous functions can have TypeScript type parameters - if (comptime is_typescript_enabled) { - try p.skipTypeScriptTypeParameters(); - } - - const func = try p.parseFn(name, FnOrArrowDataParse{ - .async_range = async_range, - .allow_await = if (is_async) .allow_expr else .allow_ident, - .allow_yield = if (is_generator) .allow_expr else .allow_ident, - }); - - p.validateFunctionName(func, .expr); - p.popScope(); - - return p.e(js_ast.E.Function{ - .func = func, - }, loc); - } - - fn parseFnBody(p: *P, data: *FnOrArrowDataParse) !G.FnBody { - var oldFnOrArrowData = p.fn_or_arrow_data_parse; - var oldAllowIn = p.allow_in; - p.fn_or_arrow_data_parse = data.*; - p.allow_in = true; - - const loc = p.lexer.loc(); - _ = try p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc()); - defer p.popScope(); - - try p.lexer.expect(.t_open_brace); - var opts = ParseStatementOptions{}; - const stmts = try p.parseStmtsUpTo(.t_close_brace, &opts); - try p.lexer.next(); - - p.allow_in = oldAllowIn; - p.fn_or_arrow_data_parse = oldFnOrArrowData; - return G.FnBody{ .loc = loc, .stmts = stmts }; - } - - fn parseArrowBody(p: *P, args: []js_ast.G.Arg, data: *FnOrArrowDataParse) !E.Arrow { - var arrow_loc = p.lexer.loc(); - - // Newlines are not allowed before "=>" - if (p.lexer.has_newline_before) { - try p.log.addRangeError(p.source, p.lexer.range(), "Unexpected newline before \"=>\""); - return error.SyntaxError; - } - - try p.lexer.expect(T.t_equals_greater_than); - - for (args) |*arg| { - var opts = ParseStatementOptions{}; - try p.declareBinding(Symbol.Kind.hoisted, &arg.binding, &opts); - } - - // The ability to use "this" and "super()" is inherited by arrow functions - data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call; - data.allow_super_property = p.fn_or_arrow_data_parse.allow_super_property; - data.is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed; - - if (p.lexer.token == .t_open_brace) { - var body = try p.parseFnBody(data); - p.after_arrow_body_loc = p.lexer.loc(); - return E.Arrow{ .args = args, .body = body }; - } - - _ = try p.pushScopeForParsePass(Scope.Kind.function_body, arrow_loc); - defer p.popScope(); - - var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); - - p.fn_or_arrow_data_parse = data.*; - var expr = try p.parseExpr(Level.comma); - p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); - - var stmts = try p.allocator.alloc(Stmt, 1); - stmts[0] = p.s(S.Return{ .value = expr }, expr.loc); - return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } }; - } - - fn declareBinding(p: *P, kind: Symbol.Kind, binding: *BindingNodeIndex, opts: *ParseStatementOptions) !void { - switch (binding.data) { - .b_missing => {}, - .b_identifier => |bind| { - if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) { - bind.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(bind.ref)); - } - }, - - .b_array => |bind| { - for (bind.items) |_, i| { - p.declareBinding(kind, &bind.items[i].binding, opts) catch unreachable; - } - }, - - .b_object => |bind| { - for (bind.properties) |*prop| { - p.declareBinding(kind, &prop.value, opts) catch unreachable; - } - }, - - else => { - // @compileError("Missing binding type"); - }, - } - } - - // This is where the allocate memory to the heap for AST objects. - // This is a short name to keep the code more readable. - // It also swallows errors, but I think that's correct here. - // We can handle errors via the log. - // We'll have to deal with @wasmHeapGrow or whatever that thing is. - pub inline fn mm(self: *P, comptime ast_object_type: type, instance: anytype) *ast_object_type { - var obj = self.allocator.create(ast_object_type) catch unreachable; - obj.* = instance; - return obj; - } - - // mmmm memmory allocation - pub inline fn m(self: *P, kind: anytype) *@TypeOf(kind) { - return self.mm(@TypeOf(kind), kind); - } - - pub fn storeNameInRef(p: *P, name: string) !Ref { - if (comptime track_symbol_usage_during_parse_pass) { - if (p.parse_pass_symbol_uses.getPtr(name)) |res| { - res.used = true; - } - } - - if (@ptrToInt(p.source.contents.ptr) <= @ptrToInt(name.ptr) and (@ptrToInt(name.ptr) + name.len) <= (@ptrToInt(p.source.contents.ptr) + p.source.contents.len)) { - const start = Ref.toInt(@ptrToInt(name.ptr) - @ptrToInt(p.source.contents.ptr)); - const end = Ref.toInt(name.len); - return Ref.initSourceEnd(.{ .source_index = start, .inner_index = end, .is_source_contents_slice = true }); - } else { - const inner_index = Ref.toInt(p.allocated_names.items.len); - try p.allocated_names.append(p.allocator, name); - return Ref.initSourceEnd(.{ .source_index = std.math.maxInt(Ref.Int), .inner_index = inner_index, .is_source_contents_slice = false }); - } - } - - pub fn loadNameFromRef(p: *P, ref: Ref) string { - if (ref.isSourceContentsSlice()) { - return p.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()]; - } else if (ref.sourceIndex() == std.math.maxInt(Ref.Int)) { - if (comptime Environment.allow_assert) - assert(ref.innerIndex() < p.allocated_names.items.len); - return p.allocated_names.items[ref.innerIndex()]; - } else { - return p.symbols.items[ref.innerIndex()].original_name; - } - } - - // This parses an expression. This assumes we've already parsed the "async" - // keyword and are currently looking at the following token. - pub fn parseAsyncPrefixExpr(p: *P, async_range: logger.Range, level: Level) !Expr { - // "async function() {}" - if (!p.lexer.has_newline_before and p.lexer.token == T.t_function) { - return try p.parseFnExpr(async_range.loc, true, async_range); - } - - // Check the precedence level to avoid parsing an arrow function in - // "new async () => {}". This also avoids parsing "new async()" as - // "new (async())()" instead. - if (!p.lexer.has_newline_before and level.lt(.member)) { - switch (p.lexer.token) { - // "async => {}" - .t_equals_greater_than => { - if (level.lte(.assign)) { - var args = try p.allocator.alloc(G.Arg, 1); - args[0] = G.Arg{ .binding = p.b( - B.Identifier{ - .ref = try p.storeNameInRef("async"), - }, - async_range.loc, - ) }; - _ = p.pushScopeForParsePass(.function_args, async_range.loc) catch unreachable; - var data = FnOrArrowDataParse{}; - var arrow_body = try p.parseArrowBody(args, &data); - p.popScope(); - return p.e(arrow_body, async_range.loc); - } - }, - // "async x => {}" - .t_identifier => { - if (level.lte(.assign)) { - // p.markLoweredSyntaxFeature(); - - const ref = try p.storeNameInRef(p.lexer.identifier); - var args = try p.allocator.alloc(G.Arg, 1); - args[0] = G.Arg{ .binding = p.b( - B.Identifier{ - .ref = ref, - }, - async_range.loc, - ) }; - try p.lexer.next(); - - _ = try p.pushScopeForParsePass(.function_args, async_range.loc); - defer p.popScope(); - - var data = FnOrArrowDataParse{ - .allow_await = .allow_expr, - }; - var arrowBody = try p.parseArrowBody(args, &data); - arrowBody.is_async = true; - return p.e(arrowBody, async_range.loc); - } - }, - - // "async()" - // "async () => {}" - .t_open_paren => { - try p.lexer.next(); - return p.parseParenExpr(async_range.loc, level, ParenExprOpts{ .is_async = true, .async_range = async_range }); - }, - - // "async<T>()" - // "async <T>() => {}" - .t_less_than => { - if (is_typescript_enabled and p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()) { - try p.lexer.next(); - return p.parseParenExpr(async_range.loc, level, ParenExprOpts{ .is_async = true, .async_range = async_range }); - } - }, - - else => {}, - } - } - - // "async" - // "async + 1" - return p.e( - E.Identifier{ .ref = try p.storeNameInRef("async") }, - async_range.loc, - ); - } - - pub const Backtracking = struct { - pub inline fn lexerBacktracker(p: *P, func: anytype) bool { - p.markTypeScriptOnly(); - var old_lexer = std.mem.toBytes(p.lexer); - const old_log_disabled = p.lexer.is_log_disabled; - p.lexer.is_log_disabled = true; - - defer p.lexer.is_log_disabled = old_log_disabled; - var backtrack = false; - func(p) catch |err| { - switch (err) { - error.Backtrack => { - backtrack = true; - }, - else => {}, - } - }; - - if (backtrack) { - p.lexer = std.mem.bytesToValue(@TypeOf(p.lexer), &old_lexer); - } - - return !backtrack; - } - - pub fn skipTypeScriptTypeParametersThenOpenParenWithBacktracking(p: *P) anyerror!void { - try p.skipTypeScriptTypeParameters(); - if (p.lexer.token != .t_open_paren) { - // try p.lexer.unexpected(); return error.SyntaxError; - return error.Backtrack; - } - } - - pub fn skipTypeScriptArrowArgsWithBacktracking(p: *P) anyerror!void { - try p.skipTypescriptFnArgs(); - p.lexer.expect(.t_equals_greater_than) catch - return error.Backtrack; - } - - pub fn skipTypeScriptTypeArgumentsWithBacktracking(p: *P) anyerror!void { - _ = try p.skipTypeScriptTypeArguments(false); - - // Check the token after this and backtrack if it's the wrong one - if (!TypeScript.canFollowTypeArgumentsInExpression(p.lexer.token)) { - // try p.lexer.unexpected(); return error.SyntaxError; - return error.Backtrack; - } - } - - pub fn skipTypeScriptArrowReturnTypeWithBacktracking(p: *P) anyerror!void { - p.lexer.expect(.t_colon) catch - return error.Backtrack; - - try p.skipTypescriptReturnType(); - // Check the token after this and backtrack if it's the wrong one - if (p.lexer.token != .t_equals_greater_than) { - // try p.lexer.unexpected(); return error.SyntaxError; - return error.Backtrack; - } - } - }; - - pub fn trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking(p: *P) bool { - return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptTypeParametersThenOpenParenWithBacktracking); - } - - pub fn trySkipTypeScriptTypeArgumentsWithBacktracking(p: *P) bool { - return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptTypeArgumentsWithBacktracking); - } - - pub fn trySkipTypeScriptArrowReturnTypeWithBacktracking(p: *P) bool { - return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowReturnTypeWithBacktracking); - } - - pub fn trySkipTypeScriptArrowArgsWithBacktracking(p: *P) bool { - return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowArgsWithBacktracking); - } - - pub inline fn parseExprOrBindings(p: *P, level: Level, errors: ?*DeferredErrors) anyerror!Expr { - return try p.parseExprCommon(level, errors, Expr.EFlags.none); - } - - pub inline fn parseExpr(p: *P, level: Level) anyerror!Expr { - return try p.parseExprCommon(level, null, Expr.EFlags.none); - } - - pub inline fn parseExprWithFlags(p: *P, level: Level, flags: Expr.EFlags) anyerror!Expr { - return try p.parseExprCommon(level, null, flags); - } - - pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { - const had_pure_comment_before = p.lexer.has_pure_comment_before and !p.options.ignore_dce_annotations; - var expr = try p.parsePrefix(level, errors, flags); - - // There is no formal spec for "__PURE__" comments but from reverse- - // engineering, it looks like they apply to the next CallExpression or - // NewExpression. So in "/* @__PURE__ */ a().b() + c()" the comment applies - // to the expression "a().b()". - - if (had_pure_comment_before and level.lt(.call)) { - expr = try p.parseSuffix(expr, @intToEnum(Level, @enumToInt(Level.call) - 1), errors, flags); - switch (expr.data) { - .e_call => |ex| { - ex.can_be_unwrapped_if_unused = true; - }, - .e_new => |ex| { - ex.can_be_unwrapped_if_unused = true; - }, - else => {}, - } - } - - return try p.parseSuffix(expr, level, errors, flags); - } - - pub inline fn addImportRecord(p: *P, kind: ImportKind, loc: logger.Loc, name: string) u32 { - return p.addImportRecordByRange(kind, p.source.rangeOfString(loc), name); - } - - pub fn addImportRecordByRange(p: *P, kind: ImportKind, range: logger.Range, name: string) u32 { - var index = p.import_records.items.len; - const record = ImportRecord{ - .kind = kind, - .range = range, - .path = fs.Path.init(name), - }; - p.import_records.append(record) catch unreachable; - return @intCast(u32, index); - } - - pub fn popScope(p: *P) void { - const current_scope = p.current_scope; - // We cannot rename anything inside a scope containing a direct eval() call - if (current_scope.contains_direct_eval) { - var iter = current_scope.members.iterator(); - while (iter.next()) |member| { - - // Using direct eval when bundling is not a good idea in general because - // esbuild must assume that it can potentially reach anything in any of - // the containing scopes. We try to make it work but this isn't possible - // in some cases. - // - // For example, symbols imported using an ESM import are a live binding - // to the underlying symbol in another file. This is emulated during - // scope hoisting by erasing the ESM import and just referencing the - // underlying symbol in the flattened bundle directly. However, that - // symbol may have a different name which could break uses of direct - // eval: - // - // // Before bundling - // import { foo as bar } from './foo.js' - // console.log(eval('bar')) - // - // // After bundling - // let foo = 123 // The contents of "foo.js" - // console.log(eval('bar')) - // - // There really isn't any way to fix this. You can't just rename "foo" to - // "bar" in the example above because there may be a third bundled file - // that also contains direct eval and imports the same symbol with a - // different conflicting import alias. And there is no way to store a - // live binding to the underlying symbol in a variable with the import's - // name so that direct eval can access it: - // - // // After bundling - // let foo = 123 // The contents of "foo.js" - // const bar = /* cannot express a live binding to "foo" here */ - // console.log(eval('bar')) - // - // Technically a "with" statement could potentially make this work (with - // a big hit to performance), but they are deprecated and are unavailable - // in strict mode. This is a non-starter since all ESM code is strict mode. - // - // So while we still try to obey the requirement that all symbol names are - // pinned when direct eval is present, we make an exception for top-level - // symbols in an ESM file when bundling is enabled. We make no guarantee - // that "eval" will be able to reach these symbols and we allow them to be - // renamed or removed by tree shaking. - // if (p.currentScope.parent == null and p.has_es_module_syntax) { - // continue; - // } - - p.symbols.items[member.value.ref.innerIndex()].must_not_be_renamed = true; - } - } - - p.current_scope = current_scope.parent orelse p.panic("Internal error: attempted to call popScope() on the topmost scope", .{}); - } - - pub fn markExprAsParenthesized(_: *P, expr: *Expr) void { - switch (expr.data) { - .e_array => |ex| { - ex.is_parenthesized = true; - }, - .e_object => |ex| { - ex.is_parenthesized = true; - }, - else => { - return; - }, - } - } - - pub fn parseYieldExpr(p: *P, loc: logger.Loc) !ExprNodeIndex { - // Parse a yield-from expression, which yields from an iterator - const isStar = p.lexer.token == T.t_asterisk; - - if (isStar) { - if (p.lexer.has_newline_before) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - try p.lexer.next(); - } - - var value: ?ExprNodeIndex = null; - switch (p.lexer.token) { - .t_close_brace, .t_close_paren, .t_colon, .t_comma, .t_semicolon => {}, - else => { - if (isStar or !p.lexer.has_newline_before) { - value = try p.parseExpr(.yield); - } - }, - } - - return p.e(E.Yield{ - .value = value, - .is_star = isStar, - }, loc); - } - - pub fn parseProperty(p: *P, kind: Property.Kind, opts: *PropertyOpts, errors: ?*DeferredErrors) anyerror!?G.Property { - var key: Expr = Expr{ .loc = logger.Loc.Empty, .data = .{ .e_missing = E.Missing{} } }; - var key_range = p.lexer.range(); - var is_computed = false; - - switch (p.lexer.token) { - .t_numeric_literal => { - key = p.e(E.Number{ - .value = p.lexer.number, - }, p.lexer.loc()); - // p.checkForLegacyOctalLiteral() - try p.lexer.next(); - }, - .t_string_literal => { - key = try p.parseStringLiteral(); - }, - .t_big_integer_literal => { - key = p.e(E.BigInt{ .value = p.lexer.identifier }, p.lexer.loc()); - // markSyntaxFeature - try p.lexer.next(); - }, - .t_private_identifier => { - if (!opts.is_class or opts.ts_decorators.len > 0) { - try p.lexer.expected(.t_identifier); - } - - key = p.e(E.PrivateIdentifier{ .ref = p.storeNameInRef(p.lexer.identifier) catch unreachable }, p.lexer.loc()); - try p.lexer.next(); - }, - .t_open_bracket => { - is_computed = true; - // p.markSyntaxFeature(compat.objectExtensions, p.lexer.range()) - try p.lexer.next(); - const wasIdentifier = p.lexer.token == .t_identifier; - const expr = try p.parseExpr(.comma); - - if (comptime is_typescript_enabled) { - - // Handle index signatures - if (p.lexer.token == .t_colon and wasIdentifier and opts.is_class) { - switch (expr.data) { - .e_identifier => { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - try p.lexer.expect(.t_close_bracket); - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); - try p.lexer.expectOrInsertSemicolon(); - - // Skip this property entirely - return null; - }, - else => {}, - } - } - } - - try p.lexer.expect(.t_close_bracket); - key = expr; - }, - .t_asterisk => { - if (kind != .normal or opts.is_generator) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - try p.lexer.next(); - opts.is_generator = true; - return try p.parseProperty(.normal, opts, errors); - }, - - else => { - const name = p.lexer.identifier; - const raw = p.lexer.raw(); - const name_range = p.lexer.range(); - - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - - try p.lexer.next(); - - // Support contextual keywords - if (kind == .normal and !opts.is_generator) { - // Does the following token look like a key? - const couldBeModifierKeyword = p.lexer.isIdentifierOrKeyword() or switch (p.lexer.token) { - .t_open_bracket, .t_numeric_literal, .t_string_literal, .t_asterisk, .t_private_identifier => true, - else => false, - }; - - // If so, check for a modifier keyword - if (couldBeModifierKeyword) { - // TODO: micro-optimization, use a smaller list for non-typescript files. - if (js_lexer.PropertyModifierKeyword.List.get(name)) |keyword| { - switch (keyword) { - .p_get => { - if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_get) { - // p.markSyntaxFeautre(ObjectAccessors, name_range) - return try p.parseProperty(.get, opts, null); - } - }, - - .p_set => { - if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_set) { - // p.markSyntaxFeautre(ObjectAccessors, name_range) - return try p.parseProperty(.set, opts, null); - } - }, - .p_async => { - if (!opts.is_async and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == .p_async and !p.lexer.has_newline_before) { - opts.is_async = true; - opts.async_range = name_range; - - // p.markSyntaxFeautre(ObjectAccessors, name_range) - return try p.parseProperty(kind, opts, null); - } - }, - .p_static => { - if (!opts.is_static and !opts.is_async and opts.is_class and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_get) == .p_static) { - opts.is_static = true; - return try p.parseProperty(kind, opts, null); - } - }, - .p_private, .p_protected, .p_public, .p_readonly, .p_abstract, .p_declare, .p_override => { - // Skip over TypeScript keywords - if (opts.is_class and is_typescript_enabled and (js_lexer.PropertyModifierKeyword.List.get(raw) orelse .p_static) == keyword) { - return try p.parseProperty(kind, opts, null); - } - }, - } - } - } else if (p.lexer.token == .t_open_brace and strings.eqlComptime(name, "static")) { - const loc = p.lexer.loc(); - try p.lexer.next(); - - const old_fn_or_arrow_data_parse = p.fn_or_arrow_data_parse; - p.fn_or_arrow_data_parse = .{ - .is_return_disallowed = true, - .allow_super_property = true, - .allow_await = .forbid_all, - }; - - _ = try p.pushScopeForParsePass(.class_static_init, loc); - var _parse_opts = ParseStatementOptions{}; - var stmts = try p.parseStmtsUpTo(.t_close_brace, &_parse_opts); - - p.popScope(); - - p.fn_or_arrow_data_parse = old_fn_or_arrow_data_parse; - try p.lexer.expect(.t_close_brace); - - var block = p.allocator.create( - G.ClassStaticBlock, - ) catch unreachable; - - block.* = G.ClassStaticBlock{ - .stmts = js_ast.BabyList(Stmt).init(stmts), - .loc = loc, - }; - - return G.Property{ - .kind = .class_static_block, - .class_static_block = block, - }; - } - } - - key = p.e(E.String{ .data = name }, name_range.loc); - - // Parse a shorthand property - const isShorthandProperty = !opts.is_class and - kind == .normal and - p.lexer.token != .t_colon and - p.lexer.token != .t_open_paren and - p.lexer.token != .t_less_than and - !opts.is_generator and - !opts.is_async and - !js_lexer.Keywords.has(name); - - if (isShorthandProperty) { - if ((p.fn_or_arrow_data_parse.allow_await != .allow_ident and - strings.eqlComptime(name, "await")) or - (p.fn_or_arrow_data_parse.allow_yield != .allow_ident and - strings.eqlComptime(name, "yield"))) - { - if (strings.eqlComptime(name, "await")) { - p.log.addRangeError(p.source, name_range, "Cannot use \"await\" here") catch unreachable; - } else { - p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" here") catch unreachable; - } - } - - const ref = p.storeNameInRef(name) catch unreachable; - const value = p.e(E.Identifier{ .ref = ref }, key.loc); - - // Destructuring patterns have an optional default value - var initializer: ?Expr = null; - if (errors != null and p.lexer.token == .t_equals) { - errors.?.invalid_expr_default_value = p.lexer.range(); - try p.lexer.next(); - initializer = try p.parseExpr(.comma); - } - - return G.Property{ - .kind = kind, - .key = key, - .value = value, - .initializer = initializer, - .flags = Flags.Property.init(.{ - .was_shorthand = true, - }), - }; - } - }, - } - - if (comptime is_typescript_enabled) { - // "class X { foo?: number }" - // "class X { foo!: number }" - if (opts.is_class and (p.lexer.token == .t_question or p.lexer.token == .t_exclamation)) { - try p.lexer.next(); - } - - // "class X { foo?<T>(): T }" - // "const x = { foo<T>(): T {} }" - try p.skipTypeScriptTypeParameters(); - } - - // Parse a class field with an optional initial value - if (opts.is_class and kind == .normal and !opts.is_async and !opts.is_generator and p.lexer.token != .t_open_paren) { - var initializer: ?Expr = null; - - // Forbid the names "constructor" and "prototype" in some cases - if (!is_computed) { - switch (key.data) { - .e_string => |str| { - if (str.eqlComptime("constructor") or (opts.is_static and str.eqlComptime("prototype"))) { - // TODO: fmt error message to include string value. - p.log.addRangeError(p.source, key_range, "Invalid field name") catch unreachable; - } - }, - else => {}, - } - } - - if (comptime is_typescript_enabled) { - // Skip over types - if (p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - } - - if (p.lexer.token == .t_equals) { - if (comptime is_typescript_enabled) { - if (!opts.declare_range.isEmpty()) { - try p.log.addRangeError(p.source, p.lexer.range(), "Class fields that use \"declare\" cannot be initialized"); - } - } - - try p.lexer.next(); - - // "this" and "super" property access is allowed in field initializers - const old_is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed; - const old_allow_super_property = p.fn_or_arrow_data_parse.allow_super_property; - p.fn_or_arrow_data_parse.is_this_disallowed = false; - p.fn_or_arrow_data_parse.allow_super_property = true; - - initializer = try p.parseExpr(.comma); - - p.fn_or_arrow_data_parse.is_this_disallowed = old_is_this_disallowed; - p.fn_or_arrow_data_parse.allow_super_property = old_allow_super_property; - } - - // Special-case private identifiers - switch (key.data) { - .e_private_identifier => |*private| { - const name = p.loadNameFromRef(private.ref); - if (strings.eqlComptime(name, "#constructor")) { - p.log.addRangeError(p.source, key_range, "Invalid field name \"#constructor\"") catch unreachable; - } - - var declare: js_ast.Symbol.Kind = undefined; - if (opts.is_static) { - declare = .private_static_field; - } else { - declare = .private_field; - } - private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable; - }, - else => {}, - } - - try p.lexer.expectOrInsertSemicolon(); - - return G.Property{ - .ts_decorators = ExprNodeList.init(opts.ts_decorators), - .kind = kind, - .flags = Flags.Property.init(.{ - .is_computed = is_computed, - .is_static = opts.is_static, - }), - .key = key, - .initializer = initializer, - }; - } - - // Parse a method expression - if (p.lexer.token == .t_open_paren or kind != .normal or opts.is_class or opts.is_async or opts.is_generator) { - if (p.lexer.token == .t_open_paren and kind != .get and kind != .set) { - // markSyntaxFeature object extensions - } - - const loc = p.lexer.loc(); - const scope_index = p.pushScopeForParsePass(.function_args, loc) catch unreachable; - var is_constructor = false; - - // Forbid the names "constructor" and "prototype" in some cases - if (opts.is_class and !is_computed) { - switch (key.data) { - .e_string => |str| { - if (!opts.is_static and str.eqlComptime("constructor")) { - if (kind == .get) { - p.log.addRangeError(p.source, key_range, "Class constructor cannot be a getter") catch unreachable; - } else if (kind == .set) { - p.log.addRangeError(p.source, key_range, "Class constructor cannot be a setter") catch unreachable; - } else if (opts.is_async) { - p.log.addRangeError(p.source, key_range, "Class constructor cannot be an async function") catch unreachable; - } else if (opts.is_generator) { - p.log.addRangeError(p.source, key_range, "Class constructor cannot be a generator function") catch unreachable; - } else { - is_constructor = true; - } - } else if (opts.is_static and str.eqlComptime("prototype")) { - p.log.addRangeError(p.source, key_range, "Invalid static method name \"prototype\"") catch unreachable; - } - }, - else => {}, - } - } - - var func = try p.parseFn(null, FnOrArrowDataParse{ - .async_range = opts.async_range, - .has_async_range = !opts.async_range.isEmpty(), - .allow_await = if (opts.is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, - .allow_yield = if (opts.is_generator) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, - .allow_super_call = opts.class_has_extends and is_constructor, - .allow_super_property = true, - .allow_ts_decorators = opts.allow_ts_decorators, - .is_constructor = is_constructor, - - // Only allow omitting the body if we're parsing TypeScript class - .allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class, - }); - - // "class Foo { foo(): void; foo(): void {} }" - if (func.flags.contains(.is_forward_declaration)) { - // Skip this property entirely - p.popAndDiscardScope(scope_index); - return null; - } - - p.popScope(); - func.flags.insert(.is_unique_formal_parameters); - const value = p.e(E.Function{ .func = func }, loc); - - // Enforce argument rules for accessors - switch (kind) { - .get => { - if (func.args.len > 0) { - const r = js_lexer.rangeOfIdentifier(p.source, func.args[0].binding.loc); - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Getter {s} must have zero arguments", .{p.keyNameForError(key)}) catch unreachable; - } - }, - .set => { - if (func.args.len != 1) { - var r = js_lexer.rangeOfIdentifier(p.source, if (func.args.len > 0) func.args[0].binding.loc else loc); - if (func.args.len > 1) { - r = js_lexer.rangeOfIdentifier(p.source, func.args[1].binding.loc); - } - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Setter {s} must have exactly 1 argument (there are {d})", .{ p.keyNameForError(key), func.args.len }) catch unreachable; - } - }, - else => {}, - } - - // Special-case private identifiers - switch (key.data) { - .e_private_identifier => |*private| { - var declare: Symbol.Kind = undefined; - var suffix: string = ""; - switch (kind) { - .get => { - if (opts.is_static) { - declare = .private_static_get; - } else { - declare = .private_get; - } - suffix = "_get"; - }, - .set => { - if (opts.is_static) { - declare = .private_static_set; - } else { - declare = .private_set; - } - suffix = "_set"; - }, - else => { - if (opts.is_static) { - declare = .private_static_method; - } else { - declare = .private_method; - } - suffix = "_fn"; - }, - } - - const name = p.loadNameFromRef(private.ref); - if (strings.eqlComptime(name, "#constructor")) { - p.log.addRangeError(p.source, key_range, "Invalid method name \"#constructor\"") catch unreachable; - } - private.ref = p.declareSymbol(declare, key.loc, name) catch unreachable; - }, - else => {}, - } - - return G.Property{ - .ts_decorators = ExprNodeList.init(opts.ts_decorators), - .kind = kind, - .flags = Flags.Property.init(.{ - .is_computed = is_computed, - .is_method = true, - .is_static = opts.is_static, - }), - .key = key, - .value = value, - }; - } - - // Parse an object key/value pair - try p.lexer.expect(.t_colon); - const value = try p.parseExprOrBindings(.comma, errors); - - return G.Property{ - .kind = kind, - .flags = Flags.Property.init(.{ - .is_computed = is_computed, - }), - .key = key, - .value = value, - }; - } - - // By the time we call this, the identifier and type parameters have already - // been parsed. We need to start parsing from the "extends" clause. - pub fn parseClass(p: *P, class_keyword: logger.Range, name: ?js_ast.LocRef, class_opts: ParseClassOptions) !G.Class { - var extends: ?Expr = null; - - if (p.lexer.token == .t_extends) { - try p.lexer.next(); - extends = try p.parseExpr(.new); - - // TypeScript's type argument parser inside expressions backtracks if the - // first token after the end of the type parameter list is "{", so the - // parsed expression above will have backtracked if there are any type - // arguments. This means we have to re-parse for any type arguments here. - // This seems kind of wasteful to me but it's what the official compiler - // does and it probably doesn't have that high of a performance overhead - // because "extends" clauses aren't that frequent, so it should be ok. - if (comptime is_typescript_enabled) { - _ = try p.skipTypeScriptTypeArguments(false); // isInsideJSXElement - } - } - - if (comptime is_typescript_enabled) { - if (p.lexer.isContextualKeyword("implements")) { - try p.lexer.next(); - - while (true) { - try p.skipTypeScriptType(.lowest); - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - } - } - - var body_loc = p.lexer.loc(); - try p.lexer.expect(T.t_open_brace); - var properties = ListManaged(G.Property).init(p.allocator); - - // Allow "in" and private fields inside class bodies - const old_allow_in = p.allow_in; - const old_allow_private_identifiers = p.allow_private_identifiers; - p.allow_in = true; - p.allow_private_identifiers = true; - - // A scope is needed for private identifiers - const scopeIndex = p.pushScopeForParsePass(.class_body, body_loc) catch unreachable; - - var opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null }; - while (p.lexer.token != T.t_close_brace) { - if (p.lexer.token == .t_semicolon) { - try p.lexer.next(); - continue; - } - - opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null }; - - // Parse decorators for this property - const first_decorator_loc = p.lexer.loc(); - if (opts.allow_ts_decorators) { - opts.ts_decorators = try p.parseTypeScriptDecorators(); - } else { - opts.ts_decorators = &[_]Expr{}; - } - - // This property may turn out to be a type in TypeScript, which should be ignored - if (try p.parseProperty(.normal, &opts, null)) |property| { - properties.append(property) catch unreachable; - - // Forbid decorators on class constructors - if (opts.ts_decorators.len > 0) { - switch ((property.key orelse p.panic("Internal error: Expected property {s} to have a key.", .{property})).data) { - .e_string => |str| { - if (str.eqlComptime("constructor")) { - p.log.addError(p.source, first_decorator_loc, "TypeScript does not allow decorators on class constructors") catch unreachable; - } - }, - else => {}, - } - } - } - } - - if (class_opts.is_type_script_declare) { - p.popAndDiscardScope(scopeIndex); - } else { - p.popScope(); - } - - p.allow_in = old_allow_in; - p.allow_private_identifiers = old_allow_private_identifiers; - const close_brace_loc = p.lexer.loc(); - try p.lexer.expect(.t_close_brace); - - return G.Class{ - .class_name = name, - .extends = extends, - .close_brace_loc = close_brace_loc, - .ts_decorators = ExprNodeList.init(class_opts.ts_decorators), - .class_keyword = class_keyword, - .body_loc = body_loc, - .properties = properties.toOwnedSlice(), - }; - } - - pub fn skipTypeScriptTypeArguments(p: *P, comptime isInsideJSXElement: bool) anyerror!bool { - p.markTypeScriptOnly(); - switch (p.lexer.token) { - .t_less_than, .t_less_than_equals, .t_less_than_less_than, .t_less_than_less_than_equals => {}, - else => { - return false; - }, - } - - try p.lexer.expectLessThan(false); - - while (true) { - try p.skipTypeScriptType(.lowest); - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - - // This type argument list must end with a ">" - try p.lexer.expectGreaterThan(isInsideJSXElement); - return true; - } - - pub fn parseTemplateParts(p: *P, _: bool) ![]E.TemplatePart { - var parts = ListManaged(E.TemplatePart).initCapacity(p.allocator, 1) catch unreachable; - // Allow "in" inside template literals - var oldAllowIn = p.allow_in; - p.allow_in = true; - - parseTemplatePart: while (true) { - try p.lexer.next(); - const value = try p.parseExpr(.lowest); - const tail_loc = p.lexer.loc(); - try p.lexer.rescanCloseBraceAsTemplateToken(); - - var tail = p.lexer.toEString(); - - parts.append(E.TemplatePart{ - .value = value, - .tail_loc = tail_loc, - .tail = tail, - }) catch unreachable; - - if (p.lexer.token == .t_template_tail) { - try p.lexer.next(); - break :parseTemplatePart; - } - if (comptime Environment.allow_assert) - assert(p.lexer.token != .t_end_of_file); - } - - p.allow_in = oldAllowIn; - - return parts.toOwnedSlice(); - } - - // This assumes the caller has already checked for TStringLiteral or TNoSubstitutionTemplateLiteral - pub fn parseStringLiteral(p: *P) anyerror!Expr { - const loc = p.lexer.loc(); - var str = p.lexer.toEString(); - str.prefer_template = p.lexer.token == .t_no_substitution_template_literal; - - const expr = p.e(str, loc); - try p.lexer.next(); - return expr; - } - - pub fn parseCallArgs(p: *P) anyerror!ExprListLoc { - // Allow "in" inside call arguments - const old_allow_in = p.allow_in; - p.allow_in = true; - defer p.allow_in = old_allow_in; - - var args = ListManaged(Expr).init(p.allocator); - try p.lexer.expect(.t_open_paren); - - while (p.lexer.token != .t_close_paren) { - const loc = p.lexer.loc(); - const is_spread = p.lexer.token == .t_dot_dot_dot; - if (is_spread) { - // p.mark_syntax_feature(compat.rest_argument, p.lexer.range()); - try p.lexer.next(); - } - var arg = try p.parseExpr(.comma); - if (is_spread) { - arg = p.e(E.Spread{ .value = arg }, loc); - } - args.append(arg) catch unreachable; - if (p.lexer.token != .t_comma) { - break; - } - try p.lexer.next(); - } - const close_paren_loc = p.lexer.loc(); - try p.lexer.expect(.t_close_paren); - return ExprListLoc{ .list = ExprNodeList.fromList(args), .loc = close_paren_loc }; - } - - pub fn parseSuffix(p: *P, _left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { - var left = _left; - var optional_chain: ?js_ast.OptionalChain = null; - while (true) { - if (p.lexer.loc().start == p.after_arrow_body_loc.start) { - while (true) { - switch (p.lexer.token) { - .t_comma => { - if (level.gte(.comma)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ - .op = .bin_comma, - .left = left, - .right = try p.parseExpr(.comma), - }, left.loc); - }, - else => { - return left; - }, - } - } - } - - if (comptime is_typescript_enabled) { - // Stop now if this token is forbidden to follow a TypeScript "as" cast - if (p.forbid_suffix_after_as_loc.start > -1 and p.lexer.loc().start == p.forbid_suffix_after_as_loc.start) { - return left; - } - } - - // Reset the optional chain flag by default. That way we won't accidentally - // treat "c.d" as OptionalChainContinue in "a?.b + c.d". - var old_optional_chain = optional_chain; - optional_chain = null; - switch (p.lexer.token) { - .t_dot => { - try p.lexer.next(); - if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) { - // "a.#b" - // "a?.b.#c" - switch (left.data) { - .e_super => { - try p.lexer.expected(.t_identifier); - }, - else => {}, - } - - const name = p.lexer.identifier; - const name_loc = p.lexer.loc(); - try p.lexer.next(); - const ref = p.storeNameInRef(name) catch unreachable; - left = p.e(E.Index{ - .target = left, - .index = p.e( - E.PrivateIdentifier{ - .ref = ref, - }, - name_loc, - ), - .optional_chain = old_optional_chain, - }, left.loc); - } else { - // "a.b" - // "a?.b.c" - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - - const name = p.lexer.identifier; - const name_loc = p.lexer.loc(); - try p.lexer.next(); - - left = p.e(E.Dot{ .target = left, .name = name, .name_loc = name_loc, .optional_chain = old_optional_chain }, left.loc); - } - - optional_chain = old_optional_chain; - }, - .t_question_dot => { - try p.lexer.next(); - var optional_start = js_ast.OptionalChain.start; - - // TODO: Remove unnecessary optional chains - // if p.options.mangleSyntax { - // if isNullOrUndefined, _, ok := toNullOrUndefinedWithSideEffects(left.Data); ok and !isNullOrUndefined { - // optionalStart = js_ast.OptionalChainNone - // } - // } - - switch (p.lexer.token) { - .t_open_bracket => { - // "a?.[b]" - try p.lexer.next(); - - // allow "in" inside the brackets; - const old_allow_in = p.allow_in; - p.allow_in = true; - - const index = try p.parseExpr(.lowest); - - p.allow_in = old_allow_in; - - try p.lexer.expect(.t_close_bracket); - left = p.e( - E.Index{ .target = left, .index = index, .optional_chain = optional_start }, - left.loc, - ); - }, - - .t_open_paren => { - // "a?.()" - if (level.gte(.call)) { - return left; - } - - const list_loc = try p.parseCallArgs(); - left = p.e(E.Call{ - .target = left, - .args = list_loc.list, - .close_paren_loc = list_loc.loc, - .optional_chain = optional_start, - }, left.loc); - }, - .t_less_than => { - // "a?.<T>()" - if (comptime !is_typescript_enabled) { - try p.lexer.expected(.t_identifier); - return error.SyntaxError; - } - - _ = try p.skipTypeScriptTypeArguments(false); - if (p.lexer.token != .t_open_paren) { - try p.lexer.expected(.t_open_paren); - } - - if (level.gte(.call)) { - return left; - } - - const list_loc = try p.parseCallArgs(); - left = p.e(E.Call{ - .target = left, - .args = list_loc.list, - .close_paren_loc = list_loc.loc, - .optional_chain = optional_start, - }, left.loc); - }, - else => { - if (p.lexer.token == .t_private_identifier and p.allow_private_identifiers) { - // "a?.#b" - const name = p.lexer.identifier; - const name_loc = p.lexer.loc(); - try p.lexer.next(); - const ref = p.storeNameInRef(name) catch unreachable; - left = p.e(E.Index{ - .target = left, - .index = p.e( - E.PrivateIdentifier{ - .ref = ref, - }, - name_loc, - ), - .optional_chain = optional_start, - }, left.loc); - } else { - // "a?.b" - if (!p.lexer.isIdentifierOrKeyword()) { - try p.lexer.expect(.t_identifier); - } - const name = p.lexer.identifier; - const name_loc = p.lexer.loc(); - try p.lexer.next(); - - left = p.e(E.Dot{ - .target = left, - .name = name, - .name_loc = name_loc, - .optional_chain = optional_start, - }, left.loc); - } - }, - } - - // Only continue if we have started - if (optional_start == .start) { - optional_start = .ccontinue; - } - }, - .t_no_substitution_template_literal => { - if (old_optional_chain != null) { - p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable; - } - // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); - const head = p.lexer.toEString(); - try p.lexer.next(); - left = p.e(E.Template{ - .tag = left, - .head = head, - }, left.loc); - }, - .t_template_head => { - if (old_optional_chain != null) { - p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable; - } - // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); - const head = p.lexer.toEString(); - const partsGroup = try p.parseTemplateParts(true); - const tag = left; - left = p.e(E.Template{ .tag = tag, .head = head, .parts = partsGroup }, left.loc); - }, - .t_open_bracket => { - // When parsing a decorator, ignore EIndex expressions since they may be - // part of a computed property: - // - // class Foo { - // @foo ['computed']() {} - // } - // - // This matches the behavior of the TypeScript compiler. - if (flags == .ts_decorator) { - return left; - } - - try p.lexer.next(); - - // Allow "in" inside the brackets - const old_allow_in = p.allow_in; - p.allow_in = true; - - const index = try p.parseExpr(.lowest); - - p.allow_in = old_allow_in; - - try p.lexer.expect(.t_close_bracket); - - left = p.e(E.Index{ - .target = left, - .index = index, - .optional_chain = old_optional_chain, - }, left.loc); - optional_chain = old_optional_chain; - }, - .t_open_paren => { - if (level.gte(.call)) { - return left; - } - - const list_loc = try p.parseCallArgs(); - left = p.e( - E.Call{ - .target = left, - .args = list_loc.list, - .close_paren_loc = list_loc.loc, - .optional_chain = old_optional_chain, - }, - left.loc, - ); - optional_chain = old_optional_chain; - }, - .t_question => { - if (level.gte(.conditional)) { - return left; - } - try p.lexer.next(); - - // Stop now if we're parsing one of these: - // "(a?) => {}" - // "(a?: b) => {}" - // "(a?, b?) => {}" - if (is_typescript_enabled and left.loc.start == p.latest_arrow_arg_loc.start and (p.lexer.token == .t_colon or - p.lexer.token == .t_close_paren or p.lexer.token == .t_comma)) - { - if (errors == null) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - errors.?.invalid_expr_after_question = p.lexer.range(); - return left; - } - - // Allow "in" in between "?" and ":" - const old_allow_in = p.allow_in; - p.allow_in = true; - - const yes = try p.parseExpr(.comma); - - p.allow_in = old_allow_in; - - try p.lexer.expect(.t_colon); - const no = try p.parseExpr(.comma); - - left = p.e(E.If{ - .test_ = left, - .yes = yes, - .no = no, - }, left.loc); - }, - .t_exclamation => { - // Skip over TypeScript non-null assertions - if (p.lexer.has_newline_before) { - return left; - } - - if (!is_typescript_enabled) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - if (level.gte(.postfix)) { - return left; - } - - try p.lexer.next(); - optional_chain = old_optional_chain; - }, - .t_minus_minus => { - if (p.lexer.has_newline_before or level.gte(.postfix)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Unary{ .op = .un_post_dec, .value = left }, left.loc); - }, - .t_plus_plus => { - if (p.lexer.has_newline_before or level.gte(.postfix)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Unary{ .op = .un_post_inc, .value = left }, left.loc); - }, - .t_comma => { - if (level.gte(.comma)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_comma, .left = left, .right = try p.parseExpr(.comma) }, left.loc); - }, - .t_plus => { - if (level.gte(.add)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_add, .left = left, .right = try p.parseExpr(.add) }, left.loc); - }, - .t_plus_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_add_assign, .left = left, .right = try p.parseExpr(@intToEnum(Op.Level, @enumToInt(Op.Level.assign) - 1)) }, left.loc); - }, - .t_minus => { - if (level.gte(.add)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_sub, .left = left, .right = try p.parseExpr(.add) }, left.loc); - }, - .t_minus_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_sub_assign, .left = left, .right = try p.parseExpr(Op.Level.sub(Op.Level.assign, 1)) }, left.loc); - }, - .t_asterisk => { - if (level.gte(.multiply)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_mul, .left = left, .right = try p.parseExpr(.multiply) }, left.loc); - }, - .t_asterisk_asterisk => { - if (level.gte(.exponentiation)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_pow, .left = left, .right = try p.parseExpr(Op.Level.exponentiation.sub(1)) }, left.loc); - }, - .t_asterisk_asterisk_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_pow_assign, .left = left, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); - }, - .t_asterisk_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_mul_assign, .left = left, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); - }, - .t_percent => { - if (level.gte(.multiply)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_rem, .left = left, .right = try p.parseExpr(Op.Level.multiply) }, left.loc); - }, - .t_percent_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_rem_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_slash => { - if (level.gte(.multiply)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_div, .left = left, .right = try p.parseExpr(Level.multiply) }, left.loc); - }, - .t_slash_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_div_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_equals_equals => { - if (level.gte(.equals)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_loose_eq, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); - }, - .t_exclamation_equals => { - if (level.gte(.equals)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_loose_ne, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); - }, - .t_equals_equals_equals => { - if (level.gte(.equals)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_strict_eq, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); - }, - .t_exclamation_equals_equals => { - if (level.gte(.equals)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_strict_ne, .left = left, .right = try p.parseExpr(Level.equals) }, left.loc); - }, - .t_less_than => { - // TypeScript allows type arguments to be specified with angle brackets - // inside an expression. Unlike in other languages, this unfortunately - // appears to require backtracking to parse. - if (is_typescript_enabled and p.trySkipTypeScriptTypeArgumentsWithBacktracking()) { - optional_chain = old_optional_chain; - continue; - } - - if (level.gte(.compare)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_lt, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_less_than_equals => { - if (level.gte(.compare)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_le, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_greater_than => { - if (level.gte(.compare)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_gt, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_greater_than_equals => { - if (level.gte(.compare)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_ge, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_less_than_less_than => { - if (level.gte(.shift)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_shl, .left = left, .right = try p.parseExpr(.shift) }, left.loc); - }, - .t_less_than_less_than_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_shl_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_greater_than_greater_than => { - if (level.gte(.shift)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_shr, .left = left, .right = try p.parseExpr(.shift) }, left.loc); - }, - .t_greater_than_greater_than_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_shr_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_greater_than_greater_than_greater_than => { - if (level.gte(.shift)) { - return left; - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_u_shr, .left = left, .right = try p.parseExpr(.shift) }, left.loc); - }, - .t_greater_than_greater_than_greater_than_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_u_shr_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_question_question => { - if (level.gte(.nullish_coalescing)) { - return left; - } - try p.lexer.next(); - const prev = left; - left = p.e(E.Binary{ .op = .bin_nullish_coalescing, .left = prev, .right = try p.parseExpr(.nullish_coalescing) }, left.loc); - }, - .t_question_question_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_nullish_coalescing_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_bar_bar => { - if (level.gte(.logical_or)) { - return left; - } - - // Prevent "||" inside "??" from the right - if (level.eql(.nullish_coalescing)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - try p.lexer.next(); - const right = try p.parseExpr(.logical_or); - left = p.e(E.Binary{ .op = Op.Code.bin_logical_or, .left = left, .right = right }, left.loc); - - if (level.lt(.nullish_coalescing)) { - left = try p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); - - if (p.lexer.token == .t_question_question) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - } - }, - .t_bar_bar_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_logical_or_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_ampersand_ampersand => { - if (level.gte(.logical_and)) { - return left; - } - - // Prevent "&&" inside "??" from the right - if (level.eql(.nullish_coalescing)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_logical_and, .left = left, .right = try p.parseExpr(.logical_and) }, left.loc); - - // Prevent "&&" inside "??" from the left - if (level.lt(.nullish_coalescing)) { - left = try p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); - - if (p.lexer.token == .t_question_question) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - } - }, - .t_ampersand_ampersand_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_logical_and_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_bar => { - if (level.gte(.bitwise_or)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_or, .left = left, .right = try p.parseExpr(.bitwise_or) }, left.loc); - }, - .t_bar_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_or_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_ampersand => { - if (level.gte(.bitwise_and)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_and, .left = left, .right = try p.parseExpr(.bitwise_and) }, left.loc); - }, - .t_ampersand_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_and_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_caret => { - if (level.gte(.bitwise_xor)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_xor, .left = left, .right = try p.parseExpr(.bitwise_xor) }, left.loc); - }, - .t_caret_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_bitwise_xor_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_equals => { - if (level.gte(.assign)) { - return left; - } - - try p.lexer.next(); - - left = p.e(E.Binary{ .op = .bin_assign, .left = left, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); - }, - .t_in => { - if (level.gte(.compare) or !p.allow_in) { - return left; - } - - // Warn about "!a in b" instead of "!(a in b)" - switch (left.data) { - .e_unary => |unary| { - if (unary.op == .un_not) { - // TODO: - // p.log.addRangeWarning(source: ?Source, r: Range, text: string) - } - }, - else => {}, - } - - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_in, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_instanceof => { - if (level.gte(.compare)) { - return left; - } - - // Warn about "!a instanceof b" instead of "!(a instanceof b)". Here's an - // example of code with this problem: https://github.com/mrdoob/three.js/pull/11182. - if (!p.options.suppress_warnings_about_weird_code) { - switch (left.data) { - .e_unary => |unary| { - if (unary.op == .un_not) { - // TODO: - // p.log.addRangeWarning(source: ?Source, r: Range, text: string) - } - }, - else => {}, - } - } - try p.lexer.next(); - left = p.e(E.Binary{ .op = .bin_instanceof, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - else => { - // Handle the TypeScript "as" operator - if (is_typescript_enabled and level.lt(.compare) and !p.lexer.has_newline_before and p.lexer.isContextualKeyword("as")) { - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - - // These tokens are not allowed to follow a cast expression. This isn't - // an outright error because it may be on a new line, in which case it's - // the start of a new expression when it's after a cast: - // - // x = y as z - // (something); - // - switch (p.lexer.token) { - .t_plus_plus, - .t_minus_minus, - .t_no_substitution_template_literal, - .t_template_head, - .t_open_paren, - .t_open_bracket, - .t_question_dot, - => { - p.forbid_suffix_after_as_loc = p.lexer.loc(); - return left; - }, - else => {}, - } - - if (p.lexer.token.isAssign()) { - p.forbid_suffix_after_as_loc = p.lexer.loc(); - return left; - } - continue; - } - - return left; - }, - } - } - } - - pub const MacroVisitor = struct { - p: *P, - - loc: logger.Loc, - - pub fn visitImport(this: MacroVisitor, import_data: js_ast.Macro.JSNode.ImportData) void { - var p = this.p; - - const record_id = p.addImportRecord(.stmt, this.loc, import_data.path); - var record: *ImportRecord = &p.import_records.items[record_id]; - record.was_injected_by_macro = true; - p.macro.imports.ensureUnusedCapacity(import_data.import.items.len) catch unreachable; - var import = import_data.import; - import.import_record_index = record_id; - - p.is_import_item.ensureUnusedCapacity( - p.allocator, - @intCast(u32, p.is_import_item.count() + import.items.len), - ) catch unreachable; - - for (import.items) |*clause| { - const import_hash_name = clause.original_name; - - if (strings.eqlComptime(clause.alias, "default")) { - var non_unique_name = record.path.name.nonUniqueNameString(p.allocator) catch unreachable; - clause.original_name = std.fmt.allocPrint(p.allocator, "{s}_default", .{non_unique_name}) catch unreachable; - record.contains_default_alias = true; - } - const name_ref = p.declareSymbol(.import, this.loc, clause.original_name) catch unreachable; - clause.name = LocRef{ .loc = this.loc, .ref = name_ref }; - - p.is_import_item.putAssumeCapacity(name_ref, .{}); - - p.macro.imports.putAssumeCapacity(js_ast.Macro.JSNode.SymbolMap.generateImportHash(import_hash_name, import_data.path), name_ref); - - // Ensure we don't accidentally think this is an export from - } - - p.macro.prepend_stmts.append(p.s(import, this.loc)) catch unreachable; - } - }; - - pub fn panic(p: *P, comptime str: string, args: anytype) noreturn { - @setCold(true); - var panic_buffer = p.allocator.alloc(u8, 32 * 1024) catch unreachable; - var panic_stream = std.io.fixedBufferStream(panic_buffer); - p.log.addRangeErrorFmt(p.source, p.lexer.range(), p.allocator, str, args) catch unreachable; - - p.log.printForLogLevel( - panic_stream.writer(), - ) catch unreachable; - Global.panic("{s}", .{panic_buffer[0..panic_stream.pos]}); - } - - pub fn parsePrefix(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { - const loc = p.lexer.loc(); - const l = @enumToInt(level); - // Output.print("Parse Prefix {s}:{s} @{s} ", .{ p.lexer.token, p.lexer.raw(), @tagName(level) }); - - switch (p.lexer.token) { - .t_super => { - const superRange = p.lexer.range(); - try p.lexer.next(); - - switch (p.lexer.token) { - .t_open_paren => { - if (l < @enumToInt(Level.call) and p.fn_or_arrow_data_parse.allow_super_call) { - return p.e(E.Super{}, loc); - } - }, - .t_dot, .t_open_bracket => { - if (p.fn_or_arrow_data_parse.allow_super_property) { - return p.e(E.Super{}, loc); - } - }, - else => {}, - } - - p.log.addRangeError(p.source, superRange, "Unexpected \"super\"") catch unreachable; - return p.e(E.Super{}, loc); - }, - .t_open_paren => { - try p.lexer.next(); - - // Arrow functions aren't allowed in the middle of expressions - if (level.gt(.assign)) { - // Allow "in" inside parentheses - const oldAllowIn = p.allow_in; - p.allow_in = true; - - var value = try p.parseExpr(Level.lowest); - p.markExprAsParenthesized(&value); - try p.lexer.expect(.t_close_paren); - - p.allow_in = oldAllowIn; - return value; - } - - return p.parseParenExpr(loc, level, ParenExprOpts{}); - }, - .t_false => { - try p.lexer.next(); - return p.e(E.Boolean{ .value = false }, loc); - }, - .t_true => { - try p.lexer.next(); - return p.e(E.Boolean{ .value = true }, loc); - }, - .t_null => { - try p.lexer.next(); - return p.e(E.Null{}, loc); - }, - .t_this => { - if (p.fn_or_arrow_data_parse.is_this_disallowed) { - p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"this\" here") catch unreachable; - } - try p.lexer.next(); - return Expr{ .data = Prefill.Data.This, .loc = loc }; - }, - .t_private_identifier => { - if (!p.allow_private_identifiers or !p.allow_in or level.gte(.compare)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - const name = p.lexer.identifier; - try p.lexer.next(); - - // Check for "#foo in bar" - if (p.lexer.token != .t_in) { - try p.lexer.expected(.t_in); - } - - return p.e(E.PrivateIdentifier{ .ref = try p.storeNameInRef(name) }, loc); - }, - .t_identifier => { - const name = p.lexer.identifier; - const name_range = p.lexer.range(); - const raw = p.lexer.raw(); - - try p.lexer.next(); - - // Handle async and await expressions - switch (AsyncPrefixExpression.find(name)) { - .is_async => { - if ((raw.ptr == name.ptr and raw.len == name.len) or AsyncPrefixExpression.find(raw) == .is_async) { - return try p.parseAsyncPrefixExpr(name_range, level); - } - }, - - .is_await => { - switch (p.fn_or_arrow_data_parse.allow_await) { - .forbid_all => { - p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be used here") catch unreachable; - }, - .allow_expr => { - if (AsyncPrefixExpression.find(raw) != .is_await) { - p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be escaped") catch unreachable; - } else { - if (p.fn_or_arrow_data_parse.is_top_level) { - p.top_level_await_keyword = name_range; - } - - if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) { - p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_await = name_range; - } - - const value = try p.parseExpr(.prefix); - if (p.lexer.token == T.t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Await{ .value = value }, loc); - } - }, - else => {}, - } - }, - - .is_yield => { - switch (p.fn_or_arrow_data_parse.allow_yield) { - .forbid_all => { - p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be used here") catch unreachable; - }, - .allow_expr => { - if (AsyncPrefixExpression.find(raw) != .is_yield) { - p.log.addRangeError(p.source, name_range, "The keyword \"yield\" cannot be escaped") catch unreachable; - } else { - if (level.gt(.assign)) { - p.log.addRangeError(p.source, name_range, "Cannot use a \"yield\" here without parentheses") catch unreachable; - } - - if (p.fn_or_arrow_data_parse.track_arrow_arg_errors) { - p.fn_or_arrow_data_parse.arrow_arg_errors.invalid_expr_yield = name_range; - } - - return p.parseYieldExpr(loc); - } - }, - // .allow_ident => { - - // }, - else => { - // Try to gracefully recover if "yield" is used in the wrong place - if (!p.lexer.has_newline_before) { - switch (p.lexer.token) { - .t_null, .t_identifier, .t_false, .t_true, .t_numeric_literal, .t_big_integer_literal, .t_string_literal => { - p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" outside a generator function") catch unreachable; - }, - else => {}, - } - } - }, - } - }, - .none => {}, - } - - // Handle the start of an arrow expression - if (p.lexer.token == .t_equals_greater_than and level.lte(.assign)) { - const ref = p.storeNameInRef(name) catch unreachable; - var args = p.allocator.alloc(Arg, 1) catch unreachable; - args[0] = Arg{ .binding = p.b(B.Identifier{ - .ref = ref, - }, loc) }; - - _ = p.pushScopeForParsePass(.function_args, loc) catch unreachable; - defer p.popScope(); - - var fn_or_arrow_data = FnOrArrowDataParse{}; - const ret = p.e(try p.parseArrowBody(args, &fn_or_arrow_data), loc); - return ret; - } - - const ref = p.storeNameInRef(name) catch unreachable; - - return Expr.initIdentifier(ref, loc); - }, - .t_string_literal, .t_no_substitution_template_literal => { - return try p.parseStringLiteral(); - }, - .t_template_head => { - const head = p.lexer.toEString(); - - const parts = try p.parseTemplateParts(false); - - // Check if TemplateLiteral is unsupported. We don't care for this product.` - // if () - - return p.e(E.Template{ - .head = head, - .parts = parts, - }, loc); - }, - .t_numeric_literal => { - const value = p.e(E.Number{ .value = p.lexer.number }, loc); - // p.checkForLegacyOctalLiteral() - try p.lexer.next(); - return value; - }, - .t_big_integer_literal => { - const value = p.lexer.identifier; - // markSyntaxFeature bigInt - try p.lexer.next(); - return p.e(E.BigInt{ .value = value }, loc); - }, - .t_slash, .t_slash_equals => { - try p.lexer.scanRegExp(); - // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later - defer p.lexer.regex_flags_start = null; - const value = p.lexer.raw(); - try p.lexer.next(); - - return p.e(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc); - }, - .t_void => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ - .op = .un_void, - .value = value, - }, loc); - }, - .t_typeof => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ .op = .un_typeof, .value = value }, loc); - }, - .t_delete => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - if (value.data == .e_index) { - if (value.data.e_index.index.data == .e_private_identifier) { - const private = value.data.e_index.index.data.e_private_identifier; - const name = p.loadNameFromRef(private.ref); - const range = logger.Range{ .loc = value.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeErrorFmt(p.source, range, p.allocator, "Deleting the private name \"{s}\" is forbidden", .{name}) catch unreachable; - } - } - - return p.e(E.Unary{ .op = .un_delete, .value = value }, loc); - }, - .t_plus => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ .op = .un_pos, .value = value }, loc); - }, - .t_minus => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ .op = .un_neg, .value = value }, loc); - }, - .t_tilde => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ .op = .un_cpl, .value = value }, loc); - }, - .t_exclamation => { - try p.lexer.next(); - const value = try p.parseExpr(.prefix); - if (p.lexer.token == .t_asterisk_asterisk) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - return p.e(E.Unary{ .op = .un_not, .value = value }, loc); - }, - .t_minus_minus => { - try p.lexer.next(); - return p.e(E.Unary{ .op = .un_pre_dec, .value = try p.parseExpr(.prefix) }, loc); - }, - .t_plus_plus => { - try p.lexer.next(); - return p.e(E.Unary{ .op = .un_pre_inc, .value = try p.parseExpr(.prefix) }, loc); - }, - .t_function => { - return try p.parseFnExpr(loc, false, logger.Range.None); - }, - .t_class => { - const classKeyword = p.lexer.range(); - // markSyntaxFEatuer class - try p.lexer.next(); - var name: ?js_ast.LocRef = null; - - _ = p.pushScopeForParsePass(.class_name, loc) catch unreachable; - - // Parse an optional class name - if (p.lexer.token == .t_identifier) { - const name_text = p.lexer.identifier; - if (!is_typescript_enabled or !strings.eqlComptime(name_text, "implements")) { - if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) { - p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here") catch unreachable; - } - - name = js_ast.LocRef{ - .loc = p.lexer.loc(), - .ref = p.newSymbol( - .other, - name_text, - ) catch unreachable, - }; - try p.lexer.next(); - } - } - - // Even anonymous classes can have TypeScript type parameters - if (is_typescript_enabled) { - try p.skipTypeScriptTypeParameters(); - } - - const class = try p.parseClass(classKeyword, name, ParseClassOptions{}); - p.popScope(); - - return p.e(class, loc); - }, - .t_new => { - try p.lexer.next(); - - // Special-case the weird "new.target" expression here - if (p.lexer.token == .t_dot) { - try p.lexer.next(); - - if (p.lexer.token != .t_identifier or !strings.eqlComptime(p.lexer.raw(), "target")) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - const range = logger.Range{ .loc = loc, .len = p.lexer.range().end().start - loc.start }; - - try p.lexer.next(); - return p.e(E.NewTarget{ .range = range }, loc); - } - - const target = try p.parseExprWithFlags(.member, flags); - var args = ExprNodeList{}; - - if (comptime is_typescript_enabled) { - // Skip over TypeScript non-null assertions - if (p.lexer.token == .t_exclamation and !p.lexer.has_newline_before) { - try p.lexer.next(); - } - - // Skip over TypeScript type arguments here if there are any - if (p.lexer.token == .t_less_than) { - _ = p.trySkipTypeScriptTypeArgumentsWithBacktracking(); - } - } - - var close_parens_loc = logger.Loc.Empty; - if (p.lexer.token == .t_open_paren) { - const call_args = try p.parseCallArgs(); - args = call_args.list; - close_parens_loc = call_args.loc; - } - - return p.e(E.New{ - .target = target, - .args = args, - .close_parens_loc = close_parens_loc, - }, loc); - }, - .t_open_bracket => { - try p.lexer.next(); - var is_single_line = !p.lexer.has_newline_before; - var items = ListManaged(Expr).init(p.allocator); - var self_errors = DeferredErrors{}; - var comma_after_spread = logger.Loc{}; - - // Allow "in" inside arrays - const old_allow_in = p.allow_in; - p.allow_in = true; - - while (p.lexer.token != .t_close_bracket) { - switch (p.lexer.token) { - .t_comma => { - items.append(Expr{ .data = Prefill.Data.EMissing, .loc = p.lexer.loc() }) catch unreachable; - }, - .t_dot_dot_dot => { - if (errors != null) - errors.?.array_spread_feature = p.lexer.range(); - - const dots_loc = p.lexer.loc(); - try p.lexer.next(); - items.append( - p.e(E.Spread{ .value = try p.parseExprOrBindings(.comma, &self_errors) }, dots_loc), - ) catch unreachable; - - // Commas are not allowed here when destructuring - if (p.lexer.token == .t_comma) { - comma_after_spread = p.lexer.loc(); - } - }, - else => { - items.append( - try p.parseExprOrBindings(.comma, &self_errors), - ) catch unreachable; - }, - } - - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - try p.lexer.next(); - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - const close_bracket_loc = p.lexer.loc(); - try p.lexer.expect(.t_close_bracket); - p.allow_in = old_allow_in; - - // Is this a binding pattern? - if (p.willNeedBindingPattern()) { - // noop - } else if (errors == null) { - // Is this an expression? - p.logExprErrors(&self_errors); - } else { - // In this case, we can't distinguish between the two yet - self_errors.mergeInto(errors.?); - } - return p.e(E.Array{ - .items = ExprNodeList.fromList(items), - .comma_after_spread = comma_after_spread.toNullable(), - .is_single_line = is_single_line, - .close_bracket_loc = close_bracket_loc, - }, loc); - }, - .t_open_brace => { - try p.lexer.next(); - var is_single_line = !p.lexer.has_newline_before; - var properties = ListManaged(G.Property).init(p.allocator); - var self_errors = DeferredErrors{}; - var comma_after_spread: logger.Loc = logger.Loc{}; - - // Allow "in" inside object literals - const old_allow_in = p.allow_in; - p.allow_in = true; - - while (p.lexer.token != .t_close_brace) { - if (p.lexer.token == .t_dot_dot_dot) { - try p.lexer.next(); - properties.append(G.Property{ .kind = .spread, .value = try p.parseExpr(.comma) }) catch unreachable; - - // Commas are not allowed here when destructuring - if (p.lexer.token == .t_comma) { - comma_after_spread = p.lexer.loc(); - } - } else { - // This property may turn out to be a type in TypeScript, which should be ignored - var propertyOpts = PropertyOpts{}; - if (try p.parseProperty(.normal, &propertyOpts, &self_errors)) |prop| { - if (comptime Environment.allow_assert) { - assert(prop.key != null or prop.value != null); - } - properties.append(prop) catch unreachable; - } - } - - if (p.lexer.token != .t_comma) { - break; - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - try p.lexer.next(); - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - } - - if (p.lexer.has_newline_before) { - is_single_line = false; - } - - const close_brace_loc = p.lexer.loc(); - try p.lexer.expect(.t_close_brace); - p.allow_in = old_allow_in; - - if (p.willNeedBindingPattern()) { - // Is this a binding pattern? - } else if (errors == null) { - // Is this an expression? - p.logExprErrors(&self_errors); - } else { - // In this case, we can't distinguish between the two yet - self_errors.mergeInto(errors.?); - } - - return p.e(E.Object{ - .properties = G.Property.List.fromList(properties), - .comma_after_spread = if (comma_after_spread.start > 0) - comma_after_spread - else - null, - .is_single_line = is_single_line, - .close_brace_loc = close_brace_loc, - }, loc); - }, - .t_less_than => { - // This is a very complicated and highly ambiguous area of TypeScript - // syntax. Many similar-looking things are overloaded. - // - // TS: - // - // A type cast: - // <A>(x) - // <[]>(x) - // <A[]>(x) - // - // An arrow function with type parameters: - // <A>(x) => {} - // <A, B>(x) => {} - // <A = B>(x) => {} - // <A extends B>(x) => {} - // - // TSX: - // - // A JSX element: - // <A>(x) => {}</A> - // <A extends>(x) => {}</A> - // <A extends={false}>(x) => {}</A> - // - // An arrow function with type parameters: - // <A, B>(x) => {} - // <A extends B>(x) => {} - // - // A syntax error: - // <[]>(x) - // <A[]>(x) - // <A>(x) => {} - // <A = B>(x) => {} - if (comptime is_typescript_enabled and is_jsx_enabled) { - var oldLexer = std.mem.toBytes(p.lexer); - - try p.lexer.next(); - // Look ahead to see if this should be an arrow function instead - var is_ts_arrow_fn = false; - - if (p.lexer.token == .t_identifier) { - try p.lexer.next(); - if (p.lexer.token == .t_comma) { - is_ts_arrow_fn = true; - } else if (p.lexer.token == .t_extends) { - try p.lexer.next(); - is_ts_arrow_fn = p.lexer.token != .t_equals and p.lexer.token != .t_greater_than; - } - } - - // Restore the lexer - p.lexer = std.mem.bytesToValue(@TypeOf(p.lexer), &oldLexer); - - if (is_ts_arrow_fn) { - try p.skipTypeScriptTypeParameters(); - try p.lexer.expect(.t_open_paren); - return try p.parseParenExpr(loc, level, ParenExprOpts{ .force_arrow_fn = true }); - } - } - - if (is_jsx_enabled) { - // Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<" - try p.lexer.nextInsideJSXElement(); - const element = try p.parseJSXElement(loc); - - // The call to parseJSXElement() above doesn't consume the last - // TGreaterThan because the caller knows what Next() function to call. - // Use Next() instead of NextInsideJSXElement() here since the next - // token is an expression. - try p.lexer.next(); - return element; - } - - if (is_typescript_enabled) { - // This is either an old-style type cast or a generic lambda function - - // "<T>(x)" - // "<T>(x) => {}" - if (p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()) { - try p.lexer.expect(.t_open_paren); - return p.parseParenExpr(loc, level, ParenExprOpts{}); - } - - // "<T>x" - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - try p.lexer.expectGreaterThan(false); - return p.parsePrefix(level, errors, flags); - } - - try p.lexer.unexpected(); - return error.SyntaxError; - }, - .t_import => { - try p.lexer.next(); - return p.parseImportExpr(loc, level); - }, - else => { - try p.lexer.unexpected(); - return error.SyntaxError; - }, - } - return error.SyntaxError; - } - - // esbuild's version of this function is much more complicated. - // I'm not sure why defines is strictly relevant for this case - // do people do <API_URL>? - fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { - p.recordUsage(ref); - return p.e(E.Identifier{ - .ref = ref, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, loc); - } - - // Note: The caller has already parsed the "import" keyword - fn parseImportExpr(p: *P, loc: logger.Loc, level: Level) anyerror!Expr { - // Parse an "import.meta" expression - if (p.lexer.token == .t_dot) { - p.es6_import_keyword = js_lexer.rangeOfIdentifier(p.source, loc); - try p.lexer.next(); - if (p.lexer.isContextualKeyword("meta")) { - try p.lexer.next(); - p.has_import_meta = true; - return p.e(E.ImportMeta{}, loc); - } else { - try p.lexer.expectedString("\"meta\""); - } - } - - if (level.gt(.call)) { - const r = js_lexer.rangeOfIdentifier(p.source, loc); - p.log.addRangeError(p.source, r, "Cannot use an \"import\" expression here without parentheses") catch unreachable; - } - - // allow "in" inside call arguments; - var old_allow_in = p.allow_in; - p.allow_in = true; - - p.lexer.preserve_all_comments_before = true; - try p.lexer.expect(.t_open_paren); - const comments = p.lexer.comments_to_preserve_before.toOwnedSlice(); - p.lexer.preserve_all_comments_before = false; - - const value = try p.parseExpr(.comma); - - if (p.lexer.token == .t_comma) { - // "import('./foo.json', )" - try p.lexer.next(); - - if (p.lexer.token != .t_close_paren) { - // for now, we silently strip import assertions - // "import('./foo.json', { assert: { type: 'json' } })" - _ = try p.parseExpr(.comma); - - if (p.lexer.token == .t_comma) { - // "import('./foo.json', { assert: { type: 'json' } }, , )" - try p.lexer.next(); - } - } - } - - try p.lexer.expect(.t_close_paren); - - p.allow_in = old_allow_in; - - if (comptime only_scan_imports_and_do_not_visit) { - if (value.data == .e_string and value.data.e_string.isUTF8() and value.data.e_string.isPresent()) { - const import_record_index = p.addImportRecord(.dynamic, value.loc, value.data.e_string.slice(p.allocator)); - - return p.e(E.Import{ - .expr = value, - .leading_interior_comments = comments, - .import_record_index = import_record_index, - }, loc); - } - } - - return p.e(E.Import{ .expr = value, .leading_interior_comments = comments, .import_record_index = 0 }, loc); - } - - fn parseJSXPropValueIdentifier(p: *P, previous_string_with_backslash_loc: *logger.Loc) !Expr { - // Use NextInsideJSXElement() not Next() so we can parse a JSX-style string literal - try p.lexer.nextInsideJSXElement(); - if (p.lexer.token == .t_string_literal) { - previous_string_with_backslash_loc.start = std.math.max(p.lexer.loc().start, p.lexer.previous_backslash_quote_in_jsx.loc.start); - const expr = p.e(p.lexer.toEString(), previous_string_with_backslash_loc.*); - - try p.lexer.nextInsideJSXElement(); - return expr; - } else { - // Use Expect() not ExpectInsideJSXElement() so we can parse expression tokens - try p.lexer.expect(.t_open_brace); - const value = try p.parseExpr(.lowest); - - try p.lexer.expectInsideJSXElement(.t_close_brace); - return value; - } - } - - fn parseJSXElement(p: *P, loc: logger.Loc) anyerror!Expr { - if (only_scan_imports_and_do_not_visit) { - p.needs_jsx_import = true; - } - - var tag = try JSXTag.parse(P, p); - - // The tag may have TypeScript type arguments: "<Foo<T>/>" - if (is_typescript_enabled) { - // Pass a flag to the type argument skipper because we need to call - _ = try p.skipTypeScriptTypeArguments(true); - } - - var previous_string_with_backslash_loc = logger.Loc{}; - var properties = G.Property.List{}; - var key_prop: ?ExprNodeIndex = null; - var flags = Flags.JSXElement.Bitset{}; - var start_tag: ?ExprNodeIndex = null; - - // Fragments don't have props - // Fragments of the form "React.Fragment" are not parsed as fragments. - if (@as(JSXTag.TagType, tag.data) == .tag) { - start_tag = tag.data.tag; - var spread_loc: logger.Loc = logger.Loc.Empty; - var props = ListManaged(G.Property).init(p.allocator); - var key_prop_i: i32 = -1; - var spread_prop_i: i32 = -1; - var i: i32 = 0; - parse_attributes: while (true) { - switch (p.lexer.token) { - .t_identifier => { - defer i += 1; - // Parse the prop name - var key_range = p.lexer.range(); - const prop_name_literal = p.lexer.identifier; - const special_prop = E.JSXElement.SpecialProp.Map.get(prop_name_literal) orelse E.JSXElement.SpecialProp.any; - try p.lexer.nextInsideJSXElement(); - - if (special_prop == .key) { - - // <ListItem key> - if (p.lexer.token != .t_equals) { - // Unlike Babel, we're going to just warn here and move on. - try p.log.addWarning(p.source, key_range.loc, "\"key\" prop ignored. Must be a string, number or symbol."); - continue; - } - - key_prop_i = i; - key_prop = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc); - continue; - } - - const prop_name = p.e(E.String{ .data = prop_name_literal }, key_range.loc); - - // Parse the value - var value: Expr = undefined; - if (p.lexer.token != .t_equals) { - - // Implicitly true value - // <button selected> - value = p.e(E.Boolean{ .value = true }, logger.Loc{ .start = key_range.loc.start + key_range.len }); - } else { - value = try p.parseJSXPropValueIdentifier(&previous_string_with_backslash_loc); - if (comptime jsx_transform_type == .solid) { - switch (value.knownPrimitive()) { - .unknown => { - flags.insert(.has_any_dynamic); - }, - else => {}, - } - } - } - - try props.append(G.Property{ .key = prop_name, .value = value }); - }, - .t_open_brace => { - defer i += 1; - // Use Next() not ExpectInsideJSXElement() so we can parse "..." - try p.lexer.next(); - - switch (p.lexer.token) { - .t_dot_dot_dot => { - try p.lexer.next(); - - spread_prop_i = i; - spread_loc = p.lexer.loc(); - try props.append(G.Property{ .value = try p.parseExpr(.comma), .kind = .spread }); - }, - // This implements - // <div {foo} /> - // -> - // <div foo={foo} /> - T.t_identifier => { - // we need to figure out what the key they mean is - // to do that, we must determine the key name - const expr = try p.parseExpr(Level.lowest); - - const key = brk: { - switch (expr.data) { - .e_import_identifier => |ident| { - break :brk p.e(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); - }, - .e_identifier => |ident| { - break :brk p.e(E.String{ .data = p.loadNameFromRef(ident.ref) }, expr.loc); - }, - .e_dot => |dot| { - break :brk p.e(E.String{ .data = dot.name }, dot.name_loc); - }, - .e_index => |index| { - if (index.index.data == .e_string) { - break :brk index.index; - } - }, - else => {}, - } - - // If we get here, it's invalid - try p.log.addError(p.source, expr.loc, "Invalid JSX prop shorthand, must be identifier, dot or string"); - return error.SyntaxError; - }; - - if (comptime jsx_transform_type == .solid) { - switch (expr.knownPrimitive()) { - .unknown => { - flags.insert(.has_any_dynamic); - }, - else => {}, - } - } - - try props.append(G.Property{ .value = expr, .key = key, .kind = .normal }); - }, - // This implements - // <div {"foo"} /> - // <div {'foo'} /> - // -> - // <div foo="foo" /> - // note: template literals are not supported, operations on strings are not supported either - T.t_string_literal => { - const key = p.e(p.lexer.toEString(), p.lexer.loc()); - try p.lexer.next(); - try props.append(G.Property{ .value = key, .key = key, .kind = .normal }); - }, - - else => try p.lexer.unexpected(), - } - - try p.lexer.nextInsideJSXElement(); - }, - else => { - break :parse_attributes; - }, - } - } - - const is_key_before_rest = key_prop_i > -1 and spread_prop_i > key_prop_i; - flags.setPresent(.is_key_before_rest, is_key_before_rest); - if (is_key_before_rest and p.options.jsx.runtime == .automatic and !p.has_classic_runtime_warned) { - try p.log.addWarning(p.source, spread_loc, "\"key\" prop before a {...spread} is deprecated in JSX. Falling back to classic runtime."); - p.has_classic_runtime_warned = true; - } - properties = G.Property.List.fromList(props); - } - - // People sometimes try to use the output of "JSON.stringify()" as a JSX - // attribute when automatically-generating JSX code. Doing so is incorrect - // because JSX strings work like XML instead of like JS (since JSX is XML-in- - // JS). Specifically, using a backslash before a quote does not cause it to - // be escaped: - // - // JSX ends the "content" attribute here and sets "content" to 'some so-called \\' - // v - // <Button content="some so-called \"button text\"" /> - // ^ - // There is no "=" after the JSX attribute "text", so we expect a ">" - // - // This code special-cases this error to provide a less obscure error message. - if (p.lexer.token == .t_syntax_error and strings.eqlComptime(p.lexer.raw(), "\\") and previous_string_with_backslash_loc.start > 0) { - const r = p.lexer.range(); - // Not dealing with this right now. - try p.log.addRangeError(p.source, r, "Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead"); - return error.SyntaxError; - } - - // A slash here is a self-closing element - if (p.lexer.token == .t_slash) { - const close_tag_loc = p.lexer.loc(); - // Use NextInsideJSXElement() not Next() so we can parse ">>" as ">" - - try p.lexer.nextInsideJSXElement(); - - if (p.lexer.token != .t_greater_than) { - try p.lexer.expected(.t_greater_than); - } - - return p.e(E.JSXElement{ - .tag = start_tag, - .properties = properties, - .key = key_prop, - .flags = flags, - .close_tag_loc = close_tag_loc, - }, loc); - } - - // Use ExpectJSXElementChild() so we parse child strings - try p.lexer.expectJSXElementChild(.t_greater_than); - var children = ListManaged(Expr).init(p.allocator); - // var last_element_i: usize = 0; - - while (true) { - switch (p.lexer.token) { - .t_string_literal => { - try children.append(p.e(p.lexer.toEString(), loc)); - try p.lexer.nextJSXElementChild(); - }, - .t_open_brace => { - // Use Next() instead of NextJSXElementChild() here since the next token is an expression - try p.lexer.next(); - - // The "..." here is ignored (it's used to signal an array type in TypeScript) - if (p.lexer.token == .t_dot_dot_dot and is_typescript_enabled) { - try p.lexer.next(); - } - - // The expression is optional, and may be absent - if (p.lexer.token != .t_close_brace) { - if (comptime jsx_transform_type == .solid) { - const child = try p.parseExpr(.lowest); - switch (child.knownPrimitive()) { - .unknown => { - flags.insert(.has_any_dynamic); - }, - else => {}, - } - try children.append(child); - } else { - try children.append(try p.parseExpr(.lowest)); - } - } - - // Use ExpectJSXElementChild() so we parse child strings - try p.lexer.expectJSXElementChild(.t_close_brace); - }, - .t_less_than => { - const less_than_loc = p.lexer.loc(); - try p.lexer.nextInsideJSXElement(); - - if (p.lexer.token != .t_slash) { - // This is a child element - const child = try p.parseJSXElement(less_than_loc); - if (comptime jsx_transform_type == .solid) { - // if (!flags.contains(.has_dynamic_children)) { - // if (@as(Expr.Tag, child.data) == .e_jsx_element) { - // if (child.data.e_jsx_element.flags.contains(.has_dynamic_children) or child.data.e_jsx_element.flags.contains(.has_dynamic_prop)) { - // flags.insert(.has_dynamic_children); - - // } - // } else { - // switch (child.knownPrimitive()) { - // .unknown => { - // flags.insert(.has_dynamic_children); - // }, - // else => {}, - // } - // } - // } - - if (!flags.contains(.has_any_dynamic)) { - if (@as(Expr.Tag, child.data) == .e_jsx_element) { - if (child.data.e_jsx_element.flags.contains(.has_any_dynamic)) { - flags.insert(.has_any_dynamic); - } - } else { - switch (child.knownPrimitive()) { - .unknown => { - flags.insert(.has_any_dynamic); - }, - else => {}, - } - } - } - - children.append(child) catch unreachable; - } else { - children.append(p.parseJSXElement(less_than_loc) catch unreachable) catch unreachable; - } - - // The call to parseJSXElement() above doesn't consume the last - // TGreaterThan because the caller knows what Next() function to call. - // Use NextJSXElementChild() here since the next token is an element - // child. - try p.lexer.nextJSXElementChild(); - continue; - } - - // This is the closing element - try p.lexer.nextInsideJSXElement(); - const end_tag = try JSXTag.parse(P, p); - - if (!strings.eql(end_tag.name, tag.name)) { - try p.log.addRangeErrorFmt(p.source, end_tag.range, p.allocator, "Expected closing tag </{s}> to match opening tag <{s}>", .{ - end_tag.name, - tag.name, - }); - return error.SyntaxError; - } - - if (p.lexer.token != .t_greater_than) { - try p.lexer.expected(.t_greater_than); - } - - return p.e(E.JSXElement{ - .tag = end_tag.data.asExpr(), - .children = ExprNodeList.fromList(children), - .properties = properties, - .key = key_prop, - .flags = flags, - .close_tag_loc = end_tag.range.loc, - }, loc); - }, - else => { - try p.lexer.unexpected(); - return error.SyntaxError; - }, - } - } - } - - fn willNeedBindingPattern(p: *P) bool { - return switch (p.lexer.token) { - // "[a] = b;" - .t_equals => true, - // "for ([a] in b) {}" - .t_in => !p.allow_in, - // "for ([a] of b) {}" - .t_identifier => !p.allow_in and p.lexer.isContextualKeyword("of"), - else => false, - }; - } - - fn appendPart(p: *P, parts: *ListManaged(js_ast.Part), stmts: []Stmt) !void { - // Reuse the memory if possible - // This is reusable if the last part turned out to be dead - p.symbol_uses.clearRetainingCapacity(); - p.declared_symbols.clearRetainingCapacity(); - p.scopes_for_current_part.clearRetainingCapacity(); - p.import_records_for_current_part.clearRetainingCapacity(); - - const allocator = p.allocator; - var opts = PrependTempRefsOpts{}; - var partStmts = ListManaged(Stmt).fromOwnedSlice(allocator, stmts); - try p.visitStmtsAndPrependTempRefs(&partStmts, &opts); - - // Insert any relocated variable statements now - if (p.relocated_top_level_vars.items.len > 0) { - var already_declared = RefMap{}; - var already_declared_allocator_stack = std.heap.stackFallback(1024, allocator); - var already_declared_allocator = already_declared_allocator_stack.get(); - defer if (already_declared_allocator_stack.fixed_buffer_allocator.end_index >= 1023) already_declared.deinit(already_declared_allocator); - - for (p.relocated_top_level_vars.items) |*local| { - // Follow links because "var" declarations may be merged due to hoisting - while (local.ref != null) { - const link = p.symbols.items[local.ref.?.innerIndex()].link; - if (link.isNull()) { - break; - } - local.ref = link; - } - const ref = local.ref orelse continue; - var declaration_entry = try already_declared.getOrPut(already_declared_allocator, ref); - if (!declaration_entry.found_existing) { - const decls = try allocator.alloc(G.Decl, 1); - decls[0] = Decl{ - .binding = p.b(B.Identifier{ .ref = ref }, local.loc), - }; - try partStmts.append(p.s(S.Local{ .decls = decls }, local.loc)); - } - } - p.relocated_top_level_vars.clearRetainingCapacity(); - - // Follow links because "var" declarations may be merged due to hoisting - - // while (true) { - // const link = p.symbols.items[local.ref.innerIndex()].link; - // } - } - - if (partStmts.items.len > 0) { - const _stmts = partStmts.toOwnedSlice(); - - try parts.append(js_ast.Part{ - .stmts = _stmts, - .symbol_uses = p.symbol_uses, - .declared_symbols = p.declared_symbols.toOwnedSlice( - p.allocator, - ), - .import_record_indices = p.import_records_for_current_part.toOwnedSlice( - p.allocator, - ), - .scopes = p.scopes_for_current_part.toOwnedSlice(p.allocator), - .can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts), - }); - p.symbol_uses = .{}; - } else if (p.declared_symbols.items.len > 0 or p.symbol_uses.count() > 0) { - // if the part is dead, invalidate all the usage counts - p.clearSymbolUsagesFromDeadPart(.{ .stmts = undefined, .declared_symbols = p.declared_symbols.items, .symbol_uses = p.symbol_uses }); - } - } - - fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool { - switch (binding.data) { - .b_array => |bi| { - for (bi.items) |*item| { - if (!p.bindingCanBeRemovedIfUnused(item.binding)) { - return false; - } - - if (item.default_value) |*default| { - if (!p.exprCanBeRemovedIfUnused(default)) { - return false; - } - } - } - }, - .b_object => |bi| { - for (bi.properties) |*property| { - if (!property.flags.contains(.is_spread) and !p.exprCanBeRemovedIfUnused(&property.key)) { - return false; - } - - if (!p.bindingCanBeRemovedIfUnused(property.value)) { - return false; - } - - if (property.default_value) |*default| { - if (!p.exprCanBeRemovedIfUnused(default)) { - return false; - } - } - } - }, - else => {}, - } - - return true; - } - - fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool { - for (stmts) |stmt| { - switch (stmt.data) { - // These never have side effects - .s_function, .s_empty => {}, - - // Let these be removed if they are unused. Note that we also need to - // check if the imported file is marked as "sideEffects: false" before we - // can remove a SImport statement. Otherwise the import must be kept for - // its side effects. - .s_import => {}, - .s_class => |st| { - if (!p.classCanBeRemovedIfUnused(&st.class)) { - return false; - } - }, - .s_expr => |st| { - if (st.does_not_affect_tree_shaking) { - // Expressions marked with this are automatically generated and have - // no side effects by construction. - break; - } - - if (!p.exprCanBeRemovedIfUnused(&st.value)) { - return false; - } - }, - .s_local => |st| { - for (st.decls) |*decl| { - if (!p.bindingCanBeRemovedIfUnused(decl.binding)) { - return false; - } - - if (decl.value) |*decl_value| { - if (!p.exprCanBeRemovedIfUnused(decl_value)) { - return false; - } - } - } - }, - - .s_try => |try_| { - if (!p.stmtsCanBeRemovedIfUnused(try_.body) or (try_.finally != null and !p.stmtsCanBeRemovedIfUnused(try_.finally.?.stmts))) { - return false; - } - }, - - // Exports are tracked separately, so this isn't necessary - .s_export_clause, .s_export_from => {}, - - .s_export_default => |st| { - switch (st.value) { - .stmt => |s2| { - switch (s2.data) { - .s_expr => |s_expr| { - if (!p.exprCanBeRemovedIfUnused(&s_expr.value)) { - return false; - } - }, - - // These never have side effects - .s_function => {}, - - .s_class => { - if (!p.classCanBeRemovedIfUnused(&s2.data.s_class.class)) { - return false; - } - }, - else => { - Global.panic("Unexpected type in export default: {s}", .{s2}); - }, - } - }, - .expr => |*exp| { - if (!p.exprCanBeRemovedIfUnused(exp)) { - return false; - } - }, - } - }, - else => { - return false; - }, - } - } - - return true; - } - - fn visitStmtsAndPrependTempRefs(p: *P, stmts: *ListManaged(Stmt), opts: *PrependTempRefsOpts) !void { - if (only_scan_imports_and_do_not_visit) { - @compileError("only_scan_imports_and_do_not_visit must not run this."); - } - - p.temp_refs_to_declare.deinit(p.allocator); - p.temp_refs_to_declare = @TypeOf(p.temp_refs_to_declare){}; - p.temp_ref_count = 0; - - try p.visitStmts(stmts, opts.kind); - - // Prepend values for "this" and "arguments" - if (opts.fn_body_loc != null) { - // Capture "this" - if (p.fn_only_data_visit.this_capture_ref) |ref| { - try p.temp_refs_to_declare.append(p.allocator, TempRef{ - .ref = ref, - .value = p.e(E.This{}, opts.fn_body_loc orelse p.panic("Internal error: Expected opts.fn_body_loc to exist", .{})), - }); - } - } - } - - fn recordDeclaredSymbol(p: *P, ref: Ref) !void { - try p.declared_symbols.append(p.allocator, js_ast.DeclaredSymbol{ - .ref = ref, - .is_top_level = p.current_scope == p.module_scope, - }); - } - - // public for JSNode.JSXWriter usage - pub fn visitExpr(p: *P, expr: Expr) Expr { - if (only_scan_imports_and_do_not_visit) { - @compileError("only_scan_imports_and_do_not_visit must not run this."); - } - // Inline to avoid the extra unnecessary function call in the stack - return @call(.{ .modifier = .always_inline }, P.visitExprInOut, .{ p, expr, ExprIn{} }); - } - - fn visitFunc(p: *P, _func: G.Fn, open_parens_loc: logger.Loc) G.Fn { - if (only_scan_imports_and_do_not_visit) { - @compileError("only_scan_imports_and_do_not_visit must not run this."); - } - - var func = _func; - const old_fn_or_arrow_data = p.fn_or_arrow_data_visit; - const old_fn_only_data = p.fn_only_data_visit; - p.fn_or_arrow_data_visit = FnOrArrowDataVisit{ .is_async = func.flags.contains(.is_async) }; - p.fn_only_data_visit = FnOnlyDataVisit{ .is_this_nested = true, .arguments_ref = func.arguments_ref }; - - if (func.name) |name| { - if (name.ref) |name_ref| { - p.recordDeclaredSymbol(name_ref) catch unreachable; - const symbol_name = p.loadNameFromRef(name_ref); - if (isEvalOrArguments(symbol_name)) { - p.markStrictModeFeature(.eval_or_arguments, js_lexer.rangeOfIdentifier(p.source, name.loc), symbol_name) catch unreachable; - } - } - } - - const body = func.body; - - p.pushScopeForVisitPass(.function_args, open_parens_loc) catch unreachable; - p.visitArgs( - func.args, - VisitArgsOpts{ - .has_rest_arg = func.flags.contains(.has_rest_arg), - .body = body.stmts, - .is_unique_formal_parameters = true, - }, - ); - - p.pushScopeForVisitPass(.function_body, body.loc) catch unreachable; - var stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, body.stmts); - var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body, .fn_body_loc = body.loc }; - p.visitStmtsAndPrependTempRefs(&stmts, &temp_opts) catch unreachable; - func.body = G.FnBody{ .stmts = stmts.toOwnedSlice(), .loc = body.loc }; - - p.popScope(); - p.popScope(); - - p.fn_or_arrow_data_visit = old_fn_or_arrow_data; - p.fn_only_data_visit = old_fn_only_data; - return func; - } - - fn maybeKeepExprSymbolName(p: *P, expr: Expr, original_name: string, was_anonymous_named_expr: bool) Expr { - return if (was_anonymous_named_expr) p.keepExprSymbolName(expr, original_name) else expr; - } - - fn valueForThis(p: *P, loc: logger.Loc) ?Expr { - // Substitute "this" if we're inside a static class property initializer - if (p.fn_only_data_visit.this_class_static_ref) |ref| { - p.recordUsage(ref); - return p.e(E.Identifier{ .ref = ref }, loc); - } - - // oroigianlly was !=- modepassthrough - if (!p.fn_only_data_visit.is_this_nested) { - if (p.has_es_module_syntax) { - // In an ES6 module, "this" is supposed to be undefined. Instead of - // doing this at runtime using "fn.call(undefined)", we do it at - // compile time using expression substitution here. - return Expr{ .loc = loc, .data = nullValueExpr }; - } else { - // In a CommonJS module, "this" is supposed to be the same as "exports". - // Instead of doing this at runtime using "fn.call(module.exports)", we - // do it at compile time using expression substitution here. - p.recordUsage(p.exports_ref); - return p.e(E.Identifier{ .ref = p.exports_ref }, loc); - } - } - - return null; - } - - fn isValidAssignmentTarget(p: *P, expr: Expr) bool { - return switch (expr.data) { - .e_identifier => |ident| !isEvalOrArguments(p.loadNameFromRef(ident.ref)), - .e_dot => |e| e.optional_chain == null, - .e_index => |e| e.optional_chain == null, - .e_array => |e| !e.is_parenthesized, - .e_object => |e| !e.is_parenthesized, - else => false, - }; - } - - fn visitExprInOut(p: *P, expr: Expr, in: ExprIn) Expr { - if (in.assign_target != .none and !p.isValidAssignmentTarget(expr)) { - p.log.addError(p.source, expr.loc, "Invalid assignment target") catch unreachable; - } - - // Output.print("\nVisit: {s} - {d}\n", .{ @tagName(expr.data), expr.loc.start }); - switch (expr.data) { - .e_null, .e_super, .e_boolean, .e_big_int, .e_reg_exp, .e_undefined => {}, - - .e_new_target => |_| { - // this error is not necessary and it is causing breakages - // if (!p.fn_only_data_visit.is_new_target_allowed) { - // p.log.addRangeError(p.source, target.range, "Cannot use \"new.target\" here") catch unreachable; - // } - }, - - .e_string => { - - // If you're using this, you're probably not using 0-prefixed legacy octal notation - // if e.LegacyOctalLoc.Start > 0 { - }, - .e_number => { - - // idc about legacy octal loc - }, - .e_this => { - if (p.valueForThis(expr.loc)) |exp| { - return exp; - } - - // // Capture "this" inside arrow functions that will be lowered into normal - // // function expressions for older language environments - // if p.fnOrArrowDataVisit.isArrow && p.options.unsupportedJSFeatures.Has(compat.Arrow) && p.fnOnlyDataVisit.isThisNested { - // return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{} - // } - }, - - .e_import_meta => { - // TODO: delete import.meta might not work - const is_delete_target = std.meta.activeTag(p.delete_target) == .e_import_meta; - - if (p.define.dots.get("meta")) |meta| { - for (meta) |define| { - if (p.isDotDefineMatch(expr, define.parts)) { - // Substitute user-specified defines - return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); - } - } - } - - if (!p.import_meta_ref.isNull()) { - p.recordUsage(p.import_meta_ref); - return p.e(E.Identifier{ .ref = p.import_meta_ref }, expr.loc); - } - }, - .e_spread => |exp| { - exp.value = p.visitExpr(exp.value); - }, - .e_identifier => { - var e_ = expr.data.e_identifier; - const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_identifier and expr.data.e_identifier.ref.eql(p.delete_target.e_identifier.ref); - - const name = p.loadNameFromRef(e_.ref); - if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) { - p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, expr.loc), name) catch unreachable; - } - - const result = p.findSymbol(expr.loc, name) catch unreachable; - - e_.must_keep_due_to_with_stmt = result.is_inside_with_scope; - e_.ref = result.ref; - - // TODO: fix the underyling cause here - // The problem seems to be that result.ref.innerIndex() is not always set. - - // Handle assigning to a constant - // if (in.assign_target != .none and p.symbols.items[result.ref.innerIndex()].kind == .cconst) { - // const r = js_lexer.rangeOfIdentifier(p.source, expr.loc); - // p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot assign to {s} because it is a constant", .{name}) catch unreachable; - // } - - var original_name: ?string = null; - - // Substitute user-specified defines for unbound symbols - if (p.symbols.items[e_.ref.innerIndex()].kind == .unbound and !result.is_inside_with_scope and !is_delete_target) { - if (p.define.identifiers.get(name)) |def| { - if (!def.isUndefined()) { - const newvalue = p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &def); - - // Don't substitute an identifier for a non-identifier if this is an - // assignment target, since it'll cause a syntax error - if (@as(Expr.Tag, newvalue.data) == .e_identifier or in.assign_target == .none) { - return newvalue; - } - - original_name = def.original_name; - } - - // Copy the side effect flags over in case this expression is unused - if (def.can_be_removed_if_unused) { - e_.can_be_removed_if_unused = true; - } - if (def.call_can_be_unwrapped_if_unused and !p.options.ignore_dce_annotations) { - e_.call_can_be_unwrapped_if_unused = true; - } - } - } - - return p.handleIdentifier(expr.loc, e_, original_name, IdentifierOpts{ - .assign_target = in.assign_target, - .is_delete_target = is_delete_target, - .was_originally_identifier = true, - }); - }, - - .e_jsx_element => |e_| { - switch (comptime jsx_transform_type) { - .macro => { - const WriterType = js_ast.Macro.JSNode.NewJSXWriter(P); - var writer = WriterType.initWriter(p, &BunJSX.bun_jsx_identifier); - return writer.writeFunctionCall(e_.*); - }, - .solid => { - // The rules: - // 1. HTML string literals of static JSX elements are generated & escaped, injected at the top of the file - // 1a. Static elements are contiguous in the HTML, but dynamic elements get a marker string during if client-side hydration - // Each element becomes a declaration in the top-level scope of the JSX expression (i.e. either the anonymous IIFE or an array) - // Those elements may be markers - // The final count of the markers is passed to the template function - // 3. The first element in a a group of elements becomes .cloneNode(true) - // Subsequent elements call .nextSibling on the previous element. - // The specific function differs if SSR is enabled and if client-side hydration is enabled. - // 4. Non-static JSX children are added like this: - // insert(topElement, createComponent(MyComponent, props), markerElement) - // 5. Non-statically analyzable attributes are added like this: - // setAttribute(topElement, "data-foo", "bar") - // 6. - var solid = &p.solid; - const old_is_in_jsx_component = solid.is_in_jsx_component; - solid.is_in_jsx_component = true; - defer solid.is_in_jsx_component = old_is_in_jsx_component; - - if (!old_is_in_jsx_component) { - solid.current_template_string.reset(); - solid.buffered_writer.pos = 0; - solid.component_body.clearRetainingCapacity(); - solid.component_body_decls.clearRetainingCapacity(); - - // prepend an empty statement - // this will later become an S.Local for the decls - solid.component_body.append(p.allocator, p.s(S.Empty{}, expr.loc)) catch unreachable; - - solid.last_element_id = E.Identifier{}; - solid.prev_scope = p.current_scope; - solid.temporary_scope.reset(); - solid.node_count = 0; - solid.temporary_scope.kind = .function_body; - solid.temporary_scope.parent = p.current_scope; - - solid.last_template_id.ref = Ref.None; - } - - var writer = &solid.buffered_writer; - - // The JSX tag used - const tag: Expr = tagger: { - if (e_.tag) |_tag| { - break :tagger p.visitExpr(_tag); - } else { - break :tagger p.e(E.Array{}, expr.loc); - } - }; - - const jsx_props = e_.properties.slice(); - - var template_expression = Expr{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; - var element: ?E.Identifier = null; - var needs_end_bracket = false; - var children = e_.children.slice(); - defer { - if (old_is_in_jsx_component) { - if (element) |el| { - solid.last_element_id = el; - } - } - } - switch (tag.data) { - .e_string => { - // write the template - _ = writer.writeAll("<") catch unreachable; - _ = writer.writeString(tag.data.e_string) catch unreachable; - needs_end_bracket = true; - - var wrote_anything = false; - for (jsx_props) |*property, i| { - if (property.kind != .spread) { - property.key = p.visitExpr(e_.properties.ptr[i].key.?); - } - - if (property.value != null) { - property.value = p.visitExpr(e_.properties.ptr[i].value.?); - - if (property.kind != .spread) { - var key = property.key.?.data.e_string; - - const is_event_listener = key.hasPrefixComptime("on:"); - const is_class = !is_event_listener and - // TODO: should this be case-insensitive? - (key.eqlComptime("class") or key.eqlComptime("className")); - - const primitive = property.value.?.knownPrimitive(); - const is_dynamic = !primitive.isStatic(); - const appears_in_template = !is_event_listener and !is_dynamic; - if (appears_in_template) { - _ = writer.writeAll(" ") catch unreachable; - wrote_anything = true; - } - - if (is_class and !is_dynamic) { - _ = writer.writeAll("class") catch unreachable; - } else if (!is_dynamic) { - _ = writer.writeString(key) catch unreachable; - } - if (appears_in_template) { - switch (primitive) { - .number, .string => { - if (property.value.?.data == .e_string) { - const str = property.value.?.data.e_string; - if (str.len() > 0) { - _ = writer.writeAll("=") catch unreachable; - writer.writeHTMLAttributeValueString(str) catch unreachable; - } - } else { - writer.writer().print("={d}", .{property.value.?.data.e_number.value}) catch unreachable; - } - }, - // TODO: should "null" be written? - // TODO: should "undefined" be written? - .@"null", .@"undefined" => {}, - .boolean => { - // existence of an HTML attribute implicitly is true - // so we only need to write if it's false - if (!property.value.?.data.e_boolean.value) { - // TODO: verify that SolidJS writes the same value - _ = writer.writeAll("=false") catch unreachable; - } - }, - - else => unreachable, - } - } else { - if (template_expression.data.e_identifier.ref.isNull()) { - var new_template_name = solid.generateTemplateName(p.allocator); - // declare the template in the module scope - p.current_scope = p.module_scope; - solid.last_template_id = .{ - .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }; - p.current_scope = solid.prev_scope.?; - template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; - } - if (element == null) { - element = solid.generateElement( - p, - template_expression, - property.value.?.loc, - ) catch unreachable; - } - - var stmt: Stmt = undefined; - if (!is_event_listener) { - var args = p.allocator.alloc(Expr, 4) catch unreachable; - args[0] = p.e(element.?, expr.loc); - if (is_class) { - args[1] = p.e(E.String.init("className"), property.key.?.loc); - } else { - args[1] = property.key.?; - } - - args[2] = property.value.?; - - // setAttribute(template_expression, key, value); - const setAttr = p.e( - E.Call{ - .target = p.e( - E.Identifier{ - .ref = solid.setAttribute.ref, - .can_be_removed_if_unused = false, - .call_can_be_unwrapped_if_unused = false, - }, - property.value.?.loc, - ), - .args = ExprNodeList.init(args[0..3]), - }, - property.key.?.loc, - ); - - p.recordUsage(solid.setAttribute.ref); - if (args[2].data == .e_identifier or args[2].data == .e_import_identifier) { - if (args[2].data == .e_identifier) p.recordUsage(args[2].data.e_identifier.ref); - if (args[2].data == .e_import_identifier) p.recordUsage(args[2].data.e_import_identifier.ref); - stmt = p.s(S.SExpr{ .value = setAttr }, property.value.?.loc); - } else { - var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - stmts[0] = p.s(S.Return{ .value = setAttr }, property.value.?.loc); - - args[3] = p.e( - E.Arrow{ - .args = &[_]G.Arg{}, - .body = G.FnBody{ - .stmts = stmts, - .loc = args[2].loc, - }, - }, - property.value.?.loc, - ); - stmt = p.s(S.SExpr{ - .value = p.e( - E.Call{ - .target = p.e( - E.Identifier{ - .ref = solid.effect.ref, - }, - ), - .args = ExprNodeList.init(args[3..4]), - }, - ), - }, property.value.?.loc); - } - } else { - var args = p.allocator.alloc(Expr, 2) catch unreachable; - - // on:MyEvent => MyEvent - property.key.?.data.e_string.data = property.key.?.data.e_string.data[3..]; - args[0] = property.key.?; - args[1] = property.value.?; - // $element.addEventListener("MyEvent", (e) => { ... }); - const addEventListener = p.e( - E.Call{ - .target = p.e( - E.Dot{ - .target = p.e( - element.?, - expr.loc, - ), - .name = "addEventListener", - .name_loc = property.key.?.loc, - }, - property.key.?.loc, - ), - .args = ExprNodeList.init(args), - }, - property.key.?.loc, - ); - - p.recordUsage(element.?.ref); - stmt = p.s(S.SExpr{ .value = addEventListener }, property.value.?.loc); - } - - solid.component_body.append(p.allocator, stmt) catch unreachable; - } - } else {} - } - - if (property.initializer != null) { - property.initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); - } - } - - var wrote_any_children = false; - for (children) |*el, k| { - if (needs_end_bracket and el.data == .e_jsx_element) { - _ = writer.writeAll(">") catch unreachable; - solid.node_count += 1; - - needs_end_bracket = false; - } - - const child = p.visitExpr(el.*); - switch (child.data) { - // skip it - .e_missing => {}, - - // we need to serialize it to HTML - // it's probably a text node - .e_string => |str| { - if (str.len() > 0) { - if (needs_end_bracket) { - _ = writer.writeAll(">") catch unreachable; - solid.node_count += 1; - needs_end_bracket = false; - } - writer.writeHTMLAttributeValueString(str) catch unreachable; - wrote_any_children = true; - } - }, - .e_number => |str| { - if (needs_end_bracket) { - _ = writer.writeAll(">") catch unreachable; - needs_end_bracket = false; - } - writer.writer().print("{d}", .{str.value}) catch unreachable; - wrote_any_children = true; - }, - - // debug assertion that we don't get here - .e_jsx_element => unreachable, - - else => { - if (template_expression.data.e_identifier.ref.isNull()) { - var new_template_name = solid.generateTemplateName(p.allocator); - // declare the template in the module scope - p.current_scope = p.module_scope; - solid.last_template_id = .{ - .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }; - p.current_scope = solid.prev_scope.?; - template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; - } - p.recordUsage(solid.insert.ref); - p.recordUsage(template_expression.data.e_identifier.ref); - var args = p.allocator.alloc(Expr, 3) catch unreachable; - args[0] = template_expression; - args[1] = child; - args[2] = if (k != children.len - 1 and !solid.last_element_id.ref.eql(Ref.None)) - p.e(solid.last_element_id, expr.loc) - else - p.e(E.Null{}, expr.loc); - solid.node_count += 1; - solid.component_body.append( - p.allocator, - p.s( - S.SExpr{ - .value = p.e( - E.Call{ - .target = p.e(E.ImportIdentifier{ .ref = solid.insert.ref }, child.loc), - .args = ExprNodeList.init(args), - }, - child.loc, - ), - }, - child.loc, - ), - ) catch unreachable; - }, - } - } - - if (wrote_any_children) { - solid.node_count += 1; - _ = writer.writeAll("</") catch unreachable; - _ = writer.writeString(tag.data.e_string) catch unreachable; - _ = writer.writeAll(">") catch unreachable; - } else if (needs_end_bracket) { - _ = writer.writeAll("/>") catch unreachable; - } - - // this is the root of a template tag, we just finished - // <div> - // /* some stuff in here */ - // </div> - // ^ - // we are here! - if (!old_is_in_jsx_component) { - var args = p.allocator.alloc(Expr, 2) catch unreachable; - - // we are done, so it's time to turn our template into a string we can write - // note that we are writing as UTF-8 but the input may be UTF-16 or UTF-8, depending. - if (writer.pos < writer.buffer.len and writer.context.list.items.len == 0) { - args[0] = p.e(E.String.init(p.allocator.dupe(u8, writer.buffer[0..writer.pos]) catch unreachable), expr.loc); - } else if (writer.pos == 0 and writer.context.list.items.len == 0) { - args[0] = p.e(E.String.init(""), expr.loc); - } else { - const total = writer.context.list.items.len + writer.pos; - var buffer = p.allocator.alloc(u8, total) catch unreachable; - @memcpy(buffer.ptr, writer.context.list.items.ptr, writer.context.list.items.len); - @memcpy(buffer.ptr + writer.context.list.items.len, &writer.buffer, writer.buffer.len); - args[0] = p.e(E.String.init(buffer), expr.loc); - } - - args[1] = p.e(E.Number{ .value = @intToFloat(f64, solid.node_count) }, expr.loc); - solid.node_count = 0; - - if (template_expression.data.e_identifier.ref.isNull()) { - var new_template_name = solid.generateTemplateName(p.allocator); - // declare the template in the module scope - p.current_scope = p.module_scope; - solid.last_template_id = .{ - .ref = p.declareSymbolMaybeGenerated(.other, expr.loc, new_template_name, true) catch unreachable, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }; - p.current_scope = solid.prev_scope.?; - template_expression = .{ .loc = expr.loc, .data = .{ .e_identifier = solid.last_template_id } }; - } - - solid.template_decls.append( - p.allocator, - G.Decl{ - .binding = p.b(B.Identifier{ .ref = template_expression.data.e_identifier.ref }, template_expression.loc), - .value = p.e( - E.Call{ - .args = ExprNodeList.init(args), - .target = p.e( - E.ImportIdentifier{ - .ref = solid.template.ref, - }, - expr.loc, - ), - .can_be_unwrapped_if_unused = true, - }, - template_expression.loc, - ), - }, - ) catch unreachable; - p.recordUsage(solid.template.ref); - - if (p.is_control_flow_dead) { - return p.e(E.Missing{}, expr.loc); - } - - // 1 means it was actually static - // that means we can just turn it into a single $template.cloneNode(true) - if (solid.component_body.items.len == 1) { - return p.e(E.Call{ - .target = p.e( - E.Dot{ - .name = "cloneNode", - .name_loc = expr.loc, - .target = template_expression, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - template_expression.loc, - ), - .args = ExprNodeList.init(true_args), - .can_be_unwrapped_if_unused = true, - }, expr.loc); - } - if (solid.component_body_decls.items.len == 0) { - solid.component_body_decls.ensureTotalCapacityPrecise(p.allocator, 1) catch unreachable; - solid.component_body_decls.appendAssumeCapacity(G.Decl{ - .binding = p.b(B.Identifier{ .ref = solid.last_template_id.ref }, expr.loc), - .value = p.e(E.Call{ - .target = p.e( - E.Dot{ - .name = "cloneNode", - .name_loc = expr.loc, - .target = template_expression, - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - template_expression.loc, - ), - .args = ExprNodeList.init(true_args), - .can_be_unwrapped_if_unused = true, - }, expr.loc), - }); - } - - // we need to wrap the template in a function - const ret = p.e(E.Identifier{ .ref = solid.component_body_decls.items[0].binding.data.b_identifier.ref }, expr.loc); - solid.component_body.items[0] = p.s(S.Local{ .decls = solid.component_body_decls.toOwnedSlice(p.allocator) }, expr.loc); - solid.component_body.append(p.allocator, p.s(S.Return{ .value = ret }, expr.loc)) catch unreachable; - return p.e( - E.Arrow{ .args = &[_]G.Arg{}, .body = G.FnBody{ .stmts = solid.component_body.toOwnedSlice(p.allocator), .loc = expr.loc } }, - expr.loc, - ); - // we don't need to return anything because it's a static element that will live in the template - } else { - return p.e(E.Missing{}, expr.loc); - } - }, - .e_import_identifier, .e_identifier => { - var out_props = p.allocator.alloc(G.Property, jsx_props.len + @as(usize, @boolToInt(e_.key != null)) + @as(usize, @boolToInt(e_.children.len > 0))) catch unreachable; - var out_props_i: usize = 0; - for (jsx_props) |property, i| { - if (property.kind != .spread) { - e_.properties.ptr[i].key = p.visitExpr(e_.properties.ptr[i].key.?); - } - - if (property.value != null) { - e_.properties.ptr[i].value = p.visitExpr(e_.properties.ptr[i].value.?); - } - - if (property.initializer != null) { - e_.properties.ptr[i].initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); - } - - if (property.kind != .spread) { - const kind = if (property.value.?.data == .e_arrow or property.value.?.data == .e_function) G.Property.Kind.get else G.Property.Kind.normal; - out_props[out_props_i] = G.Property{ - .key = property.key, - .value = property.value, - .kind = kind, - }; - out_props_i += 1; - } - } - - if (e_.key) |k| { - const key = p.visitExpr(k); - if (key.data != .e_missing) { - const kind = if (key.data == .e_arrow or key.data == .e_function) Property.Kind.get else Property.Kind.normal; - out_props[out_props_i] = G.Property{ - .key = p.e(Prefill.String.Key, k.loc), - .value = key, - .kind = kind, - }; - out_props_i += 1; - } - } - - var out_child_i: usize = 0; - for (children) |child, j| { - children[j] = p.visitExpr(child); - if (children[j].data != .e_missing) { - children[out_child_i] = children[j]; - out_child_i += 1; - } - } - - if (out_child_i > 0) { - const kind = Property.Kind.get; - - out_props[out_props_i] = G.Property{ - .key = p.e(Prefill.String.Children, expr.loc), - .value = p.e(E.Array{ .items = ExprNodeList.init(children[0..out_child_i]) }, expr.loc), - .kind = kind, - }; - out_props_i += 1; - } - - var args = p.allocator.alloc(Expr, 2) catch unreachable; - args[0] = tag; - args[1] = p.e(E.Object{ - .properties = G.Property.List.init(out_props[0..out_props_i]), - }, expr.loc); - p.recordUsage(solid.createComponent.ref); - return p.e( - E.Call{ - .target = p.e(E.ImportIdentifier{ .ref = solid.createComponent.ref }, expr.loc), - .args = ExprNodeList.init(args), - .close_paren_loc = e_.close_tag_loc, - }, - expr.loc, - ); - }, - .e_array => {}, - else => unreachable, - } - }, - .react => { - const tag: Expr = tagger: { - if (e_.tag) |_tag| { - break :tagger p.visitExpr(_tag); - } else { - break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref); - } - }; - - const jsx_props = e_.properties.slice(); - for (jsx_props) |property, i| { - if (property.kind != .spread) { - e_.properties.ptr[i].key = p.visitExpr(e_.properties.ptr[i].key.?); - } - - if (property.value != null) { - e_.properties.ptr[i].value = p.visitExpr(e_.properties.ptr[i].value.?); - } - - if (property.initializer != null) { - e_.properties.ptr[i].initializer = p.visitExpr(e_.properties.ptr[i].initializer.?); - } - } - - if (e_.key) |key| { - e_.key = p.visitExpr(key); - } - - const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.contains(.is_key_before_rest)) options.JSX.Runtime.automatic else options.JSX.Runtime.classic; - var children_count = e_.children.len; - - const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.slice(p.allocator)); - - children_count = if (is_childless_tag) 0 else children_count; - - if (children_count != e_.children.len) { - // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. - // ^ from react-dom - p.log.addWarningFmt(p.source, tag.loc, p.allocator, "<{s} /> is a void element and must not have \"children\"", .{tag.data.e_string.slice(p.allocator)}) catch {}; - } - - // TODO: maybe we should split these into two different AST Nodes - // That would reduce the amount of allocations a little - switch (runtime) { - .classic => { - // Arguments to createElement() - const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable; - // There are at least two args: - // - name of the tag - // - props - var i: usize = 1; - args[0] = tag; - if (e_.properties.len > 0) { - if (e_.key) |key| { - var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable; - std.mem.copy(G.Property, props, e_.properties.slice()); - props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key }; - args[1] = p.e(E.Object{ .properties = G.Property.List.init(props) }, expr.loc); - } else { - args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc); - } - i = 2; - } else { - args[1] = p.e(E.Null{}, expr.loc); - i = 2; - } - - const children_elements = e_.children.slice()[0..children_count]; - for (children_elements) |child| { - args[i] = p.visitExpr(child); - i += @intCast(usize, @boolToInt(args[i].data != .e_missing)); - } - - // Call createElement() - return p.e(E.Call{ - .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref), - .args = ExprNodeList.init(args[0..i]), - // Enable tree shaking - .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, - .close_paren_loc = e_.close_tag_loc, - }, expr.loc); - }, - // function jsxDEV(type, config, maybeKey, source, self) { - .automatic => { - // Either: - // jsxDEV(type, arguments, key, isStaticChildren, source, self) - // jsx(type, arguments, key) - const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; - const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable; - args[0] = tag; - const allocator = p.allocator; - var props = e_.properties.list(); - // arguments needs to be like - // { - // ...props, - // children: [el1, el2] - // } - - { - var last_child: u32 = 0; - var children = e_.children.slice()[0..children_count]; - for (children) |child| { - e_.children.ptr[last_child] = p.visitExpr(child); - // if tree-shaking removes the element, we must also remove it here. - last_child += @intCast(u32, @boolToInt(e_.children.ptr[last_child].data != .e_missing)); - } - e_.children.len = last_child; - } - - const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; - - // Babel defines static jsx as children.len > 1 - const is_static_jsx = e_.children.len > 1; - - // Optimization: if the only non-child prop is a spread object - // we can just pass the object as the first argument - // this goes as deep as there are spreads - // <div {{...{...{...{...foo}}}}} /> - // -> - // <div {{...foo}} /> - // jsx("div", {...foo}) - while (props.items.len == 1 and props.items[0].kind == .spread and props.items[0].value.?.data == .e_object) { - props = props.items[0].value.?.data.e_object.properties.list(); - } - - // if (p.options.jsx.development) { - switch (e_.children.len) { - 0 => {}, - 1 => { - props.append(allocator, G.Property{ - .key = children_key, - .value = e_.children.ptr[0], - }) catch unreachable; - }, - else => { - props.append(allocator, G.Property{ - .key = children_key, - .value = p.e(E.Array{ - .items = e_.children, - .is_single_line = e_.children.len < 2, - }, e_.close_tag_loc), - }) catch unreachable; - }, - } - - args[1] = p.e(E.Object{ - .properties = G.Property.List.fromList(props), - }, expr.loc); - - if (e_.key) |key| { - args[2] = key; - } else { - // if (maybeKey !== undefined) - args[2] = Expr{ - .loc = expr.loc, - .data = .{ - .e_undefined = E.Undefined{}, - }, - }; - } - - if (p.options.jsx.development) { - // is the return type of the first child an array? - // It's dynamic - // Else, it's static - args[3] = Expr{ - .loc = expr.loc, - .data = .{ - .e_boolean = .{ - .value = is_static_jsx, - }, - }, - }; - - if (include_filename) { - var source = p.allocator.alloc(G.Property, 2) catch unreachable; - p.recordUsage(p.jsx_filename.ref); - source[0] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, - .value = p.e(E.Identifier{ - .ref = p.jsx_filename.ref, - .can_be_removed_if_unused = true, - }, expr.loc), - }; - - source[1] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, - .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - }; - - // Officially, they ask for columnNumber. But I don't see any usages of it in the code! - // source[2] = G.Property{ - // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, - // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - // }; - args[4] = p.e(E.Object{ - .properties = G.Property.List.init(source), - }, expr.loc); - - // When disabled, this must specifically be undefined - // Not an empty object - // See this code from react: - // > if (source !== undefined) { - // > var fileName = source.fileName.replace(/^.*[\\\/]/, ""); - // > var lineNumber = source.lineNumber; - // > return "\n\nCheck your code at " + fileName + ":" + lineNumber + "."; - // > } - } else { - args[4] = p.e(E.Undefined{}, expr.loc); - } - - args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; - } - - return p.e(E.Call{ - .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), - .args = ExprNodeList.init(args), - // Enable tree shaking - .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, - .was_jsx_element = true, - .close_paren_loc = e_.close_tag_loc, - }, expr.loc); - }, - else => unreachable, - } - }, - else => unreachable, - } - }, - - .e_template => |e_| { - if (e_.tag) |tag| { - e_.tag = p.visitExpr(tag); - - if (comptime allow_macros) { - if (e_.tag.?.data == .e_import_identifier) { - const ref = e_.tag.?.data.e_import_identifier.ref; - - if (p.macro.refs.get(ref)) |import_record_id| { - const name = p.symbols.items[ref.innerIndex()].original_name; - p.ignoreUsage(ref); - if (p.is_control_flow_dead) { - return p.e(E.Undefined{}, e_.tag.?.loc); - } - p.macro_call_count += 1; - const record = &p.import_records.items[import_record_id]; - // We must visit it to convert inline_identifiers and record usage - const macro_result = (p.options.macro_context.call( - record.path.text, - p.source.path.sourceDir(), - p.log, - p.source, - record.range, - expr, - &.{}, - name, - MacroVisitor, - MacroVisitor{ - .p = p, - .loc = expr.loc, - }, - ) catch return expr); - - if (macro_result.data != .e_template) { - return p.visitExpr(macro_result); - } - } - } - } - } - - for (e_.parts) |*part| { - part.value = p.visitExpr(part.value); - } - }, - - .inline_identifier => |id| { - const ref = p.macro.imports.get(id) orelse { - p.panic("Internal error: missing identifier from macro: {d}", .{id}); - }; - - if (!p.is_control_flow_dead) { - p.recordUsage(ref); - } - - return p.e( - E.ImportIdentifier{ - .was_originally_identifier = false, - .ref = ref, - }, - expr.loc, - ); - }, - - .e_binary => |e_| { - switch (e_.left.data) { - // Special-case private identifiers - .e_private_identifier => |_private| { - if (e_.op == .bin_in) { - var private = _private; - const name = p.loadNameFromRef(private.ref); - const result = p.findSymbol(e_.left.loc, name) catch unreachable; - private.ref = result.ref; - - // Unlike regular identifiers, there are no unbound private identifiers - const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind; - if (!Symbol.isKindPrivate(kind)) { - const r = logger.Range{ .loc = e_.left.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable; - } - - e_.right = p.visitExpr(e_.right); - e_.left = .{ .data = .{ .e_private_identifier = private }, .loc = e_.left.loc }; - - // privateSymbolNeedsToBeLowered - return expr; - } - }, - else => {}, - } - - const is_call_target = @as(Expr.Tag, p.call_target) == .e_binary and expr.data.e_binary == p.call_target.e_binary; - // const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary; - const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); - - if (comptime jsx_transform_type == .macro) { - if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) { - // foo instanceof <string /> - // -> - // bunJSX.isNodeType(foo, 13) - - // <string /> instanceof foo - // -> - // bunJSX.isNodeType(foo, 13) - var call_args = p.allocator.alloc(Expr, 2) catch unreachable; - call_args[0] = e_.left; - call_args[1] = e_.right; - - if (e_.right.data == .e_jsx_element) { - const jsx_element = e_.right.data.e_jsx_element; - if (jsx_element.tag) |tag| { - if (tag.data == .e_string) { - const tag_string = tag.data.e_string.slice(p.allocator); - if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { - call_args[1] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; - } else { - p.log.addRangeErrorFmt( - p.source, - js_lexer.rangeOfIdentifier(p.source, tag.loc), - p.allocator, - "Invalid JSX tag: \"{s}\"", - .{tag_string}, - ) catch unreachable; - return expr; - } - } - } else { - call_args[1] = p.visitExpr(call_args[1]); - } - } else { - call_args[1] = p.visitExpr(call_args[1]); - } - - if (e_.left.data == .e_jsx_element) { - const jsx_element = e_.left.data.e_jsx_element; - if (jsx_element.tag) |tag| { - if (tag.data == .e_string) { - const tag_string = tag.data.e_string.slice(p.allocator); - if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { - call_args[0] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; - } else { - p.log.addRangeErrorFmt( - p.source, - js_lexer.rangeOfIdentifier(p.source, tag.loc), - p.allocator, - "Invalid JSX tag: \"{s}\"", - .{tag_string}, - ) catch unreachable; - return expr; - } - } - } else { - call_args[0] = p.visitExpr(call_args[0]); - } - } else { - call_args[0] = p.visitExpr(call_args[0]); - } - - return p.e( - E.Call{ - .target = p.e( - E.Dot{ - .name = "isNodeType", - .name_loc = expr.loc, - .target = p.e(BunJSX.bun_jsx_identifier, expr.loc), - .can_be_removed_if_unused = true, - .call_can_be_unwrapped_if_unused = true, - }, - expr.loc, - ), - .args = ExprNodeList.init(call_args), - .can_be_unwrapped_if_unused = true, - }, - expr.loc, - ); - } - } - - e_.left = p.visitExprInOut(e_.left, ExprIn{ - .assign_target = e_.op.binaryAssignTarget(), - }); - - // Mark the control flow as dead if the branch is never taken - switch (e_.op) { - .bin_logical_or => { - const side_effects = SideEffects.toBoolean(e_.left.data); - if (side_effects.ok and side_effects.value) { - // "true || dead" - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - e_.right = p.visitExpr(e_.right); - p.is_control_flow_dead = old; - } else { - e_.right = p.visitExpr(e_.right); - } - }, - .bin_logical_and => { - const side_effects = SideEffects.toBoolean(e_.left.data); - if (side_effects.ok and !side_effects.value) { - // "false && dead" - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - e_.right = p.visitExpr(e_.right); - p.is_control_flow_dead = old; - } else { - e_.right = p.visitExpr(e_.right); - } - }, - .bin_nullish_coalescing => { - const side_effects = SideEffects.toNullOrUndefined(e_.left.data); - if (side_effects.ok and !side_effects.value) { - // "notNullOrUndefined ?? dead" - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - e_.right = p.visitExpr(e_.right); - p.is_control_flow_dead = old; - } else { - e_.right = p.visitExpr(e_.right); - } - }, - else => { - e_.right = p.visitExpr(e_.right); - }, - } - - // Always put constants on the right for equality comparisons to help - // reduce the number of cases we have to check during pattern matching. We - // can only reorder expressions that do not have any side effects. - switch (e_.op) { - .bin_loose_eq, .bin_loose_ne, .bin_strict_eq, .bin_strict_ne => { - if (SideEffects.isPrimitiveToReorder(e_.left.data) and !SideEffects.isPrimitiveToReorder(e_.right.data)) { - const _left = e_.left; - const _right = e_.right; - e_.left = _right; - e_.right = _left; - } - }, - else => {}, - } - - switch (e_.op) { - .bin_comma => { - // notimpl(); - }, - .bin_loose_eq => { - const equality = e_.left.data.eql(e_.right.data); - if (equality.ok) { - return p.e( - E.Boolean{ .value = equality.equal }, - expr.loc, - ); - } - - // const after_op_loc = locAfterOp(e_.); - // TODO: warn about equality check - // TODO: warn about typeof string - - }, - .bin_strict_eq => { - const equality = e_.left.data.eql(e_.right.data); - if (equality.ok) { - return p.e(E.Boolean{ .value = equality.equal }, expr.loc); - } - - // const after_op_loc = locAfterOp(e_.); - // TODO: warn about equality check - // TODO: warn about typeof string - }, - .bin_loose_ne => { - const equality = e_.left.data.eql(e_.right.data); - if (equality.ok) { - return p.e(E.Boolean{ .value = !equality.equal }, expr.loc); - } - // const after_op_loc = locAfterOp(e_.); - // TODO: warn about equality check - // TODO: warn about typeof string - - // "x != void 0" => "x != null" - if (@as(Expr.Tag, e_.right.data) == .e_undefined) { - e_.right = p.e(E.Null{}, e_.right.loc); - } - }, - .bin_strict_ne => { - const equality = e_.left.data.eql(e_.right.data); - if (equality.ok) { - return p.e(E.Boolean{ .value = !equality.equal }, expr.loc); - } - }, - .bin_nullish_coalescing => { - const nullorUndefined = SideEffects.toNullOrUndefined(e_.left.data); - if (nullorUndefined.ok) { - if (!nullorUndefined.value) { - return e_.left; - } else if (nullorUndefined.side_effects == .no_side_effects) { - // "(null ?? fn)()" => "fn()" - // "(null ?? this.fn)" => "this.fn" - // "(null ?? this.fn)()" => "(0, this.fn)()" - if (is_call_target and e_.right.hasValueForThisInCall()) { - return Expr.joinWithComma(Expr{ .data = .{ .e_number = .{ .value = 0.0 } }, .loc = e_.left.loc }, e_.right, p.allocator); - } - - return e_.right; - } - } - }, - .bin_logical_or => { - const side_effects = SideEffects.toBoolean(e_.left.data); - if (side_effects.ok and side_effects.value) { - return e_.left; - } else if (side_effects.ok and side_effects.side_effects == .no_side_effects) { - // "(0 || fn)()" => "fn()" - // "(0 || this.fn)" => "this.fn" - // "(0 || this.fn)()" => "(0, this.fn)()" - if (is_call_target and e_.right.hasValueForThisInCall()) { - return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator); - } - - return e_.right; - } - }, - .bin_logical_and => { - const side_effects = SideEffects.toBoolean(e_.left.data); - if (side_effects.ok) { - if (!side_effects.value) { - return e_.left; - } else if (side_effects.side_effects == .no_side_effects) { - // "(1 && fn)()" => "fn()" - // "(1 && this.fn)" => "this.fn" - // "(1 && this.fn)()" => "(0, this.fn)()" - if (is_call_target and e_.right.hasValueForThisInCall()) { - return Expr.joinWithComma(Expr{ .data = Prefill.Data.Zero, .loc = e_.left.loc }, e_.right, p.allocator); - } - - return e_.right; - } - } - - // TODO: - // "(1 && fn)()" => "fn()" - // "(1 && this.fn)" => "this.fn" - // "(1 && this.fn)()" => "(0, this.fn)()" - }, - .bin_add => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - return p.e(E.Number{ .value = vals[0] + vals[1] }, expr.loc); - } - } - - if (foldStringAddition(e_.left, e_.right)) |res| { - return res; - } - }, - .bin_sub => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - return p.e(E.Number{ .value = vals[0] - vals[1] }, expr.loc); - } - } - }, - .bin_mul => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - return p.e(E.Number{ .value = vals[0] * vals[1] }, expr.loc); - } - } - }, - .bin_div => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - return p.e(E.Number{ .value = vals[0] / vals[1] }, expr.loc); - } - } - }, - .bin_rem => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // is this correct? - return p.e(E.Number{ .value = std.math.mod(f64, vals[0], vals[1]) catch 0.0 }, expr.loc); - } - } - }, - .bin_pow => { - if (p.should_fold_numeric_constants) { - if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - return p.e(E.Number{ .value = std.math.pow(f64, vals[0], vals[1]) }, expr.loc); - } - } - }, - .bin_shl => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) << @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - .bin_shr => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - .bin_u_shr => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - .bin_bitwise_and => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - .bin_bitwise_or => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - .bin_bitwise_xor => { - // TODO: - // if (p.should_fold_numeric_constants) { - // if (Expr.extractNumericValues(e_.left.data, e_.right.data)) |vals| { - // return p.e(E.Number{ .value = ((@floatToInt(i32, vals[0]) >> @floatToInt(u32, vals[1])) & 31) }, expr.loc); - // } - // } - }, - // --------------------------------------------------------------------------------------------------- - // --------------------------------------------------------------------------------------------------- - // --------------------------------------------------------------------------------------------------- - // --------------------------------------------------------------------------------------------------- - .bin_assign => { - - // Optionally preserve the name - if (@as(Expr.Tag, e_.left.data) == .e_identifier) { - e_.right = p.maybeKeepExprSymbolName(e_.right, p.symbols.items[e_.left.data.e_identifier.ref.innerIndex()].original_name, was_anonymous_named_expr); - } - }, - .bin_add_assign => { - // notimpl(); - }, - .bin_sub_assign => { - // notimpl(); - }, - .bin_mul_assign => { - // notimpl(); - }, - .bin_div_assign => { - // notimpl(); - }, - .bin_rem_assign => { - // notimpl(); - }, - .bin_pow_assign => { - // notimpl(); - }, - .bin_shl_assign => { - // notimpl(); - }, - .bin_shr_assign => { - // notimpl(); - }, - .bin_u_shr_assign => { - // notimpl(); - }, - .bin_bitwise_or_assign => { - // notimpl(); - }, - .bin_bitwise_and_assign => { - // notimpl(); - }, - .bin_bitwise_xor_assign => { - // notimpl(); - }, - .bin_nullish_coalescing_assign => { - // notimpl(); - }, - .bin_logical_and_assign => { - // notimpl(); - }, - .bin_logical_or_assign => { - // notimpl(); - }, - else => {}, - } - }, - .e_index => |e_| { - const is_call_target = std.meta.activeTag(p.call_target) == .e_index and expr.data.e_index == p.call_target.e_index; - const is_delete_target = std.meta.activeTag(p.delete_target) == .e_index and expr.data.e_index == p.delete_target.e_index; - - const target = p.visitExprInOut(e_.target, ExprIn{ - // this is awkward due to a zig compiler bug - .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == js_ast.OptionalChain.ccontinue, - }); - e_.target = target; - - switch (e_.index.data) { - .e_private_identifier => |_private| { - var private = _private; - const name = p.loadNameFromRef(private.ref); - const result = p.findSymbol(e_.index.loc, name) catch unreachable; - private.ref = result.ref; - - // Unlike regular identifiers, there are no unbound private identifiers - const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind; - var r: logger.Range = undefined; - if (!Symbol.isKindPrivate(kind)) { - r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable; - } else { - if (in.assign_target != .none and (kind == .private_method or kind == .private_static_method)) { - r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to read-only method \"{s}\" will throw", .{name}) catch unreachable; - } else if (in.assign_target != .none and (kind == .private_get or kind == .private_static_get)) { - r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to getter-only property \"{s}\" will throw", .{name}) catch unreachable; - } else if (in.assign_target != .replace and (kind == .private_set or kind == .private_static_set)) { - r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) }; - p.log.addRangeWarningFmt(p.source, r, p.allocator, "Reading from setter-only property \"{s}\" will throw", .{name}) catch unreachable; - } - } - - e_.index = .{ .data = .{ .e_private_identifier = private }, .loc = e_.index.loc }; - }, - else => { - const index = p.visitExpr(e_.index); - e_.index = index; - }, - } - - if (e_.optional_chain == null and e_.index.data == .e_string and e_.index.data.e_string.isUTF8()) { - const literal = e_.index.data.e_string.slice(p.allocator); - if (p.maybeRewritePropertyAccess( - expr.loc, - e_.target, - literal, - e_.index.loc, - is_call_target, - )) |val| { - return val; - } - - // delete process.env["NODE_ENV"] - // shouldn't be transformed into - // delete undefined - if (!is_delete_target and !is_call_target) { - // We check for defines here as well - // esbuild doesn't do this - // In a lot of codebases, people will sometimes do: - // process.env["NODE_ENV"] - // Often not intentionally - // So we want to be able to detect this and still Do The Right Thing - if (p.define.dots.get(literal)) |parts| { - for (parts) |define| { - if (p.isDotDefineMatch(expr, define.parts)) { - if (!define.data.isUndefined()) { - return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); - } - - return p.e(E.Undefined{}, expr.loc); - } - } - } - } - // "foo"[2] - } else if (e_.optional_chain == null and target.data == .e_string and e_.index.data == .e_number and target.data.e_string.isUTF8() and e_.index.data.e_number.value >= 0) { - const literal = target.data.e_string.slice(p.allocator); - const index = e_.index.data.e_number.toUsize(); - if (literal.len > index) { - return p.e(E.String{ .data = literal[index .. index + 1] }, expr.loc); - } - } - // Create an error for assigning to an import namespace when bundling. Even - // though this is a run-time error, we make it a compile-time error when - // bundling because scope hoisting means these will no longer be run-time - // errors. - if ((in.assign_target != .none or is_delete_target) and @as(Expr.Tag, e_.target.data) == .e_identifier and p.symbols.items[e_.target.data.e_identifier.ref.innerIndex()].kind == .import) { - const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc); - p.log.addRangeErrorFmt( - p.source, - r, - p.allocator, - "Cannot assign to property on import \"{s}\"", - .{p.symbols.items[e_.target.data.e_identifier.ref.innerIndex()].original_name}, - ) catch unreachable; - } - - return p.e(e_, expr.loc); - }, - .e_unary => |e_| { - switch (e_.op) { - .un_typeof => { - const id_before = std.meta.activeTag(e_.value.data) == Expr.Tag.e_identifier; - e_.value = p.visitExprInOut(e_.value, ExprIn{ .assign_target = e_.op.unaryAssignTarget() }); - const id_after = std.meta.activeTag(e_.value.data) == Expr.Tag.e_identifier; - - // The expression "typeof (0, x)" must not become "typeof x" if "x" - // is unbound because that could suppress a ReferenceError from "x" - if (!id_before and id_after and p.symbols.items[e_.value.data.e_identifier.ref.innerIndex()].kind == .unbound) { - e_.value = Expr.joinWithComma( - Expr{ .loc = e_.value.loc, .data = Prefill.Data.Zero }, - e_.value, - p.allocator, - ); - } - - if (SideEffects.typeof(e_.value.data)) |typeof| { - return p.e(E.String{ .data = typeof }, expr.loc); - } - }, - .un_delete => { - e_.value = p.visitExprInOut(e_.value, ExprIn{ .has_chain_parent = true }); - }, - else => { - e_.value = p.visitExprInOut(e_.value, ExprIn{ .assign_target = e_.op.unaryAssignTarget() }); - - // Post-process the unary expression - - switch (e_.op) { - .un_not => { - e_.value = SideEffects.simplifyBoolean(p, e_.value); - - const side_effects = SideEffects.toBoolean(e_.value.data); - if (side_effects.ok) { - return p.e(E.Boolean{ .value = !side_effects.value }, expr.loc); - } - - if (e_.value.maybeSimplifyNot(p.allocator)) |exp| { - return exp; - } - }, - .un_void => { - if (p.exprCanBeRemovedIfUnused(&e_.value)) { - return p.e(E.Undefined{}, e_.value.loc); - } - }, - .un_pos => { - if (SideEffects.toNumber(e_.value.data)) |num| { - return p.e(E.Number{ .value = num }, expr.loc); - } - }, - .un_neg => { - if (SideEffects.toNumber(e_.value.data)) |num| { - return p.e(E.Number{ .value = -num }, expr.loc); - } - }, - - //////////////////////////////////////////////////////////////////////////////// - - .un_pre_dec => { - // TODO: private fields - }, - .un_pre_inc => { - // TODO: private fields - }, - .un_post_dec => { - // TODO: private fields - }, - .un_post_inc => { - // TODO: private fields - }, - else => {}, - } - - // "-(a, b)" => "a, -b" - if (switch (e_.op) { - .un_delete, .un_typeof => false, - else => true, - }) { - switch (e_.value.data) { - .e_binary => |comma| { - if (comma.op == .bin_comma) { - return Expr.joinWithComma( - comma.left, - p.e( - E.Unary{ - .op = e_.op, - .value = comma.right, - }, - comma.right.loc, - ), - p.allocator, - ); - } - }, - else => {}, - } - } - }, - } - }, - .e_dot => |e_| { - const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_dot and expr.data.e_dot == p.delete_target.e_dot; - const is_call_target = @as(Expr.Tag, p.call_target) == .e_dot and expr.data.e_dot == p.call_target.e_dot; - - if (p.define.dots.get(e_.name)) |parts| { - for (parts) |define| { - if (p.isDotDefineMatch(expr, define.parts)) { - // Substitute user-specified defines - if (!define.data.isUndefined()) { - return p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &define.data); - } - - // Copy the side effect flags over in case this expression is unused - if (define.data.can_be_removed_if_unused) { - e_.can_be_removed_if_unused = true; - } - - if (define.data.call_can_be_unwrapped_if_unused and !p.options.ignore_dce_annotations) { - e_.call_can_be_unwrapped_if_unused = true; - } - - break; - } - } - } - - // Track ".then().catch()" chains - if (is_call_target and @as(Expr.Tag, p.then_catch_chain.next_target) == .e_dot and p.then_catch_chain.next_target.e_dot == expr.data.e_dot) { - if (strings.eqlComptime(e_.name, "catch")) { - p.then_catch_chain = ThenCatchChain{ - .next_target = e_.target.data, - .has_catch = true, - }; - } else if (strings.eqlComptime(e_.name, "then")) { - p.then_catch_chain = ThenCatchChain{ - .next_target = e_.target.data, - .has_catch = p.then_catch_chain.has_catch or p.then_catch_chain.has_multiple_args, - }; - } - } - - e_.target = p.visitExpr(e_.target); - if (e_.optional_chain == null) { - if (p.maybeRewritePropertyAccess( - expr.loc, - e_.target, - e_.name, - e_.name_loc, - is_call_target, - )) |_expr| { - return _expr; - } - - if (comptime allow_macros) { - if (p.macro_call_count > 0 and e_.target.data == .e_object and e_.target.data.e_object.was_originally_macro) { - if (e_.target.get(e_.name)) |obj| { - return obj; - } - } - } - } - }, - .e_if => |e_| { - const is_call_target = @as(Expr.Data, p.call_target) == .e_if and expr.data.e_if == p.call_target.e_if; - - e_.test_ = p.visitExpr(e_.test_); - - e_.test_ = SideEffects.simplifyBoolean(p, e_.test_); - - const side_effects = SideEffects.toBoolean(e_.test_.data); - - if (!side_effects.ok) { - e_.yes = p.visitExpr(e_.yes); - e_.no = p.visitExpr(e_.no); - } else { - // Mark the control flow as dead if the branch is never taken - if (side_effects.value) { - // "true ? live : dead" - e_.yes = p.visitExpr(e_.yes); - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - e_.no = p.visitExpr(e_.no); - p.is_control_flow_dead = old; - - if (side_effects.side_effects == .could_have_side_effects) { - return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.e(E.Missing{}, e_.test_.loc), e_.yes, p.allocator); - } - - // "(1 ? fn : 2)()" => "fn()" - // "(1 ? this.fn : 2)" => "this.fn" - // "(1 ? this.fn : 2)()" => "(0, this.fn)()" - if (is_call_target and e_.yes.hasValueForThisInCall()) { - return p.e(E.Number{ .value = 0 }, e_.test_.loc).joinWithComma(e_.yes, p.allocator); - } - - return e_.yes; - } else { - // "false ? dead : live" - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - e_.yes = p.visitExpr(e_.yes); - p.is_control_flow_dead = old; - e_.no = p.visitExpr(e_.no); - - // "(a, false) ? b : c" => "a, c" - if (side_effects.side_effects == .could_have_side_effects) { - return Expr.joinWithComma(SideEffects.simpifyUnusedExpr(p, e_.test_) orelse p.e(E.Missing{}, e_.test_.loc), e_.no, p.allocator); - } - - // "(1 ? fn : 2)()" => "fn()" - // "(1 ? this.fn : 2)" => "this.fn" - // "(1 ? this.fn : 2)()" => "(0, this.fn)()" - if (is_call_target and e_.no.hasValueForThisInCall()) { - return p.e(E.Number{ .value = 0 }, e_.test_.loc).joinWithComma(e_.no, p.allocator); - } - return e_.no; - } - } - }, - .e_await => |e_| { - p.await_target = e_.value.data; - e_.value = p.visitExpr(e_.value); - }, - .e_yield => |e_| { - if (e_.value) |val| { - e_.value = p.visitExpr(val); - } - }, - .e_array => |e_| { - if (in.assign_target != .none) { - p.maybeCommaSpreadError(e_.comma_after_spread); - } - var items = e_.items.slice(); - for (items) |*item| { - switch (item.data) { - .e_missing => {}, - .e_spread => |spread| { - spread.value = p.visitExprInOut(spread.value, ExprIn{ .assign_target = in.assign_target }); - }, - .e_binary => |e2| { - if (in.assign_target != .none and e2.op == .bin_assign) { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(e2.right); - e2.left = p.visitExprInOut(e2.left, ExprIn{ .assign_target = .replace }); - e2.right = p.visitExpr(e2.right); - - if (@as(Expr.Tag, e2.left.data) == .e_identifier) { - e2.right = p.maybeKeepExprSymbolName( - e2.right, - p.symbols.items[e2.left.data.e_identifier.ref.innerIndex()].original_name, - was_anonymous_named_expr, - ); - } - } else { - item.* = p.visitExprInOut(item.*, ExprIn{ .assign_target = in.assign_target }); - } - }, - else => { - item.* = p.visitExprInOut(item.*, ExprIn{ .assign_target = in.assign_target }); - }, - } - } - }, - .e_object => |e_| { - if (in.assign_target != .none) { - p.maybeCommaSpreadError(e_.comma_after_spread); - } - - var has_spread = false; - var has_proto = false; - var i: usize = 0; - while (i < e_.properties.len) : (i += 1) { - var property = e_.properties.ptr[i]; - - if (property.kind != .spread) { - property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{})); - const key = property.key.?; - // Forbid duplicate "__proto__" properties according to the specification - if (!property.flags.contains(.is_computed) and - !property.flags.contains(.was_shorthand) and - !property.flags.contains(.is_method) and - in.assign_target == .none and - key.data.isStringValue() and - strings.eqlComptime( - // __proto__ is utf8, assume it lives in refs - key.data.e_string.slice(p.allocator), - "__proto__", - )) { - if (has_proto) { - const r = js_lexer.rangeOfIdentifier(p.source, key.loc); - p.log.addRangeError(p.source, r, "Cannot specify the \"__proto__\" property more than once per object") catch unreachable; - } - has_proto = true; - } - } else { - has_spread = true; - } - - // Extract the initializer for expressions like "({ a: b = c } = d)" - if (in.assign_target != .none and property.initializer == null and property.value != null) { - switch (property.value.?.data) { - .e_binary => |bin| { - if (bin.op == .bin_assign) { - property.initializer = bin.right; - property.value = bin.left; - } - }, - else => {}, - } - } - - if (property.value != null) { - property.value = p.visitExprInOut(property.value.?, ExprIn{ .assign_target = in.assign_target }); - } - - if (property.initializer != null) { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(property.initializer orelse unreachable); - property.initializer = p.visitExpr(property.initializer.?); - - if (property.value) |val| { - if (@as(Expr.Tag, val.data) == .e_identifier) { - property.initializer = p.maybeKeepExprSymbolName( - property.initializer orelse unreachable, - p.symbols.items[val.data.e_identifier.ref.innerIndex()].original_name, - was_anonymous_named_expr, - ); - } - } - } - - e_.properties.ptr[i] = property; - } - }, - .e_import => |e_| { - const state = TransposeState{ - // we must check that the await_target is an e_import or it will crash - // example from next.js where not checking causes a panic: - // ``` - // const { - // normalizeLocalePath, - // } = require('../shared/lib/i18n/normalize-locale-path') as typeof import('../shared/lib/i18n/normalize-locale-path') - // ``` - .is_await_target = if (p.await_target != null) p.await_target.? == .e_import and p.await_target.?.e_import == e_ else false, - .is_then_catch_target = p.then_catch_chain.has_catch and std.meta.activeTag(p.then_catch_chain.next_target) == .e_import and expr.data.e_import == p.then_catch_chain.next_target.e_import, - .loc = e_.expr.loc, - }; - - e_.expr = p.visitExpr(e_.expr); - return p.import_transposer.maybeTransposeIf(e_.expr, state); - }, - .e_call => |e_| { - p.call_target = e_.target.data; - - p.then_catch_chain = ThenCatchChain{ - .next_target = e_.target.data, - .has_multiple_args = e_.args.len >= 2, - .has_catch = @as(Expr.Tag, p.then_catch_chain.next_target) == .e_call and p.then_catch_chain.next_target.e_call == expr.data.e_call and p.then_catch_chain.has_catch, - }; - - e_.target = p.visitExprInOut(e_.target, ExprIn{ - .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue, - }); - var could_be_require_resolve: bool = false; - - // Copy the call side effect flag over if this is a known target - switch (e_.target.data) { - .e_identifier => |ident| { - e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or ident.call_can_be_unwrapped_if_unused; - }, - .e_dot => |dot| { - e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or dot.call_can_be_unwrapped_if_unused; - // Prepare to recognize "require.resolve()" calls - could_be_require_resolve = (e_.args.len >= 1 and - dot.optional_chain == null and - @as(Expr.Tag, dot.target.data) == .e_identifier and - dot.target.data.e_identifier.ref.eql(p.require_ref) and - strings.eqlComptime(dot.name, "resolve")); - }, - else => {}, - } - - const is_macro_ref: bool = if (comptime FeatureFlags.is_macro_enabled and - jsx_transform_type != .macro) - e_.target.data == .e_import_identifier and p.macro.refs.contains(e_.target.data.e_import_identifier.ref) - else - false; - - { - const old_ce = p.options.ignore_dce_annotations; - defer p.options.ignore_dce_annotations = old_ce; - if (is_macro_ref) - p.options.ignore_dce_annotations = true; - - for (e_.args.slice()) |_, i| { - const arg = e_.args.ptr[i]; - e_.args.ptr[i] = p.visitExpr(arg); - } - } - - if (e_.optional_chain == null and @as(Expr.Tag, e_.target.data) == .e_identifier and e_.target.data.e_identifier.ref.eql(p.require_ref)) { - e_.can_be_unwrapped_if_unused = false; - - // Heuristic: omit warnings inside try/catch blocks because presumably - // the try/catch statement is there to handle the potential run-time - // error from the unbundled require() call failing. - if (e_.args.len == 1) { - const first = e_.args.first_(); - switch (first.data) { - .e_string => { - // require(FOO) => require(FOO) - return p.transposeRequire(first, null); - }, - .e_if => { - // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') - // This makes static analysis later easier - return p.require_transposer.maybeTransposeIf(first, null); - }, - else => {}, - } - } - - if (p.options.warn_about_unbundled_modules) { - const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc); - p.log.addRangeDebug(p.source, r, "This call to \"require\" will not be bundled because it has multiple arguments") catch unreachable; - } - } - - if (could_be_require_resolve) { - // Ignore calls to require.resolve() if the control flow is provably - // dead here. We don't want to spend time scanning the required files - // if they will never be used. - if (p.is_control_flow_dead) { - return p.e(E.Null{}, expr.loc); - } - - if (p.options.features.dynamic_require) { - p.ignoreUsage(p.require_ref); - // require.resolve(FOO) => import.meta.resolveSync(FOO) - // require.resolve(FOO) => import.meta.resolveSync(FOO, pathsObject) - return p.e( - E.Call{ - .target = p.e( - E.Dot{ - .target = p.e(E.ImportMeta{}, e_.target.loc), - .name = "resolveSync", - .name_loc = e_.target.data.e_dot.name_loc, - }, - e_.target.loc, - ), - .args = e_.args, - .close_paren_loc = e_.close_paren_loc, - }, - expr.loc, - ); - } - - if (e_.args.len == 1) { - const first = e_.args.first_(); - switch (first.data) { - .e_string => { - // require(FOO) => require(FOO) - return p.transposeRequireResolve(first, expr); - }, - .e_if => { - // require(FOO ? '123' : '456') => FOO ? require('123') : require('456') - // This makes static analysis later easier - return p.require_resolve_transposer.maybeTransposeIf(first, expr); - }, - else => {}, - } - } - } - - if (comptime allow_macros) { - if (is_macro_ref) { - const ref = e_.target.data.e_import_identifier.ref; - const import_record_id = p.macro.refs.get(ref).?; - p.ignoreUsage(ref); - if (p.is_control_flow_dead) { - return p.e(E.Undefined{}, e_.target.loc); - } - const name = p.symbols.items[ref.innerIndex()].original_name; - const record = &p.import_records.items[import_record_id]; - const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } }; - const start_error_count = p.log.msgs.items.len; - p.macro_call_count += 1; - const macro_result = - p.options.macro_context.call( - record.path.text, - p.source.path.sourceDir(), - p.log, - p.source, - record.range, - copied, - &.{}, - name, - MacroVisitor, - MacroVisitor{ .p = p, .loc = expr.loc }, - ) catch |err| { - if (err == error.MacroFailed) { - if (p.log.msgs.items.len == start_error_count) { - p.log.addError(p.source, expr.loc, "macro threw exception") catch unreachable; - } - } else { - p.log.addErrorFmt(p.source, expr.loc, p.allocator, "{s} error in macro", .{@errorName(err)}) catch unreachable; - } - return expr; - }; - - if (macro_result.data != .e_call) { - return p.visitExpr(macro_result); - } - } - } - - return expr; - }, - .e_new => |e_| { - e_.target = p.visitExpr(e_.target); - // p.warnA - - for (e_.args.slice()) |*arg| { - arg.* = p.visitExpr(arg.*); - } - }, - .e_arrow => |e_| { - const old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_visit); - p.fn_or_arrow_data_visit = FnOrArrowDataVisit{ - .is_arrow = true, - .is_async = e_.is_async, - }; - - // Mark if we're inside an async arrow function. This value should be true - // even if we're inside multiple arrow functions and the closest inclosing - // arrow function isn't async, as long as at least one enclosing arrow - // function within the current enclosing function is async. - const old_inside_async_arrow_fn = p.fn_only_data_visit.is_inside_async_arrow_fn; - p.fn_only_data_visit.is_inside_async_arrow_fn = e_.is_async or p.fn_only_data_visit.is_inside_async_arrow_fn; - - p.pushScopeForVisitPass(.function_args, expr.loc) catch unreachable; - var dupe = p.allocator.dupe(Stmt, e_.body.stmts) catch unreachable; - - p.visitArgs(e_.args, VisitArgsOpts{ - .has_rest_arg = e_.has_rest_arg, - .body = dupe, - .is_unique_formal_parameters = true, - }); - p.pushScopeForVisitPass(.function_body, e_.body.loc) catch unreachable; - - var stmts_list = ListManaged(Stmt).fromOwnedSlice(p.allocator, dupe); - var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body }; - p.visitStmtsAndPrependTempRefs(&stmts_list, &temp_opts) catch unreachable; - p.allocator.free(e_.body.stmts); - e_.body.stmts = stmts_list.toOwnedSlice(); - p.popScope(); - p.popScope(); - - p.fn_only_data_visit.is_inside_async_arrow_fn = old_inside_async_arrow_fn; - p.fn_or_arrow_data_visit = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_visit), &old_fn_or_arrow_data); - }, - .e_function => |e_| { - e_.func = p.visitFunc(e_.func, expr.loc); - if (e_.func.name) |name| { - return p.keepExprSymbolName(expr, p.symbols.items[name.ref.?.innerIndex()].original_name); - } - }, - .e_class => |e_| { - - // This might be wrong. - _ = p.visitClass(expr.loc, e_); - }, - else => {}, - } - return expr; - } - - fn visitArgs(p: *P, args: []G.Arg, opts: VisitArgsOpts) void { - const strict_loc = fnBodyContainsUseStrict(opts.body); - const has_simple_args = isSimpleParameterList(args, opts.has_rest_arg); - var duplicate_args_check: ?*StringVoidMap.Node = null; - defer { - if (duplicate_args_check) |checker| { - StringVoidMap.release(checker); - } - } - - // Section 15.2.1 Static Semantics: Early Errors: "It is a Syntax Error if - // FunctionBodyContainsUseStrict of FunctionBody is true and - // IsSimpleParameterList of FormalParameters is false." - if (strict_loc != null and !has_simple_args) { - p.log.addRangeError(p.source, p.source.rangeOfString(strict_loc.?), "Cannot use a \"use strict\" directive in a function with a non-simple parameter list") catch unreachable; - } - - // Section 15.1.1 Static Semantics: Early Errors: "Multiple occurrences of - // the same BindingIdentifier in a FormalParameterList is only allowed for - // functions which have simple parameter lists and which are not defined in - // strict mode code." - if (opts.is_unique_formal_parameters or strict_loc != null or !has_simple_args or p.isStrictMode()) { - duplicate_args_check = StringVoidMap.get(bun.default_allocator); - } - - var i: usize = 0; - var duplicate_args_check_ptr: ?*StringVoidMap = if (duplicate_args_check != null) - &duplicate_args_check.?.data - else - null; - - while (i < args.len) : (i += 1) { - if (args[i].ts_decorators.len > 0) { - args[i].ts_decorators = p.visitTSDecorators(args[i].ts_decorators); - } - - p.visitBinding(args[i].binding, duplicate_args_check_ptr); - if (args[i].default) |default| { - args[i].default = p.visitExpr(default); - } - } - } - - pub fn visitTSDecorators(p: *P, decs: ExprNodeList) ExprNodeList { - var i: usize = 0; - while (i < decs.len) : (i += 1) { - decs.ptr[i] = p.visitExpr(decs.ptr[i]); - } - - return decs; - } - - pub fn keepExprSymbolName(_: *P, _value: Expr, _: string) Expr { - return _value; - // var start = p.expr_list.items.len; - // p.expr_list.ensureUnusedCapacity(2) catch unreachable; - // p.expr_list.appendAssumeCapacity(_value); - // p.expr_list.appendAssumeCapacity(p.e(E.String{ - // .utf8 = name, - // }, _value.loc)); - - // var value = p.callRuntime(_value.loc, "ℹ", p.expr_list.items[start..p.expr_list.items.len]); - // // Make sure tree shaking removes this if the function is never used - // value.getCall().can_be_unwrapped_if_unused = true; - // return value; - } - - pub fn fnBodyContainsUseStrict(body: []Stmt) ?logger.Loc { - for (body) |stmt| { - switch (stmt.data) { - .s_comment => { - continue; - }, - .s_directive => |dir| { - if (strings.utf16EqlString(dir.value, "use strict")) { - return stmt.loc; - } - }, - else => {}, - } - } - - return null; - } - - pub fn isSimpleParameterList(args: []G.Arg, has_rest_arg: bool) bool { - if (has_rest_arg) { - return false; - } - - for (args) |arg| { - if (@as(Binding.Tag, arg.binding.data) != .b_identifier or arg.default != null) { - return false; - } - } - - return true; - } - - pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool { - if (class.extends) |*extends| { - if (!p.exprCanBeRemovedIfUnused(extends)) { - return false; - } - } - - for (class.properties) |*property| { - if (property.kind == .class_static_block) { - if (!p.stmtsCanBeRemovedIfUnused(property.class_static_block.?.stmts.slice())) { - return false; - } - continue; - } - - if (!p.exprCanBeRemovedIfUnused(&(property.key orelse unreachable))) { - return false; - } - - if (property.value) |*val| { - if (!p.exprCanBeRemovedIfUnused(val)) { - return false; - } - } - - if (property.initializer) |*val| { - if (!p.exprCanBeRemovedIfUnused(val)) { - return false; - } - } - } - - return true; - } - - // TODO: - // When React Fast Refresh is enabled, anything that's a JSX component should not be removable - // This is to improve the reliability of fast refresh between page loads. - pub fn exprCanBeRemovedIfUnused(p: *P, expr: *const Expr) bool { - switch (expr.data) { - .e_null, - .e_undefined, - .e_missing, - .e_boolean, - .e_number, - .e_big_int, - .e_string, - .e_this, - .e_reg_exp, - .e_function, - .e_arrow, - .e_import_meta, - => { - return true; - }, - - .e_dot => |ex| { - return ex.can_be_removed_if_unused; - }, - .e_class => |ex| { - return p.classCanBeRemovedIfUnused(ex); - }, - .e_identifier => |ex| { - if (ex.must_keep_due_to_with_stmt) { - return false; - } - - // Unbound identifiers cannot be removed because they can have side effects. - // One possible side effect is throwing a ReferenceError if they don't exist. - // Another one is a getter with side effects on the global object: - // - // Object.defineProperty(globalThis, 'x', { - // get() { - // sideEffect(); - // }, - // }); - // - // Be very careful about this possibility. It's tempting to treat all - // identifier expressions as not having side effects but that's wrong. We - // must make sure they have been declared by the code we are currently - // compiling before we can tell that they have no side effects. - // - // Note that we currently ignore ReferenceErrors due to TDZ access. This is - // incorrect but proper TDZ analysis is very complicated and would have to - // be very conservative, which would inhibit a lot of optimizations of code - // inside closures. This may need to be revisited if it proves problematic. - if (ex.can_be_removed_if_unused or p.symbols.items[ex.ref.innerIndex()].kind != .unbound) { - return true; - } - }, - .e_import_identifier => { - - // References to an ES6 import item are always side-effect free in an - // ECMAScript environment. - // - // They could technically have side effects if the imported module is a - // CommonJS module and the import item was translated to a property access - // (which esbuild's bundler does) and the property has a getter with side - // effects. - // - // But this is very unlikely and respecting this edge case would mean - // disabling tree shaking of all code that references an export from a - // CommonJS module. It would also likely violate the expectations of some - // developers because the code *looks* like it should be able to be tree - // shaken. - // - // So we deliberately ignore this edge case and always treat import item - // references as being side-effect free. - return true; - }, - .e_if => |ex| { - return p.exprCanBeRemovedIfUnused(&ex.test_) and - (p.isSideEffectFreeUnboundIdentifierRef( - ex.yes, - ex.test_, - true, - ) or - p.exprCanBeRemovedIfUnused(&ex.yes)) and - (p.isSideEffectFreeUnboundIdentifierRef( - ex.no, - ex.test_, - false, - ) or p.exprCanBeRemovedIfUnused( - &ex.no, - )); - }, - .e_array => |ex| { - for (ex.items.slice()) |*item| { - if (!p.exprCanBeRemovedIfUnused(item)) { - return false; - } - } - - return true; - }, - .e_object => |ex| { - for (ex.properties.slice()) |*property| { - - // The key must still be evaluated if it's computed or a spread - if (property.kind == .spread or property.flags.contains(.is_computed) or property.flags.contains(.is_spread)) { - return false; - } - - if (property.value) |*val| { - if (!p.exprCanBeRemovedIfUnused(val)) { - return false; - } - } - } - return true; - }, - .e_call => |ex| { - - // A call that has been marked "__PURE__" can be removed if all arguments - // can be removed. The annotation causes us to ignore the target. - if (ex.can_be_unwrapped_if_unused) { - for (ex.args.slice()) |*arg| { - if (!p.exprCanBeRemovedIfUnused(arg)) { - return false; - } - } - return true; - } - }, - .e_new => |ex| { - - // A call that has been marked "__PURE__" can be removed if all arguments - // can be removed. The annotation causes us to ignore the target. - if (ex.can_be_unwrapped_if_unused) { - for (ex.args.slice()) |*arg| { - if (!p.exprCanBeRemovedIfUnused(arg)) { - return false; - } - } - - return true; - } - }, - .e_unary => |ex| { - switch (ex.op) { - // These operators must not have any type conversions that can execute code - // such as "toString" or "valueOf". They must also never throw any exceptions. - .un_void, .un_not => { - return p.exprCanBeRemovedIfUnused(&ex.value); - }, - - // The "typeof" operator doesn't do any type conversions so it can be removed - // if the result is unused and the operand has no side effects. However, it - // has a special case where if the operand is an identifier expression such - // as "typeof x" and "x" doesn't exist, no reference error is thrown so the - // operation has no side effects. - // - // Note that there *is* actually a case where "typeof x" can throw an error: - // when "x" is being referenced inside of its TDZ (temporal dead zone). TDZ - // checks are not yet handled correctly by bun or esbuild, so this possibility is - // currently ignored. - .un_typeof => { - if (ex.value.data == .e_identifier) { - return true; - } - - return p.exprCanBeRemovedIfUnused(&ex.value); - }, - - else => {}, - } - }, - .e_binary => |ex| { - switch (ex.op) { - // These operators must not have any type conversions that can execute code - // such as "toString" or "valueOf". They must also never throw any exceptions. - .bin_strict_eq, - .bin_strict_ne, - .bin_comma, - .bin_nullish_coalescing, - => return p.exprCanBeRemovedIfUnused(&ex.left) and p.exprCanBeRemovedIfUnused(&ex.right), - - // Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed - .bin_logical_or => return p.exprCanBeRemovedIfUnused(&ex.left) and - (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, false) or p.exprCanBeRemovedIfUnused(&ex.right)), - - // Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed - .bin_logical_and => return p.exprCanBeRemovedIfUnused(&ex.left) and - (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, true) or p.exprCanBeRemovedIfUnused(&ex.right)), - - // For "==" and "!=", pretend the operator was actually "===" or "!==". If - // we know that we can convert it to "==" or "!=", then we can consider the - // operator itself to have no side effects. This matters because our mangle - // logic will convert "typeof x === 'object'" into "typeof x == 'object'" - // and since "typeof x === 'object'" is considered to be side-effect free, - // we must also consider "typeof x == 'object'" to be side-effect free. - .bin_loose_eq, .bin_loose_ne => return SideEffects.canChangeStrictToLoose( - ex.left.data, - ex.right.data, - ) and - p.exprCanBeRemovedIfUnused(&ex.left) and p.exprCanBeRemovedIfUnused(&ex.right), - else => {}, - } - }, - .e_template => |templ| { - if (templ.tag == null) { - for (templ.parts) |part| { - if (!p.exprCanBeRemovedIfUnused(&part.value) or part.value.knownPrimitive() == .unknown) { - return false; - } - } - } - - return true; - }, - else => {}, - } - - return false; - } - - fn isSideEffectFreeUnboundIdentifierRef(p: *P, value: Expr, guard_condition: Expr, is_yes_branch: bool) bool { - if (value.data != .e_identifier or - p.symbols.items[value.data.e_identifier.ref.innerIndex()].kind != .unbound or - guard_condition.data != .e_binary) - return false; - - const binary = guard_condition.data.e_binary.*; - - switch (binary.op) { - .bin_strict_eq, .bin_strict_ne, .bin_loose_eq, .bin_loose_ne => { - // typeof x !== 'undefined' - var typeof: Expr.Data = binary.left.data; - var compare: Expr.Data = binary.right.data; - // typeof 'undefined' !== x - if (typeof == .e_string) { - typeof = binary.right.data; - compare = binary.left.data; - } - - // this order because Expr.Data Tag is not a pointer - // so it should be slightly faster to compare - if (compare != .e_string or - typeof != .e_unary) - return false; - const unary = typeof.e_unary.*; - - if (unary.op != .un_typeof or unary.value.data != .e_identifier) - return false; - - const id = value.data.e_identifier.ref; - const id2 = unary.value.data.e_identifier.ref; - return ((compare.e_string.eqlComptime("undefined") == is_yes_branch) == - (binary.op == .bin_strict_ne or binary.op == .bin_loose_ne)) and - id.eql(id2); - }, - else => return false, - } - } - - fn jsxStringsToMemberExpressionAutomatic(p: *P, loc: logger.Loc, is_static: bool) Expr { - return p.jsxStringsToMemberExpression(loc, if (is_static and !p.options.jsx.development) - p.jsxs_runtime.ref - else - p.jsx_runtime.ref); - } - - fn maybeRelocateVarsToTopLevel(p: *P, decls: []const G.Decl, mode: RelocateVars.Mode) RelocateVars { - // Only do this when the scope is not already top-level and when we're not inside a function. - if (p.current_scope == p.module_scope) { - return .{ .ok = false }; - } - - var scope = p.current_scope; - while (!scope.kindStopsHoisting()) { - if (comptime Environment.allow_assert) assert(scope.parent != null); - scope = scope.parent.?; - } - - if (scope != p.module_scope) { - return .{ .ok = false }; - } - - var value: Expr = Expr{ .loc = logger.Loc.Empty, .data = Expr.Data{ .e_missing = E.Missing{} } }; - var any_initializers = false; - for (decls) |decl| { - const binding = Binding.toExpr( - &decl.binding, - p.to_expr_wrapper_hoisted, - ); - if (decl.value) |decl_value| { - value = value.joinWithComma(Expr.assign(binding, decl_value, p.allocator), p.allocator); - any_initializers = true; - } else if (mode == .for_in_or_for_of) { - value = value.joinWithComma(binding, p.allocator); - } - } - - if (std.meta.activeTag(value.data) == .e_missing or !any_initializers) { - return .{ .ok = true }; - } - - return .{ .stmt = p.s(S.SExpr{ .value = value }, value.loc), .ok = true }; - } - - // fn maybeInlineMacroObject(p: *P, decl: *G.Decl, macro: Expr) void { - // if (decl.value == null) return; - // switch (decl.binding.data) { - // .b_identifier => |ident| { - // if (macro.get(p.loadNameFromRef(ident.ref))) |val| { - // decl - // } - // } - // } - // } - // if (comptime allow_macros) { - // if (p.macro_call_count and data.decls[i].value != null and - // data.decls[i].value.?.data == .e_object and data.decls[i].value.?.data.e_object.was_originally_macro) - // { - // p.maybeInlineMacroObject(&data.decls[i], data.decls[i].value.?); - // } - // } - - // EDot nodes represent a property access. This function may return an - // expression to replace the property access with. It assumes that the - // target of the EDot expression has already been visited. - fn maybeRewritePropertyAccess( - p: *P, - loc: logger.Loc, - target: js_ast.Expr, - name: string, - name_loc: logger.Loc, - is_call_target: bool, - ) ?Expr { - switch (target.data) { - .e_identifier => |id| { - // Rewrite "module.require()" to "require()" for Webpack compatibility. - // See https://github.com/webpack/webpack/pull/7750 for more info. - // This also makes correctness a little easier. - if (is_call_target and id.ref.eql(p.module_ref) and strings.eqlComptime(name, "require")) { - p.ignoreUsage(p.module_ref); - p.recordUsage(p.require_ref); - return p.e(E.Identifier{ .ref = p.require_ref }, name_loc); - } - - // If this is a known enum value, inline the value of the enum - if (is_typescript_enabled) { - if (p.known_enum_values.get(id.ref)) |enum_value_map| { - if (enum_value_map.get(name)) |enum_value| { - return p.e(E.Number{ .value = enum_value }, loc); - } - } - } - }, - .e_string => |str| { - // minify "long-string".length to 11 - if (strings.eqlComptime(name, "length")) { - return p.e(E.Number{ .value = @intToFloat(f64, str.len()) }, loc); - } - }, - else => {}, - } - - return null; - } - - pub fn ignoreUsage(p: *P, ref: Ref) void { - if (!p.is_control_flow_dead) { - p.symbols.items[ref.innerIndex()].use_count_estimate -|= 1; - var use = p.symbol_uses.get(ref) orelse p.panic("Expected symbol_uses to exist {s}\n{s}", .{ ref, p.symbol_uses }); - use.count_estimate -|= 1; - if (use.count_estimate == 0) { - _ = p.symbol_uses.swapRemove(ref); - } else { - p.symbol_uses.putAssumeCapacity(ref, use); - } - } - - // Don't roll back the "tsUseCounts" increment. This must be counted even if - // the value is ignored because that's what the TypeScript compiler does. - } - - fn visitAndAppendStmt(p: *P, stmts: *ListManaged(Stmt), stmt: *Stmt) !void { - switch (stmt.data) { - // These don't contain anything to traverse - - .s_debugger, .s_empty, .s_comment => {}, - .s_type_script => { - - // Erase TypeScript constructs from the output completely - return; - }, - .s_directive => { - - // if p.isStrictMode() && s.LegacyOctalLoc.Start > 0 { - // p.markStrictModeFeature(legacyOctalEscape, p.source.RangeOfLegacyOctalEscape(s.LegacyOctalLoc), "") - // } - return; - }, - .s_import => |data| { - try p.recordDeclaredSymbol(data.namespace_ref); - - if (data.default_name) |default_name| { - try p.recordDeclaredSymbol(default_name.ref.?); - } - - if (data.items.len > 0) { - for (data.items) |*item| { - try p.recordDeclaredSymbol(item.name.ref.?); - } - } - }, - .s_export_clause => |data| { - // "export {foo}" - var end: usize = 0; - var any_replaced = false; - if (p.options.features.replace_exports.count() > 0) { - for (data.items) |*item| { - const name = p.loadNameFromRef(item.name.ref.?); - - const symbol = try p.findSymbol(item.alias_loc, name); - const ref = symbol.ref; - - if (p.options.features.replace_exports.getPtr(name)) |entry| { - if (entry.* != .replace) p.ignoreUsage(symbol.ref); - _ = p.injectReplacementExport(stmts, symbol.ref, stmt.loc, entry); - any_replaced = true; - continue; - } - - if (p.symbols.items[ref.innerIndex()].kind == .unbound) { - // Silently strip exports of non-local symbols in TypeScript, since - // those likely correspond to type-only exports. But report exports of - // non-local symbols as errors in JavaScript. - if (!is_typescript_enabled) { - const r = js_lexer.rangeOfIdentifier(p.source, item.name.loc); - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "\"{s}\" is not declared in this file", .{name}); - } - continue; - } - - item.name.ref = ref; - data.items[end] = item.*; - end += 1; - } - } else { - for (data.items) |*item| { - const name = p.loadNameFromRef(item.name.ref.?); - const symbol = try p.findSymbol(item.alias_loc, name); - const ref = symbol.ref; - - if (p.symbols.items[ref.innerIndex()].kind == .unbound) { - // Silently strip exports of non-local symbols in TypeScript, since - // those likely correspond to type-only exports. But report exports of - // non-local symbols as errors in JavaScript. - if (!is_typescript_enabled) { - const r = js_lexer.rangeOfIdentifier(p.source, item.name.loc); - try p.log.addRangeErrorFmt(p.source, r, p.allocator, "\"{s}\" is not declared in this file", .{name}); - continue; - } - continue; - } - - item.name.ref = ref; - data.items[end] = item.*; - end += 1; - } - } - - const remove_for_tree_shaking = any_replaced and end == 0 and data.items.len > 0 and p.options.tree_shaking; - data.items.len = end; - - if (remove_for_tree_shaking) { - return; - } - }, - .s_export_from => |data| { - // When HMR is enabled, we need to transform this into - // import {foo} from "./foo"; - // export {foo}; - - // From: - // export {foo as default} from './foo'; - // To: - // import {default as foo} from './foo'; - // export {foo}; - - // "export {foo} from 'path'" - const name = p.loadNameFromRef(data.namespace_ref); - - data.namespace_ref = try p.newSymbol(.other, name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); - try p.recordDeclaredSymbol(data.namespace_ref); - - if (p.options.features.replace_exports.count() > 0) { - var j: usize = 0; - // This is a re-export and the symbols created here are used to reference - for (data.items) |item| { - const old_ref = item.name.ref.?; - - if (p.options.features.replace_exports.count() > 0) { - if (p.options.features.replace_exports.getPtr(item.alias)) |entry| { - _ = p.injectReplacementExport(stmts, old_ref, logger.Loc.Empty, entry); - - continue; - } - } - - const _name = p.loadNameFromRef(old_ref); - - const ref = try p.newSymbol(.other, _name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); - try p.recordDeclaredSymbol(data.namespace_ref); - data.items[j] = item; - data.items[j].name.ref = ref; - j += 1; - } - - data.items.len = j; - - if (j == 0 and data.items.len > 0) { - return; - } - } else { - - // This is a re-export and the symbols created here are used to reference - for (data.items) |*item| { - const _name = p.loadNameFromRef(item.name.ref.?); - const ref = try p.newSymbol(.other, _name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); - try p.recordDeclaredSymbol(data.namespace_ref); - item.name.ref = ref; - } - } - }, - .s_export_star => |data| { - - // "export * from 'path'" - const name = p.loadNameFromRef(data.namespace_ref); - data.namespace_ref = try p.newSymbol(.other, name); - try p.current_scope.generated.append(p.allocator, data.namespace_ref); - try p.recordDeclaredSymbol(data.namespace_ref); - - // "export * as ns from 'path'" - if (data.alias) |alias| { - if (p.options.features.replace_exports.count() > 0) { - if (p.options.features.replace_exports.getPtr(alias.original_name)) |entry| { - _ = p.injectReplacementExport(stmts, p.declareSymbol(.other, logger.Loc.Empty, alias.original_name) catch unreachable, logger.Loc.Empty, entry); - return; - } - } - // "import * as ns from 'path'" - // "export {ns}" - - // jarred: For now, just always do this transform. - // because Safari doesn't support it and I've seen cases where this breaks - - p.recordUsage(data.namespace_ref); - try stmts.ensureTotalCapacity(stmts.items.len + 2); - stmts.appendAssumeCapacity(p.s(S.Import{ .namespace_ref = data.namespace_ref, .star_name_loc = alias.loc, .import_record_index = data.import_record_index }, stmt.loc)); - - var items = try List(js_ast.ClauseItem).initCapacity(p.allocator, 1); - items.appendAssumeCapacity(js_ast.ClauseItem{ .alias = alias.original_name, .original_name = alias.original_name, .alias_loc = alias.loc, .name = LocRef{ .loc = alias.loc, .ref = data.namespace_ref } }); - stmts.appendAssumeCapacity(p.s(S.ExportClause{ .items = items.toOwnedSlice(p.allocator), .is_single_line = true }, stmt.loc)); - return; - } - }, - .s_export_default => |data| { - if (data.default_name.ref) |ref| { - try p.recordDeclaredSymbol(ref); - } - - var mark_for_replace: bool = false; - - const orig_dead = p.is_control_flow_dead; - if (p.options.features.replace_exports.count() > 0) { - if (p.options.features.replace_exports.getPtr("default")) |entry| { - p.is_control_flow_dead = entry.* != .replace; - mark_for_replace = true; - } - } - - defer { - p.is_control_flow_dead = orig_dead; - } - - switch (data.value) { - .expr => |expr| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(expr); - - data.value.expr = p.visitExpr(expr); - - if (p.is_control_flow_dead) { - return; - } - - // Optionally preserve the name - - data.value.expr = p.maybeKeepExprSymbolName(data.value.expr, js_ast.ClauseItem.default_alias, was_anonymous_named_expr); - - // Discard type-only export default statements - if (is_typescript_enabled) { - switch (data.value.expr.data) { - .e_identifier => |ident| { - if (!ident.ref.isSourceContentsSlice()) { - const symbol = p.symbols.items[ident.ref.innerIndex()]; - if (symbol.kind == .unbound) { - if (p.local_type_names.get(symbol.original_name)) |local_type| { - if (local_type) { - return; - } - } - } - } - }, - else => {}, - } - } - - if (mark_for_replace) { - var entry = p.options.features.replace_exports.getPtr("default").?; - if (entry.* == .replace) { - data.value.expr = entry.replace; - } else { - _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); - return; - } - } - - // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(expr.loc); - export_default_args[1] = data.value.expr; - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc)) catch unreachable; - return; - } - }, - - .stmt => |s2| { - switch (s2.data) { - .s_function => |func| { - var name: string = ""; - const had_name = func.func.name != null; - if (func.func.name) |func_loc| { - name = p.loadNameFromRef(func_loc.ref.?); - } else { - func.func.name = data.default_name; - name = js_ast.ClauseItem.default_alias; - } - - func.func = p.visitFunc(func.func, func.func.open_parens_loc); - - if (p.is_control_flow_dead) { - return; - } - - if (mark_for_replace) { - var entry = p.options.features.replace_exports.getPtr("default").?; - if (entry.* == .replace) { - data.value = .{ .expr = entry.replace }; - } else { - _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); - return; - } - - // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(data.value.expr.loc); - export_default_args[1] = data.value.expr; - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; - return; - } - } else if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(s2.loc); - - if (had_name) { - export_default_args[1] = p.e(E.Identifier{ .ref = func.func.name.?.ref.? }, s2.loc); - stmts.ensureUnusedCapacity(2) catch unreachable; - - stmts.appendAssumeCapacity(s2); - } else { - export_default_args[1] = p.e(E.Function{ .func = func.func }, s2.loc); - } - - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(s2.loc, "__exportDefault", export_default_args) }, s2.loc)) catch unreachable; - return; - } - - stmts.append(stmt.*) catch unreachable; - - // if (func.func.name != null and func.func.name.?.ref != null) { - // stmts.append(p.keepStmtSymbolName(func.func.name.?.loc, func.func.name.?.ref.?, name)) catch unreachable; - // } - // prevent doubling export default function name - return; - }, - .s_class => |class| { - // TODO: https://github.com/Jarred-Sumner/bun/issues/51 - _ = p.visitClass(s2.loc, &class.class); - - if (p.is_control_flow_dead) - return; - - if (mark_for_replace) { - var entry = p.options.features.replace_exports.getPtr("default").?; - if (entry.* == .replace) { - data.value = .{ .expr = entry.replace }; - } else { - _ = p.injectReplacementExport(stmts, Ref.None, logger.Loc.Empty, entry); - return; - } - - // When bundling, replace ExportDefault with __exportDefault(exportsRef, expr); - if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(data.value.expr.loc); - export_default_args[1] = data.value.expr; - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(data.value.expr.loc, "__exportDefault", export_default_args) }, data.value.expr.loc)) catch unreachable; - return; - } - } else if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(s2.loc); - - const class_name_ref = brk: { - if (class.class.class_name) |class_name_ref| { - if (class_name_ref.ref) |ref| { - break :brk ref; - } - } - break :brk null; - }; - if (class_name_ref) |ref| { - stmts.ensureUnusedCapacity(2) catch unreachable; - stmts.appendAssumeCapacity(s2); - export_default_args[1] = p.e(E.Identifier{ .ref = ref }, s2.loc); - } else { - export_default_args[1] = p.e(class.class, s2.loc); - } - - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(s2.loc, "__exportDefault", export_default_args) }, s2.loc)) catch unreachable; - return; - } - - stmts.append(stmt.*) catch unreachable; - return; - }, - else => {}, - } - }, - } - }, - .s_export_equals => |data| { - if (p.options.enable_bundling) { - var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; - export_default_args[0] = p.@"module.exports"(stmt.loc); - export_default_args[1] = data.value; - - stmts.append(p.s(S.SExpr{ .value = p.callRuntime(stmt.loc, "__exportDefault", export_default_args) }, stmt.loc)) catch unreachable; - return; - } - - // "module.exports = value" - stmts.append( - Expr.assignStmt( - p.@"module.exports"( - stmt.loc, - ), - p.visitExpr(data.value), - p.allocator, - ), - ) catch unreachable; - p.recordUsage(p.module_ref); - }, - .s_break => |data| { - if (data.label) |*label| { - const name = p.loadNameFromRef(label.ref orelse p.panic("Expected label to have a ref", .{})); - const res = p.findLabelSymbol(label.loc, name); - if (res.found) { - label.ref = res.ref; - } else { - data.label = null; - } - } else if (!p.fn_or_arrow_data_visit.is_inside_loop and !p.fn_or_arrow_data_visit.is_inside_switch) { - const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); - p.log.addRangeError(p.source, r, "Cannot use \"break\" here") catch unreachable; - } - }, - .s_continue => |data| { - if (data.label) |*label| { - const name = p.loadNameFromRef(label.ref orelse p.panic("Expected continue label to have a ref", .{})); - const res = p.findLabelSymbol(label.loc, name); - label.ref = res.ref; - if (res.found and !res.is_loop) { - const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot \"continue\" to label {s}", .{name}) catch unreachable; - } - } else if (!p.fn_or_arrow_data_visit.is_inside_loop) { - const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc); - p.log.addRangeError(p.source, r, "Cannot use \"continue\" here") catch unreachable; - } - }, - .s_label => |data| { - p.pushScopeForVisitPass(.label, stmt.loc) catch unreachable; - const name = p.loadNameFromRef(data.name.ref.?); - const ref = p.newSymbol(.label, name) catch unreachable; - data.name.ref = ref; - p.current_scope.label_ref = ref; - switch (data.stmt.data) { - .s_for, .s_for_in, .s_for_of, .s_while, .s_do_while => { - p.current_scope.label_stmt_is_loop = true; - }, - else => {}, - } - - data.stmt = p.visitSingleStmt(data.stmt, StmtsKind.none); - p.popScope(); - }, - .s_local => |data| { - const decls_len = if (!(data.is_export and p.options.features.replace_exports.entries.len > 0)) - p.visitDecls(data.decls, false) - else - p.visitDecls(data.decls, true); - - const is_now_dead = data.decls.len > 0 and decls_len == 0; - if (is_now_dead) { - return; - } - - data.decls.len = decls_len; - - // Handle being exported inside a namespace - if (data.is_export and p.enclosing_namespace_arg_ref != null) { - for (data.decls) |*d| { - if (d.value) |val| { - p.recordUsage((p.enclosing_namespace_arg_ref orelse unreachable)); - // TODO: is it necessary to lowerAssign? why does esbuild do it _most_ of the time? - stmts.append(p.s(S.SExpr{ - .value = Expr.assign(Binding.toExpr(&d.binding, p.to_expr_wrapper_namespace), val, p.allocator), - }, stmt.loc)) catch unreachable; - } - } - - return; - } - - // We must relocate vars in order to safely handle removing if/else depending on NODE_ENV. - // Edgecase: - // `export var` is skipped because it's unnecessary. That *should* be a noop, but it loses the `is_export` flag if we're in HMR. - if (data.kind == .k_var and !data.is_export) { - const relocated = p.maybeRelocateVarsToTopLevel(data.decls, .normal); - if (relocated.ok) { - if (relocated.stmt) |new_stmt| { - stmts.append(new_stmt) catch unreachable; - } - - return; - } - } - }, - .s_expr => |data| { - p.stmt_expr_value = data.value.data; - data.value = p.visitExpr(data.value); - // simplify unused - data.value = SideEffects.simpifyUnusedExpr(p, data.value) orelse data.value.toEmpty(); - }, - .s_throw => |data| { - data.value = p.visitExpr(data.value); - }, - .s_return => |data| { - - // Forbid top-level return inside modules with ECMAScript-style exports - if (p.fn_or_arrow_data_visit.is_outside_fn_or_arrow) { - const where = where: { - if (p.es6_export_keyword.len > 0) { - break :where p.es6_export_keyword; - } else if (p.top_level_await_keyword.len > 0) { - break :where p.top_level_await_keyword; - } else { - break :where logger.Range.None; - } - }; - - if (where.len > 0) { - p.log.addRangeError(p.source, where, "Top-level return cannot be used inside an ECMAScript module") catch unreachable; - } - } - - if (data.value) |val| { - data.value = p.visitExpr(val); - - // "return undefined;" can safely just always be "return;" - if (data.value != null and @as(Expr.Tag, data.value.?.data) == .e_undefined) { - // Returning undefined is implicit - data.value = null; - } - } - }, - .s_block => |data| { - { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - - // Pass the "is loop body" status on to the direct children of a block used - // as a loop body. This is used to enable optimizations specific to the - // topmost scope in a loop body block. - const kind = if (std.meta.eql(p.loop_body, stmt.data)) StmtsKind.loop_body else StmtsKind.none; - var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.stmts); - p.visitStmts(&_stmts, kind) catch unreachable; - data.stmts = _stmts.toOwnedSlice(); - p.popScope(); - } - - // // trim empty statements - if (data.stmts.len == 0) { - stmts.append(Stmt{ .data = Prefill.Data.SEmpty, .loc = stmt.loc }) catch unreachable; - return; - } else if (data.stmts.len == 1 and !statementCaresAboutScope(data.stmts[0])) { - // Unwrap blocks containing a single statement - stmts.append(data.stmts[0]) catch unreachable; - return; - } - stmts.append(stmt.*) catch unreachable; - return; - }, - .s_with => |data| { - // using with is forbidden in strict mode - // we largely only deal with strict mode - // however, old code should still technically transpile - // we do not attempt to preserve all the semantics of with - data.value = p.visitExpr(data.value); - // This stmt should be a block - if (comptime Environment.allow_assert) assert(data.body.data == .s_block); - data.body = p.visitSingleStmt(data.body, StmtsKind.none); - }, - .s_while => |data| { - data.test_ = p.visitExpr(data.test_); - data.body = p.visitLoopBody(data.body); - - data.test_ = SideEffects.simplifyBoolean(p, data.test_); - const result = SideEffects.toBoolean(data.test_.data); - if (result.ok and result.side_effects == .no_side_effects) { - data.test_ = p.e(E.Boolean{ .value = result.value }, data.test_.loc); - } - }, - .s_do_while => |data| { - data.body = p.visitLoopBody(data.body); - data.test_ = p.visitExpr(data.test_); - - data.test_ = SideEffects.simplifyBoolean(p, data.test_); - }, - .s_if => |data| { - data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(data.test_)); - - const effects = SideEffects.toBoolean(data.test_.data); - if (effects.ok and !effects.value) { - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - data.yes = p.visitSingleStmt(data.yes, StmtsKind.none); - p.is_control_flow_dead = old; - } else { - data.yes = p.visitSingleStmt(data.yes, StmtsKind.none); - } - - // The "else" clause is optional - if (data.no) |no| { - if (effects.ok and effects.value) { - const old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - defer p.is_control_flow_dead = old; - data.no = p.visitSingleStmt(no, .none); - } else { - data.no = p.visitSingleStmt(no, .none); - } - - // Trim unnecessary "else" clauses - if (data.no != null and @as(Stmt.Tag, data.no.?.data) == .s_empty) { - data.no = null; - } - } - - if (effects.ok) { - if (effects.value) { - if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) { - if (effects.side_effects == .could_have_side_effects) { - // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { - stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; - } - } - - return try p.appendIfBodyPreservingScope(stmts, data.yes); - } else { - // We have to keep the "no" branch - } - } else { - // The test is falsy - if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) { - if (effects.side_effects == .could_have_side_effects) { - // Keep the condition if it could have side effects (but is still known to be truthy) - if (SideEffects.simpifyUnusedExpr(p, data.test_)) |test_| { - stmts.append(p.s(S.SExpr{ .value = test_ }, test_.loc)) catch unreachable; - } - } - - // if (false) { - // } - if (data.no == null) { - return; - } - - return try p.appendIfBodyPreservingScope(stmts, data.no.?); - } - } - } - }, - .s_for => |data| { - { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - - if (data.init) |initst| { - data.init = p.visitForLoopInit(initst, false); - } - - if (data.test_) |test_| { - data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_)); - - const result = SideEffects.toBoolean(data.test_.?.data); - if (result.ok and result.value and result.side_effects == .no_side_effects) { - data.test_ = null; - } - } - - if (data.update) |update| { - data.update = p.visitExpr(update); - } - - data.body = p.visitLoopBody(data.body); - - // Potentially relocate "var" declarations to the top level. Note that this - // must be done inside the scope of the for loop or they won't be relocated. - if (data.init) |init_| { - if (init_.data == .s_local and init_.data.s_local.kind == .k_var) { - const relocate = p.maybeRelocateVarsToTopLevel(init_.data.s_local.decls, .normal); - if (relocate.stmt) |relocated| { - data.init = relocated; - } - } - } - p.popScope(); - } - }, - .s_for_in => |data| { - { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - defer p.popScope(); - _ = p.visitForLoopInit(data.init, true); - data.value = p.visitExpr(data.value); - data.body = p.visitLoopBody(data.body); - - if (data.init.data == .s_local and data.init.data.s_local.kind == .k_var) { - const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls, RelocateVars.Mode.for_in_or_for_of); - if (relocate.stmt) |relocated_stmt| { - data.init = relocated_stmt; - } - } - } - }, - .s_for_of => |data| { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - defer p.popScope(); - _ = p.visitForLoopInit(data.init, true); - data.value = p.visitExpr(data.value); - data.body = p.visitLoopBody(data.body); - - if (data.init.data == .s_local and data.init.data.s_local.kind == .k_var) { - const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls, RelocateVars.Mode.for_in_or_for_of); - if (relocate.stmt) |relocated_stmt| { - data.init = relocated_stmt; - } - } - }, - .s_try => |data| { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - { - var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.body); - p.fn_or_arrow_data_visit.try_body_count += 1; - p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; - p.fn_or_arrow_data_visit.try_body_count -= 1; - data.body = _stmts.toOwnedSlice(); - } - p.popScope(); - - if (data.catch_) |*catch_| { - p.pushScopeForVisitPass(.block, catch_.loc) catch unreachable; - { - if (catch_.binding != null) { - p.visitBinding(catch_.binding.?, null); - } - var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, catch_.body); - p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; - catch_.body = _stmts.toOwnedSlice(); - } - p.popScope(); - } - - if (data.finally) |*finally| { - p.pushScopeForVisitPass(.block, finally.loc) catch unreachable; - { - var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, finally.stmts); - p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; - finally.stmts = _stmts.toOwnedSlice(); - } - p.popScope(); - } - }, - .s_switch => |data| { - data.test_ = p.visitExpr(data.test_); - { - p.pushScopeForVisitPass(.block, data.body_loc) catch unreachable; - defer p.popScope(); - var old_is_inside_Swsitch = p.fn_or_arrow_data_visit.is_inside_switch; - p.fn_or_arrow_data_visit.is_inside_switch = true; - defer p.fn_or_arrow_data_visit.is_inside_switch = old_is_inside_Swsitch; - var i: usize = 0; - while (i < data.cases.len) : (i += 1) { - const case = data.cases[i]; - if (case.value) |val| { - data.cases[i].value = p.visitExpr(val); - // TODO: error messages - // Check("case", *c.Value, c.Value.Loc) - // p.warnAboutTypeofAndString(s.Test, *c.Value) - } - var _stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, case.body); - p.visitStmts(&_stmts, StmtsKind.none) catch unreachable; - data.cases[i].body = _stmts.toOwnedSlice(); - } - } - // TODO: duplicate case checker - - }, - .s_function => |data| { - // We mark it as dead, but the value may not actually be dead - // We just want to be sure to not increment the usage counts for anything in the function - const mark_as_dead = data.func.flags.contains(.is_export) and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.func.name.?.ref.?); - const original_is_dead = p.is_control_flow_dead; - - if (mark_as_dead) { - p.is_control_flow_dead = true; - } - defer { - if (mark_as_dead) { - p.is_control_flow_dead = original_is_dead; - } - } - - data.func = p.visitFunc(data.func, data.func.open_parens_loc); - - // Handle exporting this function from a namespace - if (data.func.flags.contains(.is_export) and p.enclosing_namespace_arg_ref != null) { - data.func.flags.remove(.is_export); - - const enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref orelse unreachable; - stmts.ensureUnusedCapacity(3) catch unreachable; - stmts.appendAssumeCapacity(stmt.*); - stmts.appendAssumeCapacity(Expr.assignStmt(p.e(E.Dot{ - .target = p.e(E.Identifier{ .ref = enclosing_namespace_arg_ref }, stmt.loc), - .name = p.loadNameFromRef(data.func.name.?.ref.?), - .name_loc = data.func.name.?.loc, - }, stmt.loc), p.e(E.Identifier{ .ref = data.func.name.?.ref.? }, data.func.name.?.loc), p.allocator)); - } else if (!mark_as_dead) { - stmts.append(stmt.*) catch unreachable; - } else if (mark_as_dead) { - const name = data.func.name.?.ref.?; - if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(name))) |replacement| { - _ = p.injectReplacementExport(stmts, name, data.func.name.?.loc, replacement); - } - } - - // stmts.appendAssumeCapacity( - // // i wonder if this will crash - // p.keepStmtSymbolName( - // data.func.name.?.loc, - // data.func.name.?.ref.?, - // p.symbols.items[data.func.name.?.ref.?.innerIndex()].original_name, - // ), - // ); - return; - }, - .s_class => |data| { - const mark_as_dead = data.is_export and p.options.features.replace_exports.count() > 0 and p.isExportToEliminate(data.class.class_name.?.ref.?); - const original_is_dead = p.is_control_flow_dead; - - if (mark_as_dead) { - p.is_control_flow_dead = true; - } - defer { - if (mark_as_dead) { - p.is_control_flow_dead = original_is_dead; - } - } - - const shadow_ref = p.visitClass(stmt.loc, &data.class); - - // Remove the export flag inside a namespace - const was_export_inside_namespace = data.is_export and p.enclosing_namespace_arg_ref != null; - if (was_export_inside_namespace) { - data.is_export = false; - } - - const lowered = p.lowerClass(js_ast.StmtOrExpr{ .stmt = stmt.* }, shadow_ref); - - if (!mark_as_dead or was_export_inside_namespace) - // Lower class field syntax for browsers that don't support it - stmts.appendSlice(lowered) catch unreachable - else { - const ref = data.class.class_name.?.ref.?; - if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(ref))) |replacement| { - if (p.injectReplacementExport(stmts, ref, data.class.class_name.?.loc, replacement)) { - p.is_control_flow_dead = original_is_dead; - } - } - } - - // Handle exporting this class from a namespace - if (was_export_inside_namespace) { - stmts.appendAssumeCapacity(Expr.assignStmt(p.e(E.Dot{ - .target = p.e(E.Identifier{ .ref = p.enclosing_namespace_arg_ref.? }, stmt.loc), - .name = p.symbols.items[data.class.class_name.?.ref.?.innerIndex()].original_name, - .name_loc = data.class.class_name.?.loc, - }, stmt.loc), p.e(E.Identifier{ .ref = data.class.class_name.?.ref.? }, data.class.class_name.?.loc), p.allocator)); - } - - return; - }, - .s_enum => |data| { - p.recordDeclaredSymbol(data.name.ref.?) catch unreachable; - p.pushScopeForVisitPass(.entry, stmt.loc) catch unreachable; - defer p.popScope(); - p.recordDeclaredSymbol(data.arg) catch unreachable; - - const allocator = p.allocator; - // Scan ahead for any variables inside this namespace. This must be done - // ahead of time before visiting any statements inside the namespace - // because we may end up visiting the uses before the declarations. - // We need to convert the uses into property accesses on the namespace. - for (data.values) |value| { - if (!value.ref.isNull()) { - p.is_exported_inside_namespace.put(allocator, value.ref, data.arg) catch unreachable; - } - } - - // Values without initializers are initialized to one more than the - // previous value if the previous value is numeric. Otherwise values - // without initializers are initialized to undefined. - var next_numeric_value: f64 = 0.0; - var has_numeric_value = true; - - var value_exprs = ListManaged(Expr).initCapacity(allocator, data.values.len) catch unreachable; - - // Track values so they can be used by constant folding. We need to follow - // links here in case the enum was merged with a preceding namespace - var values_so_far = _hash_map.StringHashMapUnmanaged(f64){}; - - p.known_enum_values.put(allocator, data.name.ref orelse p.panic("Expected data.name.ref", .{}), values_so_far) catch unreachable; - p.known_enum_values.put(allocator, data.arg, values_so_far) catch unreachable; - - // We normally don't fold numeric constants because they might increase code - // size, but it's important to fold numeric constants inside enums since - // that's what the TypeScript compiler does. - const old_should_fold_numeric_constants = p.should_fold_numeric_constants; - p.should_fold_numeric_constants = true; - for (data.values) |*enum_value| { - // gotta allocate here so it lives after this function stack frame goes poof - const name = enum_value.name; - var assign_target: Expr = Expr{ .loc = logger.Loc.Empty, .data = Prefill.Data.EMissing }; - var has_string_value = false; - - if (enum_value.value != null) { - enum_value.value = p.visitExpr(enum_value.value.?); - switch (enum_value.value.?.data) { - .e_number => |num| { - - // prob never allocates in practice - values_so_far.put(allocator, name.string(allocator) catch unreachable, num.value) catch unreachable; - has_numeric_value = true; - next_numeric_value = num.value + 1.0; - }, - .e_string => { - has_string_value = true; - }, - else => {}, - } - } else if (has_numeric_value) { - enum_value.value = p.e(E.Number{ .value = next_numeric_value }, enum_value.loc); - values_so_far.put(allocator, name.string(allocator) catch unreachable, next_numeric_value) catch unreachable; - next_numeric_value += 1; - } else { - enum_value.value = p.e(E.Undefined{}, enum_value.loc); - } - // "Enum['Name'] = value" - assign_target = Expr.assign(p.e(E.Index{ - .target = p.e( - E.Identifier{ .ref = data.arg }, - enum_value.loc, - ), - .index = p.e( - enum_value.name, - enum_value.loc, - ), - }, enum_value.loc), enum_value.value orelse unreachable, allocator); - - p.recordUsage(data.arg); - - // String-valued enums do not form a two-way map - if (has_string_value) { - value_exprs.append(assign_target) catch unreachable; - } else { - // "Enum[assignTarget] = 'Name'" - value_exprs.append( - Expr.assign( - p.e(E.Index{ - .target = p.e( - E.Identifier{ .ref = data.arg }, - enum_value.loc, - ), - .index = assign_target, - }, enum_value.loc), - p.e(enum_value.name, enum_value.loc), - allocator, - ), - ) catch unreachable; - } - p.recordUsage(data.arg); - } - - p.should_fold_numeric_constants = old_should_fold_numeric_constants; - - var value_stmts = ListManaged(Stmt).initCapacity(allocator, value_exprs.items.len) catch unreachable; - // Generate statements from expressions - for (value_exprs.items) |expr| { - value_stmts.appendAssumeCapacity(p.s(S.SExpr{ .value = expr }, expr.loc)); - } - value_exprs.deinit(); - try p.generateClosureForTypeScriptNamespaceOrEnum( - stmts, - stmt.loc, - data.is_export, - data.name.loc, - data.name.ref.?, - data.arg, - value_stmts.toOwnedSlice(), - ); - return; - }, - .s_namespace => |data| { - p.recordDeclaredSymbol(data.name.ref.?) catch unreachable; - - // Scan ahead for any variables inside this namespace. This must be done - // ahead of time before visiting any statements inside the namespace - // because we may end up visiting the uses before the declarations. - // We need to convert the uses into property accesses on the namespace. - for (data.stmts) |child_stmt| { - switch (child_stmt.data) { - .s_local => |local| { - if (local.is_export) { - p.markExportedDeclsInsideNamespace(data.arg, local.decls); - } - }, - else => {}, - } - } - - var prepend_temp_refs = PrependTempRefsOpts{ .kind = StmtsKind.fn_body }; - var prepend_list = ListManaged(Stmt).fromOwnedSlice(p.allocator, data.stmts); - - const old_enclosing_namespace_arg_ref = p.enclosing_namespace_arg_ref; - p.enclosing_namespace_arg_ref = data.arg; - p.pushScopeForVisitPass(.entry, stmt.loc) catch unreachable; - p.recordDeclaredSymbol(data.arg) catch unreachable; - p.visitStmtsAndPrependTempRefs(&prepend_list, &prepend_temp_refs) catch unreachable; - p.popScope(); - p.enclosing_namespace_arg_ref = old_enclosing_namespace_arg_ref; - - try p.generateClosureForTypeScriptNamespaceOrEnum( - stmts, - stmt.loc, - data.is_export, - data.name.loc, - data.name.ref.?, - data.arg, - prepend_list.items, - ); - return; - }, - else => { - notimpl(); - }, - } - - // if we get this far, it stays - try stmts.append(stmt.*); - } - - fn isExportToEliminate(p: *P, ref: Ref) bool { - const symbol_name = p.loadNameFromRef(ref); - return p.options.features.replace_exports.contains(symbol_name); - } - - fn visitDecls(p: *P, decls: []G.Decl, comptime is_possibly_decl_to_remove: bool) usize { - var i: usize = 0; - const count = decls.len; - var j: usize = 0; - var out_decls = decls; - while (i < count) : (i += 1) { - p.visitBinding(decls[i].binding, null); - - if (decls[i].value != null) { - var val = decls[i].value.?; - const was_anonymous_named_expr = p.isAnonymousNamedExpr(val); - var replacement: ?*const RuntimeFeatures.ReplaceableExport = null; - - const prev_macro_call_count = p.macro_call_count; - const orig_dead = p.is_control_flow_dead; - if (comptime is_possibly_decl_to_remove) { - if (decls[i].binding.data == .b_identifier) { - if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decls[i].binding.data.b_identifier.ref))) |replacer| { - replacement = replacer; - if (replacer.* != .replace) { - p.is_control_flow_dead = true; - } - } - } - } - - decls[i].value = p.visitExpr(val); - - if (comptime is_possibly_decl_to_remove) { - p.is_control_flow_dead = orig_dead; - } - if (comptime is_possibly_decl_to_remove) { - if (decls[i].binding.data == .b_identifier) { - if (replacement) |ptr| { - if (!p.replaceDeclAndPossiblyRemove(&decls[i], ptr)) { - continue; - } - } - } - } - - p.visitDecl( - &decls[i], - was_anonymous_named_expr, - if (comptime allow_macros) - prev_macro_call_count != p.macro_call_count - else - false, - ); - } else if (comptime is_possibly_decl_to_remove) { - if (decls[i].binding.data == .b_identifier) { - if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decls[i].binding.data.b_identifier.ref))) |ptr| { - if (!p.replaceDeclAndPossiblyRemove(&decls[i], ptr)) { - p.visitDecl( - &decls[i], - false, - false, - ); - } else { - continue; - } - } - } - } - - if (comptime is_possibly_decl_to_remove) { - out_decls[j] = decls[i]; - j += 1; - } - } - - if (comptime is_possibly_decl_to_remove) { - return j; - } - - return decls.len; - } - - fn injectReplacementExport(p: *P, stmts: *StmtList, name_ref: Ref, loc: logger.Loc, replacement: *const RuntimeFeatures.ReplaceableExport) bool { - switch (replacement.*) { - .delete => return false, - .replace => |value| { - const count = stmts.items.len; - var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; - - decls[0] = .{ .binding = p.b(B.Identifier{ .ref = name_ref }, loc), .value = value }; - var local = p.s( - S.Local{ - .is_export = true, - .decls = decls, - }, - loc, - ); - p.visitAndAppendStmt(stmts, &local) catch unreachable; - return count != stmts.items.len; - }, - .inject => |with| { - const count = stmts.items.len; - var decls = p.allocator.alloc(G.Decl, 1) catch unreachable; - decls[0] = .{ - .binding = p.b( - B.Identifier{ .ref = p.declareSymbol(.other, loc, with.name) catch unreachable }, - loc, - ), - .value = with.value, - }; - - var local = p.s( - S.Local{ - .is_export = true, - .decls = decls, - }, - loc, - ); - p.visitAndAppendStmt(stmts, &local) catch unreachable; - return count != stmts.items.len; - }, - } - } - - fn replaceDeclAndPossiblyRemove(p: *P, decl: *G.Decl, replacement: *const RuntimeFeatures.ReplaceableExport) bool { - switch (replacement.*) { - .delete => return false, - .replace => |value| { - decl.*.value = p.visitExpr(value); - return true; - }, - .inject => |with| { - decl.* = .{ - .binding = p.b( - B.Identifier{ .ref = p.declareSymbol(.other, decl.binding.loc, with.name) catch unreachable }, - decl.binding.loc, - ), - .value = p.visitExpr(Expr{ .data = with.value.data, .loc = if (decl.value != null) decl.value.?.loc else decl.binding.loc }), - }; - return true; - }, - } - } - - fn visitBindingAndExprForMacro(p: *P, binding: Binding, expr: Expr) void { - switch (binding.data) { - .b_object => |bound_object| { - if (expr.data == .e_object and - expr.data.e_object.was_originally_macro) - { - var object = expr.data.e_object; - for (bound_object.properties) |property| { - if (property.flags.contains(.is_spread)) return; - } - var output_properties = object.properties.slice(); - var end: u32 = 0; - for (bound_object.properties) |property| { - if (property.key.asString(p.allocator)) |name| { - if (object.asProperty(name)) |query| { - switch (query.expr.data) { - .e_object, .e_array => p.visitBindingAndExprForMacro(property.value, query.expr), - else => {}, - } - output_properties[end] = output_properties[query.i]; - end += 1; - } - } - } - - object.properties.len = end; - } - }, - .b_array => |bound_array| { - if (expr.data == .e_array and - expr.data.e_array.was_originally_macro and !bound_array.has_spread) - { - var array = expr.data.e_array; - - array.items.len = @minimum(array.items.len, @truncate(u32, bound_array.items.len)); - var slice = array.items.slice(); - for (bound_array.items[0..array.items.len]) |item, item_i| { - const child_expr = slice[item_i]; - if (item.binding.data == .b_missing) { - slice[item_i] = p.e(E.Missing{}, expr.loc); - continue; - } - - p.visitBindingAndExprForMacro(item.binding, child_expr); - } - } - }, - else => {}, - } - } - - fn visitDecl(p: *P, decl: *Decl, was_anonymous_named_expr: bool, could_be_macro: bool) void { - // Optionally preserve the name - switch (decl.binding.data) { - .b_identifier => |id| { - decl.value = p.maybeKeepExprSymbolName( - decl.value.?, - p.symbols.items[id.ref.innerIndex()].original_name, - was_anonymous_named_expr, - ); - }, - .b_object, .b_array => { - if (comptime allow_macros) { - if (could_be_macro and decl.value != null) { - p.visitBindingAndExprForMacro(decl.binding, decl.value.?); - } - } - }, - else => {}, - } - } - - pub fn markExportedDeclsInsideNamespace(p: *P, ns_ref: Ref, decls: []G.Decl) void { - for (decls) |decl| { - p.markExportedBindingInsideNamespace(ns_ref, decl.binding); - } - } - - pub fn appendIfBodyPreservingScope(p: *P, stmts: *ListManaged(Stmt), body: Stmt) !void { - switch (body.data) { - .s_block => |block| { - var keep_block = false; - for (block.stmts) |stmt| { - if (statementCaresAboutScope(stmt)) { - keep_block = true; - break; - } - } - - if (!keep_block and block.stmts.len > 0) { - try stmts.appendSlice(block.stmts); - return; - } - }, - else => {}, - } - - if (statementCaresAboutScope(body)) { - var block_stmts = try p.allocator.alloc(Stmt, 1); - block_stmts[0] = body; - try stmts.append(p.s(S.Block{ .stmts = block_stmts }, body.loc)); - return; - } - - try stmts.append(body); - return; - } - - fn markExportedBindingInsideNamespace(p: *P, ref: Ref, binding: BindingNodeIndex) void { - switch (binding.data) { - .b_missing => {}, - .b_identifier => |ident| { - p.is_exported_inside_namespace.put(p.allocator, ident.ref, ref) catch unreachable; - }, - .b_array => |array| { - for (array.items) |item| { - p.markExportedBindingInsideNamespace(ref, item.binding); - } - }, - .b_object => |obj| { - for (obj.properties) |item| { - p.markExportedBindingInsideNamespace(ref, item.value); - } - }, - else => { - Global.panic("Unexpected binding type in namespace. This is a bug. {s}", .{binding}); - }, - } - } - - fn generateClosureForTypeScriptNamespaceOrEnum( - p: *P, - stmts: *ListManaged(Stmt), - stmt_loc: logger.Loc, - is_export: bool, - name_loc: logger.Loc, - _name_ref: Ref, - arg_ref: Ref, - stmts_inside_closure: []Stmt, - ) anyerror!void { - var name_ref = _name_ref; - // Follow the link chain in case symbols were merged - var symbol: Symbol = p.symbols.items[name_ref.innerIndex()]; - while (symbol.hasLink()) { - const link = symbol.link; - name_ref = link; - symbol = p.symbols.items[name_ref.innerIndex()]; - } - const allocator = p.allocator; - - // Make sure to only emit a variable once for a given namespace, since there - // can be multiple namespace blocks for the same namespace - if (symbol.kind == .ts_namespace or symbol.kind == .ts_enum and !p.emitted_namespace_vars.contains(name_ref)) { - p.emitted_namespace_vars.put(allocator, name_ref, .{}) catch unreachable; - - var decls = allocator.alloc(G.Decl, 1) catch unreachable; - decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = name_ref }, name_loc) }; - - if (p.enclosing_namespace_arg_ref == null) { - // Top-level namespace - stmts.append( - p.s( - S.Local{ - .kind = .k_var, - .decls = decls, - .is_export = is_export, - }, - stmt_loc, - ), - ) catch unreachable; - } else { - // Nested namespace - stmts.append( - p.s( - S.Local{ - .kind = .k_let, - .decls = decls, - .is_export = is_export, - }, - stmt_loc, - ), - ) catch unreachable; - } - } - - var arg_expr: Expr = undefined; - - if (is_export and p.enclosing_namespace_arg_ref != null) { - const namespace = p.enclosing_namespace_arg_ref.?; - // "name = enclosing.name || (enclosing.name = {})" - const name = p.symbols.items[name_ref.innerIndex()].original_name; - arg_expr = Expr.assign( - Expr.initIdentifier(name_ref, name_loc), - p.e( - E.Binary{ - .op = .bin_logical_or, - .left = p.e( - E.Dot{ - .target = Expr.initIdentifier(namespace, name_loc), - .name = name, - .name_loc = name_loc, - }, - name_loc, - ), - .right = Expr.assign( - p.e( - E.Dot{ - .target = Expr.initIdentifier(namespace, name_loc), - .name = name, - .name_loc = name_loc, - }, - name_loc, - ), - p.e(E.Object{}, name_loc), - allocator, - ), - }, - name_loc, - ), - allocator, - ); - p.recordUsage(namespace); - p.recordUsage(namespace); - p.recordUsage(name_ref); - } else { - // "name || (name = {})" - arg_expr = p.e(E.Binary{ - .op = .bin_logical_or, - .left = Expr.initIdentifier(name_ref, name_loc), - .right = Expr.assign( - Expr.initIdentifier(name_ref, name_loc), - p.e( - E.Object{}, - name_loc, - ), - allocator, - ), - }, name_loc); - p.recordUsage(name_ref); - p.recordUsage(name_ref); - } - - var func_args = allocator.alloc(G.Arg, 1) catch unreachable; - func_args[0] = .{ .binding = p.b(B.Identifier{ .ref = arg_ref }, name_loc) }; - var args_list = allocator.alloc(ExprNodeIndex, 1) catch unreachable; - args_list[0] = arg_expr; - const func = G.Fn{ - .args = func_args, - .name = null, - .open_parens_loc = stmt_loc, - .body = G.FnBody{ - .loc = stmt_loc, - .stmts = try allocator.dupe(StmtNodeIndex, stmts_inside_closure), - }, - }; - const target = p.e( - E.Function{ - .func = func, - }, - stmt_loc, - ); - - const call = p.e( - E.Call{ - .target = target, - .args = ExprNodeList.init(args_list), - }, - stmt_loc, - ); - - const closure = p.s( - S.SExpr{ - .value = call, - }, - stmt_loc, - ); - - stmts.append(closure) catch unreachable; - } - - // TODO: https://github.com/Jarred-Sumner/bun/issues/51 - fn lowerClass( - p: *P, - stmtorexpr: js_ast.StmtOrExpr, - // ref - _: Ref, - ) []Stmt { - switch (stmtorexpr) { - .stmt => |stmt| { - var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - stmts[0] = stmt; - return stmts; - }, - .expr => |expr| { - var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; - stmts[0] = p.s(S.SExpr{ .value = expr }, expr.loc); - return stmts; - }, - } - } - - fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt { - switch (stmt.data) { - .s_expr => |st| { - const assign_target = if (is_in_or_of) js_ast.AssignTarget.replace else js_ast.AssignTarget.none; - p.stmt_expr_value = st.value.data; - st.value = p.visitExprInOut(st.value, ExprIn{ .assign_target = assign_target }); - }, - .s_local => |st| { - for (st.decls) |*dec| { - p.visitBinding(dec.binding, null); - if (dec.value) |val| { - dec.value = p.visitExpr(val); - } - } - // st.kind = .k_var; - // s.Decls = p.lowerObjectRestInDecls(s.Decls) - // s.Kind = p.selectLocalKind(s.Kind) - }, - else => { - p.panic("Unexpected stmt in visitForLoopInit: {s}", .{stmt}); - }, - } - - return stmt; - } - - fn wrapIdentifierNamespace( - p: *P, - loc: logger.Loc, - ref: Ref, - ) Expr { - const enclosing_ref = p.enclosing_namespace_arg_ref.?; - p.recordUsage(enclosing_ref); - - return p.e(E.Dot{ - .target = Expr.initIdentifier(enclosing_ref, loc), - .name = p.symbols.items[ref.innerIndex()].original_name, - .name_loc = loc, - }, loc); - } - - fn wrapIdentifierHoisting( - p: *P, - loc: logger.Loc, - ref: Ref, - ) Expr { - p.relocated_top_level_vars.append(p.allocator, LocRef{ .loc = loc, .ref = ref }) catch unreachable; - var _ref = ref; - p.recordUsage(_ref); - return Expr.initIdentifier(_ref, loc); - } - - fn isAnonymousNamedExpr(_: *P, expr: ExprNodeIndex) bool { - switch (expr.data) { - .e_arrow => { - return true; - }, - .e_function => |func| { - return func.func.name == null; - }, - .e_class => |class| { - return class.class_name == null; - }, - else => { - return false; - }, - } - } - - fn valueForDefine(p: *P, loc: logger.Loc, assign_target: js_ast.AssignTarget, is_delete_target: bool, define_data: *const DefineData) Expr { - switch (define_data.value) { - .e_identifier => { - return p.handleIdentifier( - loc, - define_data.value.e_identifier, - define_data.original_name.?, - IdentifierOpts{ - .assign_target = assign_target, - .is_delete_target = is_delete_target, - .was_originally_identifier = true, - }, - ); - }, - .e_string => |str| { - return p.e(str, loc); - }, - else => {}, - } - - return Expr{ - .data = define_data.value, - .loc = loc, - }; - } - - // This function is recursive - // But it shouldn't be that long - fn isDotDefineMatch(p: *P, expr: Expr, parts: []const string) bool { - switch (expr.data) { - .e_dot => |ex| { - if (parts.len > 1) { - if (ex.optional_chain != null) { - return false; - } - - // Intermediates must be dot expressions - const last = parts.len - 1; - const is_tail_match = strings.eql(parts[last], ex.name); - return is_tail_match and p.isDotDefineMatch(ex.target, parts[0..last]); - } - }, - .e_import_meta => { - return parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"); - }, - // Note: this behavior differs from esbuild - // esbuild does not try to match index accessors - // we do, but only if it's a UTF8 string - // the intent is to handle people using this form instead of E.Dot. So we really only want to do this if the accessor can also be an identifier - .e_index => |index| { - if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) { - if (index.optional_chain != null) { - return false; - } - - const last = parts.len - 1; - const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator)); - return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]); - } - }, - .e_identifier => |ex| { - - // The last expression must be an identifier - if (parts.len == 1) { - const name = p.loadNameFromRef(ex.ref); - if (!strings.eql(name, parts[0])) { - return false; - } - - const result = p.findSymbol(expr.loc, name) catch return false; - - // We must not be in a "with" statement scope - if (result.is_inside_with_scope) { - return false; - } - - // The last symbol must be unbound - return p.symbols.items[result.ref.innerIndex()].kind == .unbound; - } - }, - else => {}, - } - - return false; - } - - fn visitBinding(p: *P, binding: BindingNodeIndex, duplicate_arg_check: ?*StringVoidMap) void { - switch (binding.data) { - .b_missing => {}, - .b_identifier => |bind| { - p.recordDeclaredSymbol(bind.ref) catch unreachable; - const name = p.symbols.items[bind.ref.innerIndex()].original_name; - if (isEvalOrArguments(name)) { - p.markStrictModeFeature(.eval_or_arguments, js_lexer.rangeOfIdentifier(p.source, binding.loc), name) catch unreachable; - } - if (duplicate_arg_check) |dup| { - if (dup.getOrPutContains(name)) { - p.log.addRangeErrorFmt( - p.source, - js_lexer.rangeOfIdentifier(p.source, binding.loc), - p.allocator, - "\"{s}\" cannot be bound multiple times in the same parameter list", - .{name}, - ) catch unreachable; - } - } - }, - .b_array => |bind| { - for (bind.items) |*item| { - p.visitBinding(item.binding, duplicate_arg_check); - if (item.default_value) |default_value| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); - item.default_value = p.visitExpr(default_value); - - switch (item.binding.data) { - .b_identifier => |bind_| { - item.default_value = p.maybeKeepExprSymbolName( - item.default_value orelse unreachable, - p.symbols.items[bind_.ref.innerIndex()].original_name, - was_anonymous_named_expr, - ); - }, - else => {}, - } - } - } - }, - .b_object => |bind| { - for (bind.properties) |*property| { - if (!property.flags.contains(.is_spread)) { - property.key = p.visitExpr(property.key); - } - - p.visitBinding(property.value, duplicate_arg_check); - if (property.default_value) |default_value| { - const was_anonymous_named_expr = p.isAnonymousNamedExpr(default_value); - property.default_value = p.visitExpr(default_value); - - switch (property.value.data) { - .b_identifier => |bind_| { - property.default_value = p.maybeKeepExprSymbolName( - property.default_value orelse unreachable, - p.symbols.items[bind_.ref.innerIndex()].original_name, - was_anonymous_named_expr, - ); - }, - else => {}, - } - } - } - }, - else => { - p.panic("Unexpected binding {s}", .{binding}); - }, - } - } - - fn visitLoopBody(p: *P, stmt: StmtNodeIndex) StmtNodeIndex { - const old_is_inside_loop = p.fn_or_arrow_data_visit.is_inside_loop; - p.fn_or_arrow_data_visit.is_inside_loop = true; - p.loop_body = stmt.data; - const res = p.visitSingleStmt(stmt, .loop_body); - p.fn_or_arrow_data_visit.is_inside_loop = old_is_inside_loop; - return res; - } - - fn visitSingleStmt(p: *P, stmt: Stmt, kind: StmtsKind) Stmt { - const has_if_scope = switch (stmt.data) { - .s_function => stmt.data.s_function.func.flags.contains(.has_if_scope), - else => false, - }; - - // Introduce a fake block scope for function declarations inside if statements - if (has_if_scope) { - p.pushScopeForVisitPass(.block, stmt.loc) catch unreachable; - } - - var stmts = ListManaged(Stmt).initCapacity(p.allocator, 1) catch unreachable; - stmts.append(stmt) catch unreachable; - p.visitStmts(&stmts, kind) catch unreachable; - - if (has_if_scope) { - p.popScope(); - } - - return p.stmtsToSingleStmt(stmt.loc, stmts.toOwnedSlice()); - } - - // One statement could potentially expand to several statements - fn stmtsToSingleStmt(p: *P, loc: logger.Loc, stmts: []Stmt) Stmt { - if (stmts.len == 0) { - return Stmt{ .data = Prefill.Data.SEmpty, .loc = loc }; - } - - if (stmts.len == 1 and std.meta.activeTag(stmts[0].data) != .s_local or (std.meta.activeTag(stmts[0].data) == .s_local and stmts[0].data.s_local.kind == S.Local.Kind.k_var)) { - // "let" and "const" must be put in a block when in a single-statement context - return stmts[0]; - } - - return p.s(S.Block{ .stmts = stmts }, loc); - } - - fn findLabelSymbol(p: *P, loc: logger.Loc, name: string) FindLabelSymbolResult { - var res = FindLabelSymbolResult{ .ref = Ref.None, .is_loop = false }; - - var _scope: ?*Scope = p.current_scope; - - while (_scope != null and !_scope.?.kindStopsHoisting()) : (_scope = _scope.?.parent.?) { - const scope = _scope orelse unreachable; - const label_ref = scope.label_ref orelse continue; - if (scope.kind == .label and strings.eql(name, p.symbols.items[label_ref.innerIndex()].original_name)) { - // Track how many times we've referenced this symbol - p.recordUsage(label_ref); - res.ref = label_ref; - res.is_loop = scope.label_stmt_is_loop; - res.found = true; - return res; - } - } - - const r = js_lexer.rangeOfIdentifier(p.source, loc); - p.log.addRangeErrorFmt(p.source, r, p.allocator, "There is no containing label named \"{s}\"", .{name}) catch unreachable; - - // Allocate an "unbound" symbol - var ref = p.newSymbol(.unbound, name) catch unreachable; - - // Track how many times we've referenced this symbol - p.recordUsage(ref); - - return res; - } - - fn visitClass(p: *P, name_scope_loc: logger.Loc, class: *G.Class) Ref { - if (only_scan_imports_and_do_not_visit) { - @compileError("only_scan_imports_and_do_not_visit must not run this."); - } - - class.ts_decorators = p.visitTSDecorators(class.ts_decorators); - - if (class.class_name) |name| { - p.recordDeclaredSymbol(name.ref.?) catch unreachable; - } - - p.pushScopeForVisitPass(.class_name, name_scope_loc) catch unreachable; - const old_enclosing_class_keyword = p.enclosing_class_keyword; - p.enclosing_class_keyword = class.class_keyword; - p.current_scope.recursiveSetStrictMode(.implicit_strict_mode_class); - var class_name_ref: Ref = if (class.class_name != null) - class.class_name.?.ref.? - else - p.newSymbol(.other, "this") catch unreachable; - - var shadow_ref = Ref.None; - - if (!class_name_ref.eql(Ref.None)) { - // are not allowed to assign to this symbol (it throws a TypeError). - const name = p.symbols.items[class_name_ref.innerIndex()].original_name; - var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; - std.mem.copy(u8, identifier[1..identifier.len], name); - identifier[0] = '_'; - shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; - p.recordDeclaredSymbol(shadow_ref) catch unreachable; - if (class.class_name) |class_name| { - p.current_scope.members.put(p.allocator, identifier, Scope.Member{ .loc = class_name.loc, .ref = shadow_ref }) catch unreachable; - } - } - - if (class.extends) |extends| { - class.extends = p.visitExpr(extends); - } - - p.pushScopeForVisitPass(.class_body, class.body_loc) catch unreachable; - defer { - p.popScope(); - p.enclosing_class_keyword = old_enclosing_class_keyword; - } - - var i: usize = 0; - var constructor_function: ?*E.Function = null; - while (i < class.properties.len) : (i += 1) { - var property = &class.properties[i]; - - if (property.kind == .class_static_block) { - var old_fn_or_arrow_data = p.fn_or_arrow_data_visit; - var old_fn_only_data = p.fn_only_data_visit; - p.fn_or_arrow_data_visit = .{}; - p.fn_only_data_visit = .{ .is_this_nested = true, .is_new_target_allowed = true }; - - p.pushScopeForVisitPass(.class_static_init, property.class_static_block.?.loc) catch unreachable; - - // Make it an error to use "arguments" in a static class block - p.current_scope.forbid_arguments = true; - - var list = property.class_static_block.?.stmts.listManaged(p.allocator); - p.visitStmts(&list, .fn_body) catch unreachable; - property.class_static_block.?.stmts = js_ast.BabyList(Stmt).fromList(list); - p.popScope(); - - p.fn_or_arrow_data_visit = old_fn_or_arrow_data; - p.fn_only_data_visit = old_fn_only_data; - - continue; - } - property.ts_decorators = p.visitTSDecorators(property.ts_decorators); - const is_private = if (property.key != null) @as(Expr.Tag, property.key.?.data) == .e_private_identifier else false; - - // Special-case EPrivateIdentifier to allow it here - - if (is_private) { - p.recordDeclaredSymbol(property.key.?.data.e_private_identifier.ref) catch unreachable; - } else if (property.key) |key| { - class.properties[i].key = p.visitExpr(key); - } - - // Make it an error to use "arguments" in a class body - p.current_scope.forbid_arguments = true; - defer p.current_scope.forbid_arguments = false; - - // The value of "this" is shadowed inside property values - const old_is_this_captured = p.fn_only_data_visit.is_this_nested; - const old_this = p.fn_only_data_visit.this_class_static_ref; - p.fn_only_data_visit.is_this_nested = true; - p.fn_only_data_visit.is_new_target_allowed = true; - p.fn_only_data_visit.this_class_static_ref = null; - defer p.fn_only_data_visit.is_this_nested = old_is_this_captured; - defer p.fn_only_data_visit.this_class_static_ref = old_this; - - // We need to explicitly assign the name to the property initializer if it - // will be transformed such that it is no longer an inline initializer. - - var constructor_function_: ?*E.Function = null; - - var name_to_keep: ?string = null; - if (is_private) {} else if (!property.flags.contains(.is_method) and !property.flags.contains(.is_computed)) { - if (property.key) |key| { - if (@as(Expr.Tag, key.data) == .e_string) { - name_to_keep = key.data.e_string.string(p.allocator) catch unreachable; - } - } - } else if (property.flags.contains(.is_method)) { - if (comptime is_typescript_enabled) { - if (property.value.?.data == .e_function and property.key.?.data == .e_string and - property.key.?.data.e_string.eqlComptime("constructor")) - { - constructor_function_ = property.value.?.data.e_function; - constructor_function = constructor_function_; - } - } - } - - if (property.value) |val| { - if (name_to_keep) |name| { - const was_anon = p.isAnonymousNamedExpr(val); - property.value = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); - } else { - property.value = p.visitExpr(val); - } - - if (comptime is_typescript_enabled) { - if (constructor_function_ != null and property.value != null and property.value.?.data == .e_function) { - constructor_function = property.value.?.data.e_function; - } - } - } - - if (property.initializer) |val| { - // if (property.flags.is_static and ) - if (name_to_keep) |name| { - const was_anon = p.isAnonymousNamedExpr(val); - property.initializer = p.maybeKeepExprSymbolName(p.visitExpr(val), name, was_anon); - } else { - property.initializer = p.visitExpr(val); - } - } - } - - // note: our version assumes useDefineForClassFields is true - if (comptime is_typescript_enabled) { - if (constructor_function) |constructor| { - var to_add: usize = 0; - for (constructor.func.args) |arg| { - to_add += @boolToInt(arg.is_typescript_ctor_field and arg.binding.data == .b_identifier); - } - - if (to_add > 0) { - // to match typescript behavior, we also must prepend to the class body - var stmts = std.ArrayList(Stmt).fromOwnedSlice(p.allocator, constructor.func.body.stmts); - stmts.ensureUnusedCapacity(to_add) catch unreachable; - var class_body = std.ArrayList(G.Property).fromOwnedSlice(p.allocator, class.properties); - class_body.ensureUnusedCapacity(to_add) catch unreachable; - var j: usize = 0; - - for (constructor.func.args) |arg| { - if (arg.is_typescript_ctor_field) { - switch (arg.binding.data) { - .b_identifier => |id| { - const name = p.symbols.items[id.ref.innerIndex()].original_name; - const ident = p.e(E.Identifier{ .ref = id.ref }, arg.binding.loc); - stmts.appendAssumeCapacity( - Expr.assignStmt( - p.e(E.Dot{ - .target = p.e(E.This{}, arg.binding.loc), - .name = name, - .name_loc = arg.binding.loc, - }, arg.binding.loc), - ident, - p.allocator, - ), - ); - // O(N) - class_body.items.len += 1; - std.mem.copyBackwards(G.Property, class_body.items[j + 1 .. class_body.items.len], class_body.items[j .. class_body.items.len - 1]); - class_body.items[j] = G.Property{ .key = ident }; - j += 1; - }, - else => {}, - } - } - } - - class.properties = class_body.toOwnedSlice(); - constructor.func.body.stmts = stmts.toOwnedSlice(); - } - } - } - - if (!shadow_ref.eql(Ref.None)) { - if (p.symbols.items[shadow_ref.innerIndex()].use_count_estimate == 0) { - // Don't generate a shadowing name if one isn't needed - shadow_ref = Ref.None; - } else if (class.class_name) |_| { - // If there was originally no class name but something inside needed one - // (e.g. there was a static property initializer that referenced "this"), - // store our generated name so the class expression ends up with a name. - class.class_name = LocRef{ .loc = name_scope_loc, .ref = class_name_ref }; - p.current_scope.generated.append(p.allocator, class_name_ref) catch unreachable; - p.recordDeclaredSymbol(class_name_ref) catch unreachable; - } - } - - return shadow_ref; - } - - fn keepStmtSymbolName(p: *P, loc: logger.Loc, ref: Ref, name: string) Stmt { - p.expr_list.ensureUnusedCapacity(2) catch unreachable; - const start = p.expr_list.items.len; - p.expr_list.appendAssumeCapacity(p.e(E.Identifier{ - .ref = ref, - }, loc)); - p.expr_list.appendAssumeCapacity(p.e(E.String{ .data = name }, loc)); - return p.s(S.SExpr{ - // I believe that this is a spot we can do $RefreshReg$(name) - .value = p.callRuntime(loc, "__name", p.expr_list.items[start..p.expr_list.items.len]), - - // Make sure tree shaking removes this if the function is never used - .does_not_affect_tree_shaking = true, - }, loc); - } - - pub fn callRuntime(p: *P, loc: logger.Loc, comptime name: string, args: []Expr) Expr { - var ref: Ref = undefined; - p.has_called_runtime = true; - - if (!p.runtime_imports.contains(name)) { - ref = brk: { - if (comptime strings.eqlComptime(name, "__require")) { - p.runtime_imports.__require = GeneratedSymbol{ - .backup = declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, StaticSymbolName.List.__require.backup, true) catch unreachable, - .primary = p.require_ref, - .ref = declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, StaticSymbolName.List.__require.internal, true) catch unreachable, - }; - break :brk p.runtime_imports.__require.?.ref; - } - const generated_symbol = p.declareGeneratedSymbol(.other, name) catch unreachable; - p.runtime_imports.put(name, generated_symbol); - break :brk generated_symbol.ref; - }; - - p.module_scope.generated.append(p.allocator, ref) catch unreachable; - } else { - ref = p.runtime_imports.at(name).?; - } - - p.recordUsage(ref); - return p.e(E.Call{ - .target = p.e(E.Identifier{ - .ref = ref, - }, loc), - .args = ExprNodeList.init(args), - }, loc); - } - - // Try separating the list for appending, so that it's not a pointer. - fn visitStmts(p: *P, stmts: *ListManaged(Stmt), _: StmtsKind) !void { - if (only_scan_imports_and_do_not_visit) { - @compileError("only_scan_imports_and_do_not_visit must not run this."); - } - - // Save the current control-flow liveness. This represents if we are - // currently inside an "if (false) { ... }" block. - var old_is_control_flow_dead = p.is_control_flow_dead; - defer p.is_control_flow_dead = old_is_control_flow_dead; - - // visit all statements first - var visited = try ListManaged(Stmt).initCapacity(p.allocator, stmts.items.len); - var before = ListManaged(Stmt).init(p.allocator); - var after = ListManaged(Stmt).init(p.allocator); - - if (p.current_scope == p.module_scope) { - p.macro.prepend_stmts = &before; - } - - defer before.deinit(); - defer visited.deinit(); - defer after.deinit(); - - for (stmts.items) |*stmt| { - const list = list_getter: { - switch (stmt.data) { - .s_export_equals => { - // TypeScript "export = value;" becomes "module.exports = value;". This - // must happen at the end after everything is parsed because TypeScript - // moves this statement to the end when it generates code. - break :list_getter &after; - }, - .s_function => |data| { - // Manually hoist block-level function declarations to preserve semantics. - // This is only done for function declarations that are not generators - // or async functions, since this is a backwards-compatibility hack from - // Annex B of the JavaScript standard. - if (!p.current_scope.kindStopsHoisting() and p.symbols.items[data.func.name.?.ref.?.innerIndex()].kind == .hoisted_function) { - break :list_getter &before; - } - }, - else => {}, - } - break :list_getter &visited; - }; - try p.visitAndAppendStmt(list, stmt); - } - - var visited_count = visited.items.len; - if (p.is_control_flow_dead) { - var end: usize = 0; - for (visited.items) |item| { - if (!SideEffects.shouldKeepStmtInDeadControlFlow(item, p.allocator)) { - continue; - } - - visited.items[end] = item; - end += 1; - } - visited_count = end; - } - - const total_size = visited_count + before.items.len + after.items.len; - - if (total_size != stmts.items.len) { - try stmts.resize(total_size); - } - - var i: usize = 0; - - for (before.items) |item| { - stmts.items[i] = item; - i += 1; - } - - const visited_slice = visited.items[0..visited_count]; - for (visited_slice) |item| { - stmts.items[i] = item; - i += 1; - } - - for (after.items) |item| { - stmts.items[i] = item; - i += 1; - } - } - - fn extractDeclsForBinding(binding: Binding, decls: *ListManaged(G.Decl)) !void { - switch (binding.data) { - .b_property, .b_missing => {}, - .b_identifier => { - try decls.append(G.Decl{ .binding = binding }); - }, - .b_array => |arr| { - for (arr.items) |item| { - extractDeclsForBinding(item.binding, decls) catch unreachable; - } - }, - .b_object => |obj| { - for (obj.properties) |prop| { - extractDeclsForBinding(prop.value, decls) catch unreachable; - } - }, - } - } - - pub inline fn @"module.exports"(p: *P, loc: logger.Loc) Expr { - return p.e(E.Dot{ .name = exports_string_name, .name_loc = loc, .target = p.e(E.Identifier{ .ref = p.module_ref }, loc) }, loc); - } - - // This assumes that the open parenthesis has already been parsed by the caller - pub fn parseParenExpr(p: *P, loc: logger.Loc, level: Level, opts: ParenExprOpts) anyerror!Expr { - var items_list = ListManaged(Expr).init(p.allocator); - var errors = DeferredErrors{}; - var arrowArgErrors = DeferredArrowArgErrors{}; - var spread_range = logger.Range{}; - var type_colon_range = logger.Range{}; - var comma_after_spread: ?logger.Loc = null; - - // Push a scope assuming this is an arrow function. It may not be, in which - // case we'll need to roll this change back. This has to be done ahead of - // parsing the arguments instead of later on when we hit the "=>" token and - // we know it's an arrow function because the arguments may have default - // values that introduce new scopes and declare new symbols. If this is an - // arrow function, then those new scopes will need to be parented under the - // scope of the arrow function itself. - const scope_index = try p.pushScopeForParsePass(.function_args, loc); - - // Allow "in" inside parentheses - var oldAllowIn = p.allow_in; - p.allow_in = true; - - // Forbid "await" and "yield", but only for arrow functions - var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse); - p.fn_or_arrow_data_parse.arrow_arg_errors = arrowArgErrors; - p.fn_or_arrow_data_parse.track_arrow_arg_errors = true; - - // Scan over the comma-separated arguments or expressions - while (p.lexer.token != .t_close_paren) { - const is_spread = p.lexer.token == .t_dot_dot_dot; - - if (is_spread) { - spread_range = p.lexer.range(); - // p.markSyntaxFeature() - try p.lexer.next(); - } - - // We don't know yet whether these are arguments or expressions, so parse - p.latest_arrow_arg_loc = p.lexer.loc(); - - var item = try p.parseExprOrBindings(.comma, &errors); - - if (is_spread) { - item = p.e(E.Spread{ .value = item }, loc); - } - - // Skip over types - if (is_typescript_enabled and p.lexer.token == .t_colon) { - type_colon_range = p.lexer.range(); - try p.lexer.next(); - try p.skipTypeScriptType(.lowest); - } - - // There may be a "=" after the type (but not after an "as" cast) - if (is_typescript_enabled and p.lexer.token == .t_equals and !p.forbid_suffix_after_as_loc.eql(p.lexer.loc())) { - try p.lexer.next(); - item = Expr.assign(item, try p.parseExpr(.comma), p.allocator); - } - - items_list.append(item) catch unreachable; - - if (p.lexer.token != .t_comma) { - break; - } - - // Spread arguments must come last. If there's a spread argument followed - if (is_spread) { - comma_after_spread = p.lexer.loc(); - } - - // Eat the comma token - try p.lexer.next(); - } - var items = items_list.items; - - // The parenthetical construct must end with a close parenthesis - try p.lexer.expect(.t_close_paren); - - // Restore "in" operator status before we parse the arrow function body - p.allow_in = oldAllowIn; - - // Also restore "await" and "yield" expression errors - p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data); - - // Are these arguments to an arrow function? - if (p.lexer.token == .t_equals_greater_than or opts.force_arrow_fn or (is_typescript_enabled and p.lexer.token == .t_colon)) { - // Arrow functions are not allowed inside certain expressions - if (level.gt(.assign)) { - try p.lexer.unexpected(); - return error.SyntaxError; - } - - var invalidLog = LocList.init(p.allocator); - var args = ListManaged(G.Arg).init(p.allocator); - - if (opts.is_async) { - // markl,oweredsyntaxpoksdpokasd - } - - // First, try converting the expressions to bindings - for (items) |_, i| { - var is_spread = false; - switch (items[i].data) { - .e_spread => |v| { - is_spread = true; - items[i] = v.value; - }, - else => {}, - } - - var item = items[i]; - const tuple = p.convertExprToBindingAndInitializer(&item, &invalidLog, is_spread); - // double allocations - args.append(G.Arg{ - .binding = tuple.binding orelse Binding{ .data = Prefill.Data.BMissing, .loc = item.loc }, - .default = tuple.expr, - }) catch unreachable; - } - - // Avoid parsing TypeScript code like "a ? (1 + 2) : (3 + 4)" as an arrow - // function. The ":" after the ")" may be a return type annotation, so we - // attempt to convert the expressions to bindings first before deciding - // whether this is an arrow function, and only pick an arrow function if - // there were no conversion errors. - if (p.lexer.token == .t_equals_greater_than or ((comptime is_typescript_enabled) and - invalidLog.items.len == 0 and - p.trySkipTypeScriptArrowReturnTypeWithBacktracking()) or - opts.force_arrow_fn) - { - p.maybeCommaSpreadError(comma_after_spread); - p.logArrowArgErrors(&arrowArgErrors); - - // Now that we've decided we're an arrow function, report binding pattern - // conversion errors - if (invalidLog.items.len > 0) { - for (invalidLog.items) |_loc| { - _loc.addError( - p.log, - p.source, - ); - } - } - var arrow_data = FnOrArrowDataParse{ - .allow_await = if (opts.is_async) AwaitOrYield.allow_expr else AwaitOrYield.allow_ident, - }; - var arrow = try p.parseArrowBody(args.items, &arrow_data); - arrow.is_async = opts.is_async; - arrow.has_rest_arg = spread_range.len > 0; - p.popScope(); - return p.e(arrow, loc); - } - } - - // If we get here, it's not an arrow function so undo the pushing of the - // scope we did earlier. This needs to flatten any child scopes into the - // parent scope as if the scope was never pushed in the first place. - p.popAndFlattenScope(scope_index); - - // If this isn't an arrow function, then types aren't allowed - if (type_colon_range.len > 0) { - try p.log.addRangeError(p.source, type_colon_range, "Unexpected \":\""); - return error.SyntaxError; - } - - // Are these arguments for a call to a function named "async"? - if (opts.is_async) { - p.logExprErrors(&errors); - const async_expr = p.e(E.Identifier{ .ref = try p.storeNameInRef("async") }, loc); - return p.e(E.Call{ .target = async_expr, .args = ExprNodeList.init(items) }, loc); - } - - // Is this a chain of expressions and comma operators? - if (items.len > 0) { - p.logExprErrors(&errors); - if (spread_range.len > 0) { - try p.log.addRangeError(p.source, type_colon_range, "Unexpected \"...\""); - return error.SyntaxError; - } - - var value = Expr.joinAllWithComma(items, p.allocator); - p.markExprAsParenthesized(&value); - return value; - } - - // Indicate that we expected an arrow function - try p.lexer.expected(.t_equals_greater_than); - return error.SyntaxError; - } - - // This code is tricky. - // - Doing it incorrectly will cause segfaults. - // - Doing it correctly drastically affects runtime performance while parsing larger files - // The key is in how we remove scopes from the list - // If we do an orderedRemove, it gets very slow. - // swapRemove is fast. But a little more dangerous. - // Instead, we just tombstone it. - pub fn popAndFlattenScope(p: *P, scope_index: usize) void { - // Move up to the parent scope - var to_flatten = p.current_scope; - var parent = to_flatten.parent.?; - p.current_scope = parent; - - // Erase this scope from the order. This will shift over the indices of all - // the scopes that were created after us. However, we shouldn't have to - // worry about other code with outstanding scope indices for these scopes. - // These scopes were all created in between this scope's push and pop - // operations, so they should all be child scopes and should all be popped - // by the time we get here. - p.scopes_in_order.items[scope_index] = null; - // Remove the last child from the parent scope - const last = parent.children.items.len - 1; - if (comptime Environment.allow_assert) assert(parent.children.items[last] == to_flatten); - _ = parent.children.popOrNull(); - - for (to_flatten.children.items) |item| { - item.parent = parent; - parent.children.append(p.allocator, item) catch unreachable; - } - } - - fn maybeCommaSpreadError(p: *P, _comma_after_spread: ?logger.Loc) void { - const comma_after_spread = _comma_after_spread orelse return; - if (comma_after_spread.start == -1) return; - - p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable; - } - - pub fn toAST(p: *P, _parts: []js_ast.Part, exports_kind: js_ast.ExportsKind, commonjs_wrapper_expr: ?Expr) !js_ast.Ast { - const allocator = p.allocator; - var parts = _parts; - // Insert an import statement for any runtime imports we generated - - if (p.options.tree_shaking and p.options.features.trim_unused_imports) { - p.treeShake(&parts, false); - } - - var parts_end: usize = 0; - // Handle import paths after the whole file has been visited because we need - // symbol usage counts to be able to remove unused type-only imports in - // TypeScript code. - while (true) { - var kept_import_equals = false; - var removed_import_equals = false; - - var i: usize = 0; - // Potentially remove some statements, then filter out parts to remove any - // with no statements - while (i < parts.len) : (i += 1) { - var part = parts[i]; - p.import_records_for_current_part.shrinkRetainingCapacity(0); - p.declared_symbols.shrinkRetainingCapacity(0); - - var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != null); - kept_import_equals = kept_import_equals or result.kept_import_equals; - removed_import_equals = removed_import_equals or result.removed_import_equals; - part.import_record_indices = part.import_record_indices; - part.declared_symbols = p.declared_symbols.toOwnedSlice(allocator); - part.stmts = result.stmts; - if (part.stmts.len > 0) { - if (p.module_scope.contains_direct_eval and part.declared_symbols.len > 0) { - // If this file contains a direct call to "eval()", all parts that - // declare top-level symbols must be kept since the eval'd code may - // reference those symbols. - part.can_be_removed_if_unused = false; - } - parts[parts_end] = part; - parts_end += 1; - } - } - - // We need to iterate multiple times if an import-equals statement was - // removed and there are more import-equals statements that may be removed - if (!kept_import_equals or !removed_import_equals) { - break; - } - } - - parts = parts[0..parts_end]; - - // Do a second pass for exported items now that imported items are filled out - for (parts) |part| { - for (part.stmts) |stmt| { - switch (stmt.data) { - .s_export_clause => |clause| { - for (clause.items) |item| { - if (p.named_imports.getEntry(item.name.ref.?)) |_import| { - _import.value_ptr.is_exported = true; - } - } - }, - else => {}, - } - } - } - - if (p.options.tree_shaking) { - p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_bundling); - } - - if (commonjs_wrapper_expr) |commonjs_wrapper| { - var part = &parts[parts.len - 1]; - - var require_function_args = allocator.alloc(Arg, 2) catch unreachable; - - var imports_count: u32 = 0; - // We have to also move export from, since we will preserve those - var exports_from_count: u32 = 0; - - // Two passes. First pass just counts. - for (parts[parts.len - 1].stmts) |stmt| { - imports_count += switch (stmt.data) { - .s_import => @as(u32, 1), - else => @as(u32, 0), - }; - - exports_from_count += switch (stmt.data) { - .s_export_star, .s_export_from => @as(u32, 1), - else => @as(u32, 0), - }; - } - - var new_stmts_list = allocator.alloc(Stmt, exports_from_count + imports_count + 1) catch unreachable; - var imports_list = new_stmts_list[0..imports_count]; - - var exports_list = if (exports_from_count > 0) new_stmts_list[imports_list.len + 1 ..] else &[_]Stmt{}; - - require_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }; - require_function_args[1] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; - - var imports_list_i: u32 = 0; - var exports_list_i: u32 = 0; - - for (part.stmts) |_, i| { - switch (part.stmts[i].data) { - .s_import => { - imports_list[imports_list_i] = part.stmts[i]; - part.stmts[i] = Stmt.empty(); - part.stmts[i].loc = imports_list[imports_list_i].loc; - imports_list_i += 1; - }, - - .s_export_star, .s_export_from => { - exports_list[exports_list_i] = part.stmts[i]; - part.stmts[i] = Stmt.empty(); - part.stmts[i].loc = exports_list[exports_list_i].loc; - exports_list_i += 1; - }, - else => {}, - } - } - - commonjs_wrapper.data.e_call.args.ptr[0] = p.e( - E.Function{ .func = G.Fn{ - .name = null, - .open_parens_loc = logger.Loc.Empty, - .args = require_function_args, - .body = .{ .loc = logger.Loc.Empty, .stmts = parts[parts.len - 1].stmts }, - .flags = Flags.Function.init(.{ .is_export = true }), - } }, - logger.Loc.Empty, - ); - var sourcefile_name = p.source.path.pretty; - if (strings.lastIndexOf(sourcefile_name, "node_modules")) |node_modules_i| { - // 1 for the separator - const end = node_modules_i + 1 + "node_modules".len; - // If you were to name your file "node_modules.js" it shouldn't appear as ".js" - if (end < sourcefile_name.len) { - sourcefile_name = sourcefile_name[end..]; - } - } - commonjs_wrapper.data.e_call.args.ptr[1] = p.e(E.String{ .data = sourcefile_name }, logger.Loc.Empty); - - new_stmts_list[imports_list.len] = p.s( - S.ExportDefault{ - .value = .{ - .expr = commonjs_wrapper, - }, - .default_name = LocRef{ .ref = null, .loc = logger.Loc.Empty }, - }, - logger.Loc.Empty, - ); - part.stmts = new_stmts_list; - } else if (p.options.features.hot_module_reloading and p.options.features.allow_runtime) { - var named_exports_count: usize = p.named_exports.count(); - const named_imports: js_ast.Ast.NamedImports = p.named_imports; - - // To transform to something HMR'able, we must: - // 1. Wrap the top level code in an IIFE - // 2. Move imports to the top of the file (preserving the order) - // 3. Remove export clauses (done during ImportScanner) - // 4. Move export * from and export from to the bottom of the file (or the top, it doesn't matter I don't think) - // 5. Export everything as getters in our HMR module - // 6. Call the HMRModule's exportAll function like so: - // __hmrModule.exportAll({ - // exportAlias: () => identifier, - // exportAlias: () => identifier, - // }); - // This has the unfortunate property of making property accesses of exports slower at runtime. - // But, I'm not sure there's a way to use regular properties without breaking stuff. - var imports_count: usize = 0; - // We have to also move export from, since we will preserve those - var exports_from_count: usize = 0; - // Two passes. First pass just counts. - for (parts[parts.len - 1].stmts) |stmt| { - imports_count += switch (stmt.data) { - .s_import => @as(usize, 1), - else => @as(usize, 0), - }; - exports_from_count += switch (stmt.data) { - .s_export_star, .s_export_from => @as(usize, 1), - else => @as(usize, 0), - }; - } - var part = &parts[parts.len - 1]; - - const end_iife_stmts_count = part.stmts.len - imports_count - exports_from_count + 1; - // Why 7? - // 1. HMRClient.activate(${isDebug}); - // 2. var __hmrModule = new HMMRModule(id, file_path), __exports = __hmrModule.exports; - // 3. (__hmrModule.load = function() { - // ${...end_iffe_stmts_count - 1} - // ${end_iffe_stmts_count} - // __hmrModule.exportAll({exportAlias: () => identifier}) <-- ${named_exports_count} - // (); - // 4. var __hmrExport_exportName = __hmrModule.exports.exportName, - // 5. export { __hmrExport_exportName as blah, ... } - // 6. __hmrModule.onSetExports = (newExports) => { - // $named_exports_count __hmrExport_exportName = newExports.exportName; <-- ${named_exports_count} - // } - - // if there are no exports: - // - there shouldn't be an export statement - // - we don't need the S.Local for wrapping the exports - // We still call exportAll just with an empty object. - const has_any_exports = named_exports_count > 0; - - const toplevel_stmts_count = 3 + (@intCast(usize, @boolToInt(has_any_exports)) * 2); - var _stmts = allocator.alloc( - Stmt, - end_iife_stmts_count + toplevel_stmts_count + (named_exports_count * 2) + imports_count + exports_from_count, - ) catch unreachable; - // Normally, we'd have to grow that inner function's stmts list by one - // But we can avoid that by just making them all use this same array. - var curr_stmts = _stmts; - - // in debug: crash in the printer due to undefined memory - // in release: print ";" instead. - // this should never happen regardless, but i'm just being cautious here. - if (comptime !Environment.isDebug) { - std.mem.set(Stmt, _stmts, Stmt.empty()); - } - - // Second pass: move any imports from the part's stmts array to the new stmts - var imports_list = curr_stmts[0..imports_count]; - curr_stmts = curr_stmts[imports_list.len..]; - var toplevel_stmts = curr_stmts[0..toplevel_stmts_count]; - curr_stmts = curr_stmts[toplevel_stmts.len..]; - var exports_from = curr_stmts[0..exports_from_count]; - curr_stmts = curr_stmts[exports_from.len..]; - // This is used for onSetExports - var update_function_stmts = curr_stmts[0..named_exports_count]; - curr_stmts = curr_stmts[update_function_stmts.len..]; - var export_all_function_body_stmts = curr_stmts[0..named_exports_count]; - curr_stmts = curr_stmts[export_all_function_body_stmts.len..]; - // This is the original part statements + 1 - var part_stmts = curr_stmts; - if (comptime Environment.allow_assert) assert(part_stmts.len == end_iife_stmts_count); - var part_stmts_i: usize = 0; - - var import_list_i: usize = 0; - var export_list_i: usize = 0; - - // We must always copy it into the new stmts array - for (part.stmts) |stmt| { - switch (stmt.data) { - .s_import => { - imports_list[import_list_i] = stmt; - import_list_i += 1; - }, - .s_export_star, .s_export_from => { - exports_from[export_list_i] = stmt; - export_list_i += 1; - }, - else => { - part_stmts[part_stmts_i] = stmt; - part_stmts_i += 1; - }, - } - } - - const new_call_args_count: usize = if (p.options.features.react_fast_refresh) 3 else 2; - var call_args = try allocator.alloc(Expr, new_call_args_count + 1); - var new_call_args = call_args[0..new_call_args_count]; - var hmr_module_ident = p.e(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty); - - new_call_args[0] = p.e(E.Number{ .value = @intToFloat(f64, p.options.filepath_hash_for_hmr) }, logger.Loc.Empty); - // This helps us provide better error messages - new_call_args[1] = p.e(E.String{ .data = p.source.path.pretty }, logger.Loc.Empty); - if (p.options.features.react_fast_refresh) { - new_call_args[2] = p.e(E.Identifier{ .ref = p.jsx_refresh_runtime.ref }, logger.Loc.Empty); - } - - var toplevel_stmts_i: u8 = 0; - - var decls = try allocator.alloc(G.Decl, 2 + named_exports_count); - var first_decl = decls[0..2]; - // We cannot rely on import.meta.url because if we import it within a blob: url, it will be nonsensical - // var __hmrModule = new HMRModule(123123124, "/index.js"), __exports = __hmrModule.exports; - const hmr_import_module_ = if (p.options.features.react_fast_refresh) - p.runtime_imports.__FastRefreshModule.? - else - p.runtime_imports.__HMRModule.?; - - const hmr_import_ref = hmr_import_module_.ref; - first_decl[0] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), - .value = p.e(E.New{ - .args = ExprNodeList.init(new_call_args), - .target = p.e( - E.Identifier{ - .ref = hmr_import_ref, - }, - logger.Loc.Empty, - ), - .close_parens_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - }; - first_decl[1] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - .value = p.e(E.Dot{ - .target = p.e(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), - .name = "exports", - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - }; - - var export_clauses = try allocator.alloc(js_ast.ClauseItem, named_exports_count); - var named_export_i: usize = 0; - var named_exports_iter = p.named_exports.iterator(); - var export_properties = try allocator.alloc(G.Property, named_exports_count); - - var export_name_string_length: usize = 0; - while (named_exports_iter.next()) |named_export| { - export_name_string_length += named_export.key_ptr.len + "$$hmr_".len; - } - - var export_name_string_all = try allocator.alloc(u8, export_name_string_length); - var export_name_string_remainder = export_name_string_all; - var hmr_module_exports_dot = p.e( - E.Dot{ - .target = hmr_module_ident, - .name = "exports", - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ); - var exports_decls = decls[first_decl.len..]; - named_exports_iter = p.named_exports.iterator(); - var update_function_args = try allocator.alloc(G.Arg, 1); - var exports_ident = p.e(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty); - update_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; - - while (named_exports_iter.next()) |named_export| { - const named_export_value = named_export.value_ptr.*; - - // Do not try to HMR export {foo} from 'bar'; - if (named_imports.get(named_export_value.ref)) |named_import| { - if (named_import.is_exported) continue; - } - - const named_export_symbol: Symbol = p.symbols.items[named_export_value.ref.innerIndex()]; - - var export_name_string = export_name_string_remainder[0 .. named_export.key_ptr.len + "$$hmr_".len]; - export_name_string_remainder = export_name_string_remainder[export_name_string.len..]; - std.mem.copy(u8, export_name_string, "$$hmr_"); - std.mem.copy(u8, export_name_string["$$hmr_".len..], named_export.key_ptr.*); - - var name_ref = try p.declareSymbol(.other, logger.Loc.Empty, export_name_string); - - var body_stmts = export_all_function_body_stmts[named_export_i .. named_export_i + 1]; - body_stmts[0] = p.s( - // was this originally a named import? - // preserve the identifier - S.Return{ .value = if (named_export_symbol.namespace_alias != null) - p.e(E.ImportIdentifier{ - .ref = named_export_value.ref, - .was_originally_identifier = true, - }, logger.Loc.Empty) - else - p.e(E.Identifier{ - .ref = named_export_value.ref, - }, logger.Loc.Empty) }, - logger.Loc.Empty, - ); - export_clauses[named_export_i] = js_ast.ClauseItem{ - .original_name = "", - .alias = named_export.key_ptr.*, - .alias_loc = named_export_value.alias_loc, - .name = .{ .ref = name_ref, .loc = logger.Loc.Empty }, - }; - - var decl_value = p.e( - E.Dot{ .target = hmr_module_exports_dot, .name = named_export.key_ptr.*, .name_loc = logger.Loc.Empty }, - logger.Loc.Empty, - ); - exports_decls[named_export_i] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = name_ref }, logger.Loc.Empty), - .value = decl_value, - }; - - update_function_stmts[named_export_i] = Expr.assignStmt( - p.e( - E.Identifier{ .ref = name_ref }, - logger.Loc.Empty, - ), - p.e(E.Dot{ - .target = exports_ident, - .name = named_export.key_ptr.*, - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - allocator, - ); - - export_properties[named_export_i] = G.Property{ - .key = p.e(E.String{ .data = named_export.key_ptr.* }, logger.Loc.Empty), - .value = p.e( - E.Arrow{ - .args = &[_]G.Arg{}, - .body = .{ - .stmts = body_stmts, - .loc = logger.Loc.Empty, - }, - .prefer_expr = true, - }, - logger.Loc.Empty, - ), - }; - named_export_i += 1; - } - var export_all_args = call_args[new_call_args.len..]; - export_all_args[0] = p.e( - E.Object{ .properties = Property.List.init(export_properties[0..named_export_i]) }, - logger.Loc.Empty, - ); - - part_stmts[part_stmts.len - 1] = p.s( - S.SExpr{ - .value = p.e( - E.Call{ - .target = p.e( - E.Dot{ - .target = hmr_module_ident, - .name = "exportAll", - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - .args = ExprNodeList.init(export_all_args), - }, - logger.Loc.Empty, - ), - }, - logger.Loc.Empty, - ); - - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Local{ - .decls = first_decl, - }, - logger.Loc.Empty, - ); - - toplevel_stmts_i += 1; - - const is_async = !p.top_level_await_keyword.isEmpty(); - - var func = p.e( - E.Function{ - .func = .{ - .body = .{ .loc = logger.Loc.Empty, .stmts = part_stmts[0 .. part_stmts_i + 1] }, - .name = null, - .open_parens_loc = logger.Loc.Empty, - .flags = Flags.Function.init(.{ - .print_as_iife = true, - .is_async = is_async, - }), - }, - }, - logger.Loc.Empty, - ); - - const call_load = p.e( - E.Call{ - .target = Expr.assign( - p.e( - E.Dot{ - .name = "_load", - .target = hmr_module_ident, - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - func, - allocator, - ), - }, - logger.Loc.Empty, - ); - // (__hmrModule._load = function())() - toplevel_stmts[toplevel_stmts_i] = p.s( - S.SExpr{ - .value = if (is_async) - p.e(E.Await{ .value = call_load }, logger.Loc.Empty) - else - call_load, - }, - logger.Loc.Empty, - ); - - toplevel_stmts_i += 1; - - if (has_any_exports) { - if (named_export_i > 0) { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Local{ - .decls = exports_decls[0..named_export_i], - }, - logger.Loc.Empty, - ); - } else { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Empty{}, - logger.Loc.Empty, - ); - } - - toplevel_stmts_i += 1; - } - - toplevel_stmts[toplevel_stmts_i] = p.s( - S.SExpr{ - .value = Expr.assign( - p.e( - E.Dot{ - .name = "_update", - .target = hmr_module_ident, - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - p.e( - E.Function{ - .func = .{ - .body = .{ .loc = logger.Loc.Empty, .stmts = if (named_export_i > 0) update_function_stmts[0..named_export_i] else &.{} }, - .name = null, - .args = update_function_args, - .open_parens_loc = logger.Loc.Empty, - }, - }, - logger.Loc.Empty, - ), - allocator, - ), - }, - logger.Loc.Empty, - ); - toplevel_stmts_i += 1; - if (has_any_exports) { - if (named_export_i > 0) { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.ExportClause{ - .items = export_clauses[0..named_export_i], - }, - logger.Loc.Empty, - ); - } else { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Empty{}, - logger.Loc.Empty, - ); - } - } - - part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; - } else if (p.options.features.hot_module_reloading) {} - - { - - // Each part tracks the other parts it depends on within this file - // var local_dependencies = AutoHashMap(u32, u32).init(p.allocator); - - // while (i < parts.len) : (i += 1) { - // const part = parts[i]; - // if (part.symbol_uses.count() > 0) { - // var iter = part.symbol_uses.iterator(); - // var dependencies = List(js_ast.Dependency).init(p.allocator); - // while (iter.next()) |entry| { - // const ref = entry.key; - - // if (p.top_level_symbol_to_parts.get(ref)) |tlstp| { - // for (tlstp.items) |other_part_index| { - // if (!local_dependencies.contains(other_part_index) or other_part_index != i) { - // try local_dependencies.put(other_part_index, @intCast(u32, i)); - // try dependencies.append(js_ast.Dependency{ - // .source_index = p.source.index, - // .part_index = other_part_index, - // }); - // } - // } - // } - - // // Also map from imports to parts that use them - // // TODO: will appending to this list like this be a perf issue? - // if (p.named_imports.getEntry(ref)) |named_import_entry| { - // const named_import = named_import_entry.value; - // var buf = try p.allocator.alloc(u32, named_import.local_parts_with_uses.len + 1); - // if (named_import.local_parts_with_uses.len > 0) { - // std.mem.copy(u32, buf, named_import.local_parts_with_uses); - // } - // buf[buf.len - 1] = @intCast(u32, i); - // named_import_entry.value.local_parts_with_uses = buf; - // } - // } - // } - // } - } - - return js_ast.Ast{ - .runtime_imports = p.runtime_imports, - .parts = parts, - .module_scope = p.module_scope.*, - .symbols = p.symbols.items, - .exports_ref = p.exports_ref, - .wrapper_ref = null, - .module_ref = p.module_ref, - .import_records = p.import_records.items, - .export_star_import_records = p.export_star_import_records.items, - .approximate_newline_count = p.lexer.approximate_newline_count, - .exports_kind = exports_kind, - .named_imports = p.named_imports, - .named_exports = p.named_exports, - .import_keyword = p.es6_import_keyword, - .export_keyword = p.es6_export_keyword, - .require_ref = if (p.runtime_imports.__require != null) - p.runtime_imports.__require.?.ref - else - p.require_ref, - - .uses_module_ref = (p.symbols.items[p.module_ref.innerIndex()].use_count_estimate > 0), - .uses_exports_ref = (p.symbols.items[p.exports_ref.innerIndex()].use_count_estimate > 0), - .uses_require_ref = if (p.runtime_imports.__require != null) - (p.symbols.items[p.runtime_imports.__require.?.ref.innerIndex()].use_count_estimate > 0) - else - false, - // .top_Level_await_keyword = p.top_level_await_keyword, - }; - } - - pub fn init( - allocator: Allocator, - log: *logger.Log, - source: *const logger.Source, - define: *Define, - lexer: js_lexer.Lexer, - opts: Parser.Options, - this: *P, - ) !void { - var scope_order = try ScopeOrderList.initCapacity(allocator, 1); - var scope = try allocator.create(Scope); - scope.* = Scope{ - .members = @TypeOf(scope.members){}, - .children = @TypeOf(scope.children){}, - .generated = @TypeOf(scope.generated){}, - .kind = .entry, - .label_ref = null, - .parent = null, - }; - - scope_order.appendAssumeCapacity(ScopeOrder{ .loc = locModuleScope, .scope = scope }); - this.* = P{ - .cjs_import_stmts = @TypeOf(this.cjs_import_stmts).init(allocator), - // This must default to true or else parsing "in" won't work right. - // It will fail for the case in the "in-keyword.js" file - .allow_in = true, - - .call_target = nullExprData, - .delete_target = nullExprData, - .stmt_expr_value = nullExprData, - .expr_list = .{}, - .loop_body = nullStmtData, - .define = define, - .import_records = undefined, - .named_imports = undefined, - .named_exports = js_ast.Ast.NamedExports.init(allocator), - .log = log, - .allocator = allocator, - .options = opts, - .then_catch_chain = ThenCatchChain{ .next_target = nullExprData }, - .to_expr_wrapper_namespace = undefined, - .to_expr_wrapper_hoisted = undefined, - .import_transposer = undefined, - .require_transposer = undefined, - .require_resolve_transposer = undefined, - .source = source, - .macro = MacroState.init(allocator), - .current_scope = scope, - .module_scope = scope, - .scopes_in_order = scope_order, - .needs_jsx_import = if (comptime only_scan_imports_and_do_not_visit) false else NeedsJSXType{}, - .lexer = lexer, - }; - - this.symbols = std.ArrayList(Symbol).init(allocator); - - if (comptime !only_scan_imports_and_do_not_visit) { - this.import_records = @TypeOf(this.import_records).init(allocator); - this.named_imports = NamedImportsType.init(allocator); - } - - this.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(this); - this.to_expr_wrapper_hoisted = Binding2ExprWrapper.Hoisted.init(this); - this.import_transposer = @TypeOf(this.import_transposer).init(this); - this.require_transposer = @TypeOf(this.require_transposer).init(this); - this.require_resolve_transposer = @TypeOf(this.require_resolve_transposer).init(this); - - if (opts.features.top_level_await) { - this.fn_or_arrow_data_parse.allow_await = .allow_expr; - this.fn_or_arrow_data_parse.is_top_level = true; - } - } - }; -} - -// Doing this seems to yield a 1% performance improvement parsing larger files -// ❯ hyperfine "../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable" "../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable" --min-runs=500 -// Benchmark #1: ../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable -// Time (mean ± σ): 25.1 ms ± 1.1 ms [User: 20.4 ms, System: 3.1 ms] -// Range (min … max): 23.5 ms … 31.7 ms 500 runs - -// Benchmark #2: ../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable -// Time (mean ± σ): 25.6 ms ± 1.3 ms [User: 20.9 ms, System: 3.1 ms] -// Range (min … max): 24.1 ms … 39.7 ms 500 runs -// '../../build/macos-x86_64/bun node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' ran -// 1.02 ± 0.07 times faster than '../../bun.before-comptime-js-parser node_modules/react-dom/cjs/react-dom.development.js --resolve=disable' -const JavaScriptParser = NewParser(.{}); -const JSXParser = NewParser(.{ .jsx = .react }); -const TSXParser = NewParser(.{ .jsx = .react, .typescript = true }); -const TypeScriptParser = NewParser(.{ .typescript = true }); -const SolidJSXParser = NewParser(.{ .jsx = .solid }); -const SolidTSXParser = NewParser(.{ .jsx = .solid, .typescript = true }); - -const JSParserMacro = NewParser(.{ - .jsx = .macro, -}); -const TSParserMacro = NewParser(.{ - .jsx = .macro, - .typescript = true, -}); - -const JavaScriptImportScanner = NewParser(.{ .scan_only = true }); -const JSXImportScanner = NewParser(.{ .jsx = .react, .scan_only = true }); -const TSXImportScanner = NewParser(.{ .jsx = .react, .typescript = true, .scan_only = true }); -const TypeScriptImportScanner = NewParser(.{ .typescript = true, .scan_only = true }); - -// The "await" and "yield" expressions are never allowed in argument lists but -// may or may not be allowed otherwise depending on the details of the enclosing -// function or module. This needs to be handled when parsing an arrow function -// argument list because we don't know if these expressions are not allowed until -// we reach the "=>" token (or discover the absence of one). -// -// Specifically, for await: -// -// // This is ok -// async function foo() { (x = await y) } -// -// // This is an error -// async function foo() { (x = await y) => {} } -// -// And for yield: -// -// // This is ok -// function* foo() { (x = yield y) } -// -// // This is an error -// function* foo() { (x = yield y) => {} } -// -const DeferredArrowArgErrors = struct { - invalid_expr_await: logger.Range = logger.Range.None, - invalid_expr_yield: logger.Range = logger.Range.None, -}; |