From f9d3ed55f4c1f05b85ef7c355cda50ce87d96b8a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 6 Aug 2025 22:24:19 -0700 Subject: [PATCH] try --- src/ast/parse.zig | 48 +- src/ast/parsePrefix.zig | 52 +- src/ast/parseProperty.zig | 9 +- src/ast/parseStmt.zig | 10 +- src/ast/parseSuffix.zig | 1616 +++++++++++++++++++---------------- src/ast/parseTypescript.zig | 4 +- 6 files changed, 957 insertions(+), 782 deletions(-) diff --git a/src/ast/parse.zig b/src/ast/parse.zig index 72bffde15f..c7c026f091 100644 --- a/src/ast/parse.zig +++ b/src/ast/parse.zig @@ -26,25 +26,27 @@ pub fn Parse( pub const parseTypeScriptImportEqualsStmt = @import("./parseTypescript.zig").ParseTypescript(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only).parseTypeScriptImportEqualsStmt; pub const parseTypescriptEnumStmt = @import("./parseTypescript.zig").ParseTypescript(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only).parseTypescriptEnumStmt; - pub inline fn parseExprOrBindings(p: *P, level: Level, errors: ?*DeferredErrors) anyerror!Expr { - return try p.parseExprCommon(level, errors, Expr.EFlags.none); + pub inline fn parseExprOrBindings(p: *P, level: Level, errors: ?*DeferredErrors, expr: *Expr) anyerror!void { + return p.parseExprCommon(level, errors, Expr.EFlags.none, expr); } pub inline fn parseExpr(p: *P, level: Level) anyerror!Expr { - return try p.parseExprCommon(level, null, Expr.EFlags.none); + var expr: Expr = undefined; + try p.parseExprCommon(level, null, Expr.EFlags.none, &expr); + return expr; } - pub inline fn parseExprWithFlags(p: *P, level: Level, flags: Expr.EFlags) anyerror!Expr { - return try p.parseExprCommon(level, null, flags); + pub inline fn parseExprWithFlags(p: *P, level: Level, flags: Expr.EFlags, expr: *Expr) anyerror!void { + return p.parseExprCommon(level, null, flags, expr); } - pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { + pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags, expr: *Expr) anyerror!void { if (!p.stack_check.isSafeToRecurse()) { try bun.throwStackOverflow(); } 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); + 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 @@ -52,7 +54,7 @@ pub fn Parse( // to the expression "a().b()". if (had_pure_comment_before and level.lt(.call)) { - expr = try p.parseSuffix(expr, @as(Level, @enumFromInt(@intFromEnum(Level.call) - 1)), errors, flags); + try p.parseSuffix(expr, @as(Level, @enumFromInt(@intFromEnum(Level.call) - 1)), errors, flags); switch (expr.data) { .e_call => |ex| { ex.can_be_unwrapped_if_unused = .if_unused; @@ -64,7 +66,7 @@ pub fn Parse( } } - return try p.parseSuffix(expr, level, errors, flags); + try p.parseSuffix(expr, level, errors, flags); } pub fn parseYieldExpr(p: *P, loc: logger.Loc) !ExprNodeIndex { @@ -343,10 +345,13 @@ pub fn Parse( // 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); + try items_list.ensureUnusedCapacity(1); + const item: *Expr = &items_list.unusedCapacitySlice()[0]; + try p.parseExprOrBindings(.comma, &errors, item); + items_list.items.len += 1; if (is_spread) { - item = p.newExpr(E.Spread{ .value = item }, loc); + item.* = p.newExpr(E.Spread{ .value = item.* }, loc); } // Skip over types @@ -359,11 +364,9 @@ pub fn Parse( // 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)); + item.* = Expr.assign(item.*, try p.parseExpr(.comma)); } - items_list.append(item) catch unreachable; - if (p.lexer.token != .t_comma) { break; } @@ -675,7 +678,7 @@ pub fn Parse( try p.lexer.next(); const raw2 = p.lexer.raw(); - const value = if (p.lexer.token == .t_identifier and strings.eqlComptime(raw2, "using")) value: { + var value = if (p.lexer.token == .t_identifier and strings.eqlComptime(raw2, "using")) value: { // const using_loc = p.saveExprCommentsHere(); const using_range = p.lexer.range(); try p.lexer.next(); @@ -711,13 +714,15 @@ pub fn Parse( if (p.lexer.token == .t_asterisk_asterisk) { try p.lexer.unexpected(); } - const expr = p.newExpr( - E.Await{ .value = try p.parseSuffix(value, .prefix, null, .none) }, + try p.parseSuffix(&value, .prefix, null, .none); + var expr = p.newExpr( + E.Await{ .value = value }, token_range.loc, ); + try p.parseSuffix(&expr, .lowest, null, .none); return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ - .expr = try p.parseSuffix(expr, .lowest, null, .none), + .expr = expr, }, }; } else { @@ -730,12 +735,13 @@ pub fn Parse( // Parse the remainder of this expression that starts with an identifier const ref = try p.storeNameInRef(raw); - const expr = p.newExpr(E.Identifier{ .ref = ref }, token_range.loc); - return ExprOrLetStmt{ + var result = ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ - .expr = try p.parseSuffix(expr, .lowest, null, .none), + .expr = p.newExpr(E.Identifier{ .ref = ref }, token_range.loc), }, }; + try p.parseSuffix(&result.stmt_or_expr.expr, .lowest, null, .none); + return result; } pub fn parseBinding(p: *P, comptime opts: ParseBindingOptions) anyerror!Binding { diff --git a/src/ast/parsePrefix.zig b/src/ast/parsePrefix.zig index cc705595cd..f9c282d3f7 100644 --- a/src/ast/parsePrefix.zig +++ b/src/ast/parsePrefix.zig @@ -381,8 +381,14 @@ pub fn ParsePrefix( return p.newExpr(E.NewTarget{ .range = range }, loc); } - const target = try p.parseExprWithFlags(.member, flags); - var args = ExprNodeList{}; + // This wil become the new expr + var new = p.newExpr(E.New{ + .target = undefined, + .args = undefined, + .close_parens_loc = undefined, + }, loc); + + try p.parseExprWithFlags(.member, flags, &new.data.e_new.target); if (comptime is_typescript_enabled) { // Skip over TypeScript type arguments here if there are any @@ -391,18 +397,15 @@ pub fn ParsePrefix( } } - 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; + new.data.e_new.args = call_args.list; + new.data.e_new.close_parens_loc = call_args.loc; + } else { + new.data.e_new.close_parens_loc = .Empty; } - return p.newExpr(E.New{ - .target = target, - .args = args, - .close_parens_loc = close_parens_loc, - }, loc); + return new; }, .t_open_bracket => { try p.lexer.next(); @@ -426,9 +429,11 @@ pub fn ParsePrefix( const dots_loc = p.lexer.loc(); try p.lexer.next(); - items.append( - p.newExpr(E.Spread{ .value = try p.parseExprOrBindings(.comma, &self_errors) }, dots_loc), - ) catch unreachable; + try items.ensureUnusedCapacity(1); + const spread_expr: *Expr = &items.unusedCapacitySlice()[0]; + spread_expr.* = p.newExpr(E.Spread{ .value = undefined }, dots_loc); + try p.parseExprOrBindings(.comma, &self_errors, &spread_expr.data.e_spread.value); + items.items.len += 1; // Commas are not allowed here when destructuring if (p.lexer.token == .t_comma) { @@ -436,9 +441,10 @@ pub fn ParsePrefix( } }, else => { - items.append( - try p.parseExprOrBindings(.comma, &self_errors), - ) catch unreachable; + try items.ensureUnusedCapacity(1); + const item: *Expr = &items.unusedCapacitySlice()[0]; + try p.parseExprOrBindings(.comma, &self_errors, item); + items.items.len += 1; }, } @@ -496,7 +502,19 @@ pub fn ParsePrefix( 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; + try properties.ensureUnusedCapacity(1); + const property: *G.Property = &properties.unusedCapacitySlice()[0]; + property.* = .{ + .kind = .spread, + .value = Expr.empty, + }; + + try p.parseExprOrBindings( + .comma, + &self_errors, + &(property.value.?), + ); + properties.items.len += 1; // Commas are not allowed here when destructuring if (p.lexer.token == .t_comma) { diff --git a/src/ast/parseProperty.zig b/src/ast/parseProperty.zig index 8d35ed5fb6..c19e78dd6a 100644 --- a/src/ast/parseProperty.zig +++ b/src/ast/parseProperty.zig @@ -508,16 +508,17 @@ pub fn ParseProperty( // Parse an object key/value pair try p.lexer.expect(.t_colon); - const value = try p.parseExprOrBindings(.comma, errors); - - return G.Property{ + var property: G.Property = .{ .kind = kind, .flags = Flags.Property.init(.{ .is_computed = is_computed, }), .key = key, - .value = value, + .value = Expr{ .data = .e_missing, .loc = .{} }, }; + + try p.parseExprOrBindings(.comma, errors, &property.value.?); + return property; } }; } diff --git a/src/ast/parseStmt.zig b/src/ast/parseStmt.zig index 9be6ac911c..b5d9e3b9c8 100644 --- a/src/ast/parseStmt.zig +++ b/src/ast/parseStmt.zig @@ -185,8 +185,8 @@ pub fn ParseStmt( const defaultName = try createDefaultName(p, loc); - const prefix_expr = try p.parseAsyncPrefixExpr(async_range, Level.comma); - const expr = try p.parseSuffix(prefix_expr, Level.comma, null, Expr.EFlags.none); + var expr = try p.parseAsyncPrefixExpr(async_range, Level.comma); + try p.parseSuffix(&expr, Level.comma, null, Expr.EFlags.none); try p.lexer.expectOrInsertSemicolon(); const value = js_ast.StmtOrExpr{ .expr = expr }; p.has_export_default = true; @@ -926,7 +926,8 @@ pub fn ParseStmt( // "import.meta" .t_open_paren, .t_dot => { p.esm_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); + var expr = try p.parseImportExpr(loc, .lowest); + try p.parseSuffix(&expr, .lowest, null, Expr.EFlags.none); try p.lexer.expectOrInsertSemicolon(); return p.s(S.SExpr{ .value = expr, @@ -1164,7 +1165,8 @@ pub fn ParseStmt( 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); + expr = try p.parseAsyncPrefixExpr(async_range, .lowest); + try p.parseSuffix(&expr, .lowest, null, Expr.EFlags.none); } else { const exprOrLet = try p.parseExprOrLetStmt(opts); switch (exprOrLet.stmt_or_expr) { diff --git a/src/ast/parseSuffix.zig b/src/ast/parseSuffix.zig index d8d6c484cb..078f9056be 100644 --- a/src/ast/parseSuffix.zig +++ b/src/ast/parseSuffix.zig @@ -7,239 +7,115 @@ pub fn ParseSuffix( const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only); const is_typescript_enabled = P.is_typescript_enabled; - pub fn parseSuffix(noalias p: *P, _left: Expr, level: Level, noalias 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; - } + const parse_suffix_fns = struct { + const Continuation = enum { next, done }; + + pub fn handleTypescriptAs(p: *P, level: Level) anyerror!Continuation { + if (is_typescript_enabled and level.lt(.compare) and !p.lexer.has_newline_before and (p.lexer.isContextualKeyword("as") or p.lexer.isContextualKeyword("satisfies"))) { + try p.lexer.next(); + try p.skipTypeScriptType(.lowest); - try p.lexer.next(); - left = p.newExpr(E.Binary{ - .op = .bin_comma, - .left = left, - .right = try p.parseExpr(.comma), - }, left.loc); + // 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 .done; + }, + else => {}, + } + + if (p.lexer.token.isAssign()) { + p.forbid_suffix_after_as_loc = p.lexer.loc(); + return .done; + } + return .next; + } + return .done; + } + + pub fn t_dot(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + try p.lexer.next(); + const target = left.*; + + 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.newExpr(E.Index{ + .target = target, + .index = p.newExpr( + E.PrivateIdentifier{ + .ref = ref, }, - else => { - return left; - }, - } + 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.newExpr( + E.Dot{ + .target = target, + .name = name, + .name_loc = name_loc, + .optional_chain = old_optional_chain, + }, + left.loc, + ); + } + optional_chain.* = old_optional_chain; + return .next; + } + pub fn t_question_dot(p: *P, level: Level, optional_chain: *?OptionalChain, left: *Expr) anyerror!Continuation { + try p.lexer.next(); + var optional_start: ?OptionalChain = OptionalChain.start; + + // Remove unnecessary optional chains + if (p.options.features.minify_syntax) { + const result = SideEffects.toNullOrUndefined(p, left.data); + if (result.ok and !result.value) { + optional_start = null; } } - 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". - const 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.newExpr(E.Index{ - .target = left, - .index = p.newExpr( - 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.newExpr(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 = js_ast.OptionalChain.start; - - // Remove unnecessary optional chains - if (p.options.features.minify_syntax) { - const result = SideEffects.toNullOrUndefined(p, left.data); - if (result.ok and !result.value) { - optional_start = null; - } - } - - 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.newExpr( - 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.newExpr(E.Call{ - .target = left, - .args = list_loc.list, - .close_paren_loc = list_loc.loc, - .optional_chain = optional_start, - }, left.loc); - }, - .t_less_than, .t_less_than_less_than => { - // "a?.()" - 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.newExpr(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.newExpr(E.Index{ - .target = left, - .index = p.newExpr( - 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.newExpr(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 orelse .continuation) == .start) { - optional_chain = .continuation; - } - }, - .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.rawTemplateContents(); - try p.lexer.next(); - left = p.newExpr(E.Template{ - .tag = left, - .head = .{ .raw = 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.rawTemplateContents(); - const partsGroup = try p.parseTemplateParts(true); - const tag = left; - left = p.newExpr(E.Template{ - .tag = tag, - .head = .{ .raw = 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; - } - + // "a?.[b]" try p.lexer.next(); - // Allow "in" inside the brackets + // allow "in" inside the brackets; const old_allow_in = p.allow_in; p.allow_in = true; @@ -248,541 +124,810 @@ pub fn ParseSuffix( p.allow_in = old_allow_in; try p.lexer.expect(.t_close_bracket); - - left = p.newExpr(E.Index{ - .target = left, - .index = index, - .optional_chain = old_optional_chain, - }, left.loc); - optional_chain = old_optional_chain; + left.* = p.newExpr( + E.Index{ .target = left.*, .index = index, .optional_chain = optional_start }, + left.loc, + ); }, + .t_open_paren => { + // "a?.()" if (level.gte(.call)) { - return left; + return .done; } const list_loc = try p.parseCallArgs(); - left = p.newExpr( - 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.newExpr(E.If{ - .test_ = left, - .yes = yes, - .no = no, + left.* = p.newExpr(E.Call{ + .target = left.*, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_start, }, 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(); + .t_less_than, .t_less_than_less_than => { + // "a?.()" + if (comptime !is_typescript_enabled) { + try p.lexer.expected(.t_identifier); return error.SyntaxError; } - 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.skipTypeScriptTypeArguments(false); + if (p.lexer.token != .t_open_paren) { + try p.lexer.expected(.t_open_paren); } - try p.lexer.next(); - left = p.newExpr(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; + if (level.gte(.call)) { + return .done; } - try p.lexer.next(); - left = p.newExpr(E.Unary{ .op = .un_post_inc, .value = left }, left.loc); - }, - .t_comma => { - if (level.gte(.comma)) { - return left; - } - - try p.lexer.next(); - left = p.newExpr(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.newExpr(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.newExpr(E.Binary{ .op = .bin_add_assign, .left = left, .right = try p.parseExpr(@as(Op.Level, @enumFromInt(@intFromEnum(Op.Level.assign) - 1))) }, left.loc); - }, - .t_minus => { - if (level.gte(.add)) { - return left; - } - - try p.lexer.next(); - left = p.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(E.Binary{ .op = .bin_ge, .left = left, .right = try p.parseExpr(.compare) }, left.loc); - }, - .t_less_than_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(.shift)) { - return left; - } - try p.lexer.next(); - left = p.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.addF(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.newExpr(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.newExpr(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.addF(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(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.newExpr(E.Binary{ .op = .bin_instanceof, .left = left, .right = try p.parseExpr(.compare) }, left.loc); + const list_loc = try p.parseCallArgs(); + left.* = p.newExpr(E.Call{ + .target = left.*, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_start, + }, left.loc); }, else => { - // Handle the TypeScript "as" operator - // Handle the TypeScript "satisfies" operator - if (is_typescript_enabled and level.lt(.compare) and !p.lexer.has_newline_before and (p.lexer.isContextualKeyword("as") or p.lexer.isContextualKeyword("satisfies"))) { + 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(); - 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 => {}, + const ref = p.storeNameInRef(name) catch unreachable; + left.* = p.newExpr(E.Index{ + .target = left.*, + .index = p.newExpr( + 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(); - if (p.lexer.token.isAssign()) { - p.forbid_suffix_after_as_loc = p.lexer.loc(); - return left; - } - continue; + left.* = p.newExpr(E.Dot{ + .target = left.*, + .name = name, + .name_loc = name_loc, + .optional_chain = optional_start, + }, left.loc); } - - return left; }, } + + // Only continue if we have started + if ((optional_start orelse .continuation) == .start) { + optional_chain.* = .continuation; + } + + return .next; } + pub fn t_no_substitution_template_literal(p: *P, _: Level, _: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + 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.rawTemplateContents(); + try p.lexer.next(); + + left.* = p.newExpr(E.Template{ + .tag = left.*, + .head = .{ .raw = head }, + }, left.loc); + return .next; + } + pub fn t_template_head(p: *P, _: Level, _: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + 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.rawTemplateContents(); + const partsGroup = try p.parseTemplateParts(true); + const tag = left.*; + left.* = p.newExpr(E.Template{ + .tag = tag, + .head = .{ .raw = head }, + .parts = partsGroup, + }, left.loc); + return .next; + } + pub fn t_open_bracket(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr, flags: Expr.EFlags) anyerror!Continuation { + // 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 .done; + } + + 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.newExpr(E.Index{ + .target = left.*, + .index = index, + .optional_chain = optional_chain.*, + }, left.loc); + optional_chain.* = old_optional_chain; + return .next; + } + pub fn t_open_paren(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + if (level.gte(.call)) { + return .done; + } + + const list_loc = try p.parseCallArgs(); + left.* = p.newExpr( + E.Call{ + .target = left.*, + .args = list_loc.list, + .close_paren_loc = list_loc.loc, + .optional_chain = optional_chain.*, + }, + left.loc, + ); + optional_chain.* = old_optional_chain; + return .next; + } + pub fn t_question(p: *P, level: Level, noalias errors: ?*DeferredErrors, left: *Expr) anyerror!Continuation { + if (level.gte(.conditional)) { + return .done; + } + 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 .done; + } + + const ternary = p.newExpr(E.If{ + .test_ = left.*, + .yes = undefined, + .no = undefined, + }, left.loc); + + // Allow "in" in between "?" and ":" + const old_allow_in = p.allow_in; + p.allow_in = true; + + // condition ? yes : no + // ^ + try p.parseExprWithFlags(.comma, .none, &ternary.data.e_if.yes); + + p.allow_in = old_allow_in; + + // condition ? yes : no + // ^ + try p.lexer.expect(.t_colon); + + // condition ? yes : no + // ^ + try p.parseExprWithFlags(.comma, .none, &ternary.data.e_if.no); + + // condition ? yes : no + // ^ + + left.* = ternary; + return .next; + } + pub fn t_exclamation(p: *P, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain) anyerror!Continuation { + // Skip over TypeScript non-null assertions + if (p.lexer.has_newline_before) { + return .done; + } + + if (!is_typescript_enabled) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + optional_chain.* = old_optional_chain; + + return .next; + } + pub fn t_minus_minus(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Unary{ .op = .un_post_dec, .value = left.* }, left.loc); + return .next; + } + pub fn t_plus_plus(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Unary{ .op = .un_post_inc, .value = left.* }, left.loc); + return .next; + } + pub fn t_comma(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.comma)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_comma, .left = left.*, .right = try p.parseExpr(.comma) }, left.loc); + return .next; + } + pub fn t_plus(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.add)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_add, .left = left.*, .right = try p.parseExpr(.add) }, left.loc); + return .next; + } + pub fn t_plus_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_add_assign, .left = left.*, .right = try p.parseExpr(@as(Op.Level, @enumFromInt(@intFromEnum(Op.Level.assign) - 1))) }, left.loc); + return .next; + } + pub fn t_minus(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.add)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_sub, .left = left.*, .right = try p.parseExpr(.add) }, left.loc); + return .next; + } + pub fn t_minus_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_sub_assign, .left = left.*, .right = try p.parseExpr(Op.Level.sub(Op.Level.assign, 1)) }, left.loc); + return .next; + } + pub fn t_asterisk(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.multiply)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_mul, .left = left.*, .right = try p.parseExpr(.multiply) }, left.loc); + return .next; + } + pub fn t_asterisk_asterisk(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.exponentiation)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_pow, .left = left.*, .right = try p.parseExpr(Op.Level.exponentiation.sub(1)) }, left.loc); + return .next; + } + pub fn t_asterisk_asterisk_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_pow_assign, .left = left.*, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_asterisk_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_mul_assign, .left = left.*, .right = try p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_percent(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.multiply)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_rem, .left = left.*, .right = try p.parseExpr(Op.Level.multiply) }, left.loc); + return .next; + } + pub fn t_percent_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_rem_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_slash(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.multiply)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_div, .left = left.*, .right = try p.parseExpr(Level.multiply) }, left.loc); + return .next; + } + pub fn t_slash_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_div_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.equals)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_loose_eq, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc); + return .next; + } + pub fn t_exclamation_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.equals)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_loose_ne, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc); + return .next; + } + pub fn t_equals_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.equals)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_strict_eq, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc); + return .next; + } + pub fn t_exclamation_equals_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.equals)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_strict_ne, .left = left.*, .right = try p.parseExpr(Level.equals) }, left.loc); + return .next; + } + pub fn t_less_than(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + // 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; + return .next; + } + + if (level.gte(.compare)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_lt, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + pub fn t_less_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.compare)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_le, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + pub fn t_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.compare)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_gt, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + pub fn t_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.compare)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_ge, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + pub fn t_less_than_less_than(p: *P, level: Level, optional_chain: *?OptionalChain, old_optional_chain: ?OptionalChain, left: *Expr) anyerror!Continuation { + // 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; + return .next; + } + + if (level.gte(.shift)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_shl, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc); + return .next; + } + pub fn t_less_than_less_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_shl_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_greater_than_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.shift)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_shr, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc); + return .next; + } + pub fn t_greater_than_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_shr_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_greater_than_greater_than_greater_than(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.shift)) { + return .done; + } + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_u_shr, .left = left.*, .right = try p.parseExpr(.shift) }, left.loc); + return .next; + } + pub fn t_greater_than_greater_than_greater_than_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_u_shr_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_question_question(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.nullish_coalescing)) { + return .done; + } + try p.lexer.next(); + const prev = left.*; + left.* = p.newExpr(E.Binary{ .op = .bin_nullish_coalescing, .left = prev, .right = try p.parseExpr(.nullish_coalescing) }, left.loc); + return .next; + } + pub fn t_question_question_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_nullish_coalescing_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_bar_bar(p: *P, level: Level, left: *Expr, flags: Expr.EFlags) anyerror!Continuation { + if (level.gte(.logical_or)) { + return .done; + } + + // 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.newExpr(E.Binary{ .op = Op.Code.bin_logical_or, .left = left.*, .right = right }, left.loc); + + if (level.lt(.nullish_coalescing)) { + try p.parseSuffix(left, Level.nullish_coalescing.addF(1), null, flags); + + if (p.lexer.token == .t_question_question) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + } + return .next; + } + pub fn t_bar_bar_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_logical_or_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_ampersand_ampersand(p: *P, level: Level, left: *Expr, flags: Expr.EFlags) anyerror!Continuation { + if (level.gte(.logical_and)) { + return .done; + } + + // Prevent "&&" inside "??" from the right + if (level.eql(.nullish_coalescing)) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + + try p.lexer.next(); + left.* = p.newExpr(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)) { + try p.parseSuffix(left, Level.nullish_coalescing.addF(1), null, flags); + + if (p.lexer.token == .t_question_question) { + try p.lexer.unexpected(); + return error.SyntaxError; + } + } + return .next; + } + pub fn t_ampersand_ampersand_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_logical_and_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_bar(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.bitwise_or)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_or, .left = left.*, .right = try p.parseExpr(.bitwise_or) }, left.loc); + return .next; + } + pub fn t_bar_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_or_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_ampersand(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.bitwise_and)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_and, .left = left.*, .right = try p.parseExpr(.bitwise_and) }, left.loc); + return .next; + } + pub fn t_ampersand_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_and_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_caret(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.bitwise_xor)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_xor, .left = left.*, .right = try p.parseExpr(.bitwise_xor) }, left.loc); + return .next; + } + pub fn t_caret_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ .op = .bin_bitwise_xor_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_equals(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.assign)) { + return .done; + } + + try p.lexer.next(); + + left.* = p.newExpr(E.Binary{ .op = .bin_assign, .left = left.*, .right = try p.parseExpr(Level.assign.sub(1)) }, left.loc); + return .next; + } + pub fn t_in(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.compare) or !p.allow_in) { + return .done; + } + + // 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.newExpr(E.Binary{ .op = .bin_in, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + pub fn t_instanceof(p: *P, level: Level, left: *Expr) anyerror!Continuation { + if (level.gte(.compare)) { + return .done; + } + + // 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.newExpr(E.Binary{ .op = .bin_instanceof, .left = left.*, .right = try p.parseExpr(.compare) }, left.loc); + return .next; + } + }; + + pub fn parseSuffix(p: *P, left_and_out: *Expr, level: Level, noalias errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!void { + var left_value = left_and_out.*; + // Zig has a bug where it creates a new address to stack locals each & usage. + const left = &left_value; + + var optional_chain_: ?OptionalChain = null; + const optional_chain = &optional_chain_; + 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)) { + break; + } + + try p.lexer.next(); + left.* = p.newExpr(E.Binary{ + .op = .bin_comma, + .left = left.*, + .right = try p.parseExpr(.comma), + }, left.loc); + }, + else => { + break; + }, + } + } + } + + 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) { + break; + } + } + + // Reset the optional chain flag by default. That way we won't accidentally + // treat "c.d" as OptionalChainContinue in "a?.b + c.d". + const old_optional_chain = optional_chain.*; + optional_chain.* = null; + + const continuation = switch (p.lexer.token) { + inline .t_ampersand, + .t_ampersand_ampersand_equals, + .t_ampersand_equals, + .t_asterisk, + .t_asterisk_asterisk, + .t_asterisk_asterisk_equals, + .t_asterisk_equals, + .t_bar, + .t_bar_bar_equals, + .t_bar_equals, + .t_caret, + .t_caret_equals, + .t_comma, + .t_equals, + .t_equals_equals, + .t_equals_equals_equals, + .t_exclamation_equals, + .t_exclamation_equals_equals, + .t_greater_than, + .t_greater_than_equals, + .t_greater_than_greater_than, + .t_greater_than_greater_than_equals, + .t_greater_than_greater_than_greater_than, + .t_greater_than_greater_than_greater_than_equals, + .t_in, + .t_instanceof, + .t_less_than_equals, + .t_less_than_less_than_equals, + .t_minus, + .t_minus_equals, + .t_minus_minus, + .t_percent, + .t_percent_equals, + .t_plus, + .t_plus_equals, + .t_plus_plus, + .t_question_question, + .t_question_question_equals, + .t_slash, + .t_slash_equals, + => |tag| @field(parse_suffix_fns, @tagName(tag))(p, level, left), + .t_exclamation => parse_suffix_fns.t_exclamation(p, optional_chain, old_optional_chain), + .t_bar_bar => parse_suffix_fns.t_bar_bar(p, level, left, flags), + .t_ampersand_ampersand => parse_suffix_fns.t_ampersand_ampersand(p, level, left, flags), + .t_question => parse_suffix_fns.t_question(p, level, errors, left), + .t_question_dot => parse_suffix_fns.t_question_dot(p, level, optional_chain, left), + .t_template_head => parse_suffix_fns.t_template_head(p, level, optional_chain, old_optional_chain, left), + .t_less_than => parse_suffix_fns.t_less_than(p, level, optional_chain, old_optional_chain, left), + .t_open_paren => parse_suffix_fns.t_open_paren(p, level, optional_chain, old_optional_chain, left), + .t_no_substitution_template_literal => parse_suffix_fns.t_no_substitution_template_literal(p, level, optional_chain, old_optional_chain, left), + .t_open_bracket => parse_suffix_fns.t_open_bracket(p, optional_chain, old_optional_chain, left, flags), + .t_dot => parse_suffix_fns.t_dot(p, optional_chain, old_optional_chain, left), + .t_less_than_less_than => parse_suffix_fns.t_less_than_less_than(p, level, optional_chain, old_optional_chain, left), + else => parse_suffix_fns.handleTypescriptAs(p, level), + }; + + switch (try continuation) { + .next => {}, + .done => break, + } + } + + left_and_out.* = left_value; } }; } @@ -807,3 +952,4 @@ const JSXTransformType = js_parser.JSXTransformType; const SideEffects = js_parser.SideEffects; const TypeScript = js_parser.TypeScript; const options = js_parser.options; +const OptionalChain = js_ast.OptionalChain; diff --git a/src/ast/parseTypescript.zig b/src/ast/parseTypescript.zig index b2b14e71e8..c024174346 100644 --- a/src/ast/parseTypescript.zig +++ b/src/ast/parseTypescript.zig @@ -24,7 +24,9 @@ pub fn ParseTypescript( // } // // This matches the behavior of the TypeScript compiler. - try decorators.append(try p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator)); + try decorators.ensureUnusedCapacity(1); + try p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator, &decorators.unusedCapacitySlice()[0]); + decorators.items.len += 1; } return decorators.items;