From d6a732d237582853d0b0d361383204a5ae13a3ed Mon Sep 17 00:00:00 2001 From: tjgq Date: Mon, 15 Jul 2019 10:04:39 -0700 Subject: [PATCH] Add preliminary parser and code printing support for the import.meta Stage 3 proposal. See: https://github.com/tc39/proposal-import-meta https://tc39.es/proposal-import-meta/ ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=258185594 --- .../javascript/jscomp/AstValidator.java | 4 ++ .../javascript/jscomp/CodeGenerator.java | 4 ++ .../google/javascript/jscomp/NodeUtil.java | 1 + .../google/javascript/jscomp/TypeCheck.java | 1 + .../javascript/jscomp/parsing/IRFactory.java | 9 ++++ .../jscomp/parsing/parser/FeatureSet.java | 3 +- .../jscomp/parsing/parser/Parser.java | 32 ++++++++++-- .../trees/ImportMetaExpressionTree.java | 26 ++++++++++ .../parsing/parser/trees/ParseTree.java | 4 ++ .../parsing/parser/trees/ParseTreeType.java | 1 + src/com/google/javascript/rhino/IR.java | 5 ++ src/com/google/javascript/rhino/Token.java | 1 + .../javascript/jscomp/AstValidatorTest.java | 7 +++ .../javascript/jscomp/CodePrinterTest.java | 8 +++ .../javascript/jscomp/parsing/ParserTest.java | 50 +++++++++++++++++++ 15 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 src/com/google/javascript/jscomp/parsing/parser/trees/ImportMetaExpressionTree.java diff --git a/src/com/google/javascript/jscomp/AstValidator.java b/src/com/google/javascript/jscomp/AstValidator.java index 01a55384306..23258c29f04 100644 --- a/src/com/google/javascript/jscomp/AstValidator.java +++ b/src/com/google/javascript/jscomp/AstValidator.java @@ -277,6 +277,10 @@ public void validateExpression(Node n) { validateFeature(Feature.NEW_TARGET, n); validateChildless(n); return; + case IMPORT_META: + validateFeature(Feature.IMPORT_META, n); + validateChildless(n); + return; case SUPER: validateFeature(Feature.SUPER, n); validateChildless(n); diff --git a/src/com/google/javascript/jscomp/CodeGenerator.java b/src/com/google/javascript/jscomp/CodeGenerator.java index 1ed7db31fff..bdd34d066fe 100644 --- a/src/com/google/javascript/jscomp/CodeGenerator.java +++ b/src/com/google/javascript/jscomp/CodeGenerator.java @@ -487,6 +487,10 @@ protected void add(Node n, Context context) { add(")"); break; + case IMPORT_META: + add("import.meta"); + 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 293909c0a0f..b19849c0e1d 100644 --- a/src/com/google/javascript/jscomp/NodeUtil.java +++ b/src/com/google/javascript/jscomp/NodeUtil.java @@ -1256,6 +1256,7 @@ public static int precedence(Token type) { case GETELEM: case GETPROP: case NEW_TARGET: + case IMPORT_META: // Data values case ARRAYLIT: case ARRAY_PATTERN: diff --git a/src/com/google/javascript/jscomp/TypeCheck.java b/src/com/google/javascript/jscomp/TypeCheck.java index 13d467cbd14..efc44a660c3 100644 --- a/src/com/google/javascript/jscomp/TypeCheck.java +++ b/src/com/google/javascript/jscomp/TypeCheck.java @@ -657,6 +657,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { case SUPER: case NEW_TARGET: + case IMPORT_META: case AWAIT: ensureTyped(n); break; diff --git a/src/com/google/javascript/jscomp/parsing/IRFactory.java b/src/com/google/javascript/jscomp/parsing/IRFactory.java index ef5f91f191b..8e98ec15560 100644 --- a/src/com/google/javascript/jscomp/parsing/IRFactory.java +++ b/src/com/google/javascript/jscomp/parsing/IRFactory.java @@ -93,6 +93,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.IdentifierExpressionTree; import com.google.javascript.jscomp.parsing.parser.trees.IfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ImportDeclarationTree; +import com.google.javascript.jscomp.parsing.parser.trees.ImportMetaExpressionTree; import com.google.javascript.jscomp.parsing.parser.trees.ImportSpecifierTree; import com.google.javascript.jscomp.parsing.parser.trees.IndexSignatureTree; import com.google.javascript.jscomp.parsing.parser.trees.InterfaceDeclarationTree; @@ -2642,6 +2643,12 @@ Node processDynamicImport(DynamicImportTree dynamicImportNode) { return newNode(Token.DYNAMIC_IMPORT, argument); } + Node processImportMeta(ImportMetaExpressionTree tree) { + maybeWarnForFeature(tree, Feature.MODULES); + maybeWarnForFeature(tree, Feature.IMPORT_META); + return newNode(Token.IMPORT_META); + } + Node processTypeName(TypeNameTree tree) { Node typeNode; if (tree.segments.size() == 1) { @@ -3153,6 +3160,8 @@ public Node process(ParseTree node) { return processImportSpec(node.asImportSpecifier()); case DYNAMIC_IMPORT_EXPRESSION: return processDynamicImport(node.asDynamicImportExpression()); + case IMPORT_META_EXPRESSION: + return processImportMeta(node.asImportMetaExpression()); 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 9b1015111d3..3916b928c86 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java +++ b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java @@ -180,8 +180,9 @@ public enum Feature { // https://github.com/tc39/proposal-optional-catch-binding OPTIONAL_CATCH_BINDING("Optional catch binding", LangVersion.ES2019), - // Stage 3 proposal likely to be part of ES2020 + // Stage 3 proposals likely to be part of ES2020 DYNAMIC_IMPORT("Dynamic module import", LangVersion.ES_UNSUPPORTED), + IMPORT_META("import.meta", LangVersion.ES_UNSUPPORTED), // ES6 typed features that are not at all implemented in browsers ACCESSIBILITY_MODIFIER("accessibility modifier", 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 3985038f340..f5b415e4a09 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/Parser.java +++ b/src/com/google/javascript/jscomp/parsing/parser/Parser.java @@ -70,6 +70,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.IdentifierExpressionTree; import com.google.javascript.jscomp.parsing.parser.trees.IfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ImportDeclarationTree; +import com.google.javascript.jscomp.parsing.parser.trees.ImportMetaExpressionTree; import com.google.javascript.jscomp.parsing.parser.trees.ImportSpecifierTree; import com.google.javascript.jscomp.parsing.parser.trees.IndexSignatureTree; import com.google.javascript.jscomp.parsing.parser.trees.InterfaceDeclarationTree; @@ -392,9 +393,12 @@ private ParseTree parseAmbientNamespaceElement() { return parseAmbientDeclarationHelper(); } - // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-imports private boolean peekImportDeclaration() { - return peek(TokenType.IMPORT) && !peek(1, TokenType.OPEN_PAREN); + return peek(TokenType.IMPORT) + && (peekIdOrKeyword(1) + || peek(1, TokenType.STRING) + || peek(1, TokenType.OPEN_CURLY) + || peek(1, TokenType.STAR)); } private ParseTree parseImportDeclaration() { @@ -2933,7 +2937,7 @@ private boolean peekExpression() { case YIELD: return true; case IMPORT: - return peekImportCall(); + return peekImportCall() || peekImportDot(); default: return false; } @@ -3515,6 +3519,10 @@ private boolean peekImportCall() { return peek(TokenType.IMPORT) && peek(1, TokenType.OPEN_PAREN); } + private boolean peekImportDot() { + return peek(TokenType.IMPORT) && peek(1, TokenType.PERIOD); + } + // 11.2 Left hand side expression // // Also inlines the call expression productions @@ -3571,7 +3579,9 @@ private boolean peekCallSuffix() { private ParseTree parseMemberExpressionNoNew() { SourcePosition start = getTreeStartLocation(); ParseTree operand; - if (peekAsyncFunctionStart()) { + if (peekImportDot()) { + operand = parseImportDotMeta(); + } else if (peekAsyncFunctionStart()) { operand = parseAsyncFunctionExpression(); } else if (peekFunction()) { operand = parseFunctionExpression(); @@ -3635,6 +3645,14 @@ private ParseTree parseNewDotSomething() { return new NewTargetExpressionTree(getTreeLocation(start)); } + private ParseTree parseImportDotMeta() { + SourcePosition start = getTreeStartLocation(); + eat(TokenType.IMPORT); + eat(TokenType.PERIOD); + eatPredefinedString("meta"); + return new ImportMetaExpressionTree(getTreeLocation(start)); + } + private ArgumentListTree parseArguments() { // ArgumentList : // AssignmentOrSpreadExpression @@ -4016,7 +4034,11 @@ private boolean peekId(int index) { } private boolean peekIdOrKeyword() { - TokenType type = peekType(); + return peekIdOrKeyword(0); + } + + private boolean peekIdOrKeyword(int index) { + TokenType type = peekType(index); return TokenType.IDENTIFIER == type || Keywords.isKeyword(type); } diff --git a/src/com/google/javascript/jscomp/parsing/parser/trees/ImportMetaExpressionTree.java b/src/com/google/javascript/jscomp/parsing/parser/trees/ImportMetaExpressionTree.java new file mode 100644 index 00000000000..45d2f7fa9ce --- /dev/null +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/ImportMetaExpressionTree.java @@ -0,0 +1,26 @@ +/* + * 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; + +/** Represents 'new.target'. */ +public class ImportMetaExpressionTree extends ParseTree { + public ImportMetaExpressionTree(SourceRange location) { + super(ParseTreeType.IMPORT_META_EXPRESSION, location); + } +} 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 549ee7644c9..29af49beab9 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTree.java @@ -105,6 +105,10 @@ public DynamicImportTree asDynamicImportExpression() { return (DynamicImportTree) this; } + public ImportMetaExpressionTree asImportMetaExpression() { + return (ImportMetaExpressionTree) 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 03bfdf27ade..7c452d95df8 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java +++ b/src/com/google/javascript/jscomp/parsing/parser/trees/ParseTreeType.java @@ -120,4 +120,5 @@ public enum ParseTreeType { NEW_TARGET_EXPRESSION, AWAIT_EXPRESSION, DYNAMIC_IMPORT_EXPRESSION, + IMPORT_META_EXPRESSION, } diff --git a/src/com/google/javascript/rhino/IR.java b/src/com/google/javascript/rhino/IR.java index bfbaed391b5..9005e129684 100644 --- a/src/com/google/javascript/rhino/IR.java +++ b/src/com/google/javascript/rhino/IR.java @@ -676,6 +676,10 @@ public static Node typeof(Node expr) { return unaryOp(Token.TYPEOF, expr); } + public static Node importMeta() { + return new Node(Token.IMPORT_META); + } + // helper methods private static Node binaryOp(Token token, Node expr1, Node expr2) { @@ -795,6 +799,7 @@ public static boolean mayBeExpression(Node n) { case GETELEM: case GT: case HOOK: + case IMPORT_META: case IN: case INC: case INSTANCEOF: diff --git a/src/com/google/javascript/rhino/Token.java b/src/com/google/javascript/rhino/Token.java index e0c49090128..a04f978f595 100644 --- a/src/com/google/javascript/rhino/Token.java +++ b/src/com/google/javascript/rhino/Token.java @@ -193,6 +193,7 @@ public enum Token { DEFAULT_VALUE, // Formal parameter or destructuring element with a default value NEW_TARGET, // new.target + IMPORT_META, // import.meta // Used by type declaration ASTs STRING_TYPE, diff --git a/test/com/google/javascript/jscomp/AstValidatorTest.java b/test/com/google/javascript/jscomp/AstValidatorTest.java index d24d513afbb..d1e3614a0b0 100644 --- a/test/com/google/javascript/jscomp/AstValidatorTest.java +++ b/test/com/google/javascript/jscomp/AstValidatorTest.java @@ -237,6 +237,13 @@ public void testNewTargetIsValidExpression() { expectValid(n, Check.EXPRESSION); } + @Test + public void testImportMetaIsValidExpression() { + setAcceptedLanguage(LanguageMode.UNSUPPORTED); + Node n = new Node(Token.IMPORT_META); + expectValid(n, Check.EXPRESSION); + } + @Test public void testCastOnLeftSideOfAssign() { JSDocInfoBuilder jsdoc = new JSDocInfoBuilder(false); diff --git a/test/com/google/javascript/jscomp/CodePrinterTest.java b/test/com/google/javascript/jscomp/CodePrinterTest.java index b00781ab085..c93f0ea9c1d 100644 --- a/test/com/google/javascript/jscomp/CodePrinterTest.java +++ b/test/com/google/javascript/jscomp/CodePrinterTest.java @@ -2746,6 +2746,14 @@ public void testNewTarget() { assertPrint("function f() {\nnew\n.\ntarget;\n}", "function f(){new.target}"); } + @Test + public void testImportMeta() { + useUnsupportedFeatures = true; + assertPrintSame("import.meta"); + assertPrintSame("import.meta.url"); + assertPrintSame("console.log(import.meta.url)"); + } + @Test public void testGeneratorYield() { assertPrintSame("function*f(){yield 1}"); diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index 511369995f9..0064f73b453 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -32,6 +32,7 @@ import com.google.javascript.jscomp.parsing.ParserRunner.ParseResult; import com.google.javascript.jscomp.parsing.parser.FeatureSet; import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature; +import com.google.javascript.rhino.IR; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; @@ -5179,6 +5180,55 @@ public void testAwaitDynamicImport() { } } + @Test + public void testImportMeta() { + mode = LanguageMode.UNSUPPORTED; + expectFeatures(Feature.MODULES, Feature.IMPORT_META); + + Node tree = parse("import.meta"); + assertNode(tree.getFirstFirstChild()).isEqualTo(IR.exprResult(IR.importMeta())); + } + + @Test + public void testImportMeta_es5() { + mode = LanguageMode.ECMASCRIPT5; + expectFeatures(Feature.MODULES, Feature.IMPORT_META); + + parseWarning( + "import.meta", + requiresLanguageModeMessage(LanguageMode.ECMASCRIPT6, Feature.MODULES), + unsupportedFeatureMessage(Feature.IMPORT_META)); + } + + @Test + public void testImportMeta_es6() { + mode = LanguageMode.ECMASCRIPT6; + expectFeatures(Feature.MODULES, Feature.IMPORT_META); + + parseWarning("import.meta", unsupportedFeatureMessage(Feature.IMPORT_META)); + } + + @Test + public void testImportMeta_inExpression() { + mode = LanguageMode.UNSUPPORTED; + expectFeatures(Feature.MODULES, Feature.IMPORT_META); + + Node propTree = parse("import.meta.url"); + assertNode(propTree.getFirstFirstChild()) + .isEqualTo(IR.exprResult(IR.getprop(IR.importMeta(), "url"))); + + Node callTree = parse("f(import.meta.url)"); + assertNode(callTree.getFirstFirstChild()) + .isEqualTo(IR.exprResult(IR.call(IR.name("f"), IR.getprop(IR.importMeta(), "url")))); + } + + @Test + public void testImportMeta_asDotProperty() { + Node tree = parse("x.import.meta"); + assertNode(tree.getFirstChild()) + .isEqualTo(IR.exprResult(IR.getprop(IR.name("x"), "import", "meta"))); + } + private void assertNodeHasJSDocInfoWithJSType(Node node, JSType jsType) { JSDocInfo info = node.getJSDocInfo(); assertWithMessage("Node has no JSDocInfo: %s", node).that(info).isNotNull();