Skip to content

Commit

Permalink
fix(parser): gracefully handle stalled parser in object patterns (#5198)
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr authored Feb 25, 2025
1 parent 14ad3f5 commit b0046bf
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 7 deletions.
8 changes: 8 additions & 0 deletions .changeset/gracefully-handle-stalled-parser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@biomejs/biome": patch
---
Our JavaScript parser can now gracefully handle situations where we detect the
parser to have stalled, such as in
[#4622](https://github.com/biomejs/biome/issues/4622). This means we don't fail
with an assertion anymore, but invalid code can trigger a graceful diagnostic
in such cases.
6 changes: 5 additions & 1 deletion crates/biome_js_parser/src/syntax/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ pub(crate) trait ParseObjectPattern {
let mut progress = ParserProgress::default();

while !p.at(T!['}']) {
progress.assert_progressing(p);
if !progress.has_progressed(p) {
let diagnostic = Self::expected_property_pattern_error(p, p.cur_range());
p.error(diagnostic);
break;
}

if p.at(T![,]) {
// missing element
Expand Down
3 changes: 3 additions & 0 deletions crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
r={
=
):
140 changes: 140 additions & 0 deletions crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
source: crates/biome_js_parser/tests/spec_test.rs
expression: snapshot
---
## Input

```ts
r={
=
):
```
## AST
```
JsModule {
bom_token: missing (optional),
interpreter_token: missing (optional),
directives: JsDirectiveList [],
items: JsModuleItemList [
JsExpressionStatement {
expression: JsAssignmentExpression {
left: JsIdentifierAssignment {
name_token: IDENT@0..1 "r" [] [],
},
operator_token: EQ@1..2 "=" [] [],
right: JsAssignmentExpression {
left: JsObjectAssignmentPattern {
l_curly_token: L_CURLY@2..3 "{" [] [],
properties: JsObjectAssignmentPatternPropertyList [
JsObjectAssignmentPatternShorthandProperty {
identifier: missing (required),
init: JsInitializerClause {
eq_token: EQ@3..5 "=" [Newline("\n")] [],
expression: missing (required),
},
},
missing separator,
JsObjectAssignmentPatternProperty {
member: missing (required),
colon_token: missing (required),
pattern: missing (required),
init: missing (optional),
},
],
r_curly_token: missing (required),
},
operator_token: missing (required),
right: missing (required),
},
},
semicolon_token: missing (optional),
},
JsBogusStatement {
items: [
R_PAREN@5..7 ")" [Newline("\n")] [],
COLON@7..8 ":" [] [],
],
},
],
eof_token: EOF@8..8 "" [] [],
}
```
## CST
```
0: JS_MODULE@0..8
0: (empty)
1: (empty)
2: JS_DIRECTIVE_LIST@0..0
3: JS_MODULE_ITEM_LIST@0..8
0: JS_EXPRESSION_STATEMENT@0..5
0: JS_ASSIGNMENT_EXPRESSION@0..5
0: JS_IDENTIFIER_ASSIGNMENT@0..1
0: IDENT@0..1 "r" [] []
1: EQ@1..2 "=" [] []
2: JS_ASSIGNMENT_EXPRESSION@2..5
0: JS_OBJECT_ASSIGNMENT_PATTERN@2..5
0: L_CURLY@2..3 "{" [] []
1: JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY_LIST@3..5
0: JS_OBJECT_ASSIGNMENT_PATTERN_SHORTHAND_PROPERTY@3..5
0: (empty)
1: JS_INITIALIZER_CLAUSE@3..5
0: EQ@3..5 "=" [Newline("\n")] []
1: (empty)
1: (empty)
2: JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY@5..5
0: (empty)
1: (empty)
2: (empty)
3: (empty)
2: (empty)
1: (empty)
2: (empty)
1: (empty)
1: JS_BOGUS_STATEMENT@5..8
0: R_PAREN@5..7 ")" [Newline("\n")] []
1: COLON@7..8 ":" [] []
4: EOF@8..8 "" [] []
```
## Diagnostics
```
issue4622.ts:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected an identifier but instead found '='.
1r={
> 2 │ =
│ ^
3 │ ):
i Expected an identifier here.
1 │ r={
> 2 │ =
│ ^
3 │ ):
issue4622.ts:3:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected an expression, or an assignment but instead found ')'.
1 │ r={
2 │ =
> 3 │ ):
│ ^
i Expected an expression, or an assignment here.
1 │ r={
2 │ =
> 3 │ ):
│ ^
```
15 changes: 9 additions & 6 deletions crates/biome_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,16 +510,21 @@ pub trait Parser: Sized {
pub struct ParserProgress(Option<TextSize>);

impl ParserProgress {
/// Returns true if the current parser position is passed this position
/// Returns true if the current parser position is passed this position,
/// and updates the progress.
#[inline]
pub fn has_progressed<P>(&self, p: &P) -> bool
pub fn has_progressed<P>(&mut self, p: &P) -> bool
where
P: Parser,
{
match self.0 {
let has_progressed = match self.0 {
None => true,
Some(pos) => pos < p.source().position(),
}
};

self.0 = Some(p.source().position());

has_progressed
}

/// Asserts that the parsing is still making progress.
Expand All @@ -539,8 +544,6 @@ impl ParserProgress {
p.cur(),
p.cur_range(),
);

self.0 = Some(p.source().position());
}
}

Expand Down

0 comments on commit b0046bf

Please sign in to comment.