Repository: swc-project/swc (rust)

Issue:
The parser does not throw an error for a namespace export with a named export
### Describe the bug

Named export is not supposed to be worked with export namespaced at the same time.
[Rollup Repl](https://rollupjs.org/repl/?version=2.70.2&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmV4cG9ydCUyMColMjBhcyUyMG1haW4lMkMlMjAlN0IlMjBjdWJlJTIwJTdEJTIwZnJvbSUyMCU1QyUyMi4lMkZtYWluJTVDJTIyJTIyJTJDJTIyaXNFbnRyeSUyMiUzQXRydWUlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIybWF0aHMuanMlMjIlMkMlMjJjb2RlJTIyJTNBJTIyaW1wb3J0JTIwc3F1YXJlJTIwZnJvbSUyMCcuJTJGc3F1YXJlLmpzJyUzQiU1Q24lNUNuZXhwb3J0JTIwJTdCZGVmYXVsdCUyMGFzJTIwc3F1YXJlJTdEJTIwZnJvbSUyMCcuJTJGc3F1YXJlLmpzJyUzQiU1Q24lNUNuZXhwb3J0JTIwZnVuY3Rpb24lMjBjdWJlJTIwKHglMjApJTIwJTdCJTVDbiU1Q3RyZXR1cm4lMjBzcXVhcmUoeCklMjAqJTIweCUzQiU1Q24lN0QlMjIlMkMlMjJpc0VudHJ5JTIyJTNBZmFsc2UlN0QlMkMlN0IlMjJuYW1lJTIyJTNBJTIyc3F1YXJlLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMmV4cG9ydCUyMGRlZmF1bHQlMjBmdW5jdGlvbiUyMHNxdWFyZSUyMCglMjB4JTIwKSUyMCU3QiU1Q24lNUN0cmV0dXJuJTIweCUyMColMjB4JTNCJTVDbiU3RCUyMiUyQyUyMmlzRW50cnklMjIlM0FmYWxzZSU3RCU1RCUyQyUyMm9wdGlvbnMlMjIlM0ElN0IlMjJmb3JtYXQlMjIlM0ElMjJlcyUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE)

### Input code

```typescript
export * as foo, { bar } from "./foo"
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false
    },
    "target": "es2022",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "commonjs"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.157&code=H4sIAAAAAAAAA0utKMgvKlFISU3OSSxKVUgrzUsuyczPU0jLz7ex00i0UgjRtFIoy89MsebiSoWo1VJILAbJ6yhUK5SkFpco1CqkFeXnKijp6ScWp6QpAQCCetchUwAAAA%3D%3D&config=H4sIAAAAAAAAA0WNSwrDMAxE76J1FsXL3KGHMK4SHOIPGgdqjO9euThkJ2nePDU64GhtlK2AZUyosdgvrVRqZjjxudBCBXra7AnuuljZuSjCMC9jND5TAk9goeCj3%2BqQuRSyMPBENu7nTXZ1hfS5xqH9%2F6lTKyHFA9Qf0Sx7vCdd5OL%2BAwzZOlG9AAAA

### Expected behavior

Throw an syntax error.

### Actual behavior

Successfully parsed

### Version

1.2.157

### Additional context

_No response_

Pull request description:
fix(es/parser): export default/namespace/named
<!-- 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 #4369 

Patch:
diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs
index 71850b40b3..7046241f3f 100644
--- a/crates/swc_ecma_parser/src/parser/stmt.rs
+++ b/crates/swc_ecma_parser/src/parser/stmt.rs
@@ -1644,6 +1644,67 @@ let x = 4";
         );
     }
 
+    #[test]
+    #[should_panic(expected = "Expected 'from', got ','")]
+    fn issue_4369_1() {
+        test_parser(
+            r#"export * as foo, { bar } from "mod""#,
+            Syntax::Es(EsConfig {
+                export_default_from: false,
+                ..Default::default()
+            }),
+            |p| p.parse_module(),
+        );
+    }
+
+    #[test]
+    fn issue_4369_2() {
+        test_parser(
+            r#"export foo, * as bar, { baz } from "mod""#,
+            Syntax::Es(EsConfig {
+                export_default_from: true,
+                ..Default::default()
+            }),
+            |p| p.parse_module(),
+        );
+    }
+
+    #[test]
+    fn issue_4369_3() {
+        test_parser(
+            r#"export foo, * as bar from "mod""#,
+            Syntax::Es(EsConfig {
+                export_default_from: true,
+                ..Default::default()
+            }),
+            |p| p.parse_module(),
+        );
+    }
+
+    #[test]
+    fn issue_4369_4() {
+        test_parser(
+            r#"export * as bar, { baz } from "mod""#,
+            Syntax::Es(EsConfig {
+                export_default_from: true,
+                ..Default::default()
+            }),
+            |p| p.parse_module(),
+        );
+    }
+
+    #[test]
+    fn issue_4369_5() {
+        test_parser(
+            r#"export foo, { baz } from "mod""#,
+            Syntax::Es(EsConfig {
+                export_default_from: true,
+                ..Default::default()
+            }),
+            |p| p.parse_module(),
+        );
+    }
+
     #[test]
     fn issue_257_var() {
         test_parser(
diff --git a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs
index 2a91877e50..43da5509d8 100644
--- a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs
+++ b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs
@@ -409,43 +409,14 @@ impl<I: Tokens> Parser<I> {
             }
         }
 
-        let mut has_star = false;
-        let mut export_ns = None;
         let ns_export_specifier_start = cur_pos!(self);
 
         let type_only = self.input.syntax().typescript() && eat!(self, "type");
 
-        if eat!(self, '*') {
-            has_star = true;
-            if self.input.syntax().typescript() && type_only {
-                // export type * from "mod";
-                // or
-                // export type * as foo from "mod";
-                self.emit_err(span!(self, start), SyntaxError::TS1383)
-            }
-            if is!(self, "from") {
-                let (src, asserts) = self.parse_from_clause_and_semi()?;
-                return Ok(ModuleDecl::ExportAll(ExportAll {
-                    span: span!(self, start),
-                    src,
-                    asserts,
-                }));
-            }
-            if eat!(self, "as") {
-                let _ = cur!(self, false);
-
-                let name = self.parse_module_export_name()?;
-                export_ns = Some(ExportSpecifier::Namespace(ExportNamespaceSpecifier {
-                    span: span!(self, ns_export_specifier_start),
-                    name,
-                }));
-            }
-        }
-
         // Some("default") if default is exported from 'src'
         let mut export_default = None;
 
-        if !type_only && export_ns.is_none() && eat!(self, "default") {
+        if !type_only && eat!(self, "default") {
             if self.input.syntax().typescript() {
                 if is!(self, "abstract")
                     && peeked_is!(self, "class")
@@ -492,7 +463,8 @@ impl<I: Tokens> Parser<I> {
                 let decl = self.parse_default_fn(start, decorators)?;
                 return Ok(ModuleDecl::ExportDefaultDecl(decl));
             } else if self.input.syntax().export_default_from()
-                && (is!(self, "from") || (is!(self, ',') && peeked_is!(self, '{')))
+                && (is!(self, "from")
+                    || (is!(self, ',') && (peeked_is!(self, '{') || peeked_is!(self, '*'))))
             {
                 export_default = Some(Ident::new("default".into(), self.input.prev_span()))
             } else {
@@ -547,22 +519,20 @@ impl<I: Tokens> Parser<I> {
         {
             self.parse_var_stmt(false).map(Decl::Var)?
         } else {
-            // export {};
-            // export {} from '';
-
-            if is!(self, "from") {
-                if let Some(s) = export_ns {
-                    let (src, asserts) = self.parse_from_clause_and_semi()?;
-                    return Ok(ModuleDecl::ExportNamed(NamedExport {
-                        span: span!(self, start),
-                        specifiers: vec![s],
-                        src: Some(src),
-                        type_only,
-                        asserts,
-                    }));
-                }
-            }
-
+            // ```javascript
+            // export foo, * as bar, { baz } from "mod"; // *
+            // export      * as bar, { baz } from "mod"; // *
+            // export foo,           { baz } from "mod"; // *
+            // export foo, * as bar          from "mod"; // *
+            // export foo                    from "mod"; // *
+            // export      * as bar          from "mod"; //
+            // export                { baz } from "mod"; //
+            // export                { baz }           ; //
+            // export      *                 from "mod"; //
+            // ```
+
+            // export default
+            // export foo
             let default = match export_default {
                 Some(default) => Some(default),
                 None => {
@@ -574,47 +544,78 @@ impl<I: Tokens> Parser<I> {
                 }
             };
 
-            if is!(self, "from") {
-                if let Some(default) = default {
-                    let (src, asserts) = self.parse_from_clause_and_semi()?;
-                    return Ok(ModuleDecl::ExportNamed(NamedExport {
-                        span: span!(self, start),
-                        specifiers: vec![ExportSpecifier::Default(ExportDefaultSpecifier {
-                            exported: default,
-                        })],
-                        src: Some(src),
-                        type_only,
-                        asserts,
-                    }));
-                }
+            if self.input.syntax().typescript() && type_only && !is!(self, '{') {
+                self.emit_err(span!(self, start), SyntaxError::TS1383)
             }
 
-            if has_star && export_ns.is_none() {
+            if default.is_none() && is!(self, '*') && !peeked_is!(self, "as") {
+                assert_and_bump!(self, '*');
+
                 // improve error message for `export * from foo`
                 let (src, asserts) = self.parse_from_clause_and_semi()?;
                 return Ok(ModuleDecl::ExportAll(ExportAll {
-                    span: Span::new(start, src.span.hi(), Default::default()),
+                    span: span!(self, start),
                     src,
                     asserts,
                 }));
             }
 
-            let has_ns = export_ns.is_some();
-            let has_default = default.is_some();
-            if has_ns || has_default {
-                expect!(self, ',')
-            }
-
-            expect!(self, '{');
             let mut specifiers = vec![];
-            if let Some(s) = export_ns {
-                specifiers.push(s)
-            }
+
+            let mut has_default = false;
+            let mut has_ns = false;
+
             if let Some(default) = default {
+                has_default = true;
+
                 specifiers.push(ExportSpecifier::Default(ExportDefaultSpecifier {
                     exported: default,
                 }))
             }
+
+            // export foo, * as bar
+            //           ^
+            if !specifiers.is_empty() && is!(self, ',') && peeked_is!(self, '*') {
+                assert_and_bump!(self, ',');
+
+                has_ns = true;
+            }
+            // export     * as bar
+            //            ^
+            else if specifiers.is_empty() && is!(self, '*') {
+                has_ns = true;
+            }
+
+            if has_ns {
+                assert_and_bump!(self, '*');
+                expect!(self, "as");
+                let name = self.parse_module_export_name()?;
+                specifiers.push(ExportSpecifier::Namespace(ExportNamespaceSpecifier {
+                    span: span!(self, ns_export_specifier_start),
+                    name,
+                }));
+            }
+
+            if has_default || has_ns {
+                if is!(self, "from") {
+                    let (src, asserts) = self.parse_from_clause_and_semi()?;
+                    return Ok(ModuleDecl::ExportNamed(NamedExport {
+                        span: span!(self, start),
+                        specifiers,
+                        src: Some(src),
+                        type_only,
+                        asserts,
+                    }));
+                } else if !self.input.syntax().export_default_from() {
+                    // emit error
+                    expect!(self, "from");
+                }
+
+                expect!(self, ',');
+            }
+
+            expect!(self, '{');
+
             let mut string_export_binding_span = None;
             while !eof!(self) && !is!(self, '}') {
                 let specifier = self.parse_named_export_specifier(type_only)?;


Test patch:
diff --git a/crates/swc_ecma_codegen/src/tests.rs b/crates/swc_ecma_codegen/src/tests.rs
index b7c5b37b68..d5aa335f8f 100644
--- a/crates/swc_ecma_codegen/src/tests.rs
+++ b/crates/swc_ecma_codegen/src/tests.rs
@@ -326,6 +326,7 @@ fn named_and_namespace_export_from() {
         "export * as Foo, { bar } from 'foo';",
         Default::default(),
         Syntax::Es(EsConfig {
+            export_default_from: true,
             ..EsConfig::default()
         }),
     );
@@ -341,6 +342,7 @@ fn named_and_namespace_export_from_min() {
             ..Default::default()
         },
         Syntax::Es(EsConfig {
+            export_default_from: true,
             ..EsConfig::default()
         }),
     );
diff --git a/crates/swc_ecma_transforms_proposal/tests/export.rs b/crates/swc_ecma_transforms_proposal/tests/export.rs
index c2afdfb5d6..9c054371a3 100644
--- a/crates/swc_ecma_transforms_proposal/tests/export.rs
+++ b/crates/swc_ecma_transforms_proposal/tests/export.rs
@@ -45,7 +45,7 @@ export { x, y as w } from "mod";
 );
 
 test!(
-    syntax_namespace(),
+    syntax_default(),
     |_| tr(),
     namespace_compound_es6,
     r"export * as foo, { bar } from 'bar';",