Repository: swc-project/swc (rust)

Issue:
Invalid jsx pragma like `@jsx foo-bar` make swc panic
### Describe the bug

Invalid identifier passed to `@jsx` directive panics.

### Input code

```typescript
/** @jsx foo-bar */
```

### Playground link

https://play.swc.rs/?version=1.3.74&code=H4sIAAAAAAAAA9PX0lJwyCquUEjLz9dNSixS0NIHAK97uOgTAAAA&config=H4sIAAAAAAAAA1WPTQ7CIBCF9z0FmbULdWGid%2FAQBKcNhr8w00TS9O4FSlF3zPfe480sgxDwJgUPseRnHoKMhLHPmVByLD%2BZAKeApKIODKdDZSoSxxkrWXcBWMYJuYSQrufLvQXAeE%2BY8SgNYWNWOz2m30rlbYhI9G8sVukm0%2FN749BawfrXXMV2Sll33%2BAGX9NR1j8GTc8jWe9YN%2Flzgg8WAQAA

### Expected behavior

Should ignore the directive.

### Actual behavior

Panic:
```
thread 'main' panicked at 'failed to determine top-level Id for jsx expression', /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/swc_ecma_transforms_typescript-0.180.33/src/strip.rs:253:13
```

### Version

swc_ecma_transforms_typescript 0.180.33

Pull request description:
fix(es/react): Validate pragma before parsing
**Description:**


**Related issue:**

 - Closes #7938

Patch:
diff --git a/crates/swc_ecma_transforms_react/src/jsx/mod.rs b/crates/swc_ecma_transforms_react/src/jsx/mod.rs
index be97d9f2fb..f157d9923f 100644
--- a/crates/swc_ecma_transforms_react/src/jsx/mod.rs
+++ b/crates/swc_ecma_transforms_react/src/jsx/mod.rs
@@ -118,7 +118,7 @@ pub fn parse_expr_for_jsx(
     src: String,
     top_level_mark: Mark,
 ) -> Arc<Box<Expr>> {
-    let fm = cm.new_source_file(FileName::Internal(format!("<jsx-config-{}.js>", name)), src);
+    let fm = cm.new_source_file(FileName::Internal(format!("jsx-config-{}.js", name)), src);
 
     parse_file_as_expr(
         &fm,
@@ -131,7 +131,7 @@ pub fn parse_expr_for_jsx(
         if HANDLER.is_set() {
             HANDLER.with(|h| {
                 e.into_diagnostic(h)
-                    .note("error detected while parsing option for classic jsx transform")
+                    .note("Failed to parse jsx pragma")
                     .emit()
             })
         }
@@ -339,30 +339,34 @@ impl JsxDirectives {
                         }
                         Some("@jsxFrag") => {
                             if let Some(src) = val {
-                                // TODO: Optimize
-                                let mut e = (*parse_expr_for_jsx(
-                                    cm,
-                                    "module-jsx-pragma-frag",
-                                    src.to_string(),
-                                    top_level_mark,
-                                ))
-                                .clone();
-                                respan(&mut e, cmt.span);
-                                res.pragma_frag = Some(e.into())
+                                if is_valid_for_pragma(src) {
+                                    // TODO: Optimize
+                                    let mut e = (*parse_expr_for_jsx(
+                                        cm,
+                                        "module-jsx-pragma-frag",
+                                        src.to_string(),
+                                        top_level_mark,
+                                    ))
+                                    .clone();
+                                    respan(&mut e, cmt.span);
+                                    res.pragma_frag = Some(e.into())
+                                }
                             }
                         }
                         Some("@jsx") => {
                             if let Some(src) = val {
-                                // TODO: Optimize
-                                let mut e = (*parse_expr_for_jsx(
-                                    cm,
-                                    "module-jsx-pragma",
-                                    src.to_string(),
-                                    top_level_mark,
-                                ))
-                                .clone();
-                                respan(&mut e, cmt.span);
-                                res.pragma = Some(e.into());
+                                if is_valid_for_pragma(src) {
+                                    // TODO: Optimize
+                                    let mut e = (*parse_expr_for_jsx(
+                                        cm,
+                                        "module-jsx-pragma",
+                                        src.to_string(),
+                                        top_level_mark,
+                                    ))
+                                    .clone();
+                                    respan(&mut e, cmt.span);
+                                    res.pragma = Some(e.into());
+                                }
                             }
                         }
                         _ => {}
@@ -375,6 +379,24 @@ impl JsxDirectives {
     }
 }
 
+fn is_valid_for_pragma(s: &str) -> bool {
+    if s.is_empty() {
+        return false;
+    }
+
+    if !s.starts_with(|c: char| Ident::is_valid_start(c)) {
+        return false;
+    }
+
+    for c in s.chars() {
+        if !Ident::is_valid_continue(c) && c != '.' {
+            return false;
+        }
+    }
+
+    true
+}
+
 impl<C> Jsx<C>
 where
     C: Comments,


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7938/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7938/input/.swcrc
new file mode 100644
index 0000000000..4617fdd2b7
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7938/input/.swcrc
@@ -0,0 +1,19 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "tsx": true
+        },
+        "target": "es2019",
+        "loose": false,
+        "minify": {
+            "compress": false,
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7938/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7938/input/1.js
new file mode 100644
index 0000000000..44e97cd2d1
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7938/input/1.js
@@ -0,0 +1,1 @@
+/** @jsx foo-bar */
diff --git a/crates/swc/tests/fixture/issues-8xxx/8572/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8572/input/.swcrc
new file mode 100644
index 0000000000..5e56088a8f
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8572/input/.swcrc
@@ -0,0 +1,25 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "tsx": true
+        },
+        "target": "es2022",
+        "loose": false,
+        "minify": {
+            "compress": false,
+            "mangle": false
+        },
+        "transform": {
+            "react": {
+                "importSource": "npm:react",
+                "runtime": "automatic"
+            }
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8572/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8572/input/1.js
new file mode 100644
index 0000000000..e48eb63782
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8572/input/1.js
@@ -0,0 +1,1 @@
+/** @jsx a- */
\ No newline at end of file