fix(parser): require a string source after export ... from (#26389)

Named exports must have a string literal module source once `from` is present. The parser previously combined consuming `from` with checking whether the next token was a literal:

```rust
if self.eat(Kind::From) && self.cur_kind().is_literal()
```

When the next token was not a literal, `from` had already been consumed, but parsing fell back to a local export. This incorrectly accepted `export {} from;`, `export {} from` at EOF, and this input:

```js
export {} from
foo();
```

In the last case, the newline allowed automatic semicolon insertion, so the parser accepted a local empty export followed by `foo()` despite the incomplete `from` clause.

Remove the literal-kind guard so consuming `from` always leads to `parse_literal_string()`. That helper already requires a string literal and handles malformed input through the existing parser diagnostics. A missing source now produces a syntax error instead of becoming a local export, while unterminated strings and comments retain the standard lexer diagnostics.

Valid local exports such as `export { foo };` and re-exports such as `export { foo } from "./module.js";` keep their existing behavior, including a module source placed on the next line.
This commit is contained in:
camc314 committed 2026-09-07 12:41:32 +00:00
1 parent 5303f5c884
commit 1400a0f8be
10 files changed
+50 -8

No files matched your search

+1 -1
View File
@@ -560,7 +560,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
})
});
self.expect(Kind::RCurly);
let (source, with_clause) = if self.eat(Kind::From) && self.cur_kind().is_literal() {
let (source, with_clause) = if self.eat(Kind::From) {
let source = self.parse_literal_string();
(Some(source), self.parse_import_attributes())
} else {
@@ -0,0 +1 @@
export {} from
@@ -0,0 +1,2 @@
export {} from
foo();
@@ -0,0 +1 @@
export {} from;
@@ -0,0 +1 @@
export { "\xG" as foo } from
@@ -0,0 +1 @@
export {} from /*
@@ -0,0 +1 @@
export {} from "
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 255/255 (100.00%)
Positive Passed: 255/255 (100.00%)
AST Parsed : 261/261 (100.00%)
Positive Passed: 261/261 (100.00%)
+3 -4
View File
@@ -9916,12 +9916,11 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1 │ import { foo, bar }
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/esprima/es2015-import-declaration/invalid-import-module-specifier/input.js:1:18]
× Unexpected token
╭─[babel/packages/babel-parser/test/fixtures/esprima/es2015-import-declaration/invalid-import-module-specifier/input.js:1:19]
1 │ export {foo} from bar
· ▲
· ───
╰────
help: Try inserting a semicolon here
× Expected `from` but found `,`
╭─[babel/packages/babel-parser/test/fixtures/esprima/es2015-import-declaration/invalid-import-named-after-named/input.js:1:13]
+37 -1
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 84/84 (100.00%)
Positive Passed: 84/84 (100.00%)
Negative Passed: 171/171 (100.00%)
Negative Passed: 177/177 (100.00%)
× Cannot assign to 'arguments' in strict mode
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
@@ -341,6 +341,42 @@ Negative Passed: 171/171 (100.00%)
╰────
help: If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.
× Unexpected token
╭─[misc/fail/export-from-missing-source-eof.js:2:1]
1 │ export {} from
╰────
× Unexpected token
╭─[misc/fail/export-from-missing-source-newline.js:2:1]
1 │ export {} from
2 │ foo();
· ───
╰────
× Unexpected token
╭─[misc/fail/export-from-missing-source-semicolon.js:1:15]
1 │ export {} from;
· ─
╰────
× Invalid escape sequence
╭─[misc/fail/export-from-prior-lexer-error.js:1:11]
1 │ export { "\xG" as foo } from
· ──
╰────
× Unterminated multiline comment
╭─[misc/fail/export-from-unterminated-comment.js:1:16]
1 │ export {} from /*
· ───
╰────
× Unterminated string
╭─[misc/fail/export-from-unterminated-source.js:1:16]
1 │ export {} from "
· ──
╰────
× HTML comments are not allowed in modules
╭─[misc/fail/html-comment-in-module.mjs:1:1]
1 │ <!-- comment