Repository: swc-project/swc (rust)

Issue:
Error: Unexpected token `div`. Expected jsx identifier
### Describe the bug

SWC fails to compile the following code:
```typescript
import React from 'react';

export function demoBug(): any {
    return (
        <div
            onAttach={(node) => {
                if (node) {
                    add(2, 3);
                }
                function add(a: number, b: number): void {
                    console.log(a + b);
                }
            }}></div>
    );
}
```
It throws the following error:
```
error: Unexpected token `div`. Expected jsx identifier
 --> input.tsx:6:10
  |
6 |         <div
  |          ^^^



Caused by:
    0: failed to process js file
    1: failed to process js file
    2: Syntax Error
```

 It compiles fine if we remove the preceding if condition before the method definition as like below:
```typescript
import React from 'react';

export function demoBug(): any {
    return (
        <div
            onAttach={(node) => {
                add(2, 3);
                function add(a: number, b: number): void {
                    console.log(a + b);
                }
            }}></div>
    );
}
```

The only time it fails is when we have a conditional statement before a function definition inside a callback to any of the methods of div. For e.g, it fails when we have a while condition before that, like in code below:

```typescript
import React from 'react';

export function demoBug(): any {
    return (
        <div
            onAttach={(node) => {
                while (node) {
                    add(2, 3);
                }
                function add(a: number, b: number): void {
                    console.log(a + b);
                }
            }}></div>
    );
}
```

I have also linked the swc playground link to reproduce the issue. Also this seems to be similar to another closed bug here: https://github.com/swc-project/swc/issues/2237

### Input code

```typescript
import React from 'react';

export function demoBug(): any {
    return (
        <div
            onAttach={(node) => {
                if (node) {
                    add(2, 3);
                }
                function add(a: number, b: number): void {
                    console.log(a + b);
                }
            }}></div>
    );
}
```


### Config

```json
{
  "sourceMaps": true,
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    },
    "transform": {
      "react": {
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment"
      }
    },
    "target": "es2019",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "amd"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.128&code=H4sIAAAAAAAAA4WPTQrCMBCF9z3F7JpiUdBd%2F0CP4A3SJG0DTUZiUpWSu9uUWoQifquZeW94M1Ld0Fi4CsosNAYVxCbUcR5F4jlrjdPMStTAhcKLa0mSAdUvGCOYMMI6o4HMTaDgclibAOqztZR15Ug0cpFAWS273zw62QtYHFs5QDknxxROSb6R%2FWayXh22aAbaqVqYFOpPOX0xoOQ%2FshjqO%2FZi32NLKOyg%2FhvqfVUcpt%2BreTrZ%2FRv0xHeYXAEAAA%3D%3D&config=H4sIAAAAAAAAA0WPPQ6DMAyF7%2BIZVW23Zm83lt7ACgalIj%2BygwRC3L1xoGWxbL%2F3vsQrSJzYUotJwGSeqIGPWDArJGQh1k6WkHEGA3lJJJZdytBAlnlPbKVnDNJH9mpnQpsrgXHwWHJv3VxsETI9R%2FIUFLDLr1L%2FFh2quikUeaACApL79fYoiTFGITA9jlL%2B6V1w%2FaIP2egTk8gpYRjGn1NZPnaTLtZ6Q2Gi72A7GUfOSXsY62FfZg5wWB4BAAA%3D

### Expected behavior

It should compile fine and produce appropriate JS file.

### Actual behavior

```
error: Unexpected token `div`. Expected jsx identifier
 --> input.tsx:6:10
  |
6 |         <div
  |          ^^^



Caused by:
    0: failed to process js file
    1: failed to process js file
    2: Syntax Error
```

### Version

1.2.128

### Additional context

_No response_

Pull request:
fix(es/parser): Fix the logic for token contexts
**Description:**

**Related issue (if exists):**


 - Closes #3241

Patch:
diff --git a/crates/swc_ecma_parser/src/lexer/state.rs b/crates/swc_ecma_parser/src/lexer/state.rs
index 7796720e15..39a03ca4b0 100644
--- a/crates/swc_ecma_parser/src/lexer/state.rs
+++ b/crates/swc_ecma_parser/src/lexer/state.rs
@@ -617,7 +617,18 @@ impl TokenContexts {
             }
 
             // If previous token was `{`
-            Some(TokenType::LBrace) => return self.current() == Some(TokenContext::BraceStmt),
+            Some(TokenType::LBrace) => {
+                // https://github.com/swc-project/swc/issues/3241#issuecomment-1029584460
+                // <Blah blah={function (): void {}} />
+                if self.current() == Some(TokenContext::BraceExpr) {
+                    let len = self.len();
+                    if let Some(TokenContext::JSXOpeningTag) = self.0.get(len - 2) {
+                        return true;
+                    }
+                }
+
+                return self.current() == Some(TokenContext::BraceStmt);
+            }
 
             // `class C<T> { ... }`
             Some(TokenType::BinOp(Lt)) | Some(TokenType::BinOp(Gt)) => return true,
@@ -627,6 +638,15 @@ impl TokenContexts {
             _ => {}
         }
 
+        if had_line_break {
+            if let Some(TokenType::Other {
+                before_expr: false, ..
+            }) = prev
+            {
+                return true;
+            }
+        }
+
         !is_expr_allowed
     }
 
diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs
index 3145470c21..c5755a7214 100644
--- a/crates/swc_ecma_parser/src/parser/expr.rs
+++ b/crates/swc_ecma_parser/src/parser/expr.rs
@@ -16,6 +16,8 @@ impl<'a, I: Tokens> Parser<I> {
     pub fn parse_expr(&mut self) -> PResult<Box<Expr>> {
         trace_cur!(self, parse_expr);
 
+        let _tracing = debug_tracing!(self, "parse_expr");
+
         let expr = self.parse_assignment_expr()?;
         let start = expr.span().lo();
 
diff --git a/crates/swc_ecma_parser/src/parser/jsx.rs b/crates/swc_ecma_parser/src/parser/jsx.rs
index affc5ecd65..78c197fbc9 100644
--- a/crates/swc_ecma_parser/src/parser/jsx.rs
+++ b/crates/swc_ecma_parser/src/parser/jsx.rs
@@ -10,6 +10,7 @@ impl<'a, I: Tokens> Parser<I> {
     /// Parse next token as JSX identifier
     pub(super) fn parse_jsx_ident(&mut self) -> PResult<Ident> {
         debug_assert!(self.input.syntax().jsx());
+        trace_cur!(self, parse_jsx_ident);
 
         let ctx = self.ctx();
         match *cur!(self, true)? {
@@ -28,6 +29,7 @@ impl<'a, I: Tokens> Parser<I> {
     /// Parse namespaced identifier.
     pub(super) fn parse_jsx_namespaced_name(&mut self) -> PResult<JSXAttrName> {
         debug_assert!(self.input.syntax().jsx());
+        trace_cur!(self, parse_jsx_namespaced_name);
 
         let ns = self.parse_jsx_ident()?;
         if !eat!(self, ':') {
@@ -45,6 +47,7 @@ impl<'a, I: Tokens> Parser<I> {
     /// identifier.
     pub(super) fn parse_jsx_element_name(&mut self) -> PResult<JSXElementName> {
         debug_assert!(self.input.syntax().jsx());
+        trace_cur!(self, parse_jsx_element_name);
 
         let mut node = match self.parse_jsx_namespaced_name()? {
             JSXAttrName::Ident(i) => JSXElementName::Ident(i),
@@ -70,6 +73,7 @@ impl<'a, I: Tokens> Parser<I> {
     /// TODO(kdy1): Change return type to JSXAttrValue
     pub(super) fn parse_jsx_attr_value(&mut self) -> PResult<JSXAttrValue> {
         debug_assert!(self.input.syntax().jsx());
+        trace_cur!(self, parse_jsx_attr_value);
 
         let start = cur_pos!(self);
 
@@ -153,6 +157,8 @@ impl<'a, I: Tokens> Parser<I> {
         debug_assert!(self.input.syntax().jsx());
         let start = cur_pos!(self);
 
+        let _tracing = debug_tracing!(self, "parse_jsx_attr");
+
         if eat!(self, '{') {
             let dot3_start = cur_pos!(self);
             expect!(self, "...");
@@ -211,6 +217,8 @@ impl<'a, I: Tokens> Parser<I> {
 
         let mut attrs = vec![];
         while let Ok(..) = cur!(self, false) {
+            trace_cur!(self, parse_jsx_opening__attrs_loop);
+
             if is!(self, '/') || is!(self, JSXTagEnd) {
                 break;
             }
@@ -275,7 +283,12 @@ impl<'a, I: Tokens> Parser<I> {
             ..self.ctx()
         };
         self.with_ctx(ctx).parse_with(|p| {
+            let _tracing = debug_tracing!(p, "parse_jsx_element");
+
             let opening_element = p.parse_jsx_opening_element_at(start_pos)?;
+
+            trace_cur!(p, parse_jsx_element__after_opening_element);
+
             let mut children = vec![];
             let mut closing_element = None;
 
diff --git a/crates/swc_ecma_parser/src/parser/macros.rs b/crates/swc_ecma_parser/src/parser/macros.rs
index b52572efee..2c066abaaa 100644
--- a/crates/swc_ecma_parser/src/parser/macros.rs
+++ b/crates/swc_ecma_parser/src/parser/macros.rs
@@ -368,3 +368,17 @@ macro_rules! syntax_error {
         return Err(err.into());
     }};
 }
+
+macro_rules! debug_tracing {
+    ($p:expr, $name:tt) => {{
+        #[cfg(feature = "debug")]
+        {
+            tracing::span!(
+                tracing::Level::ERROR,
+                $name,
+                cur = tracing::field::debug(&$p.input.cur())
+            )
+            .entered()
+        }
+    }};
+}
diff --git a/crates/swc_ecma_parser/src/parser/object.rs b/crates/swc_ecma_parser/src/parser/object.rs
index 332cceef09..e5b46fff17 100644
--- a/crates/swc_ecma_parser/src/parser/object.rs
+++ b/crates/swc_ecma_parser/src/parser/object.rs
@@ -4,7 +4,7 @@ use swc_atoms::js_word;
 use swc_common::Spanned;
 
 use super::*;
-use crate::{lexer::TokenContext, parser::class_and_fn::is_not_this, token::Keyword};
+use crate::parser::class_and_fn::is_not_this;
 
 impl<'a, I: Tokens> Parser<I> {
     /// Parse a object literal or object pattern.
@@ -76,16 +76,7 @@ impl<'a, I: Tokens> Parser<I> {
                     _ => unreachable!(),
                 },
                 Word(..) => match bump!(p) {
-                    Word(w) => {
-                        if let Word::Keyword(Keyword::Function) = w {
-                            // See https://github.com/swc-project/swc/issues/2075
-                            let ctx = p.input.token_context_mut();
-                            let token_ctx = ctx.pop();
-
-                            debug_assert_eq!(token_ctx, Some(TokenContext::FnExpr));
-                        }
-                        PropName::Ident(Ident::new(w.into(), span!(p, start)))
-                    }
+                    Word(w) => PropName::Ident(Ident::new(w.into(), span!(p, start))),
                     _ => unreachable!(),
                 },
                 tok!('[') => {
diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs
index 71ab436707..afa9980bd6 100644
--- a/crates/swc_ecma_parser/src/parser/stmt.rs
+++ b/crates/swc_ecma_parser/src/parser/stmt.rs
@@ -82,6 +82,9 @@ impl<'a, I: Tokens> Parser<I> {
         Type: IsDirective + From<Stmt>,
     {
         trace_cur!(self, parse_stmt_like);
+
+        let _tracing = debug_tracing!(self, "parse_stmt_like");
+
         let start = cur_pos!(self);
         let decorators = self.parse_decorators(true)?;
 
diff --git a/crates/swc_ecma_parser/src/parser/typescript.rs b/crates/swc_ecma_parser/src/parser/typescript.rs
index 4e6d78e59b..24621de5f9 100644
--- a/crates/swc_ecma_parser/src/parser/typescript.rs
+++ b/crates/swc_ecma_parser/src/parser/typescript.rs
@@ -532,11 +532,7 @@ impl<I: Tokens> Parser<I> {
         if !self.input.syntax().typescript() {
             return None;
         }
-        #[cfg(feature = "debug")]
-        let _tracing = {
-            let cur = format!("{:?}", self.input.cur());
-            tracing::span!(tracing::Level::ERROR, "try_parse_ts", cur = &*cur).entered()
-        };
+        let _tracing = debug_tracing!(self, "try_parse_ts");
 
         trace_cur!(self, try_parse_ts);
 
@@ -2636,6 +2632,8 @@ impl<I: Tokens> Parser<I> {
     {
         debug_assert!(self.input.syntax().typescript());
 
+        trace_cur!(self, ts_in_no_context__before);
+
         let cloned = self.input.token_context().clone();
 
         #[cfg(debug_assertions)]
@@ -2647,6 +2645,8 @@ impl<I: Tokens> Parser<I> {
         let res = op(self);
         self.input.set_token_context(cloned);
 
+        trace_cur!(self, ts_in_no_context__after);
+
         res
     }
 }


Test patch:
diff --git a/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.1.normal.js b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.1.normal.js
new file mode 100644
index 0000000000..df9588ce59
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.1.normal.js
@@ -0,0 +1,12 @@
+import * as swcHelpers from "@swc/helpers";
+function make1(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // OK
+}
+function make2(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // Error (x is number, not string)
+}
+function make3(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // Error, missing x
+}
+/*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, {})); // Error, missing x
+/*#__PURE__*/ React.createElement("test2", swcHelpers.extends({}, {})); // Error, missing toString
diff --git a/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.2.minified.js b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.2.minified.js
new file mode 100644
index 0000000000..cd6eeaf575
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es2015.2.minified.js
@@ -0,0 +1,2 @@
+import * as swcHelpers from "@swc/helpers";
+swcHelpers.extends({}, {}), swcHelpers.extends({}, {});
diff --git a/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.1.normal.js b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.1.normal.js
new file mode 100644
index 0000000000..df9588ce59
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.1.normal.js
@@ -0,0 +1,12 @@
+import * as swcHelpers from "@swc/helpers";
+function make1(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // OK
+}
+function make2(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // Error (x is number, not string)
+}
+function make3(obj) {
+    return /*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, obj)); // Error, missing x
+}
+/*#__PURE__*/ React.createElement("test1", swcHelpers.extends({}, {})); // Error, missing x
+/*#__PURE__*/ React.createElement("test2", swcHelpers.extends({}, {})); // Error, missing toString
diff --git a/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.2.minified.js b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.2.minified.js
new file mode 100644
index 0000000000..cd6eeaf575
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxAttributeResolution5_es5.2.minified.js
@@ -0,0 +1,2 @@
+import * as swcHelpers from "@swc/helpers";
+swcHelpers.extends({}, {}), swcHelpers.extends({}, {});
diff --git a/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.1.normal.js b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.1.normal.js
new file mode 100644
index 0000000000..58a739dcb6
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.1.normal.js
@@ -0,0 +1,11 @@
+// @filename: foo.tsx
+// @jsx: preserve
+// @noLib: true
+// @skipLibCheck: true
+// @libFiles: react.d.ts,lib.d.ts
+const React = require('react');
+function Foo() {
+    return null;
+}
+/*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null));
+export { };
diff --git a/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.2.minified.js b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.2.minified.js
new file mode 100644
index 0000000000..1bdffe2483
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es2015.2.minified.js
@@ -0,0 +1,3 @@
+const React = require("react");
+React.Fragment;
+export { };
diff --git a/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.1.normal.js b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.1.normal.js
new file mode 100644
index 0000000000..5d2ee080cf
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.1.normal.js
@@ -0,0 +1,11 @@
+// @filename: foo.tsx
+// @jsx: preserve
+// @noLib: true
+// @skipLibCheck: true
+// @libFiles: react.d.ts,lib.d.ts
+var React = require('react');
+function Foo() {
+    return null;
+}
+/*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null), /*#__PURE__*/ React.createElement(Foo, null));
+export { };
diff --git a/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.2.minified.js b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.2.minified.js
new file mode 100644
index 0000000000..6e5dd828e7
--- /dev/null
+++ b/crates/swc/tests/tsc-references/tsxTypeArgumentsJsxPreserveOutput_es5.2.minified.js
@@ -0,0 +1,2 @@
+require("react").Fragment;
+export { };
diff --git a/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx b/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx
new file mode 100644
index 0000000000..cad36eaa1f
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+
+export function demoBug(): any {
+    return (
+        <div
+            onAttach={(node) => {
+                if (node) {
+                    add(2, 3);
+                }
+                function add(a: number, b: number): void {}
+            }}
+        ></div>
+    );
+}
diff --git a/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx.json b/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx.json
new file mode 100644
index 0000000000..c6eedd149e
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/issue-3241/1/input.tsx.json
@@ -0,0 +1,445 @@
+{
+  "type": "Module",
+  "span": {
+    "start": 0,
+    "end": 297,
+    "ctxt": 0
+  },
+  "body": [
+    {
+      "type": "ImportDeclaration",
+      "span": {
+        "start": 0,
+        "end": 26,
+        "ctxt": 0
+      },
+      "specifiers": [
+        {
+          "type": "ImportDefaultSpecifier",
+          "span": {
+            "start": 7,
+            "end": 12,
+            "ctxt": 0
+          },
+          "local": {
+            "type": "Identifier",
+            "span": {
+              "start": 7,
+              "end": 12,
+              "ctxt": 0
+            },
+            "value": "React",
+            "optional": false
+          }
+        }
+      ],
+      "source": {
+        "type": "StringLiteral",
+        "span": {
+          "start": 18,
+          "end": 25,
+          "ctxt": 0
+        },
+        "value": "react",
+        "hasEscape": false,
+        "kind": {
+          "type": "normal",
+          "containsQuote": true
+        }
+      },
+      "typeOnly": false,
+      "asserts": null
+    },
+    {
+      "type": "ExportDeclaration",
+      "span": {
+        "start": 28,
+        "end": 297,
+        "ctxt": 0
+      },
+      "declaration": {
+        "type": "FunctionDeclaration",
+        "identifier": {
+          "type": "Identifier",
+          "span": {
+            "start": 44,
+            "end": 51,
+            "ctxt": 0
+          },
+          "value": "demoBug",
+          "optional": false
+        },
+        "declare": false,
+        "params": [],
+        "decorators": [],
+        "span": {
+          "start": 35,
+          "end": 297,
+          "ctxt": 0
+        },
+        "body": {
+          "type": "BlockStatement",
+          "span": {
+            "start": 59,
+            "end": 297,
+            "ctxt": 0
+          },
+          "stmts": [
+            {
+              "type": "ReturnStatement",
+              "span": {
+                "start": 65,
+                "end": 295,
+                "ctxt": 0
+              },
+              "argument": {
+                "type": "ParenthesisExpression",
+                "span": {
+                  "start": 72,
+                  "end": 294,
+                  "ctxt": 0
+                },
+                "expression": {
+                  "type": "JSXElement",
+                  "span": {
+                    "start": 82,
+                    "end": 288,
+                    "ctxt": 0
+                  },
+                  "opening": {
+                    "type": "JSXOpeningElement",
+                    "name": {
+                      "type": "Identifier",
+                      "span": {
+                        "start": 83,
+                        "end": 86,
+                        "ctxt": 0
+                      },
+                      "value": "div",
+                      "optional": false
+                    },
+                    "span": {
+                      "start": 82,
+                      "end": 282,
+                      "ctxt": 0
+                    },
+                    "attributes": [
+                      {
+                        "type": "JSXAttribute",
+                        "span": {
+                          "start": 99,
+                          "end": 272,
+                          "ctxt": 0
+                        },
+                        "name": {
+                          "type": "Identifier",
+                          "span": {
+                            "start": 99,
+                            "end": 107,
+                            "ctxt": 0
+                          },
+                          "value": "onAttach",
+                          "optional": false
+                        },
+                        "value": {
+                          "type": "JSXExpressionContainer",
+                          "span": {
+                            "start": 108,
+                            "end": 272,
+                            "ctxt": 0
+                          },
+                          "expression": {
+                            "type": "ArrowFunctionExpression",
+                            "span": {
+                              "start": 109,
+                              "end": 271,
+                              "ctxt": 0
+                            },
+                            "params": [
+                              {
+                                "type": "Identifier",
+                                "span": {
+                                  "start": 110,
+                                  "end": 114,
+                                  "ctxt": 0
+                                },
+                                "value": "node",
+                                "optional": false,
+                                "typeAnnotation": null
+                              }
+                            ],
+                            "body": {
+                              "type": "BlockStatement",
+                              "span": {
+                                "start": 119,
+                                "end": 271,
+                                "ctxt": 0
+                              },
+                              "stmts": [
+                                {
+                                  "type": "IfStatement",
+                                  "span": {
+                                    "start": 137,
+                                    "end": 197,
+                                    "ctxt": 0
+                                  },
+                                  "test": {
+                                    "type": "Identifier",
+                                    "span": {
+                                      "start": 141,
+                                      "end": 145,
+                                      "ctxt": 0
+                                    },
+                                    "value": "node",
+                                    "optional": false
+                                  },
+                                  "consequent": {
+                                    "type": "BlockStatement",
+                                    "span": {
+                                      "start": 147,
+                                      "end": 197,
+                                      "ctxt": 0
+                                    },
+                                    "stmts": [
+                                      {
+                                        "type": "ExpressionStatement",
+                                        "span": {
+                                          "start": 169,
+                                          "end": 179,
+                                          "ctxt": 0
+                                        },
+                                        "expression": {
+                                          "type": "CallExpression",
+                                          "span": {
+                                            "start": 169,
+                                            "end": 178,
+                                            "ctxt": 0
+                                          },
+                                          "callee": {
+                                            "type": "Identifier",
+                                            "span": {
+                                              "start": 169,
+                                              "end": 172,
+                                              "ctxt": 0
+                                            },
+                                            "value": "add",
+                                            "optional": false
+                                          },
+                                          "arguments": [
+                                            {
+                                              "spread": null,
+                                              "expression": {
+                                                "type": "NumericLiteral",
+                                                "span": {
+                                                  "start": 173,
+                                                  "end": 174,
+                                                  "ctxt": 0
+                                                },
+                                                "value": 2.0
+                                              }
+                                            },
+                                            {
+                                              "spread": null,
+                                              "expression": {
+                                                "type": "NumericLiteral",
+                                                "span": {
+                                                  "start": 176,
+                                                  "end": 177,
+                                                  "ctxt": 0
+                                                },
+                                                "value": 3.0
+                                              }
+                                            }
+                                          ],
+                                          "typeArguments": null
+                                        }
+                                      }
+                                    ]
+                                  },
+                                  "alternate": null
+                                },
+                                {
+                                  "type": "FunctionDeclaration",
+                                  "identifier": {
+                                    "type": "Identifier",
+                                    "span": {
+                                      "start": 223,
+                                      "end": 226,
+                                      "ctxt": 0
+                                    },
+                                    "value": "add",
+                                    "optional": false
+                                  },
+                                  "declare": false,
+                                  "params": [
+                                    {
+                                      "type": "Parameter",
+                                      "span": {
+                                        "start": 227,
+                                        "end": 236,
+                                        "ctxt": 0
+                                      },
+                                      "decorators": [],
+                                      "pat": {
+                                        "type": "Identifier",
+                                        "span": {
+                                          "start": 227,
+                                          "end": 236,
+                                          "ctxt": 0
+                                        },
+                                        "value": "a",
+                                        "optional": false,
+                                        "typeAnnotation": {
+                                          "type": "TsTypeAnnotation",
+                                          "span": {
+                                            "start": 228,
+                                            "end": 236,
+                                            "ctxt": 0
+                                          },
+                                          "typeAnnotation": {
+                                            "type": "TsKeywordType",
+                                            "span": {
+                                              "start": 230,
+                                              "end": 236,
+                                              "ctxt": 0
+                                            },
+                                            "kind": "number"
+                                          }
+                                        }
+                                      }
+                                    },
+                                    {
+                                      "type": "Parameter",
+                                      "span": {
+                                        "start": 238,
+                                        "end": 247,
+                                        "ctxt": 0
+                                      },
+                                      "decorators": [],
+                                      "pat": {
+                                        "type": "Identifier",
+                                        "span": {
+                                          "start": 238,
+                                          "end": 247,
+                                          "ctxt": 0
+                                        },
+                                        "value": "b",
+                                        "optional": false,
+                                        "typeAnnotation": {
+                                          "type": "TsTypeAnnotation",
+                                          "span": {
+                                            "start": 239,
+                                            "end": 247,
+                                            "ctxt": 0
+                                          },
+                                          "typeAnnotation": {
+                                            "type": "TsKeywordType",
+                                            "span": {
+                                              "start": 241,
+                                              "end": 247,
+                                              "ctxt": 0
+                                            },
+                                            "kind": "number"
+                                          }
+                                        }
+                                      }
+                                    }
+                                  ],
+                                  "decorators": [],
+                                  "span": {
+                                    "start": 214,
+                                    "end": 257,
+                                    "ctxt": 0
+                                  },
+                                  "body": {
+                                    "type": "BlockStatement",
+                                    "span": {
+                                      "start": 255,
+                                      "end": 257,
+                                      "ctxt": 0
+                                    },
+                                    "stmts": []
+                                  },
+                                  "generator": false,
+                                  "async": false,
+                                  "typeParameters": null,
+                                  "returnType": {
+                                    "type": "TsTypeAnnotation",
+                                    "span": {
+                                      "start": 248,
+                                      "end": 254,
+                                      "ctxt": 0
+                                    },
+                                    "typeAnnotation": {
+                                      "type": "TsKeywordType",
+                                      "span": {
+                                        "start": 250,
+                                        "end": 254,
+                                        "ctxt": 0
+                                      },
+                                      "kind": "void"
+                                    }
+                                  }
+                                }
+                              ]
+                            },
+                            "async": false,
+                            "generator": false,
+                            "typeParameters": null,
+                            "returnType": null
+                          }
+                        }
+                      }
+                    ],
+                    "selfClosing": false,
+                    "typeArguments": null
+                  },
+                  "children": [],
+                  "closing": {
+                    "type": "JSXClosingElement",
+                    "span": {
+                      "start": 282,
+                      "end": 288,
+                      "ctxt": 0
+                    },
+                    "name": {
+                      "type": "Identifier",
+                      "span": {
+                        "start": 284,
+                        "end": 287,
+                        "ctxt": 0
+                      },
+                      "value": "div",
+                      "optional": false
+                    }
+                  }
+                }
+              }
+            }
+          ]
+        },
+        "generator": false,
+        "async": false,
+        "typeParameters": null,
+        "returnType": {
+          "type": "TsTypeAnnotation",
+          "span": {
+            "start": 53,
+            "end": 58,
+            "ctxt": 0
+          },
+          "typeAnnotation": {
+            "type": "TsKeywordType",
+            "span": {
+              "start": 55,
+              "end": 58,
+              "ctxt": 0
+            },
+            "kind": "any"
+          }
+        }
+      }
+    }
+  ],
+  "interpreter": null
+}
diff --git a/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx b/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx
new file mode 100644
index 0000000000..60776a73a5
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx
@@ -0,0 +1,1 @@
+<Blah blah={function (): void {}} />;
diff --git a/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx.json b/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx.json
new file mode 100644
index 0000000000..62c0b6eacf
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/issue-3241/2/input.tsx.json
@@ -0,0 +1,117 @@
+{
+  "type": "Script",
+  "span": {
+    "start": 0,
+    "end": 37,
+    "ctxt": 0
+  },
+  "body": [
+    {
+      "type": "ExpressionStatement",
+      "span": {
+        "start": 0,
+        "end": 37,
+        "ctxt": 0
+      },
+      "expression": {
+        "type": "JSXElement",
+        "span": {
+          "start": 0,
+          "end": 36,
+          "ctxt": 0
+        },
+        "opening": {
+          "type": "JSXOpeningElement",
+          "name": {
+            "type": "Identifier",
+            "span": {
+              "start": 1,
+              "end": 5,
+              "ctxt": 0
+            },
+            "value": "Blah",
+            "optional": false
+          },
+          "span": {
+            "start": 0,
+            "end": 36,
+            "ctxt": 0
+          },
+          "attributes": [
+            {
+              "type": "JSXAttribute",
+              "span": {
+                "start": 6,
+                "end": 33,
+                "ctxt": 0
+              },
+              "name": {
+                "type": "Identifier",
+                "span": {
+                  "start": 6,
+                  "end": 10,
+                  "ctxt": 0
+                },
+                "value": "blah",
+                "optional": false
+              },
+              "value": {
+                "type": "JSXExpressionContainer",
+                "span": {
+                  "start": 11,
+                  "end": 33,
+                  "ctxt": 0
+                },
+                "expression": {
+                  "type": "FunctionExpression",
+                  "identifier": null,
+                  "params": [],
+                  "decorators": [],
+                  "span": {
+                    "start": 12,
+                    "end": 32,
+                    "ctxt": 0
+                  },
+                  "body": {
+                    "type": "BlockStatement",
+                    "span": {
+                      "start": 30,
+                      "end": 32,
+                      "ctxt": 0
+                    },
+                    "stmts": []
+                  },
+                  "generator": false,
+                  "async": false,
+                  "typeParameters": null,
+                  "returnType": {
+                    "type": "TsTypeAnnotation",
+                    "span": {
+                      "start": 23,
+                      "end": 29,
+                      "ctxt": 0
+                    },
+                    "typeAnnotation": {
+                      "type": "TsKeywordType",
+                      "span": {
+                        "start": 25,
+                        "end": 29,
+                        "ctxt": 0
+                      },
+                      "kind": "void"
+                    }
+                  }
+                }
+              }
+            }
+          ],
+          "selfClosing": true,
+          "typeArguments": null
+        },
+        "children": [],
+        "closing": null
+      }
+    }
+  ],
+  "interpreter": null
+}