Skip to content

Commit 0651404

Browse files
committed
Handle multi-character binary operators split by SWC's lexer
The SWC lexer can tokenize multi-character binary operators as separate tokens when the operator follows a TypeScript type position. For example, in `0 as number <= 1`, after parsing the type `number` the lexer doesn't merge `<` with the trailing `=` and emits them as two tokens — even though the parser's expression layer correctly treats the whole thing as a single `<=` BinExpr. `get_flattened_bin_expr` searched for a single token whose text equals the binary operator (`<=`), found none, and panicked on the `unwrap()`: thread 'main' panicked at flatten_binary_expr.rs:25: called `Option::unwrap()` on a `None` value This affected at least `<=` and `>=` after `as`-expressions; the existing `<<` test in BinaryExpression_All hints the lexer has split multi-char operators in the past too. Move the operator-token lookup into a small helper and, when the full operator-text match fails, fall back to matching just the operator's first character. The fallback is sufficient because callers only need the token's start position (e.g. `start_line_fast` for operator position decisions); for the split case, the leading `<` token carries the same start position as the conceptual `<=` operator. Refs denoland/deno#31988.
1 parent c2aef70 commit 0651404

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

src/generation/swc/flatten_binary_expr.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub struct BinExprOp<'a> {
2222
pub fn get_flattened_bin_expr<'a, 'b>(node: &'b BinExpr<'a>, context: &mut Context<'a>) -> Vec<BinExprItem<'a>> {
2323
let mut items = Vec::new();
2424
let operator_token = BinExprOp {
25-
token: context.token_finder.get_first_operator_after(&node.left, node.op().as_str()).unwrap(),
25+
token: find_operator_token_after(&node.left, node.op(), context),
2626
op: node.op(),
2727
};
2828
let is_op_same_line = get_operator_position(node, operator_token.token, context) == OperatorPosition::SameLine;
@@ -68,6 +68,29 @@ pub fn get_flattened_bin_expr<'a, 'b>(node: &'b BinExpr<'a>, context: &mut Conte
6868

6969
return items;
7070

71+
/// Locate the binary operator's leading token, immediately after the left
72+
/// operand. Most of the time this is just `get_first_operator_after` with
73+
/// the operator's full text (e.g. `<=`), but the SWC lexer can split
74+
/// multi-character operators into separate tokens when the operator
75+
/// follows a TypeScript type position. For example `0 as number <= 1`
76+
/// tokenizes `<=` as `<` then `=`. In that case we fall back to matching
77+
/// just the operator's first character (`<`), which is always a single
78+
/// token whose start position is what callers actually need (e.g. for
79+
/// `start_line_fast`).
80+
fn find_operator_token_after<'a>(left: &impl SourceRanged, op: BinaryOp, context: &mut Context<'a>) -> &'a TokenAndSpan {
81+
let op_text = op.as_str();
82+
if let Some(tok) = context.token_finder.get_first_operator_after(left, op_text) {
83+
return tok;
84+
}
85+
if op_text.len() > 1 {
86+
let first_char_text = &op_text[..1];
87+
if let Some(tok) = context.token_finder.get_first_operator_after(left, first_char_text) {
88+
return tok;
89+
}
90+
}
91+
panic!("could not locate operator token for binary op `{op_text}`");
92+
}
93+
7194
fn is_expression_breakable(top_op: BinaryOp, op: BinaryOp) -> bool {
7295
if top_op.is_add_sub() {
7396
op.is_add_sub()

tests/specs/expressions/BinaryExpression/BinaryExpression_All.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,15 @@ const test =
286286
const test =
287287
// testing
288288
a + b + c;
289+
290+
== should format multi-character comparison after a TS as-expression (LtEq) ==
291+
0 as number <= 1;
292+
293+
[expect]
294+
0 as number <= 1;
295+
296+
== should format multi-character comparison after a TS as-expression (GtEq) ==
297+
0 as number >= 1;
298+
299+
[expect]
300+
0 as number >= 1;

0 commit comments

Comments
 (0)