diff --git a/src/js_ast.zig b/src/js_ast.zig index 9789739c6a..85ef0d5cdd 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -627,6 +627,7 @@ pub const E = struct { pub const Dot = struct { // target is Node + target: ExprNodeIndex, name: string, name_loc: logger.Loc, optional_chain: ?OptionalChain = null, @@ -649,6 +650,7 @@ pub const E = struct { pub const Index = struct { index: ExprNodeIndex, + target: ExprNodeIndex, optional_chain: ?OptionalChain = null, pub fn hasSameFlagsAs(a: *Index, b: *Index) bool { @@ -759,8 +761,13 @@ pub const E = struct { tail_raw: string, }; - pub const Template = struct { tag: ?ExprNodeIndex = null, head: JavascriptString, head_raw: string, // This is only filled out for tagged template literals - parts: ?[]TemplatePart = null, legacy_octal_loc: logger.Loc }; + pub const Template = struct { + tag: ?ExprNodeIndex = null, + head: JavascriptString, + head_raw: string, // This is only filled out for tagged template literals + parts: ?[]TemplatePart = null, + legacy_octal_loc: logger.Loc = logger.Loc.Empty, + }; pub const RegExp = struct { value: string, @@ -1587,6 +1594,357 @@ pub const Expr = struct { e_import, e_this, e_class, + + pub fn isArray(self: Tag) bool { + switch (self) { + .e_array => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isUnary(self: Tag) bool { + switch (self) { + .e_unary => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isBinary(self: Tag) bool { + switch (self) { + .e_binary => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isThis(self: Tag) bool { + switch (self) { + .e_this => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isClass(self: Tag) bool { + switch (self) { + .e_class => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isBoolean(self: Tag) bool { + switch (self) { + .e_boolean => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isSuper(self: Tag) bool { + switch (self) { + .e_super => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isNull(self: Tag) bool { + switch (self) { + .e_null => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isUndefined(self: Tag) bool { + switch (self) { + .e_undefined => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isNew(self: Tag) bool { + switch (self) { + .e_new => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isNewTarget(self: Tag) bool { + switch (self) { + .e_new_target => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isFunction(self: Tag) bool { + switch (self) { + .e_function => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isImportMeta(self: Tag) bool { + switch (self) { + .e_import_meta => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isCall(self: Tag) bool { + switch (self) { + .e_call => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isDot(self: Tag) bool { + switch (self) { + .e_dot => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isIndex(self: Tag) bool { + switch (self) { + .e_index => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isArrow(self: Tag) bool { + switch (self) { + .e_arrow => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isIdentifier(self: Tag) bool { + switch (self) { + .e_identifier => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isImportIdentifier(self: Tag) bool { + switch (self) { + .e_import_identifier => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isPrivateIdentifier(self: Tag) bool { + switch (self) { + .e_private_identifier => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isJsxElement(self: Tag) bool { + switch (self) { + .e_jsx_element => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isMissing(self: Tag) bool { + switch (self) { + .e_missing => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isNumber(self: Tag) bool { + switch (self) { + .e_number => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isBigInt(self: Tag) bool { + switch (self) { + .e_big_int => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isObject(self: Tag) bool { + switch (self) { + .e_object => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isSpread(self: Tag) bool { + switch (self) { + .e_spread => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isString(self: Tag) bool { + switch (self) { + .e_string => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isTemplatePart(self: Tag) bool { + switch (self) { + .e_template_part => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isTemplate(self: Tag) bool { + switch (self) { + .e_template => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isRegExp(self: Tag) bool { + switch (self) { + .e_reg_exp => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isAwait(self: Tag) bool { + switch (self) { + .e_await => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isYield(self: Tag) bool { + switch (self) { + .e_yield => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isIf(self: Tag) bool { + switch (self) { + .e_if => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isRequireOrRequireResolve(self: Tag) bool { + switch (self) { + .e_require_or_require_resolve => { + return true; + }, + else => { + return false; + }, + } + } + pub fn isImport(self: Tag) bool { + switch (self) { + .e_import => { + return true; + }, + else => { + return false; + }, + } + } }; pub fn assign(a: *Expr, b: *Expr, allocator: *std.mem.Allocator) Expr { @@ -2034,6 +2392,14 @@ pub const Op = struct { pub fn eql(self: Level, b: Level) callconv(.Inline) bool { return @enumToInt(self) == @enumToInt(b); } + + pub fn sub(self: Level, comptime i: anytype) callconv(.Inline) Level { + return @intToEnum(Level, @enumToInt(self) - i); + } + + pub fn add(self: Level, comptime i: anytype) callconv(.Inline) Level { + return @intToEnum(Level, @enumToInt(self) + i); + } }; text: string, diff --git a/src/js_lexer_tables.zig b/src/js_lexer_tables.zig index 1c9a050824..0ae73eac5c 100644 --- a/src/js_lexer_tables.zig +++ b/src/js_lexer_tables.zig @@ -130,12 +130,12 @@ pub const T = enum(u8) { t_while, t_with, - pub fn isAssign() bool { - return self >= T.t_ampersand_ampersand_equals and self <= T.t_slash_equals; + pub fn isAssign(self: T) bool { + return @enumToInt(self) >= @enumToInt(T.t_ampersand_ampersand_equals) and @enumToInt(self) <= @enumToInt(T.t_slash_equals); } - pub fn isReservedWord() bool { - return self >= T.t_break and self <= T.t_with; + pub fn isReservedWord(self: T) bool { + return @enumToInt(self) >= @enumToInt(T.t_break) and @enumToInt(self) <= @enumToInt(T.t_with); } pub fn isString(self: T) bool { diff --git a/src/js_parser.zig b/src/js_parser.zig index 530215b2f9..d0ea115d5d 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -1056,7 +1056,7 @@ const P = struct { } pub fn parseFn(p: *P, name: ?js_ast.LocRef, opts: FnOrArrowDataParse) G.Fn { - // if data.allowAwait && data.allowYield { + // if data.allowAwait and data.allowYield { // p.markSyntaxFeature(compat.AsyncGenerator, data.asyncRange) // } @@ -2507,13 +2507,765 @@ const P = struct { pub fn parseSuffix(p: *P, left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) Expr { return _parseSuffix(p, left, level, errors orelse &DeferredErrors.None, flags); } - pub fn _parseSuffix(p: *P, left: Expr, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) callconv(.Inline) Expr { + pub fn _parseSuffix(p: *P, _left: Expr, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) Expr { var expr: Expr = undefined; + var left = _left; var loc = p.lexer.loc(); + var optional_chain: ?js_ast.OptionalChain = null; - return expr; + 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; + } + + p.lexer.next(); + left = p.e(E.Binary{ + .op = .bin_comma, + .left = left, + .right = p.parseExpr(.comma), + }, left.loc); + }, + else => { + return left; + }, + } + } + } + + // Stop now if this token is forbidden to follow a TypeScript "as" cast + if (p.lexer.loc().start == p.forbid_suffix_after_as_loc.start) { + return left; + } + + // Reset the optional chain flag by default. That way we won't accidentally + // treat "c.d" as OptionalChainContinue in "a?.b + c.d". + var old_optional_chain = optional_chain; + optional_chain = null; + + switch (p.lexer.token) { + .t_dot => { + 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 => { + p.lexer.expected(.t_identifier); + }, + else => {}, + } + + var name = p.lexer.identifier; + var name_loc = p.lexer.loc(); + p.lexer.next(); + const ref = p.storeNameInRef(name) catch unreachable; + left = p.e(E.Index{ + .target = left, + .index = p.e( + E.PrivateIdentifier{ + .ref = ref, + }, + name_loc, + ), + .optional_chain = old_optional_chain, + }, left.loc); + } else { + // "a.b" + // "a?.b.c" + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + + var name = p.lexer.identifier; + var name_loc = p.lexer.loc(); + p.lexer.next(); + + left = p.e(E.Dot{ .target = left, .name = name, .name_loc = name_loc, .optional_chain = old_optional_chain }, left.loc); + } + + optional_chain = old_optional_chain; + }, + .t_question_dot => { + p.lexer.next(); + var optional_start = js_ast.OptionalChain.start; + + // TODO: Remove unnecessary optional chains + // if p.options.mangleSyntax { + // if isNullOrUndefined, _, ok := toNullOrUndefinedWithSideEffects(left.Data); ok and !isNullOrUndefined { + // optionalStart = js_ast.OptionalChainNone + // } + // } + + switch (p.lexer.token) { + .t_open_bracket => { + // "a?.[b]" + p.lexer.next(); + + // allow "in" inside the brackets; + const old_allow_in = p.allow_in; + p.allow_in = true; + + const index = p.parseExpr(.lowest); + + p.allow_in = old_allow_in; + + p.lexer.expect(.t_close_bracket); + left = p.e( + E.Index{ .target = left, .index = index, .optional_chain = optional_start }, + left.loc, + ); + }, + + .t_open_paren => { + // "a?.()" + if (level.gte(.call)) { + return left; + } + + left = p.e(E.Call{ + .target = left, + .args = p.parseCallArgs(), + .optional_chain = optional_start, + }, left.loc); + }, + .t_less_than => { + // "a?.()" + if (!p.options.ts) { + p.lexer.expected(.t_identifier); + } + + p.skipTypeScriptTypeArguments(false); + if (p.lexer.token != .t_open_paren) { + p.lexer.expected(.t_open_paren); + } + + if (level.gte(.call)) { + return left; + } + + left = p.e( + E.Call{ .target = left, .args = p.parseCallArgs(), .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(); + p.lexer.next(); + const ref = p.storeNameInRef(name) catch unreachable; + left = p.e(E.Index{ + .target = left, + .index = p.e( + E.PrivateIdentifier{ + .ref = ref, + }, + name_loc, + ), + .optional_chain = optional_start, + }, left.loc); + } else { + // "a?.b" + if (!p.lexer.isIdentifierOrKeyword()) { + p.lexer.expect(.t_identifier); + } + const name = p.lexer.identifier; + const name_loc = p.lexer.loc(); + p.lexer.next(); + + left = p.e(E.Dot{ + .target = left, + .name = name, + .name_loc = name_loc, + .optional_chain = optional_start, + }, left.loc); + } + }, + } + + // Only continue if we have started + if (optional_start == .start) { + optional_start = .ccontinue; + } + }, + .t_no_substitution_template_literal => { + if (old_optional_chain != null) { + p.log.addRangeError(p.source, p.lexer.range(), "Template literals cannot have an optional chain as a tag") catch unreachable; + } + // p.markSyntaxFeature(compat.TemplateLiteral, p.lexer.Range()); + const head = p.lexer.string_literal; + const head_raw = p.lexer.rawTemplateContents(); + p.lexer.next(); + left = p.e(E.Template{ + .tag = left, + .head = head, + .head_raw = head_raw, + .legacy_octal_loc = logger.Loc.Empty, + }, 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.string_literal; + const head_raw = p.lexer.rawTemplateContents(); + const partsGroup = p.parseTemplateParts(true); + p.lexer.next(); + const tag = left; + left = p.e(E.Template{ .tag = tag, .head = head, .head_raw = head_raw, .parts = partsGroup.@"0" }, 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; + } + + p.lexer.next(); + + // Allow "in" inside the brackets + const old_allow_in = p.allow_in; + p.allow_in = true; + + const index = p.parseExpr(.lowest); + + p.allow_in = old_allow_in; + + p.lexer.expect(.t_close_bracket); + + left = p.e(E.Index{ + .target = left, + .index = index, + .optional_chain = old_optional_chain, + }, left.loc); + optional_chain = old_optional_chain; + }, + .t_open_paren => { + if (level.gte(.call)) { + return left; + } + + left = p.e( + E.Call{ + .target = left, + .args = p.parseCallArgs(), + .optional_chain = old_optional_chain, + }, + left.loc, + ); + optional_chain = old_optional_chain; + }, + .t_question => { + if (level.gte(.conditional)) { + return left; + } + p.lexer.next(); + + // Stop now if we're parsing one of these: + // "(a?) => {}" + // "(a?: b) => {}" + // "(a?, b?) => {}" + if (p.options.ts 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.isEmpty()) { + p.lexer.unexpected(); + } + 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 = p.parseExpr(.comma); + + p.allow_in = old_allow_in; + p.lexer.expect(.t_colon); + const no = p.parseExpr(.comma); + + left = p.e(E.If{ + .test_ = left, + .yes = yes, + .no = no, + }, left.loc); + }, + .t_exclamation => { + // Skip over TypeScript non-null assertions + if (p.lexer.has_newline_before) { + return left; + } + + if (!p.options.ts) { + p.lexer.unexpected(); + } + + if (level.gte(.postfix)) { + return left; + } + + p.lexer.next(); + optional_chain = old_optional_chain; + }, + .t_minus_minus => { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Unary{ .op = .un_post_dec, .value = left }, left.loc); + }, + .t_plus_plus => { + if (p.lexer.has_newline_before or level.gte(.postfix)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Unary{ .op = .un_post_inc, .value = left }, left.loc); + }, + .t_comma => { + if (level.gte(.comma)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_comma, .left = left, .right = p.parseExpr(.comma) }, left.loc); + }, + .t_plus => { + if (level.gte(.add)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_add, .left = left, .right = p.parseExpr(.add) }, left.loc); + }, + .t_plus_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_add_assign, .left = left, .right = p.parseExpr(@intToEnum(Op.Level, @enumToInt(Op.Level.assign) - 1)) }, left.loc); + }, + .t_minus => { + if (level.gte(.add)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_sub, .left = left, .right = p.parseExpr(.add) }, left.loc); + }, + .t_minus_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_sub_assign, .left = left, .right = p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + }, + .t_asterisk => { + if (level.gte(.multiply)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_mul, .left = left, .right = p.parseExpr(.multiply) }, left.loc); + }, + .t_asterisk_asterisk => { + if (level.gte(.exponentiation)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_pow, .left = left, .right = p.parseExpr(Op.Level.exponentiation.sub(1)) }, left.loc); + }, + .t_asterisk_asterisk_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_pow_assign, .left = left, .right = p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + }, + .t_asterisk_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_mul_assign, .left = left, .right = p.parseExpr(Op.Level.assign.sub(1)) }, left.loc); + }, + .t_percent => { + if (level.gte(.multiply)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_rem, .left = left, .right = p.parseExpr(Op.Level.multiply) }, left.loc); + }, + .t_percent_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_rem_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_slash => { + if (level.gte(.multiply)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_div, .left = left, .right = p.parseExpr(Level.multiply) }, left.loc); + }, + .t_slash_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_div_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_loose_eq, .left = left, .right = p.parseExpr(Level.equals) }, left.loc); + }, + .t_exclamation_equals => { + if (level.gte(.equals)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_loose_ne, .left = left, .right = p.parseExpr(Level.equals) }, left.loc); + }, + .t_equals_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_strict_eq, .left = left, .right = p.parseExpr(Level.equals) }, left.loc); + }, + .t_exclamation_equals_equals => { + if (level.gte(.equals)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_strict_ne, .left = left, .right = 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 (p.options.ts and p.trySkipTypeScriptTypeArgumentsWithBacktracking()) { + optional_chain = old_optional_chain; + continue; + } + + if (level.gte(.compare)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_lt, .left = left, .right = p.parseExpr(.compare) }, left.loc); + }, + .t_less_than_equals => { + if (level.gte(.compare)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_le, .left = left, .right = p.parseExpr(.compare) }, left.loc); + }, + .t_greater_than => { + if (level.gte(.compare)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_gt, .left = left, .right = p.parseExpr(.compare) }, left.loc); + }, + .t_greater_than_equals => { + if (level.gte(.compare)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_ge, .left = left, .right = p.parseExpr(.compare) }, left.loc); + }, + .t_less_than_less_than => { + if (level.gte(.shift)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl, .left = left, .right = p.parseExpr(.shift) }, left.loc); + }, + .t_less_than_less_than_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_greater_than_greater_than => { + if (level.gte(.shift)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shr, .left = left, .right = p.parseExpr(.shift) }, left.loc); + }, + .t_greater_than_greater_than_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_greater_than_greater_than_greater_than => { + if (level.gte(.shift)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_u_shr, .left = left, .right = p.parseExpr(.shift) }, left.loc); + }, + .t_greater_than_greater_than_greater_than_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_u_shr_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_question_question => { + if (level.gte(.nullish_coalescing)) { + return left; + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_nullish_coalescing, .left = left, .right = p.parseExpr(.nullish_coalescing) }, left.loc); + }, + .t_question_question_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_nullish_coalescing_assign, .left = left, .right = 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)) { + p.lexer.unexpected(); + } + + p.lexer.next(); + const right = p.parseExpr(.logical_or); + left = p.e(E.Binary{ .op = Op.Code.bin_logical_or, .left = left, .right = right }, left.loc); + + if (level.lt(.nullish_coalescing)) { + left = p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); + + if (p.lexer.token == .t_question_question) { + p.lexer.unexpected(); + } + } + }, + .t_bar_bar_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_or_assign, .left = left, .right = 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)) { + p.lexer.unexpected(); + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_and, .left = left, .right = p.parseExpr(.logical_and) }, left.loc); + + // Prevent "&&" inside "??" from the left + if (level.lt(.nullish_coalescing)) { + left = p.parseSuffix(left, Level.nullish_coalescing.add(1), null, flags); + + if (p.lexer.token == .t_question_question) { + p.lexer.unexpected(); + } + } + }, + .t_ampersand_ampersand_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_logical_and_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_bar => { + if (level.gte(.bitwise_or)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_or, .left = left, .right = p.parseExpr(.bitwise_or) }, left.loc); + }, + .t_bar_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_or_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_ampersand => { + if (level.gte(.bitwise_and)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_and, .left = left, .right = p.parseExpr(.bitwise_and) }, left.loc); + }, + .t_ampersand_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_shl_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_caret => { + if (level.gte(.bitwise_xor)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_xor, .left = left, .right = p.parseExpr(.bitwise_xor) }, left.loc); + }, + .t_caret_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_bitwise_xor_assign, .left = left, .right = p.parseExpr(Level.assign.sub(1)) }, left.loc); + }, + .t_equals => { + if (level.gte(.assign)) { + return left; + } + + p.lexer.next(); + + left = p.e(E.Binary{ .op = .bin_assign, .left = left, .right = 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 => {}, + } + + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_in, .left = left, .right = 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 => {}, + } + } + p.lexer.next(); + left = p.e(E.Binary{ .op = .bin_instanceof, .left = left, .right = p.parseExpr(.compare) }, left.loc); + }, + else => { + // Handle the TypeScript "as" operator + if (p.options.ts and level.lt(.compare) and !p.lexer.has_newline_before and p.lexer.isContextualKeyword("as")) { + p.lexer.next(); + p.skipTypescriptType(.lowest); + + // These tokens are not allowed to follow a cast expression. This isn't + // an outright error because it may be on a new line, in which case it's + // the start of a new expression when it's after a cast: + // + // x = y as z + // (something); + // + switch (p.lexer.token) { + .t_plus_plus, + .t_minus_minus, + .t_no_substitution_template_literal, + .t_template_head, + .t_open_paren, + .t_open_bracket, + .t_question_dot, + => { + p.forbid_suffix_after_as_loc = p.lexer.loc(); + return left; + }, + else => {}, + } + + if (p.lexer.token.isAssign()) { + p.forbid_suffix_after_as_loc = p.lexer.loc(); + return left; + } + continue; + } + + return left; + }, + } + } } - pub fn _parsePrefix(p: *P, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) callconv(.Inline) Expr { + pub fn _parsePrefix(p: *P, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) Expr { const loc = p.lexer.loc(); const l = @enumToInt(level); @@ -2815,7 +3567,7 @@ const P = struct { // Skip over TypeScript type arguments here if there are any if (p.lexer.token == .t_less_than) { - p.trySkipTypeScriptTypeArgumentsWithBacktracking(); + _ = p.trySkipTypeScriptTypeArgumentsWithBacktracking(); } } @@ -3106,8 +3858,9 @@ const P = struct { } } - pub fn trySkipTypeScriptTypeArgumentsWithBacktracking(p: *P) void { + pub fn trySkipTypeScriptTypeArgumentsWithBacktracking(p: *P) bool { notimpl(); + // return false; } pub fn parsePrefix(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) Expr { return p._parsePrefix(level, errors orelse &DeferredErrors.None, flags);