Repository: swc-project/swc (rust)

Issue:
Static block with opening brace on next line fails to parse
**Describe the bug**

Having a static block with the opening brace on the next line fails to parse.

**Input code**

```ts
class Test {
    static
    {
    }
}
```

```
Unexpected token `{`. Expected identifier, string literal, numeric literal or [ for the computed key
```

**Expected behavior**

Should parse https://ts-ast-viewer.com/#code/MYGwhgzhAEAqCmEAu0DeAoaXrLEglsJthttAL7rlA

**Version**

swc_ecmascript 0.60


Pull request description:
Fix parsing static blocks with line breaks
Fixes #2195

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 389d3298ea..d6378600d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2661,7 +2661,7 @@ dependencies = [
 
 [[package]]
 name = "swc_ecma_parser"
-version = "0.70.0"
+version = "0.70.1"
 dependencies = [
  "either",
  "enum_kind",
diff --git a/ecmascript/parser/Cargo.toml b/ecmascript/parser/Cargo.toml
index 5dac363bdc..131a998e57 100644
--- a/ecmascript/parser/Cargo.toml
+++ b/ecmascript/parser/Cargo.toml
@@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"]
 license = "Apache-2.0/MIT"
 name = "swc_ecma_parser"
 repository = "https://github.com/swc-project/swc.git"
-version = "0.70.0"
+version = "0.70.1"
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/ecmascript/parser/src/parser/class_and_fn.rs b/ecmascript/parser/src/parser/class_and_fn.rs
index b90020aa1e..d4a032c279 100644
--- a/ecmascript/parser/src/parser/class_and_fn.rs
+++ b/ecmascript/parser/src/parser/class_and_fn.rs
@@ -404,23 +404,30 @@ impl<'a, I: Tokens> Parser<I> {
             } else if self.is_class_property()? {
                 // Property named `static`
 
-                let key = Either::Right(PropName::Ident(Ident::new(
-                    js_word!("static"),
-                    static_token,
-                )));
-                let is_optional = self.input.syntax().typescript() && eat!(self, '?');
-                return self.make_property(
-                    start,
-                    decorators,
-                    accessibility,
-                    key,
-                    false,
-                    is_optional,
-                    false,
-                    declare,
-                    false,
-                    false,
-                );
+                // Avoid to parse
+                //   static
+                //   {}
+                let is_parsing_static_blocks =
+                    self.input.syntax().static_blocks() && is!(self, '{');
+                if !is_parsing_static_blocks {
+                    let key = Either::Right(PropName::Ident(Ident::new(
+                        js_word!("static"),
+                        static_token,
+                    )));
+                    let is_optional = self.input.syntax().typescript() && eat!(self, '?');
+                    return self.make_property(
+                        start,
+                        decorators,
+                        accessibility,
+                        key,
+                        false,
+                        is_optional,
+                        false,
+                        declare,
+                        false,
+                        false,
+                    );
+                }
             } else {
                 // TODO: error if static contains escape
             }
diff --git a/ecmascript/parser/src/parser/stmt.rs b/ecmascript/parser/src/parser/stmt.rs
index 965388c673..95dd0d56fc 100644
--- a/ecmascript/parser/src/parser/stmt.rs
+++ b/ecmascript/parser/src/parser/stmt.rs
@@ -1976,6 +1976,90 @@ export default function waitUntil(callback, options = {}) {
         );
     }
 
+    #[test]
+    fn class_static_blocks_with_line_breaks_01() {
+        let src = "class Foo {
+            static
+            {
+                1 + 1;
+            }
+        }";
+        assert_eq_ignore_span!(
+            test_parser(
+                src,
+                Syntax::Es(EsConfig {
+                    static_blocks: true,
+                    ..Default::default()
+                }),
+                |p| p.parse_expr()
+            ),
+            Box::new(Expr::Class(ClassExpr {
+                ident: Some(Ident {
+                    span,
+                    sym: "Foo".into(),
+                    optional: false,
+                }),
+                class: Class {
+                    span,
+                    decorators: Vec::new(),
+                    super_class: None,
+                    type_params: None,
+                    super_type_params: None,
+                    is_abstract: false,
+                    implements: Vec::new(),
+                    body: vec!(ClassMember::StaticBlock(StaticBlock {
+                        span,
+                        body: BlockStmt {
+                            span,
+                            stmts: vec!(stmt("1 + 1;")),
+                        }
+                    }))
+                }
+            }))
+        );
+    }
+
+    #[test]
+    fn class_static_blocks_with_line_breaks_02() {
+        let src = "class Foo {
+            static
+            {}
+        }";
+        assert_eq_ignore_span!(
+            test_parser(
+                src,
+                Syntax::Es(EsConfig {
+                    static_blocks: true,
+                    ..Default::default()
+                }),
+                |p| p.parse_expr()
+            ),
+            Box::new(Expr::Class(ClassExpr {
+                ident: Some(Ident {
+                    span,
+                    sym: "Foo".into(),
+                    optional: false,
+                }),
+                class: Class {
+                    span,
+                    decorators: Vec::new(),
+                    super_class: None,
+                    type_params: None,
+                    super_type_params: None,
+                    is_abstract: false,
+                    implements: Vec::new(),
+                    body: vec!(ClassMember::StaticBlock(StaticBlock {
+                        span,
+                        body: BlockStmt {
+                            span,
+                            stmts: Vec::new(),
+                        }
+                    }))
+                }
+            }))
+        );
+    }
+
     #[test]
     fn class_static_blocks_in_ts() {
         let src = "class Foo { static { 1 + 1 }; }";
@@ -1984,6 +2068,30 @@ export default function waitUntil(callback, options = {}) {
         });
     }
 
+    #[test]
+    fn class_static_blocks_with_line_breaks_in_ts_01() {
+        let src = "class Foo {
+            static
+            {
+                1 + 1;
+            }
+        }";
+        test_parser(src, Syntax::Typescript(Default::default()), |p| {
+            p.parse_expr()
+        });
+    }
+
+    #[test]
+    fn class_static_blocks_with_line_breaks_in_ts_02() {
+        let src = "class Foo {
+            static
+            {}
+        }";
+        test_parser(src, Syntax::Typescript(Default::default()), |p| {
+            p.parse_expr()
+        });
+    }
+
     #[test]
     #[should_panic(expected = "Modifiers cannot appear here")]
     fn class_static_blocks_in_ts_with_invalid_modifier_01() {


Test patch:
diff --git a/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js b/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js
new file mode 100644
index 0000000000..e58aaf1190
--- /dev/null
+++ b/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js
@@ -0,0 +1,6 @@
+class Foo {
+    static
+    {
+        1 + 1;
+    }
+}
diff --git a/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js.spans b/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js.spans
new file mode 100644
index 0000000000..b21f13eede
--- /dev/null
+++ b/ecmascript/parser/tests/span/js/decl/static-blocks-with-line-breaks.js.spans
@@ -0,0 +1,149 @@
+warning: Module
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: ModuleItem
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: Stmt
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: Decl
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: ClassDecl
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: Ident
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:7
+  |
+1 | class Foo {
+  |       ^^^
+
+warning: Class
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+6 | | }
+  | |_^
+
+warning: ClassMember
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:2:5
+  |
+2 | /     static
+3 | |     {
+4 | |         1 + 1;
+5 | |     }
+  | |_____^
+
+warning: BlockStmt
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:3:5
+  |
+3 | /     {
+4 | |         1 + 1;
+5 | |     }
+  | |_____^
+
+warning: Stmt
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^^^^^^
+
+warning: ExprStmt
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^^^^^^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^^^^^
+
+warning: BinExpr
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^^^^^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^
+
+warning: Lit
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^
+
+warning: Number
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:9
+  |
+4 |         1 + 1;
+  |         ^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:13
+  |
+4 |         1 + 1;
+  |             ^
+
+warning: Lit
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:13
+  |
+4 |         1 + 1;
+  |             ^
+
+warning: Number
+ --> $DIR/tests/span/js/decl/static-blocks-with-line-breaks.js:4:13
+  |
+4 |         1 + 1;
+  |             ^
+
diff --git a/ecmascript/parser/tests/span/js/decl/static-blocks.js b/ecmascript/parser/tests/span/js/decl/static-blocks.js
new file mode 100644
index 0000000000..dd35afda67
--- /dev/null
+++ b/ecmascript/parser/tests/span/js/decl/static-blocks.js
@@ -0,0 +1,5 @@
+class Foo {
+    static {
+        1 + 1;
+    }
+}
diff --git a/ecmascript/parser/tests/span/js/decl/static-blocks.js.spans b/ecmascript/parser/tests/span/js/decl/static-blocks.js.spans
new file mode 100644
index 0000000000..19255884c4
--- /dev/null
+++ b/ecmascript/parser/tests/span/js/decl/static-blocks.js.spans
@@ -0,0 +1,143 @@
+warning: Module
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: ModuleItem
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: Stmt
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: Decl
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: ClassDecl
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: Ident
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:7
+  |
+1 | class Foo {
+  |       ^^^
+
+warning: Class
+ --> $DIR/tests/span/js/decl/static-blocks.js:1:1
+  |
+1 | / class Foo {
+2 | |     static {
+3 | |         1 + 1;
+4 | |     }
+5 | | }
+  | |_^
+
+warning: ClassMember
+ --> $DIR/tests/span/js/decl/static-blocks.js:2:5
+  |
+2 | /     static {
+3 | |         1 + 1;
+4 | |     }
+  | |_____^
+
+warning: BlockStmt
+ --> $DIR/tests/span/js/decl/static-blocks.js:2:12
+  |
+2 |       static {
+  |  ____________^
+3 | |         1 + 1;
+4 | |     }
+  | |_____^
+
+warning: Stmt
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^^^^^^
+
+warning: ExprStmt
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^^^^^^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^^^^^
+
+warning: BinExpr
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^^^^^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^
+
+warning: Lit
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^
+
+warning: Number
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:9
+  |
+3 |         1 + 1;
+  |         ^
+
+warning: Expr
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:13
+  |
+3 |         1 + 1;
+  |             ^
+
+warning: Lit
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:13
+  |
+3 |         1 + 1;
+  |             ^
+
+warning: Number
+ --> $DIR/tests/span/js/decl/static-blocks.js:3:13
+  |
+3 |         1 + 1;
+  |             ^
+