Repository: swc-project/swc (rust)

Issue:
Panic on missing @jsxFrag value
### Describe the bug

SWC panics if `@jsxFrag` is missing a value:



### Input code

```typescript
/** @jsx h */
/** @jsxFrag */
import { h } from "preact";
import { Handlers, PageProps } from "$fresh/server.ts";
import { Marked, Parsed } from "markdown";

type Data = { markup: Parsed };

export const handler: Handlers<Data> = {
  async GET(req, ctx) {
    const markdown = await Deno.readTextFile(`posts/${ctx.params.id}.md`)
    const markup = Marked.parse(markdown);
    const resp = await ctx.render({ markup });
    return resp;
  },
};

export default function Greet(props: PageProps<Data>) {
  return (
    <>
      <div
        dangerouslySetInnerHTML={{
          __html: props.data.markup.content,
        }}
      />
    </>
  );
}
```
```


### Config

```json
n/a
```


### Playground link

https://play.swc.rs/?version=1.2.208&code=H4sIAAAAAAAAA11RS2%2FCMAy%2B91dYiENBqL2Xh3ZgwKQhIY07RI2Bbm2SOS4Pof73JW0paKc49vfwl8TDIbx92yucYBgHcXtbkDj6e1YYTQx3N63gQLqAniEUKffGz9lKKJkj2RFsxBE3pI3t0P0DoT3FFumMFLF95a0F%2FaD0LLIoO0rh2lJflIMGfDMIc8ECpo7gJ6VJOoID4LUWS7WyDKdmkaTbaOKpM88NAIS9qRSW79uQ8HcEKV8HdR9a9sPXwcVFZAxzVDpyaeUWr7zIcgz3Rlu2cf%2FuyJERJAobZbKKCrkf%2FFMqjdNpEnqkxfChPxi%2FQN3rmM7QqxIqiRQ%2BwkLVwgm5JFXjfaMaBS%2FxJR5EmTMcSpVyphUsCZFD478ief5K8xxN6lYvrMUns%2FpwhczObQkghToi6dLmty%2FkD6WQVtv15%2FR%2B7yAAu92JizyB2iuSziBqNo9cQEbFow5cVW0ZN3aT%2BnT5qj%2FIWu19hQIAAA%3D%3D&config=H4sIAAAAAAAAA0WPQQ6DMAwE%2F%2BIzh4pDD%2Fyhj4iCQ1MlcWSbqhTxd5JAy23lXXvHK7zEwrBCNizIVcmS1HxgAF0yimWfFTpQKSPlGbeiDU%2BoJYHS3%2Fq%2BuIFIEAZngmAH0SfvlnrLUsyMIpdl0hR%2ByXqKTRJHHGua0Vg9hCtbz6OwgxHfGChHTPpnoKw%2B%2Bu%2BJ7GMOrbK5W%2FEjjXMtWtsbjfUO28V28nh5nMG2uQOa%2FUZ1DwEAAA%3D%3D

### Expected behavior

No panic, there's an error returned that we can report

### Actual behavior

```
thread 'main' panicked at 'not yet implemented: proper error reporting for wrong `@jsxFrag`', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/swc_ecma_transforms_react-0.120.0/src/jsx/mod.rs:338:34
stack backtrace:
   0:     0x55d31e1aa18d - std::backtrace_rs::backtrace::libunwind::trace::h22893a5306c091b4
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
   1:     0x55d31e1aa18d - std::backtrace_rs::backtrace::trace_unsynchronized::h29c3bc6f9e91819d
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x55d31e1aa18d - std::sys_common::backtrace::_print_fmt::he497d8a0ec903793
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/sys_common/backtrace.rs:66:5
   3:     0x55d31e1aa18d - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h9c2a9d2774d81873
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/sys_common/backtrace.rs:45:22
   4:     0x55d31e1d3e0c - core::fmt::write::hba4337c43d992f49
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/fmt/mod.rs:1194:17
   5:     0x55d31e1a1931 - std::io::Write::write_fmt::heb73de6e02cfabed
                               at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/io/mod.rs:1655:15
   6:
...
```


### Version

swc_ecma_transforms_react@0.120.0

### Additional context

n/a

Pull request description:
fix(es/react): error report of react pragma
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
- fix #5099 

Patch:
diff --git a/crates/swc_ecma_transforms_react/src/jsx/mod.rs b/crates/swc_ecma_transforms_react/src/jsx/mod.rs
index 1d379bf3d3..3466260cd6 100644
--- a/crates/swc_ecma_transforms_react/src/jsx/mod.rs
+++ b/crates/swc_ecma_transforms_react/src/jsx/mod.rs
@@ -313,17 +313,26 @@ impl JsxDirectives {
                         Some("@jsxRuntime") => match val {
                             Some("classic") => res.runtime = Some(Runtime::Classic),
                             Some("automatic") => res.runtime = Some(Runtime::Automatic),
-                            _ => todo!("proper error reporting for wrong `@jsxRuntime`"),
+                            None => {}
+                            _ => {
+                                HANDLER.with(|handler| {
+                                    handler
+                                        .struct_span_err(
+                                            cmt.span,
+                                            "Runtime must be either `classic` or `automatic`.",
+                                        )
+                                        .emit()
+                                });
+                            }
                         },
-                        Some("@jsxImportSource") => match val {
-                            Some(src) => {
+                        Some("@jsxImportSource") => {
+                            if let Some(src) = val {
                                 res.runtime = Some(Runtime::Automatic);
                                 res.import_source = Some(src.into());
                             }
-                            _ => todo!("proper error reporting for wrong `@jsxImportSource`"),
-                        },
-                        Some("@jsxFrag") => match val {
-                            Some(src) => {
+                        }
+                        Some("@jsxFrag") => {
+                            if let Some(src) = val {
                                 // TODO: Optimize
                                 let mut e = (*parse_expr_for_jsx(
                                     cm,
@@ -335,10 +344,9 @@ impl JsxDirectives {
                                 respan(&mut e, cmt.span);
                                 res.pragma_frag = Some(e.into())
                             }
-                            _ => todo!("proper error reporting for wrong `@jsxFrag`"),
-                        },
-                        Some("@jsx") => match val {
-                            Some(src) => {
+                        }
+                        Some("@jsx") => {
+                            if let Some(src) = val {
                                 // TODO: Optimize
                                 let mut e = (*parse_expr_for_jsx(
                                     cm,
@@ -350,8 +358,7 @@ impl JsxDirectives {
                                 respan(&mut e, cmt.span);
                                 res.pragma = Some(e.into());
                             }
-                            _ => todo!("proper error reporting for wrong `@jsxFrag`"),
-                        },
+                        }
                         _ => {}
                     }
                 }


Test patch:
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/input.js b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/input.js
new file mode 100644
index 0000000000..2f0ad8ab1f
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/input.js
@@ -0,0 +1,26 @@
+/** @jsx h */
+/** @jsxFrag */
+import { h } from "preact";
+
+import { Marked } from "markdown";
+
+export const handler = {
+    async GET(req, ctx) {
+        const markdown = await Deno.readTextFile(`posts/${ctx.params.id}.md`);
+        const markup = Marked.parse(markdown);
+        const resp = await ctx.render({ markup });
+        return resp;
+    },
+};
+
+export default function Greet(props) {
+    return (
+        <>
+            <div
+                dangerouslySetInnerHTML={{
+                    __html: props.data.markup.content,
+                }}
+            />
+        </>
+    );
+}
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/output.mjs
new file mode 100644
index 0000000000..3262ebfea6
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/1/output.mjs
@@ -0,0 +1,19 @@
+/** @jsx h */ /** @jsxFrag */ import { h } from "preact";
+import { Marked } from "markdown";
+export const handler = {
+    async GET (req, ctx) {
+        const markdown = await Deno.readTextFile(`posts/${ctx.params.id}.md`);
+        const markup = Marked.parse(markdown);
+        const resp = await ctx.render({
+            markup
+        });
+        return resp;
+    }
+};
+export default function Greet(props) {
+    return /*#__PURE__*/ h(React.Fragment, null, /*#__PURE__*/ h("div", {
+        dangerouslySetInnerHTML: {
+            __html: props.data.markup.content
+        }
+    }));
+};
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/input.js b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/input.js
new file mode 100644
index 0000000000..69d5055192
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/input.js
@@ -0,0 +1,3 @@
+/** @jsxRuntime typo */
+
+<></>;
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.mjs
new file mode 100644
index 0000000000..7583c856ab
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.mjs
@@ -0,0 +1,1 @@
+/** @jsxRuntime typo */ /*#__PURE__*/ React.createElement(React.Fragment, null);
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.stderr b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.stderr
new file mode 100644
index 0000000000..581d01a4ce
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/2/output.stderr
@@ -0,0 +1,6 @@
+
+  x Runtime must be either `classic` or `automatic`.
+   ,-[input.js:1:1]
+ 1 | /** @jsxRuntime typo */
+   : ^^^^^^^^^^^^^^^^^^^^^^^
+   `----
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/input.js b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/input.js
new file mode 100644
index 0000000000..ed90d732d1
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/input.js
@@ -0,0 +1,6 @@
+/** @jsxRuntime */
+/** @jsxImportSource */
+/** @jsxFrag */
+/** @jsx */
+
+<></>;
diff --git a/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/output.mjs b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/output.mjs
new file mode 100644
index 0000000000..7d0aa33643
--- /dev/null
+++ b/crates/swc_ecma_transforms_react/tests/jsx/fixture/issue-5099/empty-pragma/output.mjs
@@ -0,0 +1,1 @@
+/** @jsxRuntime */ /** @jsxImportSource */ /** @jsxFrag */ /** @jsx */ /*#__PURE__*/ React.createElement(React.Fragment, null);