From 5078a1557e0b00e66785e209ff79db523c5316d5 Mon Sep 17 00:00:00 2001 From: Taco de Wolff Date: Thu, 26 Oct 2023 18:45:49 -0300 Subject: [PATCH] JS: fix import/export with empty named imports, see #108 --- js/ast.go | 37 ++++++++++++++++++++++--------------- js/ast_test.go | 10 ++++++---- js/parse.go | 15 ++++++++++----- js/parse_test.go | 1 + 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/js/ast.go b/js/ast.go index 1d3bc11..6f88a27 100644 --- a/js/ast.go +++ b/js/ast.go @@ -920,7 +920,7 @@ func (n ImportStmt) String() string { } if len(n.List) == 1 && len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' { s += " " + n.List[0].String() - } else if 0 < len(n.List) { + } else if n.List != nil { s += " {" for i, item := range n.List { if i != 0 { @@ -932,7 +932,7 @@ func (n ImportStmt) String() string { } s += " }" } - if n.Default != nil || len(n.List) != 0 { + if n.Default != nil || n.List != nil { s += " from" } return s + " " + string(n.Module) + ")" @@ -945,26 +945,30 @@ func (n ImportStmt) JS(w io.Writer) { w.Write([]byte(" ")) w.Write(n.Default) if len(n.List) != 0 { - w.Write([]byte(" ,")) + w.Write([]byte(",")) } } if len(n.List) == 1 && len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' { w.Write([]byte(" ")) n.List[0].JS(w) - } else if 0 < len(n.List) { - w.Write([]byte(" {")) - for j, item := range n.List { - if j != 0 { - w.Write([]byte(" ,")) - } - if item.Binding != nil { - w.Write([]byte(" ")) - item.JS(w) + } else if n.List != nil { + if len(n.List) == 0 { + w.Write([]byte(" {}")) + } else { + w.Write([]byte(" {")) + for j, item := range n.List { + if j != 0 { + w.Write([]byte(",")) + } + if item.Binding != nil { + w.Write([]byte(" ")) + item.JS(w) + } } + w.Write([]byte(" }")) } - w.Write([]byte(" }")) } - if n.Default != nil || len(n.List) != 0 { + if n.Default != nil || n.List != nil { w.Write([]byte(" from")) } w.Write([]byte(" ")) @@ -1021,11 +1025,13 @@ func (n ExportStmt) JS(w io.Writer) { } else if len(n.List) == 1 && (len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' || n.List[0].Name == nil && len(n.List[0].Binding) == 1 && n.List[0].Binding[0] == '*') { w.Write([]byte(" ")) n.List[0].JS(w) + } else if len(n.List) == 0 { + w.Write([]byte(" {}")) } else { w.Write([]byte(" {")) for j, item := range n.List { if j != 0 { - w.Write([]byte(" ,")) + w.Write([]byte(",")) } if item.Binding != nil { w.Write([]byte(" ")) @@ -1038,6 +1044,7 @@ func (n ExportStmt) JS(w io.Writer) { w.Write([]byte(" from ")) w.Write(n.Module) } + w.Write([]byte(";")) } // DirectivePrologueStmt is a string literal at the beginning of a function or module (usually "use strict"). diff --git a/js/ast_test.go b/js/ast_test.go index 84d1530..f0f761f 100644 --- a/js/ast_test.go +++ b/js/ast_test.go @@ -51,12 +51,12 @@ func TestJS(t *testing.T) { {"import * as name from 'module-name';", "import * as name from 'module-name';"}, {"import { export1 } from 'module-name';", "import { export1 } from 'module-name';"}, {"import { export1 as alias1 } from 'module-name';", "import { export1 as alias1 } from 'module-name';"}, - {"import { export1 , export2 } from 'module-name';", "import { export1 , export2 } from 'module-name';"}, - {"import { foo , bar } from 'module-name/path/to/specific/un-exported/file';", "import { foo , bar } from 'module-name/path/to/specific/un-exported/file';"}, - {"import defaultExport, * as name from 'module-name';", "import defaultExport , * as name from 'module-name';"}, + {"import { export1 , export2 } from 'module-name';", "import { export1, export2 } from 'module-name';"}, + {"import { foo , bar } from 'module-name/path/to/specific/un-exported/file';", "import { foo, bar } from 'module-name/path/to/specific/un-exported/file';"}, + {"import defaultExport, * as name from 'module-name';", "import defaultExport, * as name from 'module-name';"}, {"import 'module-name';", "import 'module-name';"}, {"var promise = import('module-name');", "var promise = import('module-name');"}, - {"export { myFunction as default }", "export { myFunction as default }"}, + {"export { myFunction as default }", "export { myFunction as default };"}, {"export default k = 12;", "export default k = 12;"}, {"'use strict';", "'use strict';"}, {"let [name1, name2 = 6] = z;", "let [name1, name2 = 6] = z;"}, @@ -110,6 +110,8 @@ func TestJS(t *testing.T) { {"export default await x;", "export default await x;"}, {"export let a = await x;", "export let a = await x;"}, {"if(k00)while((0))", "if (k00) while ((0));"}, + {"export{};from", "export {}; from;"}, + {"import{} from 'a'", "import {} from 'a';"}, } re := regexp.MustCompile("\n *") diff --git a/js/parse.go b/js/parse.go index 4af1c90..63cfb29 100644 --- a/js/parse.go +++ b/js/parse.go @@ -620,14 +620,16 @@ func (p *Parser) parseImportStmt() (importStmt ImportStmt) { importStmt.Module = p.data p.next() } else { + expectClause := true if IsIdentifier(p.tt) || p.tt == YieldToken { importStmt.Default = p.data p.next() - if p.tt == CommaToken { + expectClause = p.tt == CommaToken + if expectClause { p.next() } } - if p.tt == MulToken { + if expectClause && p.tt == MulToken { star := p.data p.next() if !p.consume("import statement", AsToken) { @@ -639,8 +641,9 @@ func (p *Parser) parseImportStmt() (importStmt ImportStmt) { } importStmt.List = []Alias{Alias{star, p.data}} p.next() - } else if p.tt == OpenBraceToken { + } else if expectClause && p.tt == OpenBraceToken { p.next() + importStmt.List = []Alias{} for IsIdentifierName(p.tt) || p.tt == StringToken { tt := p.tt var name, binding []byte = nil, p.data @@ -670,8 +673,10 @@ func (p *Parser) parseImportStmt() (importStmt ImportStmt) { if !p.consume("import statement", CloseBraceToken) { return } - } - if importStmt.Default == nil && len(importStmt.List) == 0 { + } else if expectClause && importStmt.Default != nil { + p.fail("import statement", MulToken, OpenBraceToken) + return + } else if importStmt.Default == nil { p.fail("import statement", StringToken, IdentifierToken, MulToken, OpenBraceToken) return } diff --git a/js/parse_test.go b/js/parse_test.go index 99137f3..6319c17 100644 --- a/js/parse_test.go +++ b/js/parse_test.go @@ -545,6 +545,7 @@ func TestParseError(t *testing.T) { {"import {yield as", "expected Identifier instead of EOF in import statement"}, {"import {yield,", "expected } instead of EOF in import statement"}, {"import yield", "expected from instead of EOF in import statement"}, + {"import yield,", "expected * or { instead of EOF in import statement"}, {"import yield from", "expected String instead of EOF in import statement"}, {"export", "expected *, {, var, let, const, function, async, class, or default instead of EOF in export statement"}, {"export *", "expected from instead of EOF in export statement"},