Repository: swc-project/swc (rust)

Issue:
Panic for multiple JSX pragmas (MDX v2)
**Describe the bug**

The JS code generated by `@mdx-js/mdx` contains multiple JSX pragmas in a single coment.
If processing such code the React transform chokes with a panic `proper error reporting for wrong @jsxRuntime`.

**Input code**

```js
/* @jsxRuntime automatic @jsxImportSource react */
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'

export const Thing = () => _jsx(_Fragment, {children: 'World!'})

function MDXContent(props = {}) {
  const _components = Object.assign({h1: 'h1'}, props.components)
  const {wrapper: MDXLayout} = _components
  const _content = _jsx(_Fragment, {
    children: _jsxs(_components.h1, {
      children: ['Hello, ', _jsx(Thing, {})]
    })
  })
  return MDXLayout
    ? _jsx(MDXLayout, Object.assign({}, props, {children: _content}))
    : _content
}

export default MDXContent
```

See https://github.com/mdx-js/mdx/blob/main/docs/docs/using-mdx.server.mdx#how-mdx-works for examples.

**Config**

Next.js 12 defaults.

**Expected behavior**

The JS should be processed correctly, even if multiple pragmas are present in a single line (like Babel).

**Version**
The version of @swc/core:

I can't tell the version, it's contained in next@12.0.1.

**Additional context**

See bug reports https://github.com/vercel/next.js/issues/30436#issue-1037412772 and https://github.com/mdx-js/mdx/issues/1783.


Pull request description:
fix(es/transform/react): Allow multiple JSX pragmas in single `Comment`
Fixes #2560

This is my very first piece of Rust, so I hope it makes some sense.
The idea is to split each comment line by any amount of whitespace and process pairs of a directive and value (Babel does this by using regexps).

I wanted to include a test assertion to check for the correct `res.runtime` etc. options being set - but I couldn't figure that out using the `test!` macro.

Patch:
diff --git a/ecmascript/transforms/react/src/jsx/mod.rs b/ecmascript/transforms/react/src/jsx/mod.rs
index bfe3326e0a..5c3e740df2 100644
--- a/ecmascript/transforms/react/src/jsx/mod.rs
+++ b/ecmascript/transforms/react/src/jsx/mod.rs
@@ -279,41 +279,51 @@ impl JsxDirectives {
                     continue;
                 }
 
-                if line.starts_with("@jsxRuntime ") {
-                    let src = line.replace("@jsxRuntime ", "").trim().to_string();
-                    if src == "classic" {
-                        res.runtime = Some(Runtime::Classic);
-                    } else if src == "automatic" {
-                        res.runtime = Some(Runtime::Automatic);
-                    } else {
-                        todo!("proper error reporting for wrong `@jsxRuntime`")
+                let mut words = line.split_whitespace();
+                loop {
+                    let pragma = words.next();
+                    if pragma.is_none() {
+                        break;
                     }
-                    continue;
-                }
+                    let val = words.next();
 
-                if line.starts_with("@jsxImportSource") {
-                    let src = line.replace("@jsxImportSource", "").trim().to_string();
-                    res.runtime = Some(Runtime::Automatic);
-                    res.import_source = Some(src.into());
-                }
-
-                if line.starts_with("@jsxFrag") {
-                    let src = line.replace("@jsxFrag", "").trim().to_string();
-                    res.pragma_frag = Some(parse_expr_for_jsx(
-                        &cm,
-                        "module-jsx-pragma-frag",
-                        src,
-                        top_level_mark,
-                    ));
-                } else if line.starts_with("@jsx ") {
-                    let src = line.replace("@jsx", "").trim().to_string();
-
-                    res.pragma = Some(parse_expr_for_jsx(
-                        &cm,
-                        "module-jsx-pragma",
-                        src,
-                        top_level_mark,
-                    ));
+                    match pragma {
+                        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`"),
+                        },
+                        Some("@jsxImportSource") => match val {
+                            Some(src) => {
+                                res.runtime = Some(Runtime::Automatic);
+                                res.import_source = Some(src.into());
+                            }
+                            _ => todo!("proper error reporting for wrong `@jsxImportSource`"),
+                        },
+                        Some("@jsxFrag") => match val {
+                            Some(src) => {
+                                res.pragma_frag = Some(parse_expr_for_jsx(
+                                    &cm,
+                                    "module-jsx-pragma-frag",
+                                    src.to_string(),
+                                    top_level_mark,
+                                ))
+                            }
+                            _ => todo!("proper error reporting for wrong `@jsxFrag`"),
+                        },
+                        Some("@jsx") => match val {
+                            Some(src) => {
+                                res.pragma = Some(parse_expr_for_jsx(
+                                    &cm,
+                                    "module-jsx-pragma",
+                                    src.to_string(),
+                                    top_level_mark,
+                                ));
+                            }
+                            _ => todo!("proper error reporting for wrong `@jsxFrag`"),
+                        },
+                        _ => {}
+                    }
                 }
             }
         }


Test patch:
diff --git a/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/input.js b/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/input.js
new file mode 100644
index 0000000000..cc52ab9f6b
--- /dev/null
+++ b/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/input.js
@@ -0,0 +1,3 @@
+/* @jsxRuntime automatic @jsxImportSource preact */
+
+var div = <div>test</div>;
diff --git a/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/output.js b/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/output.js
new file mode 100644
index 0000000000..60c731b4ff
--- /dev/null
+++ b/ecmascript/transforms/react/tests/jsx/fixture/react/should-allow-multiple-pragmas-per-line/output.js
@@ -0,0 +1,5 @@
+/* @jsxRuntime automatic @jsxImportSource react */
+import { jsx as _jsx } from "preact/jsx-runtime";
+var div = _jsx("div", {
+    children: "test"
+});
\ No newline at end of file