diff --git a/src/com/google/javascript/jscomp/CodeGenerator.java b/src/com/google/javascript/jscomp/CodeGenerator.java index 29353115b46..d6ce88a78a3 100644 --- a/src/com/google/javascript/jscomp/CodeGenerator.java +++ b/src/com/google/javascript/jscomp/CodeGenerator.java @@ -463,6 +463,12 @@ protected void add(Node n, Context context) { add(n.getString()); break; + case DYNAMIC_IMPORT: + add("import("); + addExpr(first, NodeUtil.precedence(type), context); + add(")"); + break; + // CLASS -> NAME,EXPR|EMPTY,BLOCK case CLASS: { diff --git a/src/com/google/javascript/jscomp/NodeUtil.java b/src/com/google/javascript/jscomp/NodeUtil.java index cbf2ddd68d5..cc8ed86b8dd 100644 --- a/src/com/google/javascript/jscomp/NodeUtil.java +++ b/src/com/google/javascript/jscomp/NodeUtil.java @@ -1697,6 +1697,7 @@ public static int precedence(Token type) { case TRUE: case TAGGED_TEMPLATELIT: case TEMPLATELIT: + case DYNAMIC_IMPORT: // Tokens from the type declaration AST case UNION_TYPE: return 17; diff --git a/src/com/google/javascript/jscomp/parsing/IRFactory.java b/src/com/google/javascript/jscomp/parsing/IRFactory.java index a0e1fe6151e..2075252e458 100644 --- a/src/com/google/javascript/jscomp/parsing/IRFactory.java +++ b/src/com/google/javascript/jscomp/parsing/IRFactory.java @@ -73,6 +73,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.DefaultClauseTree; import com.google.javascript.jscomp.parsing.parser.trees.DefaultParameterTree; import com.google.javascript.jscomp.parsing.parser.trees.DoWhileStatementTree; +import com.google.javascript.jscomp.parsing.parser.trees.DynamicImportTree; import com.google.javascript.jscomp.parsing.parser.trees.EmptyStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.EnumDeclarationTree; import com.google.javascript.jscomp.parsing.parser.trees.ExportDeclarationTree; @@ -2480,6 +2481,12 @@ Node processImportSpec(ImportSpecifierTree tree) { return importSpec; } + Node processDynamicImport(DynamicImportTree dynamicImportNode) { + maybeWarnForFeature(dynamicImportNode, Feature.DYNAMIC_IMPORT); + Node argument = transform(dynamicImportNode.argument); + return newNode(Token.DYNAMIC_IMPORT, argument); + } + Node processTypeName(TypeNameTree tree) { Node typeNode; if (tree.segments.size() == 1) { @@ -2989,6 +2996,8 @@ public Node process(ParseTree node) { return processImportDecl(node.asImportDeclaration()); case IMPORT_SPECIFIER: return processImportSpec(node.asImportSpecifier()); + case DYNAMIC_IMPORT_EXPRESSION: + return processDynamicImport(node.asDynamicImportExpression()); case ARRAY_PATTERN: return processArrayPattern(node.asArrayPattern()); diff --git a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java index 5ff00e7c784..c788db3c896 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java +++ b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java @@ -164,6 +164,9 @@ public enum Feature { // https://github.com/tc39/proposal-regexp-unicode-property-escapes REGEXP_UNICODE_PROPERTY_ESCAPE("RegExp unicode property escape", LangVersion.ES2018), + // Stage 3 proposal likely to be part of ES2020 + DYNAMIC_IMPORT("Dynamic module import", LangVersion.ES_NEXT), + // ES6 typed features that are not at all implemented in browsers ACCESSIBILITY_MODIFIER("accessibility modifier", LangVersion.TYPESCRIPT), AMBIENT_DECLARATION("ambient declaration", LangVersion.TYPESCRIPT), diff --git a/src/com/google/javascript/jscomp/parsing/parser/Parser.java b/src/com/google/javascript/jscomp/parsing/parser/Parser.java index 199ad210749..f86848e1ca3 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/Parser.java +++ b/src/com/google/javascript/jscomp/parsing/parser/Parser.java @@ -51,6 +51,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.DefaultClauseTree; import com.google.javascript.jscomp.parsing.parser.trees.DefaultParameterTree; import com.google.javascript.jscomp.parsing.parser.trees.DoWhileStatementTree; +import com.google.javascript.jscomp.parsing.parser.trees.DynamicImportTree; import com.google.javascript.jscomp.parsing.parser.trees.EmptyStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.EnumDeclarationTree; import com.google.javascript.jscomp.parsing.parser.trees.ExportDeclarationTree; @@ -392,7 +393,7 @@ private ParseTree parseAmbientNamespaceElement() { // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-imports private boolean peekImportDeclaration() { - return peek(TokenType.IMPORT); + return peek(TokenType.IMPORT) && !peek(1, TokenType.OPEN_PAREN); } private ParseTree parseImportDeclaration() { @@ -2268,6 +2269,8 @@ private ParseTree parsePrimaryExpression() { return parseSuperExpression(); case THIS: return parseThisExpression(); + case IMPORT: + return parseDynamicImportExpression(); case IDENTIFIER: case TYPE: case DECLARE: @@ -2309,6 +2312,17 @@ private ThisExpressionTree parseThisExpression() { return new ThisExpressionTree(getTreeLocation(start)); } + // https://tc39.github.io/proposal-dynamic-import + private DynamicImportTree parseDynamicImportExpression() { + SourcePosition start = getTreeStartLocation(); + eat(TokenType.IMPORT); + eat(TokenType.OPEN_PAREN); + ParseTree argument = parseAssignmentExpression(); + eat(TokenType.CLOSE_PAREN); + recordFeatureUsed(Feature.DYNAMIC_IMPORT); + return new DynamicImportTree(getTreeLocation(start), argument); + } + private IdentifierExpressionTree parseIdentifierExpression() { SourcePosition start = getTreeStartLocation(); IdentifierToken identifier = eatId(); @@ -2898,6 +2912,8 @@ private boolean peekExpression() { case VOID: case YIELD: return true; + case IMPORT: + return peekImportCall(); default: return false; } @@ -3475,6 +3491,10 @@ private boolean peekUpdateOperator() { } } + private boolean peekImportCall() { + return peek(TokenType.IMPORT) && peek(1, TokenType.OPEN_PAREN); + } + // 11.2 Left hand side expression // // Also inlines the call expression productions diff --git a/src/com/google/javascript/jscomp/parsing/parser/trees/DynamicImportTree.java b/src/com/google/javascript/jscomp/parsing/parser/trees/DynamicImportTree.java new file mode 100644 index 00000000000..9fefedffd47 --- /dev/null +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/DynamicImportTree.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.javascript.jscomp.parsing.parser.trees; + +import com.google.javascript.jscomp.parsing.parser.util.SourceRange; + +public class DynamicImportTree extends ParseTree { + public final ParseTree argument; + + public DynamicImportTree(SourceRange location, ParseTree argument) { + super(ParseTreeType.DYNAMIC_IMPORT_EXPRESSION, location); + this.argument = argument; + } +} diff --git a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java index 95bd4c0b67a..9174254f068 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java @@ -100,6 +100,7 @@ public IdentifierExpressionTree asIdentifierExpression() { public IfStatementTree asIfStatement() { return (IfStatementTree) this; } public ImportDeclarationTree asImportDeclaration() { return (ImportDeclarationTree) this; } public ImportSpecifierTree asImportSpecifier() { return (ImportSpecifierTree) this; } + public DynamicImportTree asDynamicImportExpression() { return (DynamicImportTree) this; } public LabelledStatementTree asLabelledStatement() { return (LabelledStatementTree) this; } public LiteralExpressionTree asLiteralExpression() { return (LiteralExpressionTree) this; } public MemberExpressionTree asMemberExpression() { return (MemberExpressionTree) this; } diff --git a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java index 22ed408686c..03bfdf27ade 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java @@ -119,4 +119,5 @@ public enum ParseTreeType { CALL_SIGNATURE, NEW_TARGET_EXPRESSION, AWAIT_EXPRESSION, + DYNAMIC_IMPORT_EXPRESSION, } diff --git a/src/com/google/javascript/rhino/Token.java b/src/com/google/javascript/rhino/Token.java index 069306092bd..5ffb4f29015 100644 --- a/src/com/google/javascript/rhino/Token.java +++ b/src/com/google/javascript/rhino/Token.java @@ -179,6 +179,7 @@ public enum Token { EXPORT_SPECS, EXPORT_SPEC, MODULE_BODY, + DYNAMIC_IMPORT, REST, // "..." in formal parameters, or an array pattern. SPREAD, // "..." in a call expression, or an array literal. diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index 44a88e2ca55..8c78e3fae61 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -4958,6 +4958,51 @@ public void testIncorrectAssignmentDoesntCrash() { "Semi-colon expected"); } + @Test + public void testDynamicImport() { + List dynamicImportUses = + ImmutableList.of( + "import('foo')", + "import('foo').then(function(a) { return a; })", + "var moduleNamespace = import('foo')", + "Promise.all([import('foo')]).then(function(a) { return a; })"); + expectFeatures(Feature.DYNAMIC_IMPORT); + + for (LanguageMode m : LanguageMode.values()) { + mode = m; + strictMode = (m == LanguageMode.ECMASCRIPT3) ? SLOPPY : STRICT; + if (m.featureSet.has(Feature.DYNAMIC_IMPORT)) { + for (String importUseSource : dynamicImportUses) { + parse(importUseSource); + } + } else { + for (String importUseSource : dynamicImportUses) { + parseWarning( + importUseSource, + requiresLanguageModeMessage(LanguageMode.ES_NEXT, Feature.DYNAMIC_IMPORT)); + } + } + } + } + + @Test + public void testAwaitDynamicImport() { + List awaitDynamicImportUses = + ImmutableList.of( + "(async function() { return await import('foo'); })()", + "(async function() { await import('foo').then(function(a) { return a; }); })()", + "(async function() { var moduleNamespace = await import('foo'); })()", + lines( + "(async function() {", + "await Promise.all([import('foo')]).then(function(a) { return a; }); })()")); + expectFeatures(Feature.DYNAMIC_IMPORT, Feature.ASYNC_FUNCTIONS); + mode = LanguageMode.ES_NEXT; + + for (String importUseSource : awaitDynamicImportUses) { + parse(importUseSource); + } + } + private void assertNodeHasJSDocInfoWithJSType(Node node, JSType jsType) { JSDocInfo info = node.getJSDocInfo(); assertWithMessage("Node has no JSDocInfo: %s", node).that(info).isNotNull();