diff options
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r-- | src/js_parser.zig | 337 |
1 files changed, 247 insertions, 90 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index 8147ec947..34db0e5ac 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1126,7 +1126,7 @@ pub const ImportScanner = struct { }, .s_local => |st| { if (st.is_export) { - for (st.decls) |decl| { + for (st.decls.slice()) |decl| { p.recordExportedBinding(decl.binding); } } @@ -1134,7 +1134,7 @@ pub const ImportScanner = struct { // 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]; + var decl = st.decls.ptr[0]; // Skip to the underlying reference var value = decl.value; @@ -1234,7 +1234,7 @@ pub const ImportScanner = struct { 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, + .decls = G.Decl.List.init(decls), .kind = S.Local.Kind.k_var, .is_export = false, }, ex.loc); @@ -1254,7 +1254,7 @@ pub const ImportScanner = struct { decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = st.default_name.ref.? }, stmt.loc), .value = p.newExpr(E.Function{ .func = func.func }, stmt.loc) }; stmt = p.s(S.Local{ - .decls = decls, + .decls = Decl.List.init(decls), .kind = S.Local.Kind.k_var, .is_export = false, }, stmt.loc); @@ -1283,7 +1283,7 @@ pub const ImportScanner = struct { }; stmt = p.s(S.Local{ - .decls = decls, + .decls = Decl.List.init(decls), .kind = S.Local.Kind.k_var, .is_export = false, }, stmt.loc); @@ -1787,18 +1787,18 @@ pub const SideEffects = enum(u1) { // 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 }; + if (local.decls.len == 1 and local.decls.ptr[0].binding.data == .b_identifier) { + const prev = local.decls.ptr[0]; + stmt.data.s_local.decls.ptr[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| { + for (local.decls.slice()) |decl| { findIdentifiers(decl.binding, &decls); } - local.decls = decls.toOwnedSlice() catch @panic("TODO"); + local.decls.update(decls); return true; }, @@ -2194,7 +2194,7 @@ const IdentifierOpts = packed struct { }; fn statementCaresAboutScope(stmt: Stmt) bool { - switch (stmt.data) { + return switch (stmt.data) { .s_block, .s_empty, .s_debugger, @@ -2213,20 +2213,12 @@ fn statementCaresAboutScope(stmt: Stmt) bool { .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_label, + => false, - .s_local => |local| { - return local.kind != .k_var; - }, - else => { - return true; - }, - } + .s_local => |local| local.kind != .k_var, + else => true, + }; } const ExprIn = struct { @@ -2672,6 +2664,8 @@ pub const Parser = struct { module_type: options.ModuleType = .unknown, + transform_only: bool = false, + pub fn init(jsx: options.JSX.Pragma, loader: options.Loader) Options { var opts = Options{ .ts = loader.isTypeScript(), @@ -2767,6 +2761,7 @@ pub const Parser = struct { pub fn toLazyExportAST(this: *Parser, expr: Expr, comptime runtime_api_call: []const u8) !js_ast.Result { var p: JavaScriptParser = undefined; try JavaScriptParser.init(this.allocator, this.log, this.source, this.define, this.lexer, this.options, &p); + p.lexer.track_comments = this.options.features.minify_identifiers; p.should_fold_typescript_constant_expressions = this.options.features.should_fold_typescript_constant_expressions; defer p.lexer.deinit(); var result: js_ast.Result = undefined; @@ -2925,14 +2920,14 @@ pub const Parser = struct { switch (stmt.data) { .s_local => |local| { if (local.decls.len > 1) { - for (local.decls) |decl| { + for (local.decls.slice()) |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; + _local.decls.update(list); sliced.items[0] = p.s(_local, stmt.loc); try p.appendPart(&parts, sliced.items); } @@ -3051,7 +3046,7 @@ pub const Parser = struct { var part_stmts = p.allocator.alloc(Stmt, 1) catch unreachable; part_stmts[0] = p.s(S.Local{ .kind = .k_var, - .decls = decls, + .decls = Decl.List.init(decls), }, logger.Loc.Empty); before.append(js_ast.Part{ .stmts = part_stmts, @@ -3116,7 +3111,7 @@ pub const Parser = struct { .kind = .k_var, .is_export = false, .was_commonjs_export = true, - .decls = decls, + .decls = Decl.List.init(decls), }, export_ref.loc_ref.loc, ); @@ -3289,7 +3284,7 @@ pub const Parser = struct { exports_kind = .esm; } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { exports_kind = .cjs; - if (!p.options.bundle) { + if (!p.options.bundle and (!p.options.transform_only or p.options.features.dynamic_require)) { if (p.options.legacy_transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_legacy_bundling)) { var args = p.allocator.alloc(Expr, 2) catch unreachable; @@ -5240,7 +5235,7 @@ fn NewParser_( switch (stmt.data) { .s_local => |local| { if (local.is_export) break :can_remove_part false; - for (local.decls) |decl| { + for (local.decls.slice()) |decl| { if (isBindingUsed(p, decl.binding, default_export_ref)) break :can_remove_part false; } @@ -5386,6 +5381,60 @@ fn NewParser_( } } + fn computeCharacterFrequency(p: *P) ?js_ast.CharFreq { + if (!p.options.features.minify_identifiers or (p.options.bundle and p.source.index.isRuntime())) { + return null; + } + + // Add everything in the file to the histogram + var freq: js_ast.CharFreq = .{ + .freqs = [_]i32{0} ** 64, + }; + + freq.scan(p.source.contents, 1); + + // Subtract out all comments + for (p.lexer.all_comments.items) |comment_range| { + freq.scan(p.source.textForRange(comment_range), -1); + } + + // Subtract out all import paths + for (p.import_records.items) |record| { + freq.scan(record.path.text, -1); + } + + const ScopeVisitor = struct { + pub fn visit(symbols: []const js_ast.Symbol, char_freq: *js_ast.CharFreq, scope: *js_ast.Scope) void { + var iter = scope.members.iterator(); + + while (iter.next()) |entry| { + const symbol: *const Symbol = &symbols[entry.value_ptr.ref.innerIndex()]; + + if (symbol.slotNamespace() != .must_not_be_renamed) { + char_freq.scan(symbol.original_name, -@intCast(i32, symbol.use_count_estimate)); + } + } + + if (scope.label_ref) |ref| { + const symbol = &symbols[ref.innerIndex()]; + + if (symbol.slotNamespace() != .must_not_be_renamed) { + char_freq.scan(symbol.original_name, -@intCast(i32, symbol.use_count_estimate) - 1); + } + } + + for (scope.children.slice()) |child| { + visit(symbols, char_freq, child); + } + } + }; + ScopeVisitor.visit(p.symbols.items, &freq, p.module_scope); + + // TODO: mangledProps + + return freq; + } + pub fn newExpr(p: *P, t: anytype, loc: logger.Loc) Expr { const Type = @TypeOf(t); @@ -5808,7 +5857,7 @@ fn NewParser_( }, .s_local => |local| { if (local.decls.len > 0) { - var first: *Decl = &local.decls[0]; + var first: *Decl = &local.decls.ptr[0]; if (first.value) |*value| { if (first.binding.data == .b_identifier) { break :brk value; @@ -8914,7 +8963,7 @@ fn NewParser_( 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); + return p.s(S.Local{ .kind = .k_var, .decls = Decl.List.fromList(decls), .is_export = opts.is_export }, loc); }, .t_const => { if (opts.lexical_decl != .allow_all) { @@ -8932,12 +8981,12 @@ fn NewParser_( try p.lexer.expectOrInsertSemicolon(); if (!opts.is_typescript_declare) { - try p.requireInitializers(decls); + try p.requireInitializers(decls.items); } // 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); + return p.s(S.Local{ .kind = kind, .decls = Decl.List.fromList(decls), .is_export = opts.is_export }, loc); }, .t_if => { try p.lexer.next(); @@ -9168,7 +9217,7 @@ fn NewParser_( bad_let_range = p.lexer.range(); } - var decls: []G.Decl = &([_]G.Decl{}); + var decls: G.Decl.List = .{}; var init_loc = p.lexer.loc(); var is_var = false; switch (p.lexer.token) { @@ -9177,15 +9226,15 @@ fn NewParser_( 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); + decls.update(try p.parseAndDeclareDecls(.hoisted, &stmtOpts)); + init_ = p.s(S.Local{ .kind = .k_var, .decls = Decl.List.fromList(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); + decls.update(try p.parseAndDeclareDecls(.cconst, &stmtOpts)); + init_ = p.s(S.Local{ .kind = .k_const, .decls = Decl.List.fromList(decls) }, init_loc); }, // for (;) .t_semicolon => {}, @@ -9226,7 +9275,7 @@ fn NewParser_( } } - try p.forbidInitializers(decls, "of", false); + try p.forbidInitializers(decls.slice(), "of", false); try p.lexer.next(); const value = try p.parseExpr(.comma); try p.lexer.expect(.t_close_paren); @@ -9237,7 +9286,7 @@ fn NewParser_( // Detect for-in loops if (p.lexer.token == .t_in) { - try p.forbidInitializers(decls, "in", is_var); + try p.forbidInitializers(decls.slice(), "in", is_var); try p.lexer.next(); const value = try p.parseExpr(.lowest); try p.lexer.expect(.t_close_paren); @@ -9251,7 +9300,7 @@ fn NewParser_( switch (init_stmt.data) { .s_local => { if (init_stmt.data.s_local.kind == .k_const) { - try p.requireInitializers(decls); + try p.requireInitializers(decls.slice()); } }, else => {}, @@ -9662,14 +9711,14 @@ fn NewParser_( // 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{}); + var decls: G.Decl.List = .{}; switch (stmt.data) { .s_local => |local| { var _decls = try ListManaged(G.Decl).initCapacity(p.allocator, local.decls.len); - for (local.decls) |decl| { + for (local.decls.slice()) |decl| { try extractDeclsForBinding(decl.binding, &_decls); } - decls = _decls.items; + decls.update(_decls); }, else => {}, } @@ -9954,7 +10003,7 @@ fn NewParser_( .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); + return p.s(S.Local{ .kind = kind, .decls = Decl.List.init(decls), .is_export = opts.is_export, .was_ts_import_equals = true }, loc); } fn parseClauseAlias(p: *P, kind: string) !string { @@ -10180,11 +10229,11 @@ fn NewParser_( .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, + .decls = G.Decl.List.fromList(decls), .is_export = opts.is_export, }, let_range.loc), }, - .decls = decls, + .decls = decls.items, }; } }, @@ -10443,7 +10492,7 @@ fn NewParser_( }; } - fn parseAndDeclareDecls(p: *P, kind: Symbol.Kind, opts: *ParseStatementOptions) anyerror![]G.Decl { + fn parseAndDeclareDecls(p: *P, kind: Symbol.Kind, opts: *ParseStatementOptions) anyerror!ListManaged(G.Decl) { var decls = ListManaged(G.Decl).init(p.allocator); while (true) { @@ -10487,7 +10536,7 @@ fn NewParser_( try p.lexer.next(); } - return decls.items; + return decls; } pub fn parseTypescriptEnumStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions) anyerror!Stmt { @@ -12505,14 +12554,15 @@ fn NewParser_( }, .t_question_dot => { try p.lexer.next(); - var optional_start = js_ast.OptionalChain.start; + var optional_start: ?js_ast.OptionalChain = 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 - // } - // } + // Remove unnecessary optional chains + if (p.options.features.minify_syntax) { + const result = SideEffects.toNullOrUndefined(left.data); + if (result.ok and !result.value) { + optional_start = null; + } + } switch (p.lexer.token) { .t_open_bracket => { @@ -12609,7 +12659,7 @@ fn NewParser_( } // Only continue if we have started - if (optional_start == .start) { + if ((optional_start orelse .ccontinue) == .start) { optional_start = .ccontinue; } }, @@ -14386,7 +14436,7 @@ fn NewParser_( decls[0] = Decl{ .binding = p.b(B.Identifier{ .ref = ref }, local.loc), }; - try partStmts.append(p.s(S.Local{ .decls = decls }, local.loc)); + try partStmts.append(p.s(S.Local{ .decls = G.Decl.List.init(decls) }, local.loc)); } } p.relocated_top_level_vars.clearRetainingCapacity(); @@ -14537,7 +14587,7 @@ fn NewParser_( } }, .s_local => |st| { - for (st.decls) |*decl| { + for (st.decls.slice()) |*decl| { if (!p.bindingCanBeRemovedIfUnused(decl.binding)) { return false; } @@ -17797,20 +17847,20 @@ fn NewParser_( p.current_scope.is_after_const_local_prefix = was_after_after_const_local_prefix; const decls_len = if (!(data.is_export and p.options.features.replace_exports.entries.len > 0)) - p.visitDecls(data.decls, data.kind == .k_const, false) + p.visitDecls(data.decls.slice(), data.kind == .k_const, false) else - p.visitDecls(data.decls, data.kind == .k_const, true); + p.visitDecls(data.decls.slice(), data.kind == .k_const, true); const is_now_dead = data.decls.len > 0 and decls_len == 0; if (is_now_dead) { return; } - data.decls.len = decls_len; + data.decls.len = @truncate(u32, decls_len); // Handle being exported inside a namespace if (data.is_export and p.enclosing_namespace_arg_ref != null) { - for (data.decls) |*d| { + for (data.decls.slice()) |*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? @@ -17827,7 +17877,7 @@ fn NewParser_( // 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); + const relocated = p.maybeRelocateVarsToTopLevel(data.decls.slice(), .normal); if (relocated.ok) { if (relocated.stmt) |new_stmt| { stmts.append(new_stmt) catch unreachable; @@ -17892,7 +17942,7 @@ fn NewParser_( .kind = .k_var, .is_export = false, .was_commonjs_export = true, - .decls = decls, + .decls = G.Decl.List.init(decls), }, stmt.loc, ), @@ -18088,7 +18138,7 @@ fn NewParser_( // 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); + const relocate = p.maybeRelocateVarsToTopLevel(init_.data.s_local.decls.slice(), .normal); if (relocate.stmt) |relocated| { data.init = relocated; } @@ -18111,7 +18161,7 @@ fn NewParser_( // Lower for-in variable initializers in case the output is used in strict mode var local = data.init.data.s_local; if (local.decls.len == 1) { - var decl: *G.Decl = &local.decls[0]; + var decl: *G.Decl = &local.decls.ptr[0]; if (decl.binding.data == .b_identifier) { if (decl.value) |val| { stmts.append( @@ -18128,7 +18178,7 @@ fn NewParser_( } 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); + const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls.slice(), RelocateVars.Mode.for_in_or_for_of); if (relocate.stmt) |relocated_stmt| { data.init = relocated_stmt; } @@ -18143,7 +18193,7 @@ fn NewParser_( 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); + const relocate = p.maybeRelocateVarsToTopLevel(data.init.data.s_local.decls.slice(), RelocateVars.Mode.for_in_or_for_of); if (relocate.stmt) |relocated_stmt| { data.init = relocated_stmt; } @@ -18452,7 +18502,7 @@ fn NewParser_( switch (child_stmt.data) { .s_local => |local| { if (local.is_export) { - p.markExportedDeclsInsideNamespace(data.arg, local.decls); + p.markExportedDeclsInsideNamespace(data.arg, local.decls.slice()); } }, else => {}, @@ -18599,7 +18649,7 @@ fn NewParser_( var local = p.s( S.Local{ .is_export = true, - .decls = decls, + .decls = Decl.List.init(decls), }, loc, ); @@ -18620,7 +18670,7 @@ fn NewParser_( var local = p.s( S.Local{ .is_export = true, - .decls = decls, + .decls = Decl.List.init(decls), }, loc, ); @@ -18832,7 +18882,7 @@ fn NewParser_( p.s( S.Local{ .kind = .k_var, - .decls = decls, + .decls = G.Decl.List.init(decls), .is_export = is_export, }, stmt_loc, @@ -18844,7 +18894,7 @@ fn NewParser_( p.s( S.Local{ .kind = .k_let, - .decls = decls, + .decls = G.Decl.List.init(decls), }, stmt_loc, ), @@ -19161,7 +19211,7 @@ fn NewParser_( st.value = p.visitExprInOut(st.value, ExprIn{ .assign_target = assign_target }); }, .s_local => |st| { - for (st.decls) |*dec| { + for (st.decls.slice()) |*dec| { p.visitBinding(dec.binding, null); if (dec.value) |val| { dec.value = p.visitExpr(val); @@ -19396,7 +19446,7 @@ fn NewParser_( p.popScope(); } - return p.stmtsToSingleStmt(stmt.loc, stmts.toOwnedSlice() catch @panic("TODO")); + return p.stmtsToSingleStmt(stmt.loc, stmts.items); } // One statement could potentially expand to several statements @@ -19405,7 +19455,7 @@ fn NewParser_( 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)) { + if (stmts.len == 1 and !statementCaresAboutScope(stmts[0])) { // "let" and "const" must be put in a block when in a single-statement context return stmts[0]; } @@ -19884,7 +19934,7 @@ fn NewParser_( before.appendAssumeCapacity(p.s( S.Local{ .kind = .k_let, - .decls = let_decls.items, + .decls = Decl.List.fromList(let_decls), }, let_decls.items[0].value.?.loc, )); @@ -19900,7 +19950,7 @@ fn NewParser_( before.appendAssumeCapacity(p.s( S.Local{ .kind = .k_var, - .decls = var_decls.items, + .decls = Decl.List.fromList(var_decls), }, var_decls.items[0].value.?.loc, )); @@ -19953,7 +20003,7 @@ fn NewParser_( // if this fails it means that scope pushing/popping is not balanced assert(p.current_scope == initial_scope); - if (!p.options.features.inlining) { + if (!p.options.features.minify_syntax) { return; } @@ -19971,7 +20021,7 @@ fn NewParser_( .s_empty, .s_comment, .s_directive, .s_debugger, .s_type_script => continue, .s_local => |local| { if (!local.is_export and local.kind == .k_const and !local.was_commonjs_export) { - var decls: []Decl = local.decls; + var decls: []Decl = local.decls.slice(); var end: usize = 0; for (decls) |decl| { if (decl.binding.data == .b_identifier) { @@ -19982,7 +20032,7 @@ fn NewParser_( decls[end] = decl; end += 1; } - local.decls.len = end; + local.decls.len = @truncate(u32, end); if (end == 0) { stmt.* = stmt.*.toEmpty(); } @@ -19997,6 +20047,8 @@ fn NewParser_( } } + var is_control_flow_dead = false; + // Inline single-use variable declarations where possible: // // // Before @@ -20018,6 +20070,10 @@ fn NewParser_( var output = ListManaged(Stmt).initCapacity(p.allocator, stmts.items.len) catch unreachable; for (stmts.items) |stmt| { + if (is_control_flow_dead and !SideEffects.shouldKeepStmtInDeadControlFlow(stmt, p.allocator)) { + // Strip unnecessary statements if the control flow is dead here + continue; + } // Keep inlining variables until a failure or until there are none left. // That handles cases like this: @@ -20044,7 +20100,7 @@ fn NewParser_( break; } - var last: *Decl = &local.decls[local.decls.len - 1]; + var last: *Decl = local.decls.last().?; // The variable must be initialized, since we will be substituting // the value into the usage. if (last.value == null) @@ -20088,11 +20144,101 @@ fn NewParser_( break; } - if (stmt.data != .s_empty) { - output.appendAssumeCapacity( - stmt, - ); + switch (stmt.data) { + .s_empty => continue, + + // skip directives for now + .s_directive => continue, + + .s_local => |local| { + // Merge adjacent local statements + if (output.items.len > 0) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_local and local.kind == prev_stmt.data.s_local.kind and local.is_export == prev_stmt.data.s_local.is_export) { + prev_stmt.data.s_local.decls.append(p.allocator, local.decls.slice()) catch unreachable; + continue; + } + } + }, + + .s_expr => |s_expr| { + // Merge adjacent expression statements + if (output.items.len > 0) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_expr) { + prev_stmt.data.s_expr.does_not_affect_tree_shaking = prev_stmt.data.s_expr.does_not_affect_tree_shaking and + s_expr.does_not_affect_tree_shaking; + prev_stmt.data.s_expr.value = prev_stmt.data.s_expr.value.joinWithComma( + s_expr.value, + p.allocator, + ); + continue; + } + } + }, + .s_switch => |s_switch| { + // Absorb a previous expression statement + if (output.items.len > 0) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_expr) { + s_switch.test_ = prev_stmt.data.s_expr.value.joinWithComma(s_switch.test_, p.allocator); + output.items.len -= 1; + } + } + }, + .s_if => |s_if| { + // Absorb a previous expression statement + if (output.items.len > 0) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_expr) { + s_if.test_ = prev_stmt.data.s_expr.value.joinWithComma(s_if.test_, p.allocator); + output.items.len -= 1; + } + } + + // TODO: optimize jump + }, + + .s_return => |ret| { + // Merge return statements with the previous expression statement + if (output.items.len > 0 and ret.value != null) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_expr) { + ret.value = prev_stmt.data.s_expr.value.joinWithComma(ret.value.?, p.allocator); + prev_stmt.* = stmt; + continue; + } + } + + is_control_flow_dead = true; + }, + + .s_break, .s_continue => { + is_control_flow_dead = true; + }, + + .s_throw => { + // Merge throw statements with the previous expression statement + if (output.items.len > 0) { + var prev_stmt = &output.items[output.items.len - 1]; + if (prev_stmt.data == .s_expr) { + prev_stmt.* = p.s(S.Throw{ + .value = prev_stmt.data.s_expr.value.joinWithComma( + stmt.data.s_throw.value, + p.allocator, + ), + }, stmt.loc); + continue; + } + } + + is_control_flow_dead = true; + }, + + else => {}, } + + output.append(stmt) catch unreachable; } stmts.deinit(); stmts.* = output; @@ -20815,7 +20961,7 @@ fn NewParser_( toplevel_stmts[toplevel_stmts_i] = p.s( S.Local{ - .decls = first_decl, + .decls = G.Decl.List.init(first_decl), }, logger.Loc.Empty, ); @@ -20873,7 +21019,7 @@ fn NewParser_( if (named_export_i > 0) { toplevel_stmts[toplevel_stmts_i] = p.s( S.Local{ - .decls = exports_decls[0..named_export_i], + .decls = G.Decl.List.init(exports_decls[0..named_export_i]), }, logger.Loc.Empty, ); @@ -21023,6 +21169,17 @@ fn NewParser_( .import_keyword = p.esm_import_keyword, .export_keyword = p.esm_export_keyword, .top_level_symbols_to_parts = top_level_symbols_to_parts, + .char_freq = p.computeCharacterFrequency(), + + // Assign slots to symbols in nested scopes. This is some precomputation for + // the symbol renaming pass that will happen later in the linker. It's done + // now in the parser because we want it to be done in parallel per file and + // we're already executing code in parallel here + .nested_scope_slot_counts = if (p.options.features.minify_identifiers) + renamer.assignNestedScopeSlots(p.allocator, p.module_scope, p.symbols.items) + else + js_ast.SlotCounts{}, + .require_ref = if (p.runtime_imports.__require != null) p.runtime_imports.__require.?.ref else |