From 366b68466f1cbe7d059295135c12ce5408ae056d Mon Sep 17 00:00:00 2001 From: Harry Garrood Date: Thu, 6 Sep 2018 23:37:51 +0100 Subject: [PATCH] Add support for ES module import statements This commit adds support for all forms of the ES module import statement apart from the dynamic 'import()' form and the bare 'import "mod";' form (where the import is only being performed for the sake of some side effects). Supercedes #77, refs #71. --- src/Language/JavaScript/Parser/AST.hs | 69 ++++++++++++++++++- src/Language/JavaScript/Parser/Grammar7.y | 54 ++++++++++++++- src/Language/JavaScript/Parser/Lexer.x | 4 +- src/Language/JavaScript/Parser/Token.hs | 2 + src/Language/JavaScript/Pretty/Printer.hs | 24 +++++++ src/Language/JavaScript/Process/Minify.hs | 31 +++++++++ test/Test/Language/Javascript/Minify.hs | 9 ++- test/Test/Language/Javascript/ModuleParser.hs | 26 ++++++- test/Test/Language/Javascript/RoundTrip.hs | 7 ++ 9 files changed, 219 insertions(+), 7 deletions(-) diff --git a/src/Language/JavaScript/Parser/AST.hs b/src/Language/JavaScript/Parser/AST.hs index 9e157db..5bd25b3 100644 --- a/src/Language/JavaScript/Parser/AST.hs +++ b/src/Language/JavaScript/Parser/AST.hs @@ -25,6 +25,12 @@ module Language.JavaScript.Parser.AST -- Modules , JSModuleItem (..) + , JSImportDeclaration (..) + , JSImportClause (..) + , JSFromClause (..) + , JSImportNameSpace (..) + , JSImportsNamed (..) + , JSImportSpecifier (..) , JSExportDeclaration (..) , JSExportLocalSpecifier (..) @@ -57,11 +63,46 @@ data JSAST -- Shift AST -- https://github.com/shapesecurity/shift-spec/blob/83498b92c436180cc0e2115b225a68c08f43c53e/spec.idl#L229-L234 data JSModuleItem - -- = JSImportDeclaration - = JSModuleExportDeclaration !JSAnnot !JSExportDeclaration -- ^export,decl + = JSModuleImportDeclaration !JSAnnot !JSImportDeclaration -- ^import,decl + | JSModuleExportDeclaration !JSAnnot !JSExportDeclaration -- ^export,decl | JSModuleStatementListItem !JSStatement deriving (Data, Eq, Show, Typeable) +data JSImportDeclaration + = JSImportDeclaration !JSImportClause !JSFromClause !JSSemi -- ^imports, module, semi + -- | JSImportDeclarationBare -- ^ module, semi + deriving (Data, Eq, Show, Typeable) + +data JSImportClause + = JSImportClauseDefault !JSIdent -- ^default + | JSImportClauseNameSpace !JSImportNameSpace -- ^namespace + | JSImportClauseNamed !JSImportsNamed -- ^named imports + | JSImportClauseDefaultNameSpace !JSIdent !JSAnnot !JSImportNameSpace -- ^default, comma, namespace + | JSImportClauseDefaultNamed !JSIdent !JSAnnot !JSImportsNamed -- ^default, comma, named imports + deriving (Data, Eq, Show, Typeable) + +data JSFromClause + = JSFromClause !JSAnnot !JSAnnot !String -- ^ from, string literal, string literal contents + deriving (Data, Eq, Show, Typeable) + +-- | Import namespace, e.g. '* as whatever' +data JSImportNameSpace + = JSImportNameSpace !JSBinOp !JSBinOp !JSIdent -- ^ *, as, ident + deriving (Data, Eq, Show, Typeable) + +-- | Named imports, e.g. '{ foo, bar, baz as quux }' +data JSImportsNamed + = JSImportsNamed !JSAnnot !(JSCommaList JSImportSpecifier) !JSAnnot -- ^lb, specifiers, rb + deriving (Data, Eq, Show, Typeable) + +-- | +-- Note that this data type is separate from ExportSpecifier because the +-- grammar is slightly different (e.g. in handling of reserved words). +data JSImportSpecifier + = JSImportSpecifier !JSIdent -- ^ident + | JSImportSpecifierAs !JSIdent !JSBinOp !JSIdent -- ^ident, as, ident + deriving (Data, Eq, Show, Typeable) + data JSExportDeclaration -- = JSExportAllFrom -- | JSExportFrom @@ -338,8 +379,32 @@ instance ShowStripped JSExpression where instance ShowStripped JSModuleItem where ss (JSModuleExportDeclaration _ x1) = "JSModuleExportDeclaration (" ++ ss x1 ++ ")" + ss (JSModuleImportDeclaration _ x1) = "JSModuleImportDeclaration (" ++ ss x1 ++ ")" ss (JSModuleStatementListItem x1) = "JSModuleStatementListItem (" ++ ss x1 ++ ")" +instance ShowStripped JSImportDeclaration where + ss (JSImportDeclaration imp from _) = "JSImportDeclaration (" ++ ss imp ++ "," ++ ss from ++ ")" + +instance ShowStripped JSImportClause where + ss (JSImportClauseDefault x) = "JSImportClauseDefault (" ++ ss x ++ ")" + ss (JSImportClauseNameSpace x) = "JSImportClauseNameSpace (" ++ ss x ++ ")" + ss (JSImportClauseNamed x) = "JSImportClauseNameSpace (" ++ ss x ++ ")" + ss (JSImportClauseDefaultNameSpace x1 _ x2) = "JSImportClauseDefaultNameSpace (" ++ ss x1 ++ "," ++ ss x2 ++ ")" + ss (JSImportClauseDefaultNamed x1 _ x2) = "JSImportClauseDefaultNamed (" ++ ss x1 ++ "," ++ ss x2 ++ ")" + +instance ShowStripped JSFromClause where + ss (JSFromClause _ _ m) = "JSFromClause " ++ singleQuote m + +instance ShowStripped JSImportNameSpace where + ss (JSImportNameSpace _ _ x) = "JSImportNameSpace (" ++ ss x ++ ")" + +instance ShowStripped JSImportsNamed where + ss (JSImportsNamed _ xs _) = "JSImportsNamed (" ++ ss xs ++ ")" + +instance ShowStripped JSImportSpecifier where + ss (JSImportSpecifier x1) = "JSImportSpecifier (" ++ ss x1 ++ ")" + ss (JSImportSpecifierAs x1 _ x2) = "JSImportSpecifierAs (" ++ ss x1 ++ "," ++ ss x2 ++ ")" + instance ShowStripped JSExportDeclaration where ss (JSExportLocals _ xs _ _) = "JSExportLocals (" ++ ss xs ++ ")" ss (JSExport x1 _) = "JSExport (" ++ ss x1 ++ ")" diff --git a/src/Language/JavaScript/Parser/Grammar7.y b/src/Language/JavaScript/Parser/Grammar7.y index f2e9406..fa5e62d 100644 --- a/src/Language/JavaScript/Parser/Grammar7.y +++ b/src/Language/JavaScript/Parser/Grammar7.y @@ -101,8 +101,10 @@ import qualified Language.JavaScript.Parser.AST as AST 'finally' { FinallyToken {} } 'for' { ForToken {} } 'function' { FunctionToken {} } + 'from' { FromToken {} } 'get' { GetToken {} } 'if' { IfToken {} } + 'import' { ImportToken {} } 'in' { InToken {} } 'instanceof' { InstanceofToken {} } 'let' { LetToken {} } @@ -312,6 +314,12 @@ Let : 'let' { mkJSAnnot $1 } Const :: { AST.JSAnnot } Const : 'const' { mkJSAnnot $1 } +Import :: { AST.JSAnnot } +Import : 'import' { mkJSAnnot $1 } + +From :: { AST.JSAnnot } +From : 'from' { mkJSAnnot $1 } + Export :: { AST.JSAnnot } Export : 'export' { mkJSAnnot $1 } @@ -432,6 +440,7 @@ Identifier :: { AST.JSExpression } Identifier : 'ident' { AST.JSIdentifier (mkJSAnnot $1) (tokenLiteral $1) } | 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" } | 'set' { AST.JSIdentifier (mkJSAnnot $1) "set" } + | 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" } -- TODO: make this include any reserved word too, including future ones IdentifierName :: { AST.JSExpression } @@ -453,6 +462,7 @@ IdentifierName : Identifier {$1} | 'finally' { AST.JSIdentifier (mkJSAnnot $1) "finally" } | 'for' { AST.JSIdentifier (mkJSAnnot $1) "for" } | 'function' { AST.JSIdentifier (mkJSAnnot $1) "function" } + | 'from' { AST.JSIdentifier (mkJSAnnot $1) "from" } | 'get' { AST.JSIdentifier (mkJSAnnot $1) "get" } | 'if' { AST.JSIdentifier (mkJSAnnot $1) "if" } | 'in' { AST.JSIdentifier (mkJSAnnot $1) "in" } @@ -1183,11 +1193,53 @@ ModuleItemList : ModuleItem { [$1] {- 'ModuleItemList1' -- ExportDeclaration -- StatementListItem ModuleItem :: { AST.JSModuleItem } -ModuleItem : Export ExportDeclaration +ModuleItem : Import ImportDeclaration + { AST.JSModuleImportDeclaration $1 $2 {- 'ModuleItem1' -} } + | Export ExportDeclaration { AST.JSModuleExportDeclaration $1 $2 {- 'ModuleItem1' -} } | StatementListItem { AST.JSModuleStatementListItem $1 {- 'ModuleItem2' -} } +ImportDeclaration :: { AST.JSImportDeclaration } +ImportDeclaration : ImportClause FromClause AutoSemi + { AST.JSImportDeclaration $1 $2 $3 } + +ImportClause :: { AST.JSImportClause } +ImportClause : IdentifierName + { AST.JSImportClauseDefault (identName $1) } + | NameSpaceImport + { AST.JSImportClauseNameSpace $1 } + | NamedImports + { AST.JSImportClauseNamed $1 } + | IdentifierName ',' NameSpaceImport + { AST.JSImportClauseDefaultNameSpace (identName $1) (mkJSAnnot $2) $3 } + | IdentifierName ',' NamedImports + { AST.JSImportClauseDefaultNamed (identName $1) (mkJSAnnot $2) $3 } + +FromClause :: { AST.JSFromClause } +FromClause : From 'string' + { AST.JSFromClause $1 (mkJSAnnot $2) (tokenLiteral $2) } + +NameSpaceImport :: { AST.JSImportNameSpace } +NameSpaceImport : Mul As IdentifierName + { AST.JSImportNameSpace $1 $2 (identName $3) } + +NamedImports :: { AST.JSImportsNamed } +NamedImports : LBrace ImportsList RBrace + { AST.JSImportsNamed $1 $2 $3 } + +ImportsList :: { AST.JSCommaList AST.JSImportSpecifier } +ImportsList : ImportSpecifier + { AST.JSLOne $1 } + | ImportsList Comma ImportSpecifier + { AST.JSLCons $1 $2 $3 } + +ImportSpecifier :: { AST.JSImportSpecifier } +ImportSpecifier : IdentifierName + { AST.JSImportSpecifier (identName $1) } + | IdentifierName As IdentifierName + { AST.JSImportSpecifierAs (identName $1) $2 (identName $3) } + -- ExportDeclaration : See 15.2.3 -- [ ] export * FromClause ; -- [ ] export ExportClause FromClause ; diff --git a/src/Language/JavaScript/Parser/Lexer.x b/src/Language/JavaScript/Parser/Lexer.x index 863fabd..43aaf7e 100644 --- a/src/Language/JavaScript/Parser/Lexer.x +++ b/src/Language/JavaScript/Parser/Lexer.x @@ -525,7 +525,9 @@ keywordNames = , ( "finally", FinallyToken ) , ( "for", ForToken ) , ( "function", FunctionToken ) + , ( "from", FromToken ) , ( "if", IfToken ) + , ( "import", ImportToken ) , ( "in", InToken ) , ( "instanceof", InstanceofToken ) , ( "let", LetToken ) @@ -566,8 +568,6 @@ keywordNames = -- ( "const", FutureToken ) **** an actual token, used in productions -- enum **** an actual token, used in productions , ( "extends", FutureToken ) - - , ( "import", FutureToken ) , ( "super", FutureToken ) diff --git a/src/Language/JavaScript/Parser/Token.hs b/src/Language/JavaScript/Parser/Token.hs index 4a5a97c..7de69bd 100644 --- a/src/Language/JavaScript/Parser/Token.hs +++ b/src/Language/JavaScript/Parser/Token.hs @@ -73,6 +73,7 @@ data Token | FinallyToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ForToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | FunctionToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | FromToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | IfToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | InToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | InstanceofToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } @@ -88,6 +89,7 @@ data Token | VarToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | VoidToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | WhileToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } + | ImportToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | WithToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } | ExportToken { tokenSpan :: !TokenPosn, tokenLiteral :: !String, tokenComment :: ![CommentAnnotation] } -- Future reserved words diff --git a/src/Language/JavaScript/Pretty/Printer.hs b/src/Language/JavaScript/Pretty/Printer.hs index 3ccc30c..7c360c2 100644 --- a/src/Language/JavaScript/Pretty/Printer.hs +++ b/src/Language/JavaScript/Pretty/Printer.hs @@ -248,6 +248,7 @@ instance RenderJS [JSModuleItem] where (|>) = foldl' (|>) instance RenderJS JSModuleItem where + (|>) pacc (JSModuleImportDeclaration annot decl) = pacc |> annot |> "import" |> decl (|>) pacc (JSModuleExportDeclaration annot decl) = pacc |> annot |> "export" |> decl (|>) pacc (JSModuleStatementListItem s) = pacc |> s @@ -274,6 +275,29 @@ instance RenderJS JSArrayElement where instance RenderJS [JSArrayElement] where (|>) = foldl' (|>) +instance RenderJS JSImportDeclaration where + (|>) pacc (JSImportDeclaration imp from annot) = pacc |> imp |> from |> annot + +instance RenderJS JSImportClause where + (|>) pacc (JSImportClauseDefault x) = pacc |> x + (|>) pacc (JSImportClauseNameSpace x) = pacc |> x + (|>) pacc (JSImportClauseNamed x) = pacc |> x + (|>) pacc (JSImportClauseDefaultNameSpace x1 annot x2) = pacc |> x1 |> annot |> "," |> x2 + (|>) pacc (JSImportClauseDefaultNamed x1 annot x2) = pacc |> x1 |> annot |> "," |> x2 + +instance RenderJS JSFromClause where + (|>) pacc (JSFromClause from annot m) = pacc |> from |> "from" |> annot |> m + +instance RenderJS JSImportNameSpace where + (|>) pacc (JSImportNameSpace star as x) = pacc |> star |> as |> x + +instance RenderJS JSImportsNamed where + (|>) pacc (JSImportsNamed lb xs rb) = pacc |> lb |> "{" |> xs |> rb |> "}" + +instance RenderJS JSImportSpecifier where + (|>) pacc (JSImportSpecifier x1) = pacc |> x1 + (|>) pacc (JSImportSpecifierAs x1 as x2) = pacc |> x1 |> as |> x2 + instance RenderJS JSExportDeclaration where (|>) pacc (JSExport x1 s) = pacc |> " " |> x1 |> s (|>) pacc (JSExportLocals alb JSLNil arb semi) = pacc |> alb |> "{" |> arb |> "}" |> semi diff --git a/src/Language/JavaScript/Process/Minify.hs b/src/Language/JavaScript/Process/Minify.hs index 7e16b99..a2d27f5 100644 --- a/src/Language/JavaScript/Process/Minify.hs +++ b/src/Language/JavaScript/Process/Minify.hs @@ -268,9 +268,40 @@ instance MinifyJS JSAssignOp where fix a (JSBwOrAssign _) = JSBwOrAssign a instance MinifyJS JSModuleItem where + fix _ (JSModuleImportDeclaration _ x1) = JSModuleImportDeclaration emptyAnnot (fixEmpty x1) fix _ (JSModuleExportDeclaration _ x1) = JSModuleExportDeclaration emptyAnnot (fixEmpty x1) fix a (JSModuleStatementListItem s) = JSModuleStatementListItem (fixStmt a noSemi s) +instance MinifyJS JSImportDeclaration where + fix _ (JSImportDeclaration imps from _) = JSImportDeclaration (fixEmpty imps) (fix annot from) noSemi + where + annot = case imps of + JSImportClauseDefault {} -> spaceAnnot + JSImportClauseNameSpace {} -> spaceAnnot + JSImportClauseNamed {} -> emptyAnnot + JSImportClauseDefaultNameSpace {} -> spaceAnnot + JSImportClauseDefaultNamed {} -> emptyAnnot + +instance MinifyJS JSImportClause where + fix _ (JSImportClauseDefault n) = JSImportClauseDefault (fixSpace n) + fix _ (JSImportClauseNameSpace ns) = JSImportClauseNameSpace (fixSpace ns) + fix _ (JSImportClauseNamed named) = JSImportClauseNamed (fixEmpty named) + fix _ (JSImportClauseDefaultNameSpace def _ ns) = JSImportClauseDefaultNameSpace (fixSpace def) emptyAnnot (fixEmpty ns) + fix _ (JSImportClauseDefaultNamed def _ ns) = JSImportClauseDefaultNamed (fixSpace def) emptyAnnot (fixEmpty ns) + +instance MinifyJS JSFromClause where + fix a (JSFromClause _ _ m) = JSFromClause a emptyAnnot m + +instance MinifyJS JSImportNameSpace where + fix a (JSImportNameSpace _ _ ident) = JSImportNameSpace (JSBinOpTimes a) (JSBinOpAs spaceAnnot) (fixSpace ident) + +instance MinifyJS JSImportsNamed where + fix _ (JSImportsNamed _ imps _) = JSImportsNamed emptyAnnot (fixEmpty imps) emptyAnnot + +instance MinifyJS JSImportSpecifier where + fix _ (JSImportSpecifier x1) = JSImportSpecifier (fixEmpty x1) + fix _ (JSImportSpecifierAs x1 as x2) = JSImportSpecifierAs (fixEmpty x1) (fixSpace as) (fixSpace x2) + instance MinifyJS JSExportDeclaration where fix _ (JSExportLocals _ x1 _ _) = JSExportLocals emptyAnnot (fixEmpty x1) emptyAnnot noSemi fix _ (JSExport x1 _) = JSExport (fixStmt emptyAnnot noSemi x1) noSemi diff --git a/test/Test/Language/Javascript/Minify.hs b/test/Test/Language/Javascript/Minify.hs index f8f8eca..3358799 100644 --- a/test/Test/Language/Javascript/Minify.hs +++ b/test/Test/Language/Javascript/Minify.hs @@ -255,7 +255,14 @@ testMinifyProg = describe "Minify programs:" $ do minifyProg " try { } catch (a) {} finally {} ; try { } catch ( b ) { } ; " `shouldBe` "try{}catch(a){}finally{}try{}catch(b){}" testMinifyModule :: Spec -testMinifyModule = describe "Minify modules:" $ +testMinifyModule = describe "Minify modules:" $ do + it "import" $ do + minifyModule "import def from 'mod' ; " `shouldBe` "import def from'mod'" + minifyModule "import * as foo from \"mod\" ; " `shouldBe` "import * as foo from\"mod\"" + minifyModule "import def, * as foo from \"mod\" ; " `shouldBe` "import def,* as foo from\"mod\"" + minifyModule "import { baz, bar as foo } from \"mod\" ; " `shouldBe` "import{baz,bar as foo}from\"mod\"" + minifyModule "import def, { baz, bar as foo } from \"mod\" ; " `shouldBe` "import def,{baz,bar as foo}from\"mod\"" + it "export" $ do minifyModule " export { } ; " `shouldBe` "export{}" minifyModule " export { a } ; " `shouldBe` "export{a}" diff --git a/test/Test/Language/Javascript/ModuleParser.hs b/test/Test/Language/Javascript/ModuleParser.hs index 9b9dfad..50e3053 100644 --- a/test/Test/Language/Javascript/ModuleParser.hs +++ b/test/Test/Language/Javascript/ModuleParser.hs @@ -8,7 +8,30 @@ import Language.JavaScript.Parser testModuleParser :: Spec -testModuleParser = describe "Parse modules:" $ +testModuleParser = describe "Parse modules:" $ do + it "import" $ do + -- Not yet supported + -- test "import 'a';" `shouldBe` "" + + test "import def from 'mod';" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseDefault (JSIdentifier 'def'),JSFromClause ''mod''))])" + test "import def from \"mod\";" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseDefault (JSIdentifier 'def'),JSFromClause '\"mod\"'))])" + test "import * as thing from 'mod';" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseNameSpace (JSImportNameSpace (JSIdentifier 'thing')),JSFromClause ''mod''))])" + test "import { foo, bar, baz as quux } from 'mod';" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseNameSpace (JSImportsNamed ((JSImportSpecifier (JSIdentifier 'foo'),JSImportSpecifier (JSIdentifier 'bar'),JSImportSpecifierAs (JSIdentifier 'baz',JSIdentifier 'quux')))),JSFromClause ''mod''))])" + test "import def, * as thing from 'mod';" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseDefaultNameSpace (JSIdentifier 'def',JSImportNameSpace (JSIdentifier 'thing')),JSFromClause ''mod''))])" + test "import def, { foo, bar, baz as quux } from 'mod';" + `shouldBe` + "Right (JSAstModule [JSModuleImportDeclaration (JSImportDeclaration (JSImportClauseDefaultNamed (JSIdentifier 'def',JSImportsNamed ((JSImportSpecifier (JSIdentifier 'foo'),JSImportSpecifier (JSIdentifier 'bar'),JSImportSpecifierAs (JSIdentifier 'baz',JSIdentifier 'quux')))),JSFromClause ''mod''))])" + it "export" $ do test "export {}" `shouldBe` @@ -26,5 +49,6 @@ testModuleParser = describe "Parse modules:" $ `shouldBe` "Right (JSAstModule [JSModuleExportDeclaration (JSExportLocals ((JSExportLocalSpecifierAs (JSIdentifier 'a',JSIdentifier 'b'))))])" + test :: String -> String test str = showStrippedMaybe (parseModule str "src") diff --git a/test/Test/Language/Javascript/RoundTrip.hs b/test/Test/Language/Javascript/RoundTrip.hs index 2e2704d..27c2026 100644 --- a/test/Test/Language/Javascript/RoundTrip.hs +++ b/test/Test/Language/Javascript/RoundTrip.hs @@ -101,6 +101,13 @@ testRoundTrip = describe "Roundtrip:" $ do testRT "var x=1;let y=2;" it "module" $ do + testRTModule "import def from 'mod'" + testRTModule "import def from \"mod\";" + testRTModule "import * as foo from \"mod\" ; " + testRTModule "import def, * as foo from \"mod\" ; " + testRTModule "import { baz, bar as foo } from \"mod\" ; " + testRTModule "import def, { baz, bar as foo } from \"mod\" ; " + testRTModule "export {};" testRTModule " export {} ; " testRTModule "export { a , b , c };"