pub fn ParseSuffix( comptime parser_feature__typescript: bool, comptime parser_feature__jsx: JSXTransformType, comptime parser_feature__scan_only: bool, ) type { return struct { const P = js_parser.NewParser_(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only); const is_typescript_enabled = P.is_typescript_enabled; 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); // 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; } 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, }, 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; } 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; } } 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 .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_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 .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_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; } return .next; } 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; } 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; } 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 = old_optional_chain, }, left.loc); optional_chain.* = old_optional_chain; return .next; } 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 = old_optional_chain, }, left.loc, ); optional_chain.* = old_optional_chain; return .next; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; } 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; // Each of these tokens are split into a function to conserve // stack space. Currently in Zig, the compiler does not reuse // stack space between scopes This means that having a large // function with many scopes and local variables consumes // enormous amounts of stack space. 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(@This(), @tagName(tag))(p, level, left), .t_exclamation => t_exclamation(p, optional_chain, old_optional_chain), .t_bar_bar => t_bar_bar(p, level, left, flags), .t_ampersand_ampersand => t_ampersand_ampersand(p, level, left, flags), .t_question => t_question(p, level, errors, left), .t_question_dot => t_question_dot(p, level, optional_chain, left), .t_template_head => t_template_head(p, level, optional_chain, old_optional_chain, left), .t_less_than => t_less_than(p, level, optional_chain, old_optional_chain, left), .t_open_paren => t_open_paren(p, level, optional_chain, old_optional_chain, left), .t_no_substitution_template_literal => t_no_substitution_template_literal(p, level, optional_chain, old_optional_chain, left), .t_open_bracket => t_open_bracket(p, optional_chain, old_optional_chain, left, flags), .t_dot => t_dot(p, optional_chain, old_optional_chain, left), .t_less_than_less_than => t_less_than_less_than(p, level, optional_chain, old_optional_chain, left), else => handleTypescriptAs(p, level), }; switch (try continuation) { .next => {}, .done => break, } } left_and_out.* = left_value; } }; } const Continuation = enum { next, done }; const string = []const u8; const bun = @import("bun"); const js_ast = bun.ast; const E = js_ast.E; const Expr = js_ast.Expr; const OptionalChain = js_ast.OptionalChain; const Op = js_ast.Op; const Level = js_ast.Op.Level; const js_lexer = bun.js_lexer; const T = js_lexer.T; const js_parser = bun.js_parser; const DeferredErrors = js_parser.DeferredErrors; const JSXTransformType = js_parser.JSXTransformType; const SideEffects = js_parser.SideEffects; const TypeScript = js_parser.TypeScript; const options = js_parser.options;