diff options
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r-- | src/js_parser.zig | 1247 |
1 files changed, 985 insertions, 262 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig index a61657128..fce928a6e 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -70,7 +70,7 @@ const RefHashCtx = @import("./ast/base.zig").RefHashCtx; pub const StringHashMap = bun.StringHashMap; pub const AutoHashMap = std.AutoHashMap; -const StringHashMapUnamanged = bun.StringHashMapUnmanaged; +const StringHashMapUnmanaged = bun.StringHashMapUnmanaged; const ObjectPool = @import("./pool.zig").ObjectPool; const NodeFallbackModules = @import("./node_fallbacks.zig"); @@ -439,6 +439,155 @@ pub const TypeScript = struct { }; } + pub const Metadata = union(enum) { + m_none: void, + + m_never: void, + m_unknown: void, + m_any: void, + m_void: void, + m_null: void, + m_undefined: void, + m_function: void, + m_array: void, + m_boolean: void, + m_string: void, + m_object: void, + m_number: void, + m_bigint: void, + m_symbol: void, + m_promise: void, + m_identifier: Ref, + m_dot: List(Ref), + + pub const default: @This() = .m_none; + + // the logic in finishUnion, mergeUnion, finishIntersection and mergeIntersection is + // translated from: + // https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402 + + /// Return the final union type if possible, or return null to continue merging. + /// + /// If the current type is m_never, m_null, or m_undefined assign the current type + /// to m_none and return null to ensure it's always replaced by the next type. + pub fn finishUnion(current: *@This(), p: anytype) ?@This() { + return switch (current.*) { + .m_identifier => |ref| { + if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) { + return .m_object; + } + return null; + }, + + .m_unknown, + .m_any, + .m_object, + => .m_object, + + .m_never, + .m_null, + .m_undefined, + => { + current.* = .m_none; + return null; + }, + + else => null, + }; + } + + pub fn mergeUnion(result: *@This(), left: @This()) void { + if (left != .m_none) { + if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) { + result.* = switch (result.*) { + .m_never, + .m_undefined, + .m_null, + => left, + + else => .m_object, + }; + } else { + switch (result.*) { + .m_identifier => |ref| { + if (!ref.eql(left.m_identifier)) { + result.* = .m_object; + } + }, + else => {}, + } + } + } else { + // always take the next value if left is m_none + } + } + + /// Return the final intersection type if possible, or return null to continue merging. + /// + /// If the current type is m_unknown, m_null, or m_undefined assign the current type + /// to m_none and return null to ensure it's always replaced by the next type. + pub fn finishIntersection(current: *@This(), p: anytype) ?@This() { + return switch (current.*) { + .m_identifier => |ref| { + if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) { + return .m_object; + } + return null; + }, + + // ensure m_never is the final type + .m_never => .m_never, + + .m_any, + .m_object, + => .m_object, + + .m_unknown, + .m_null, + .m_undefined, + => { + current.* = .m_none; + return null; + }, + + else => null, + }; + } + + pub fn mergeIntersection(result: *@This(), left: @This()) void { + if (left != .m_none) { + if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) { + result.* = switch (result.*) { + .m_unknown, + .m_undefined, + .m_null, + => left, + + // ensure m_never is the final type + .m_never => .m_never, + + else => .m_object, + }; + } else { + switch (result.*) { + .m_identifier => |ref| { + if (!ref.eql(left.m_identifier)) { + result.* = .m_object; + } + }, + else => {}, + } + } + } else { + // make sure intersection of only m_unknown serializes to "undefined" + // instead of "Object" + if (result.* == .m_unknown) { + result.* = .m_undefined; + } + } + } + }; + pub fn isTSArrowFnJSX(p: anytype) !bool { var oldLexer = std.mem.toBytes(p.lexer); @@ -664,19 +813,19 @@ pub const TypeScript = struct { .{ "abstract", .abstract }, .{ "asserts", .asserts }, - .{ "keyof", .prefix }, - .{ "readonly", .prefix }, + .{ "keyof", .prefix_keyof }, + .{ "readonly", .prefix_readonly }, - .{ "any", .primitive }, - .{ "never", .primitive }, - .{ "unknown", .primitive }, - .{ "undefined", .primitive }, - .{ "object", .primitive }, - .{ "number", .primitive }, - .{ "string", .primitive }, - .{ "boolean", .primitive }, - .{ "bigint", .primitive }, - .{ "symbol", .primitive }, + .{ "any", .primitive_any }, + .{ "never", .primitive_never }, + .{ "unknown", .primitive_unknown }, + .{ "undefined", .primitive_undefined }, + .{ "object", .primitive_object }, + .{ "number", .primitive_number }, + .{ "string", .primitive_string }, + .{ "boolean", .primitive_boolean }, + .{ "bigint", .primitive_bigint }, + .{ "symbol", .primitive_symbol }, .{ "infer", .infer }, }); @@ -685,17 +834,30 @@ pub const TypeScript = struct { unique, abstract, asserts, - prefix, - primitive, + prefix_keyof, + prefix_readonly, + primitive_any, + primitive_never, + primitive_unknown, + primitive_undefined, + primitive_object, + primitive_number, + primitive_string, + primitive_boolean, + primitive_bigint, + primitive_symbol, infer, }; }; - pub const SkipTypeOptions = struct { - is_return_type: bool = false, - is_index_signature: bool = false, - allow_tuple_labels: bool = false, - disallow_conditional_types: bool = false, + pub const SkipTypeOptions = enum { + is_return_type, + is_index_signature, + allow_tuple_labels, + disallow_conditional_types, + + pub const Bitset = std.enums.EnumSet(@This()); + pub const empty = Bitset.initEmpty(); }; }; @@ -1398,8 +1560,9 @@ const StaticSymbolName = struct { pub const __HMRClient = NewStaticSymbol("Bun"); pub const __FastRefreshModule = NewStaticSymbol("FastHMR"); pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh"); - pub const __decorateClass = NewStaticSymbol("__decorateClass"); - pub const __decorateParam = NewStaticSymbol("__decorateParam"); + pub const __legacyDecorateClassTS = NewStaticSymbol("__legacyDecorateClassTS"); + pub const __legacyDecorateParamTS = NewStaticSymbol("__legacyDecorateParamTS"); + pub const __legacyMetadataTS = NewStaticSymbol("__legacyMetadataTS"); pub const @"$$typeof" = NewStaticSymbol("$$typeof"); pub const @"$$m" = NewStaticSymbol("$$m"); @@ -1437,6 +1600,7 @@ pub const SideEffects = enum(u1) { } pub fn simplifyBoolean(p: anytype, expr: Expr) Expr { + if (!p.options.features.dead_code_elimination) return expr; switch (expr.data) { .e_unary => |e| { if (e.op == .un_not) { @@ -1451,14 +1615,14 @@ pub const SideEffects = enum(u1) { .e_binary => |e| { switch (e.op) { .bin_logical_and => { - const effects = SideEffects.toBoolean(e.right.data); + const effects = SideEffects.toBoolean(p, 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); + const effects = SideEffects.toBoolean(p, e.right.data); if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) { // "if (anything || falsyNoSideEffects)" => "if (anything)" return e.left; @@ -1484,6 +1648,7 @@ pub const SideEffects = enum(u1) { } pub fn simpifyUnusedExpr(p: anytype, expr: Expr) ?Expr { + if (!p.options.features.dead_code_elimination) return 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; @@ -1746,7 +1911,8 @@ pub const SideEffects = enum(u1) { // // 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 { + pub fn shouldKeepStmtInDeadControlFlow(p: anytype, stmt: Stmt, allocator: Allocator) bool { + if (!p.options.features.dead_code_elimination) return true; 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, @@ -1777,7 +1943,7 @@ pub const SideEffects = enum(u1) { .s_block => |block| { for (block.stmts) |child| { - if (shouldKeepStmtInDeadControlFlow(child, allocator)) { + if (shouldKeepStmtInDeadControlFlow(p, child, allocator)) { return true; } } @@ -1786,43 +1952,43 @@ pub const SideEffects = enum(u1) { }, .s_if => |_if_| { - if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) { + if (shouldKeepStmtInDeadControlFlow(p, _if_.yes, allocator)) { return true; } const no = _if_.no orelse return false; - return shouldKeepStmtInDeadControlFlow(no, allocator); + return shouldKeepStmtInDeadControlFlow(p, no, allocator); }, .s_while => { - return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator); + return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_while.body, allocator); }, .s_do_while => { - return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator); + return shouldKeepStmtInDeadControlFlow(p, stmt.data.s_do_while.body, allocator); }, .s_for => |__for__| { if (__for__.init) |init_| { - if (shouldKeepStmtInDeadControlFlow(init_, allocator)) { + if (shouldKeepStmtInDeadControlFlow(p, init_, allocator)) { return true; } } - return shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + return shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator); }, .s_for_in => |__for__| { - return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator); }, .s_for_of => |__for__| { - return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator); + return shouldKeepStmtInDeadControlFlow(p, __for__.init, allocator) or shouldKeepStmtInDeadControlFlow(p, __for__.body, allocator); }, .s_label => |label| { - return shouldKeepStmtInDeadControlFlow(label.stmt, allocator); + return shouldKeepStmtInDeadControlFlow(p, label.stmt, allocator); }, else => return true, } @@ -1928,11 +2094,15 @@ pub const SideEffects = enum(u1) { pub const toTypeOf = Expr.Data.typeof; - pub fn toNullOrUndefined(exp: Expr.Data) Result { + pub fn toNullOrUndefined(p: anytype, exp: Expr.Data) Result { + if (!p.options.features.dead_code_elimination) { + // value should not be read if ok is false, all existing calls to this function already adhere to this + return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects }; + } 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 }; + return Result{ .value = false, .side_effects = .no_side_effects, .ok = true }; }, .e_object, .e_array, .e_class => { @@ -2016,7 +2186,7 @@ pub const SideEffects = enum(u1) { }, .bin_comma => { - const res = toNullOrUndefined(e.right.data); + const res = toNullOrUndefined(p, e.right.data); if (res.ok) { return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects }; } @@ -2030,7 +2200,11 @@ pub const SideEffects = enum(u1) { return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects }; } - pub fn toBoolean(exp: Expr.Data) Result { + pub fn toBoolean(p: anytype, exp: Expr.Data) Result { + if (!p.options.features.dead_code_elimination) { + // value should not be read if ok is false, all existing calls to this function already adhere to this + return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects }; + } switch (exp) { .e_null, .e_undefined => { return Result{ .ok = true, .value = false, .side_effects = .no_side_effects }; @@ -2064,7 +2238,7 @@ pub const SideEffects = enum(u1) { return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects }; }, .un_not => { - var result = toBoolean(e_.value.data); + var result = toBoolean(p, e_.value.data); if (result.ok) { result.value = !result.value; return result; @@ -2077,21 +2251,21 @@ pub const SideEffects = enum(u1) { switch (e_.op) { .bin_logical_or => { // "anything || truthy" is truthy - const result = toBoolean(e_.right.data); + const result = toBoolean(p, 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); + const result = toBoolean(p, 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); + var result = toBoolean(p, e_.right.data); if (result.ok) { result.side_effects = .could_have_side_effects; return result; @@ -2437,6 +2611,7 @@ const FnOrArrowDataParse = struct { is_typescript_declare: bool = false, has_argument_decorators: bool = false, + has_decorators: bool = false, is_return_disallowed: bool = false, is_this_disallowed: bool = false, @@ -2573,6 +2748,7 @@ const PropertyOpts = struct { is_ts_abstract: bool = false, ts_decorators: []Expr = &[_]Expr{}, has_argument_decorators: bool = false, + has_class_decorators: bool = false, }; pub const ScanPassResult = struct { @@ -4304,7 +4480,7 @@ fn NewParser_( emitted_namespace_vars: RefMap = RefMap{}, is_exported_inside_namespace: RefRefMap = .{}, - known_enum_values: Map(Ref, StringHashMapUnamanged(f64)) = .{}, + known_enum_values: Map(Ref, StringHashMapUnmanaged(f64)) = .{}, local_type_names: StringBoolMap = StringBoolMap{}, // This is the reference to the generated function argument for the namespace, @@ -4787,20 +4963,20 @@ fn NewParser_( } }, .s_if => |if_statement| { - const result = SideEffects.toBoolean(if_statement.test_.data); + const result = SideEffects.toBoolean(p, 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); + const result = SideEffects.toBoolean(p, 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); + const result = SideEffects.toBoolean(p, expr.data); if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) { break :can_remove_part false; } @@ -6729,6 +6905,7 @@ fn NewParser_( 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 rest_arg: bool = false; var arg_has_decorators: bool = false; var args = List(G.Arg){}; while (p.lexer.token != T.t_close_paren) { @@ -6737,7 +6914,7 @@ fn NewParser_( try p.lexer.next(); if (p.lexer.token == T.t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(js_ast.Op.Level.lowest); + try p.skipTypeScriptType(.lowest); } if (p.lexer.token != T.t_comma) { break; @@ -6758,6 +6935,7 @@ fn NewParser_( if (!func.flags.contains(.has_rest_arg) and p.lexer.token == T.t_dot_dot_dot) { // p.markSyntaxFeature try p.lexer.next(); + rest_arg = true; func.flags.insert(.has_rest_arg); } @@ -6765,6 +6943,7 @@ fn NewParser_( var is_identifier = p.lexer.token == T.t_identifier; var text = p.lexer.identifier; var arg = try p.parseBinding(); + var ts_metadata = TypeScript.Metadata.default; if (comptime is_typescript_enabled) { if (is_identifier and opts.is_constructor) { @@ -6804,7 +6983,19 @@ fn NewParser_( // "function foo(a: any) {}" if (p.lexer.token == .t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(.lowest); + if (!rest_arg) { + if (p.options.features.emit_decorator_metadata and + opts.allow_ts_decorators and + (opts.has_argument_decorators or opts.has_decorators or arg_has_decorators)) + { + ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + try p.skipTypeScriptType(.lowest); + } + } else { + // rest parameter is always object, leave metadata as m_none + try p.skipTypeScriptType(.lowest); + } } } @@ -6825,6 +7016,7 @@ fn NewParser_( // We need to track this because it affects code generation .is_typescript_ctor_field = is_typescript_ctor_field, + .ts_metadata = ts_metadata, }) catch unreachable; if (p.lexer.token != .t_comma) { @@ -6844,6 +7036,7 @@ fn NewParser_( } try p.lexer.next(); + rest_arg = false; } if (args.items.len > 0) { func.args = args.items; @@ -6864,9 +7057,22 @@ fn NewParser_( p.fn_or_arrow_data_parse.has_argument_decorators = arg_has_decorators; // "function foo(): any {}" - if (is_typescript_enabled and p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypescriptReturnType(); + if (is_typescript_enabled) { + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + + if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) { + func.return_ts_metadata = try p.skipTypescriptReturnTypeWithMetadata(); + } else { + try p.skipTypescriptReturnType(); + } + } else if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) { + if (func.flags.contains(.is_async)) { + func.return_ts_metadata = .m_promise; + } else { + func.return_ts_metadata = .m_undefined; + } + } } // "function foo(): any;" @@ -6881,10 +7087,15 @@ fn NewParser_( return func; } - // pub fn parseBinding(p: *P) - pub inline fn skipTypescriptReturnType(p: *P) anyerror!void { - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_return_type = true }); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), false, &result); + } + + pub inline fn skipTypescriptReturnTypeWithMetadata(p: *P) anyerror!TypeScript.Metadata { + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), true, &result); + return result; } pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex { @@ -6912,7 +7123,15 @@ fn NewParser_( inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void { p.markTypeScriptOnly(); - try p.skipTypeScriptTypeWithOpts(level, .{}); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, false, &result); + } + + inline fn skipTypeScriptTypeWithMetadata(p: *P, level: js_ast.Op.Level) anyerror!TypeScript.Metadata { + p.markTypeScriptOnly(); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, true, &result); + return result; } fn skipTypeScriptBinding(p: *P) anyerror!void { @@ -7057,41 +7276,77 @@ fn NewParser_( // let x = (y: any): (y) => {return 0}; // let x = (y: any): asserts y is (y) => {}; // - fn skipTypeScriptParenOrFnType(p: *P) anyerror!void { + fn skipTypeScriptParenOrFnType(p: *P, comptime get_metadata: bool, result: *TypeScript.Metadata) anyerror!void { p.markTypeScriptOnly(); if (p.trySkipTypeScriptArrowArgsWithBacktracking()) { try p.skipTypescriptReturnType(); + if (comptime get_metadata) + result.* = .m_function; } else { try p.lexer.expect(.t_open_paren); - try p.skipTypeScriptType(.lowest); + if (comptime get_metadata) { + result.* = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + 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 { + fn skipTypeScriptTypeWithOpts( + p: *P, + level: js_ast.Op.Level, + opts: TypeScript.SkipTypeOptions.Bitset, + comptime get_metadata: bool, + result: *TypeScript.Metadata, + ) 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_numeric_literal => { try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_number; + } + }, + .t_big_integer_literal => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_bigint; + } + }, + .t_string_literal, .t_no_substitution_template_literal => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_string; + } + }, + .t_true, .t_false => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_boolean; + } + }, + .t_null => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_null; + } + }, + .t_void => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_void; + } }, - .t_const => { const r = p.lexer.range(); try p.lexer.next(); // ["const: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { try p.log.addRangeError(p.source, r, "Unexpected \"const\""); } }, @@ -7105,6 +7360,10 @@ fn NewParser_( try p.skipTypeScriptType(.lowest); return; } + + if (comptime get_metadata) { + result.* = .m_object; + } }, .t_minus => { // "-123" @@ -7113,8 +7372,14 @@ fn NewParser_( if (p.lexer.token == .t_big_integer_literal) { try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_bigint; + } } else { try p.lexer.expect(.t_numeric_literal); + if (comptime get_metadata) { + result.* = .m_number; + } } }, .t_ampersand, .t_bar => { @@ -7127,7 +7392,7 @@ fn NewParser_( try p.lexer.next(); // "[import: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } @@ -7155,21 +7420,21 @@ fn NewParser_( try p.lexer.next(); // "[new: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } _ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true }); - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_less_than => { // "<T>() => Foo<T>" _ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true }); - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_open_paren => { // "(number | string)" - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_identifier => { const kind = TypeScript.Identifier.IMap.get(p.lexer.identifier) orelse .normal; @@ -7177,7 +7442,7 @@ fn NewParser_( var check_type_parameters = true; switch (kind) { - .prefix => { + .prefix_keyof => { try p.lexer.next(); // Valid: @@ -7188,10 +7453,28 @@ fn NewParser_( // Invalid: // "A extends B ? keyof : string" // - if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) { + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { try p.skipTypeScriptType(.prefix); } + if (comptime get_metadata) { + result.* = .m_object; + } + + break; + }, + .prefix_readonly => { + try p.lexer.next(); + + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { + try p.skipTypeScriptType(.prefix); + } + + // assume array or tuple literal + if (comptime get_metadata) { + result.* = .m_array; + } + break; }, .infer => { @@ -7201,7 +7484,7 @@ fn NewParser_( // "type Foo = Bar extends [infer T extends string] ? T : null" // "type Foo = Bar extends [infer T extends string ? infer T : never] ? T : null" // "type Foo = { [infer in Bar]: number }" - if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) { + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { try p.lexer.expect(.t_identifier); if (p.lexer.token == .t_extends) { _ = p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(opts); @@ -7232,15 +7515,90 @@ fn NewParser_( // "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)) { + if (opts.contains(.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 => { + .primitive_any => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_any; + } + }, + .primitive_never => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_never; + } + }, + .primitive_unknown => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_unknown; + } + }, + .primitive_undefined => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_undefined; + } + }, + .primitive_object => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_object; + } + }, + .primitive_number => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_number; + } + }, + .primitive_string => { try p.lexer.next(); check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_string; + } + }, + .primitive_boolean => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_boolean; + } + }, + .primitive_bigint => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_bigint; + } + }, + .primitive_symbol => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_symbol; + } }, else => { + if (comptime get_metadata) { + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + if (p.known_enum_values.contains(find_result.ref)) { + result.* = .m_number; + } else { + result.* = .{ .m_identifier = find_result.ref }; + } + } + try p.lexer.next(); }, } @@ -7261,10 +7619,15 @@ fn NewParser_( try p.lexer.next(); // "[typeof: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } + // always `Object` + if (comptime get_metadata) { + result.* = .m_object; + } + if (p.lexer.token == .t_import) { // "typeof import('fs')" continue; @@ -7296,13 +7659,16 @@ fn NewParser_( // "[first: number, second: string]" try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_array; + } + while (p.lexer.token != .t_close_bracket) { if (p.lexer.token == .t_dot_dot_dot) { try p.lexer.next(); } - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions{ - .allow_tuple_labels = true, - }); + var dummy_result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.allow_tuple_labels), false, &dummy_result); if (p.lexer.token == .t_question) { try p.lexer.next(); } @@ -7319,10 +7685,12 @@ fn NewParser_( }, .t_open_brace => { try p.skipTypeScriptObjectType(); + if (comptime get_metadata) { + result.* = .m_object; + } }, .t_template_head => { // "`${'a' | 'b'}-${'c' | 'd'}`" - while (true) { try p.lexer.next(); try p.skipTypeScriptType(.lowest); @@ -7333,11 +7701,14 @@ fn NewParser_( break; } } + if (comptime get_metadata) { + result.* = .m_string; + } }, else => { // "[function: number]" - if (opts.allow_tuple_labels and p.lexer.isIdentifierOrKeyword()) { + if (opts.contains(.allow_tuple_labels) and p.lexer.isIdentifierOrKeyword()) { if (p.lexer.token != .t_function) { try p.lexer.unexpected(); } @@ -7362,8 +7733,22 @@ fn NewParser_( if (level.gte(.bitwise_or)) { return; } + try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_or); + + if (comptime get_metadata) { + var left = result.*; + if (left.finishUnion(p)) |final| { + // finish skipping the rest of the type without collecting type metadata. + result.* = final; + try p.skipTypeScriptType(.bitwise_or); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_or, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeUnion(left); + } + } else { + try p.skipTypeScriptType(.bitwise_or); + } }, .t_ampersand => { if (level.gte(.bitwise_and)) { @@ -7371,7 +7756,20 @@ fn NewParser_( } try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_and); + + if (comptime get_metadata) { + var left = result.*; + if (left.finishIntersection(p)) |final| { + // finish skipping the rest of the type without collecting type metadata. + result.* = final; + try p.skipTypeScriptType(.bitwise_and); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeIntersection(left); + } + } else { + try p.skipTypeScriptType(.bitwise_and); + } }, .t_exclamation => { // A postfix "!" is allowed in JSDoc types in TypeScript, which are only @@ -7390,6 +7788,22 @@ fn NewParser_( if (!p.lexer.isIdentifierOrKeyword()) { try p.lexer.expect(.t_identifier); } + + if (comptime get_metadata) { + if (result.* == .m_identifier) { + var dot = List(Ref).initCapacity(p.allocator, 2) catch unreachable; + dot.appendAssumeCapacity(result.m_identifier); + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + dot.appendAssumeCapacity(find_result.ref); + result.* = .{ .m_dot = dot }; + } else if (result.* == .m_dot) { + if (p.lexer.isIdentifierOrKeyword()) { + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + result.m_dot.append(p.allocator, find_result.ref) catch unreachable; + } + } + } + try p.lexer.next(); // "{ <A extends B>(): c.d \n <E extends F>(): g.h }" must not become a single type @@ -7403,26 +7817,56 @@ fn NewParser_( return; } try p.lexer.next(); + var skipped = false; if (p.lexer.token != .t_close_bracket) { + skipped = true; try p.skipTypeScriptType(.lowest); } try p.lexer.expect(.t_close_bracket); + + if (comptime get_metadata) { + if (result.* == .m_none) { + result.* = .m_array; + } else { + // if something was skipped, it is object type + if (skipped) { + result.* = .m_object; + } else { + result.* = .m_array; + } + } + } }, .t_extends => { // "{ x: number \n extends: boolean }" must not become a single type - if (p.lexer.has_newline_before or opts.disallow_conditional_types) { + if (p.lexer.has_newline_before or opts.contains(.disallow_conditional_types)) { return; } try p.lexer.next(); // The type following "extends" is not permitted to be another conditional type - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .disallow_conditional_types = true }); + var extends_type = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), get_metadata, &extends_type); - try p.lexer.expect(.t_question); - try p.skipTypeScriptType(.lowest); - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); + if (comptime get_metadata) { + // intersection + try p.lexer.expect(.t_question); + var left = try p.skipTypeScriptTypeWithMetadata(.lowest); + try p.lexer.expect(.t_colon); + if (left.finishIntersection(p)) |final| { + result.* = final; + try p.skipTypeScriptType(.lowest); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeIntersection(left); + } + } else { + try p.lexer.expect(.t_question); + try p.skipTypeScriptType(.lowest); + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + } }, else => { return; @@ -7452,7 +7896,8 @@ fn NewParser_( if (p.lexer.token == .t_open_bracket) { // Index signature or computed property try p.lexer.next(); - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_index_signature = true }); + var metadata = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_index_signature), false, &metadata); // "{ [key: string]: number }" // "{ readonly [K in keyof T]: T[K] }" @@ -11122,13 +11567,12 @@ fn NewParser_( return result; } - pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) anyerror!bool { + pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) anyerror!bool { try p.lexer.expect(.t_extends); - try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions{ - .disallow_conditional_types = true, - }); + var metadata = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), false, &metadata); - if (!flags.disallow_conditional_types and p.lexer.token == .t_question) { + if (!flags.contains(.disallow_conditional_types) and p.lexer.token == .t_question) { return error.Backtrack; } @@ -11181,7 +11625,7 @@ fn NewParser_( return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowArgsWithBacktracking, bool); } - pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) bool { + pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) bool { return Backtracking.lexerBacktrackerWithArgs(p, Backtracking.skipTypeScriptConstraintOfInferTypeWithBacktracking, .{ p, flags }, bool); } @@ -11621,6 +12065,7 @@ fn NewParser_( (p.lexer.token != .t_open_paren or has_definite_assignment_assertion_operator)) { var initializer: ?Expr = null; + var ts_metadata = TypeScript.Metadata.default; // Forbid the names "constructor" and "prototype" in some cases if (!is_computed) { @@ -11639,7 +12084,11 @@ fn NewParser_( // Skip over types if (p.lexer.token == .t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(.lowest); + if (p.options.features.emit_decorator_metadata and opts.is_class and opts.ts_decorators.len > 0) { + ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + try p.skipTypeScriptType(.lowest); + } } } @@ -11693,6 +12142,7 @@ fn NewParser_( }), .key = key, .initializer = initializer, + .ts_metadata = ts_metadata, }; } @@ -11739,6 +12189,7 @@ fn NewParser_( .allow_super_property = true, .allow_ts_decorators = opts.allow_ts_decorators, .is_constructor = is_constructor, + .has_decorators = opts.ts_decorators.len > 0 or (opts.has_class_decorators and 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, @@ -11816,6 +12267,7 @@ fn NewParser_( }), .key = key, .value = value, + .ts_metadata = .m_function, }; } @@ -11895,6 +12347,7 @@ fn NewParser_( const first_decorator_loc = p.lexer.loc(); if (opts.allow_ts_decorators) { opts.ts_decorators = try p.parseTypeScriptDecorators(); + opts.has_class_decorators = class_opts.ts_decorators.len > 0; has_decorators = has_decorators or opts.ts_decorators.len > 0; } else { opts.ts_decorators = &[_]Expr{}; @@ -12131,7 +12584,7 @@ fn NewParser_( // Remove unnecessary optional chains if (p.options.features.minify_syntax) { - const result = SideEffects.toNullOrUndefined(left.data); + const result = SideEffects.toNullOrUndefined(p, left.data); if (result.ok and !result.value) { optional_start = null; } @@ -13568,7 +14021,7 @@ fn NewParser_( .name = part, .name_loc = loc, - .can_be_removed_if_unused = true, + .can_be_removed_if_unused = p.options.features.dead_code_elimination, }, loc, ); @@ -14031,6 +14484,7 @@ fn NewParser_( } fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool { + if (!p.options.features.dead_code_elimination) return false; switch (binding.data) { .b_array => |bi| { for (bi.items) |*item| { @@ -14069,6 +14523,7 @@ fn NewParser_( } fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool { + if (!p.options.features.dead_code_elimination) return false; for (stmts) |stmt| { switch (stmt.data) { // These never have side effects @@ -14902,7 +15357,7 @@ fn NewParser_( // 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); + const side_effects = SideEffects.toBoolean(p, e_.left.data); if (side_effects.ok and side_effects.value) { // "true || dead" const old = p.is_control_flow_dead; @@ -14914,7 +15369,7 @@ fn NewParser_( } }, .bin_logical_and => { - const side_effects = SideEffects.toBoolean(e_.left.data); + const side_effects = SideEffects.toBoolean(p, e_.left.data); if (side_effects.ok and !side_effects.value) { // "false && dead" const old = p.is_control_flow_dead; @@ -14926,7 +15381,7 @@ fn NewParser_( } }, .bin_nullish_coalescing => { - const side_effects = SideEffects.toNullOrUndefined(e_.left.data); + const side_effects = SideEffects.toNullOrUndefined(p, e_.left.data); if (side_effects.ok and !side_effects.value) { // "notNullOrUndefined ?? dead" const old = p.is_control_flow_dead; @@ -15006,7 +15461,7 @@ fn NewParser_( } }, .bin_nullish_coalescing => { - const nullorUndefined = SideEffects.toNullOrUndefined(e_.left.data); + const nullorUndefined = SideEffects.toNullOrUndefined(p, e_.left.data); if (nullorUndefined.ok) { if (!nullorUndefined.value) { return e_.left; @@ -15023,7 +15478,7 @@ fn NewParser_( } }, .bin_logical_or => { - const side_effects = SideEffects.toBoolean(e_.left.data); + const side_effects = SideEffects.toBoolean(p, 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) { @@ -15038,7 +15493,7 @@ fn NewParser_( } }, .bin_logical_and => { - const side_effects = SideEffects.toBoolean(e_.left.data); + const side_effects = SideEffects.toBoolean(p, e_.left.data); if (side_effects.ok) { if (!side_effects.value) { return e_.left; @@ -15404,7 +15859,7 @@ fn NewParser_( if (p.options.features.minify_syntax) e_.value = SideEffects.simplifyBoolean(p, e_.value); - const side_effects = SideEffects.toBoolean(e_.value.data); + const side_effects = SideEffects.toBoolean(p, e_.value.data); if (side_effects.ok) { return p.newExpr(E.Boolean{ .value = !side_effects.value }, expr.loc); } @@ -15556,7 +16011,7 @@ fn NewParser_( e_.test_ = SideEffects.simplifyBoolean(p, e_.test_); - const side_effects = SideEffects.toBoolean(e_.test_.data); + const side_effects = SideEffects.toBoolean(p, e_.test_.data); if (!side_effects.ok) { e_.yes = p.visitExpr(e_.yes); @@ -16129,6 +16584,7 @@ fn NewParser_( } pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool { + if (!p.options.features.dead_code_elimination) return false; if (class.extends) |*extends| { if (!p.exprCanBeRemovedIfUnused(extends)) { return false; @@ -16167,6 +16623,7 @@ fn NewParser_( // 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 { + if (!p.options.features.dead_code_elimination) return false; switch (expr.data) { .e_null, .e_undefined, @@ -17301,7 +17758,7 @@ fn NewParser_( 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; + p.is_control_flow_dead = p.options.features.dead_code_elimination and (entry.* != .replace); mark_for_replace = true; } } @@ -17555,7 +18012,8 @@ fn NewParser_( } }, .s_expr => |data| { - const should_trim_primitive = !p.options.features.minify_syntax and !data.value.isPrimitiveLiteral(); + const should_trim_primitive = p.options.features.dead_code_elimination and + (p.options.features.minify_syntax and data.value.isPrimitiveLiteral()); p.stmt_expr_value = data.value.data; defer p.stmt_expr_value = .{ .e_missing = .{} }; @@ -17726,7 +18184,7 @@ fn NewParser_( data.body = p.visitLoopBody(data.body); data.test_ = SideEffects.simplifyBoolean(p, data.test_); - const result = SideEffects.toBoolean(data.test_.data); + const result = SideEffects.toBoolean(p, data.test_.data); if (result.ok and result.side_effects == .no_side_effects) { data.test_ = p.newExpr(E.Boolean{ .value = result.value }, data.test_.loc); } @@ -17744,7 +18202,7 @@ fn NewParser_( data.test_ = SideEffects.simplifyBoolean(p, data.test_); } - const effects = SideEffects.toBoolean(data.test_.data); + const effects = SideEffects.toBoolean(p, data.test_.data); if (effects.ok and !effects.value) { const old = p.is_control_flow_dead; p.is_control_flow_dead = true; @@ -17776,7 +18234,7 @@ fn NewParser_( if (p.options.features.minify_syntax) { if (effects.ok) { if (effects.value) { - if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(data.no.?, p.allocator)) { + if (data.no == null or !SideEffects.shouldKeepStmtInDeadControlFlow(p, 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_| { @@ -17790,7 +18248,7 @@ fn NewParser_( } } else { // The test is falsy - if (!SideEffects.shouldKeepStmtInDeadControlFlow(data.yes, p.allocator)) { + if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, 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_| { @@ -17845,7 +18303,7 @@ fn NewParser_( if (data.test_) |test_| { data.test_ = SideEffects.simplifyBoolean(p, p.visitExpr(test_)); - const result = SideEffects.toBoolean(data.test_.?.data); + const result = SideEffects.toBoolean(p, data.test_.?.data); if (result.ok and result.value and result.side_effects == .no_side_effects) { data.test_ = null; } @@ -17986,7 +18444,8 @@ fn NewParser_( .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 mark_as_dead = p.options.features.dead_code_elimination and 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) { @@ -18036,7 +18495,8 @@ fn NewParser_( 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 mark_as_dead = p.options.features.dead_code_elimination and 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) { @@ -18123,7 +18583,7 @@ fn NewParser_( // 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 = StringHashMapUnamanged(f64){}; + var values_so_far = 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; @@ -18288,7 +18748,7 @@ fn NewParser_( if (decl.binding.data == .b_identifier) { if (p.options.features.replace_exports.getPtr(p.loadNameFromRef(decl.binding.data.b_identifier.ref))) |replacer| { replacement = replacer; - if (replacer.* != .replace) { + if (p.options.features.dead_code_elimination and (replacer.* != .replace)) { p.is_control_flow_dead = true; } } @@ -18765,7 +19225,7 @@ fn NewParser_( const args = p.allocator.alloc(Expr, 2) catch unreachable; args[0] = p.newExpr(E.Number{ .value = @as(f64, @floatFromInt(i)) }, arg_decorator.loc); args[1] = arg_decorator; - decorators.append(p.callRuntime(arg_decorator.loc, "__decorateParam", args)) catch unreachable; + decorators.append(p.callRuntime(arg_decorator.loc, "__legacyDecorateParamTS", args)) catch unreachable; if (is_constructor) { class.ts_decorators.update(decorators); } else { @@ -18792,7 +19252,12 @@ fn NewParser_( else => bun.unreachablePanic("Unexpected AST node type {any}", .{prop.key.?}), }; - const descriptor_kind: f64 = if (!prop.flags.contains(.is_method)) 2 else 1; + // TODO: when we have the `accessor` modifier, add `and !prop.flags.contains(.has_accessor_modifier)` to + // the if statement. + const descriptor_kind: Expr = if (!prop.flags.contains(.is_method)) + p.newExpr(E.Undefined{}, loc) + else + p.newExpr(E.Null{}, loc); var target: Expr = undefined; if (prop.flags.contains(.is_static)) { @@ -18802,13 +19267,60 @@ fn NewParser_( target = p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), .name = "prototype", .name_loc = loc }, loc); } + var array = prop.ts_decorators.listManaged(p.allocator); + + if (p.options.features.emit_decorator_metadata) { + { + // design:type + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty); + args[1] = p.serializeMetadata(prop.ts_metadata) catch unreachable; + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + { + // design:paramtypes and design:returntype if method + if (prop.flags.contains(.is_method)) { + if (prop.value) |prop_value| { + { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty); + + const method_args = prop_value.data.e_function.func.args; + + if (method_args.len > 0) { + var args_array = p.allocator.alloc(Expr, method_args.len) catch unreachable; + + for (method_args, 0..) |method_arg, i| { + args_array[i] = p.serializeMetadata(method_arg.ts_metadata) catch unreachable; + } + + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(args_array) }, logger.Loc.Empty); + } else { + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty); + } + + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:returntype" }, logger.Loc.Empty); + + args[1] = p.serializeMetadata(prop_value.data.e_function.func.return_ts_metadata) catch unreachable; + + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + } + } + } + } + const args = p.allocator.alloc(Expr, 4) catch unreachable; - args[0] = p.newExpr(E.Array{ .items = prop.ts_decorators }, loc); + args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, loc); args[1] = target; args[2] = descriptor_key; - args[3] = p.newExpr(E.Number{ .value = descriptor_kind }, loc); + args[3] = descriptor_kind; - const decorator = p.callRuntime(prop.key.?.loc, "__decorateClass", args); + const decorator = p.callRuntime(prop.key.?.loc, "__legacyDecorateClassTS", args); const decorator_stmt = p.s(S.SExpr{ .value = decorator }, decorator.loc); if (prop.flags.contains(.is_static)) { @@ -18916,13 +19428,38 @@ fn NewParser_( stmts.appendSliceAssumeCapacity(instance_decorators.items); stmts.appendSliceAssumeCapacity(static_decorators.items); if (class.ts_decorators.len > 0) { + var array = class.ts_decorators.listManaged(p.allocator); + + if (p.options.features.emit_decorator_metadata) { + if (constructor_function != null) { + // design:paramtypes + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty); + + const constructor_args = constructor_function.?.func.args; + if (constructor_args.len > 0) { + var param_array = p.allocator.alloc(Expr, constructor_args.len) catch unreachable; + + for (constructor_args, 0..) |constructor_arg, i| { + param_array[i] = p.serializeMetadata(constructor_arg.ts_metadata) catch unreachable; + } + + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(param_array) }, logger.Loc.Empty); + } else { + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty); + } + + array.append(p.callRuntime(stmt.loc, "__legacyMetadataTS", args)) catch unreachable; + } + } + const args = p.allocator.alloc(Expr, 2) catch unreachable; - args[0] = p.newExpr(E.Array{ .items = class.ts_decorators }, stmt.loc); + args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, stmt.loc); args[1] = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc); stmts.appendAssumeCapacity(Stmt.assign( p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), - p.callRuntime(stmt.loc, "__decorateClass", args), + p.callRuntime(stmt.loc, "__legacyDecorateClassTS", args), p.allocator, )); @@ -18939,6 +19476,234 @@ fn NewParser_( } } + fn serializeMetadata(p: *P, ts_metadata: TypeScript.Metadata) !Expr { + return switch (ts_metadata) { + .m_none, + .m_any, + .m_unknown, + .m_object, + => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_never, + .m_undefined, + .m_null, + .m_void, + => p.newExpr( + E.Undefined{}, + logger.Loc.Empty, + ), + + .m_string => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "String") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_number => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Number") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_function => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Function") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_boolean => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Boolean") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_array => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Array") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_bigint => p.maybeDefinedHelper( + p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "BigInt") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + ), + + .m_symbol => p.maybeDefinedHelper( + p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Symbol") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + ), + + .m_promise => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Promise") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_identifier => |ref| { + p.recordUsage(ref); + if (p.is_import_item.contains(ref)) { + return p.maybeDefinedHelper(p.newExpr( + E.ImportIdentifier{ + .ref = ref, + }, + logger.Loc.Empty, + )); + } + + return p.maybeDefinedHelper(p.newExpr( + E.Identifier{ .ref = ref }, + logger.Loc.Empty, + )); + }, + + .m_dot => |_refs| { + var refs = _refs; + std.debug.assert(refs.items.len >= 2); + defer refs.deinit(p.allocator); + + var dots = p.newExpr( + E.Dot{ + .name = p.loadNameFromRef(refs.items[refs.items.len - 1]), + .name_loc = logger.Loc.Empty, + .target = undefined, + }, + logger.Loc.Empty, + ); + + var current_expr = &dots.data.e_dot.target; + var i: usize = refs.items.len - 2; + while (i > 0) { + current_expr.* = p.newExpr(E.Dot{ + .name = p.loadNameFromRef(refs.items[i]), + .name_loc = logger.Loc.Empty, + .target = undefined, + }, logger.Loc.Empty); + current_expr = ¤t_expr.data.e_dot.target; + i -= 1; + } + + if (p.is_import_item.contains(refs.items[0])) { + current_expr.* = p.newExpr( + E.ImportIdentifier{ + .ref = refs.items[0], + }, + logger.Loc.Empty, + ); + } else { + current_expr.* = p.newExpr( + E.Identifier{ + .ref = refs.items[0], + }, + logger.Loc.Empty, + ); + } + + const dot_identifier = current_expr.*; + var current_dot = dots; + + var maybe_defined_dots = p.newExpr( + E.Binary{ + .op = .bin_logical_or, + .right = try p.checkIfDefinedHelper(current_dot), + .left = undefined, + }, + logger.Loc.Empty, + ); + + if (i < refs.items.len - 2) { + current_dot = current_dot.data.e_dot.target; + } + current_expr = &maybe_defined_dots.data.e_binary.left; + + while (i < refs.items.len - 2) { + current_expr.* = p.newExpr( + E.Binary{ + .op = .bin_logical_or, + .right = try p.checkIfDefinedHelper(current_dot), + .left = undefined, + }, + logger.Loc.Empty, + ); + + current_expr = ¤t_expr.data.e_binary.left; + i += 1; + if (i < refs.items.len - 2) { + current_dot = current_dot.data.e_dot.target; + } + } + + current_expr.* = try p.checkIfDefinedHelper(dot_identifier); + + var root = p.newExpr( + E.If{ + .yes = p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .no = dots, + .test_ = maybe_defined_dots, + }, + logger.Loc.Empty, + ); + + return root; + }, + }; + } + + fn checkIfDefinedHelper(p: *P, expr: Expr) !Expr { + return p.newExpr( + E.Binary{ + .op = .bin_strict_eq, + .left = p.newExpr( + E.Unary{ + .op = .un_typeof, + .value = expr, + }, + logger.Loc.Empty, + ), + .right = p.newExpr( + E.String{ .data = "undefined" }, + logger.Loc.Empty, + ), + }, + logger.Loc.Empty, + ); + } + + fn maybeDefinedHelper(p: *P, identifier_expr: Expr) !Expr { + return p.newExpr( + E.If{ + .test_ = try p.checkIfDefinedHelper(identifier_expr), + .yes = p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .no = identifier_expr, + }, + logger.Loc.Empty, + ); + } + fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt { switch (stmt.data) { .s_expr => |st| { @@ -19713,7 +20478,7 @@ fn NewParser_( if (p.is_control_flow_dead) { var end: usize = 0; for (visited.items) |item| { - if (!SideEffects.shouldKeepStmtInDeadControlFlow(item, p.allocator)) { + if (!SideEffects.shouldKeepStmtInDeadControlFlow(p, item, p.allocator)) { continue; } @@ -19802,7 +20567,9 @@ 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)) { + if (is_control_flow_dead and p.options.features.dead_code_elimination and + !SideEffects.shouldKeepStmtInDeadControlFlow(p, stmt, p.allocator)) + { // Strip unnecessary statements if the control flow is dead here continue; } @@ -20474,42 +21241,50 @@ fn NewParser_( parts[parts.len - 1].stmts = new_stmts_list; }, - // This becomes + // This transforms the user's code into. // - // (function (module, exports, require) { + // (function (exports, require, module, __filename, __dirname) { + // ... + // }).call( + // this.module.exports, + // this.module.exports, + // this.require, + // this.module, + // this.__filename, + // this.__dirname, + // ); // - // })(module, exports, require); + // `this` is a `CommonJSFunctionArgumentsStructure` + // which is initialized in `evaluateCommonJSModuleOnce` .bun_js => { var args = allocator.alloc(Arg, 5) catch unreachable; args[0..5].* = .{ - Arg{ - .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), - }, - Arg{ - .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - }, - Arg{ - .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), - }, - Arg{ - .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), - }, - Arg{ - .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), - }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty) }, + Arg{ .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty) }, + }; + + const cjsArguments = Expr{ + .data = .{ .e_this = .{} }, + .loc = logger.Loc.Empty, }; + var total_stmts_count: usize = 0; for (parts) |part| { total_stmts_count += part.stmts.len; } var stmts_to_copy = allocator.alloc(Stmt, total_stmts_count) catch unreachable; - var remaining_stmts = stmts_to_copy; - for (parts) |part| { - for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| { - dest.* = src; + { + var remaining_stmts = stmts_to_copy; + for (parts) |part| { + for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| { + dest.* = src; + } + remaining_stmts = remaining_stmts[part.stmts.len..]; } - remaining_stmts = remaining_stmts[part.stmts.len..]; } const wrapper = p.newExpr( @@ -20517,147 +21292,57 @@ fn NewParser_( .func = G.Fn{ .name = null, .open_parens_loc = logger.Loc.Empty, - .args = args, + .args = args[0..5], .body = .{ .loc = logger.Loc.Empty, .stmts = stmts_to_copy }, .flags = Flags.Function.init(.{ .is_export = false }), }, }, logger.Loc.Empty, ); - const cjsGlobal = p.newSymbol(.unbound, "$_BunCommonJSModule_$") catch unreachable; - var all_call_args = allocator.alloc(Expr, 8) catch unreachable; + const this_module = p.newExpr( E.Dot{ .name = "module", - .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .target = cjsArguments, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty, ); - var bind_args = all_call_args[0..1]; - bind_args[0] = this_module; - var bind_resolve_args = all_call_args[1..2]; - var call_args = all_call_args[2..]; - - const module_id = p.newExpr(E.Dot{ - .name = "id", - .target = this_module, - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty); - - bind_resolve_args[0] = module_id; - const get_require = p.newExpr( + const module_exports = p.newExpr( E.Dot{ - .name = "require", + .name = "exports", .target = this_module, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty, ); - const create_binding = p.newExpr( - E.Call{ - .target = p.newExpr(E.Dot{ - .name = "bind", - .name_loc = logger.Loc.Empty, - .target = get_require, - }, logger.Loc.Empty), - .args = bun.BabyList(Expr).init(bind_args), - }, - logger.Loc.Empty, - ); - - const get_resolve = p.newExpr(E.Dot{ - .name = "resolve", - .name_loc = logger.Loc.Empty, - .target = get_require, - }, logger.Loc.Empty); - - const create_resolve_binding = p.newExpr( - E.Call{ - .target = p.newExpr(E.Dot{ - .name = "bind", - .name_loc = logger.Loc.Empty, - .target = get_resolve, - }, logger.Loc.Empty), - .args = bun.BabyList(Expr).init(bind_resolve_args), - }, - logger.Loc.Empty, - ); - - const require_path = p.newExpr( - E.Dot{ - .name = "path", - .target = get_require, - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ); - const assign_binding = p.newExpr( - E.Binary{ - .left = get_require, - .right = create_binding, - .op = .bin_assign, - }, - logger.Loc.Empty, - ); - - const assign_resolve_binding = p.newExpr( - E.Binary{ - .left = get_resolve, - .right = create_resolve_binding, - .op = .bin_assign, - }, - logger.Loc.Empty, - ); - - const assign_id = p.newExpr(E.Binary{ - .left = require_path, - .right = module_id, - .op = .bin_assign, - }, logger.Loc.Empty); - - var create_require = [4]Expr{ - assign_binding, - assign_id, - assign_resolve_binding, - get_require, - }; - - // - // (function(module, exports, require, __dirname, __filename) {}).call(this.exports, this.module, this.exports, this.module.require = this.module.require.bind(module), (this.module.require.id = this.module.id, this.module.require), __dirname, __filename) + var call_args = allocator.alloc(Expr, 6) catch unreachable; call_args[0..6].* = .{ + module_exports, // this.module.exports (this value inside fn) + module_exports, // this.module.exports (arg 1) p.newExpr( E.Dot{ - .name = "exports", - .target = this_module, + .name = "require", + .target = cjsArguments, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty, ), - this_module, + this_module, // this.module p.newExpr( E.Dot{ - .name = "exports", - .target = this_module, + .name = "__filename", + .target = cjsArguments, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty, ), - Expr.joinAllWithComma(&create_require, p.allocator), p.newExpr( E.Dot{ .name = "__dirname", - .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - p.newExpr( - E.Dot{ - .name = "__filename", - .target = p.newExpr(E.Identifier{ .ref = cjsGlobal }, logger.Loc.Empty), + .target = cjsArguments, .name_loc = logger.Loc.Empty, }, logger.Loc.Empty, @@ -20679,14 +21364,50 @@ fn NewParser_( logger.Loc.Empty, ); - var only_stmt = try p.allocator.alloc(Stmt, 1); - only_stmt[0] = p.s( + var top_level_stmts = p.allocator.alloc(Stmt, 1 + @as(usize, @intFromBool(p.has_import_meta))) catch unreachable; + parts[0].stmts = top_level_stmts; + + // var $Bun_import_meta = this.createImportMeta(this.filename); + if (p.has_import_meta) { + p.import_meta_ref = p.newSymbol(.other, "$Bun_import_meta") catch unreachable; + var decl = allocator.alloc(Decl, 1) catch unreachable; + decl[0] = Decl{ + .binding = Binding.alloc( + p.allocator, + B.Identifier{ + .ref = p.import_meta_ref, + }, + logger.Loc.Empty, + ), + .value = p.newExpr( + E.Call{ + .target = p.newExpr(E.Dot{ + .target = cjsArguments, + .name = "createImportMeta", + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + // reuse the `this.__filename` argument + .args = ExprNodeList.init(call_args[5..6]), + }, + logger.Loc.Empty, + ), + }; + + top_level_stmts[0] = p.s( + S.Local{ + .decls = G.Decl.List.init(decl), + .kind = .k_var, + }, + logger.Loc.Empty, + ); + top_level_stmts = top_level_stmts[1..]; + } + top_level_stmts[0] = p.s( S.SExpr{ .value = call, }, logger.Loc.Empty, ); - parts[0].stmts = only_stmt; parts.len = 1; }, @@ -21217,6 +21938,8 @@ fn NewParser_( // TODO: // .const_values = p.const_values, + + .import_meta_ref = p.import_meta_ref, }; } |