mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
Splits up js_parser.zig into multiple files. Also changes visitExprInOut to use function calls rather than switch Not ready: - [ ] P.zig is ~70,000 tokens, still needs to get smaller - [x] ~~measure zig build time before & after (is it slower?)~~ no significant impact --------- Co-authored-by: pfgithub <6010774+pfgithub@users.noreply.github.com>
810 lines
37 KiB
Zig
810 lines
37 KiB
Zig
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;
|
|
|
|
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;
|
|
}
|
|
|
|
try p.lexer.next();
|
|
left = p.newExpr(E.Binary{
|
|
.op = .bin_comma,
|
|
.left = left,
|
|
.right = try p.parseExpr(.comma),
|
|
}, left.loc);
|
|
},
|
|
else => {
|
|
return left;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
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?.<T>()"
|
|
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;
|
|
}
|
|
|
|
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;
|
|
},
|
|
.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 = 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.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();
|
|
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.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;
|
|
}
|
|
|
|
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);
|
|
},
|
|
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"))) {
|
|
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 => {},
|
|
}
|
|
|
|
if (p.lexer.token.isAssign()) {
|
|
p.forbid_suffix_after_as_loc = p.lexer.loc();
|
|
return left;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
return left;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const bun = @import("bun");
|
|
|
|
const js_ast = bun.ast;
|
|
const E = js_ast.E;
|
|
const Expr = js_ast.Expr;
|
|
|
|
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;
|