Repository: biomejs/biome (language: rust)

Issue:
🐛 Unexpected error `The parser is no longer progressing`
### Environment information

```block
CLI:
  Version:                      0.0.0 (self compiled 4848994e4507d80e56ea07c40ff41a7b303918cd)
  Color support:                true

Platform:
  CPU Architecture:             x86_64
  OS:                           linux

Environment:
  BIOME_LOG_PATH:               unset
  BIOME_LOG_PREFIX_NAME:        unset
  BIOME_CONFIG_PATH:            unset
  NO_COLOR:                     unset
  TERM:                         "xterm-256color"
  JS_RUNTIME_VERSION:           unset
  JS_RUNTIME_NAME:              unset
  NODE_PACKAGE_MANAGER:         unset

Biome Configuration:
  Status:                       unset

Workspace:
  Open Documents:               0
```


### What happened?

File content(at the bottom should be attached raw, not formatted file - github removes some non-printable characters, so copying from here may not work):
```
r={
=
):
```

command
```
timeout -v 150 biome lint TEST___FILE.ts
```

cause this
```
Biome encountered an unexpected error

This is a bug in Biome, not an error in your code, and we would appreciate it if you could report it to https://github.com/biomejs/biome/issues/ along with the following information to help us fixing the issue:

Source Location: /home/runner/work/Automated-Fuzzer/Automated-Fuzzer/biome-main/crates/biome_parser/src/lib.rs:535:9
Thread Name: biome::worker_0
Message: The parser is no longer progressing. Stuck at ')' R_PAREN:6..7


##### Automatic Fuzzer note, output status "None", output signal "Some(6)"
```

[compressed.zip](https://github.com/user-attachments/files/17892202/compressed.zip)


### Expected result

No error

### Code of Conduct

- [X] I agree to follow Biome's Code of Conduct


Discussion / hints:

Fix patch:
diff --git a/.changeset/gracefully-handle-stalled-parser.md b/.changeset/gracefully-handle-stalled-parser.md
new file mode 100644
index 000000000000..335d4af18235
--- /dev/null
+++ b/.changeset/gracefully-handle-stalled-parser.md
@@ -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.
diff --git a/crates/biome_js_parser/src/syntax/pattern.rs b/crates/biome_js_parser/src/syntax/pattern.rs
index fc4ae027abf2..b6ca498757cd 100644
--- a/crates/biome_js_parser/src/syntax/pattern.rs
+++ b/crates/biome_js_parser/src/syntax/pattern.rs
@@ -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
diff --git a/crates/biome_parser/src/lib.rs b/crates/biome_parser/src/lib.rs
index 0e79ee868151..4603dc8721d1 100644
--- a/crates/biome_parser/src/lib.rs
+++ b/crates/biome_parser/src/lib.rs
@@ -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.
@@ -539,8 +544,6 @@ impl ParserProgress {
             p.cur(),
             p.cur_range(),
         );
-
-        self.0 = Some(p.source().position());
     }
 }
 


Test patch:
diff --git a/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts b/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts
new file mode 100644
index 000000000000..bde1dd36f254
--- /dev/null
+++ b/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts
@@ -0,0 +1,3 @@
+r={
+=
+):
\ No newline at end of file
diff --git a/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts.snap b/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts.snap
new file mode 100644
index 000000000000..23df286a5f37
--- /dev/null
+++ b/crates/biome_js_parser/tests/js_test_suite/error/issue4622.ts.snap
@@ -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 '='.
+  
+    1 │ r={
+  > 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 │ ):
+      │ ^
+  
+```