diff --git a/src/com/google/javascript/jscomp/AstValidator.java b/src/com/google/javascript/jscomp/AstValidator.java index 9cd280e74fa..00756bf2145 100644 --- a/src/com/google/javascript/jscomp/AstValidator.java +++ b/src/com/google/javascript/jscomp/AstValidator.java @@ -185,6 +185,9 @@ public void validateStatement(Node n, boolean isAmbient) { case FOR_OF: validateForOf(n); return; + case FOR_AWAIT_OF: + validateForAwaitOf(n); + return; case WHILE: validateWhile(n); return; @@ -908,6 +911,9 @@ private void validateFunctionFeatures(Node n) { if (n.isAsyncFunction()) { validateFeature(Feature.ASYNC_FUNCTIONS, n); } + if (n.isAsyncFunction() && n.isGeneratorFunction()) { + validateFeature(Feature.ASYNC_GENERATORS, n); + } } private void validateFunctionBody(Node n, boolean noBlock) { @@ -1172,6 +1178,15 @@ private void validateForOf(Node n) { validateBlock(n.getLastChild()); } + private void validateForAwaitOf(Node n) { + validateFeature(Feature.FOR_AWAIT_OF, n); + validateNodeType(Token.FOR_AWAIT_OF, n); + validateChildCount(n); + validateVarOrAssignmentTarget(n.getFirstChild()); + validateExpression(n.getSecondChild()); + validateBlock(n.getLastChild()); + } + private void validateVarOrOptionalExpression(Node n) { if (NodeUtil.isNameDeclaration(n)) { validateNameDeclarationHelper(n.getToken(), n); diff --git a/src/com/google/javascript/jscomp/CodeGenerator.java b/src/com/google/javascript/jscomp/CodeGenerator.java index 090fb9651ce..7f2442dda1f 100644 --- a/src/com/google/javascript/jscomp/CodeGenerator.java +++ b/src/com/google/javascript/jscomp/CodeGenerator.java @@ -547,15 +547,15 @@ protected void add(Node n, Context context) { add("static "); } + if (n.isMemberFunctionDef() && n.getFirstChild().isAsyncFunction()) { + add("async "); + } + if (!n.isMemberVariableDef() && n.getFirstChild().isGeneratorFunction()) { checkState(type == Token.MEMBER_FUNCTION_DEF, n); add("*"); } - if (n.isMemberFunctionDef() && n.getFirstChild().isAsyncFunction()) { - add("async "); - } - switch (type) { case GETTER_DEF: // Get methods have no parameters. @@ -703,6 +703,20 @@ protected void add(Node n, Context context) { addNonEmptyStatement(last, getContextForNonEmptyExpression(context), false); break; + case FOR_AWAIT_OF: + Preconditions.checkState(childCount == 3, n); + add("for await"); + cc.maybeInsertSpace(); + add("("); + add(first); + cc.maybeInsertSpace(); + add("of"); + cc.maybeInsertSpace(); + add(first.getNext()); + add(")"); + addNonEmptyStatement(last, getContextForNonEmptyExpression(context), false); + break; + case DO: Preconditions.checkState(childCount == 2, n); add("do"); @@ -1014,10 +1028,13 @@ protected void add(Node n, Context context) { add("get "); } else if (n.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { add("set "); - } else if (last.getBooleanProp(Node.GENERATOR_FN)) { - add("*"); - } else if (last.isAsyncFunction()) { - add("async"); + } else { + if (last.isAsyncFunction()) { + add("async"); + } + if (last.getBooleanProp(Node.GENERATOR_FN)) { + add("*"); + } } add("["); add(first); diff --git a/src/com/google/javascript/jscomp/Es6SyntacticScopeCreator.java b/src/com/google/javascript/jscomp/Es6SyntacticScopeCreator.java index 178f44f9030..3b483029c9b 100644 --- a/src/com/google/javascript/jscomp/Es6SyntacticScopeCreator.java +++ b/src/com/google/javascript/jscomp/Es6SyntacticScopeCreator.java @@ -167,6 +167,7 @@ void populate() { case FOR: case FOR_OF: + case FOR_AWAIT_OF: case FOR_IN: case SWITCH: scanVars(n, null, scope); diff --git a/src/com/google/javascript/jscomp/NodeUtil.java b/src/com/google/javascript/jscomp/NodeUtil.java index dffa2e7eb77..7bcf70a0d75 100644 --- a/src/com/google/javascript/jscomp/NodeUtil.java +++ b/src/com/google/javascript/jscomp/NodeUtil.java @@ -1518,6 +1518,7 @@ static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { case AWAIT: case FOR_IN: // assigns to a loop LHS case FOR_OF: // assigns to a loop LHS + case FOR_AWAIT_OF: // assigns to a loop LHS return true; case CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); @@ -2414,7 +2415,7 @@ static boolean isVanillaFunction(Node n) { } public static boolean isEnhancedFor(Node n) { - return n.isForOf() || n.isForIn(); + return n.isForOf() || n.isForAwaitOf() || n.isForIn(); } public static boolean isAnyFor(Node n) { @@ -2429,6 +2430,7 @@ static boolean isLoopStructure(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case DO: case WHILE: return true; @@ -2447,6 +2449,7 @@ static Node getLoopCodeBlock(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case WHILE: return n.getLastChild(); case DO: @@ -2481,6 +2484,7 @@ public static boolean isControlStructure(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case DO: case WHILE: case WITH: @@ -2510,6 +2514,7 @@ static boolean isControlStructureCodeBlock(Node parent, Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case WHILE: case LABEL: case WITH: @@ -2543,6 +2548,7 @@ static Node getConditionExpression(Node n) { return n.getSecondChild(); case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case CASE: return null; default: @@ -2577,6 +2583,7 @@ static boolean createsBlockScope(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case SWITCH: case CLASS: return true; @@ -3311,6 +3318,7 @@ public static boolean isLValue(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: return parent.getFirstChild() == n; case ARRAY_PATTERN: case STRING_KEY: @@ -3511,6 +3519,7 @@ private static Node getEnclosingTarget(Node targetNode) { case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: // e.g. `for ({length} in obj) {}` // targetNode is `{length}` // e.g. `for ({length} of obj) {}` // targetNode is `{length}` checkState(targetIsFirstChild, targetNode); @@ -4240,6 +4249,7 @@ private static void getLhsNodesHelper(Node n, List lhsNodes) { return; case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: // Enhanced for loops assign to variables in their first child // e.g. // for (some.prop in someObj) {... diff --git a/src/com/google/javascript/jscomp/RewriteAsyncIteration.java b/src/com/google/javascript/jscomp/RewriteAsyncIteration.java new file mode 100644 index 00000000000..3ea3294b5e2 --- /dev/null +++ b/src/com/google/javascript/jscomp/RewriteAsyncIteration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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; + +import com.google.javascript.jscomp.parsing.parser.FeatureSet; +import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature; +import com.google.javascript.rhino.Node; + +/** + * Noop pass reporting an error if scripts attempt to transpile asynchronous generators or + * for-await-of loops - these features are not yet ready to be transpiled. + */ +public final class RewriteAsyncIteration extends NodeTraversal.AbstractPostOrderCallback + implements HotSwapCompilerPass { + + static final DiagnosticType CANNOT_CONVERT_ASYNC_ITERATION_YET = + DiagnosticType.error( + "JSC_CANNOT_CONVERT_ASYNC_ITERATION_YET", + "Cannot convert async iteration/generators yet."); + + private static final FeatureSet transpiledFeatures = + FeatureSet.BARE_MINIMUM.with(Feature.ASYNC_GENERATORS, Feature.FOR_AWAIT_OF); + + private final AbstractCompiler compiler; + + public RewriteAsyncIteration(AbstractCompiler compiler) { + this.compiler = compiler; + } + + @Override + public void hotSwapScript(Node scriptRoot, Node originalRoot) { + TranspilationPasses.processTranspile(compiler, scriptRoot, transpiledFeatures, this); + TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); + } + + @Override + public void process(Node externs, Node root) { + TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this); + TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); + } + + @Override + public void visit(NodeTraversal t, Node n, Node parent) { + if (n.isForAwaitOf() || (n.isAsyncFunction() && n.isGeneratorFunction())) { + compiler.report(JSError.make(n, CANNOT_CONVERT_ASYNC_ITERATION_YET)); + } + } +} diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java index 8ea4780fcd2..4fab9bc54b9 100644 --- a/src/com/google/javascript/jscomp/TranspilationPasses.java +++ b/src/com/google/javascript/jscomp/TranspilationPasses.java @@ -16,6 +16,8 @@ package com.google.javascript.jscomp; +import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES2018; +import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES2018_MODULES; import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES6; import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES7; import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8; @@ -72,6 +74,11 @@ public static void addPreTypecheckTranspilationPasses( // parameter can be removed. static void addPreTypecheckTranspilationPasses( List passes, CompilerOptions options, boolean doEs6ExternsCheck) { + + if (options.needsTranspilationFrom(ES2018)) { + passes.add(rewriteAsyncIteration); + } + if (options.needsTranspilationFrom(ES_NEXT)) { passes.add(rewriteObjRestSpread); } @@ -195,6 +202,19 @@ protected FeatureSet featureSet() { } }; + private static final PassFactory rewriteAsyncIteration = + new HotSwapPassFactory("rewriteAsyncIteration") { + @Override + protected HotSwapCompilerPass create(final AbstractCompiler compiler) { + return new RewriteAsyncIteration(compiler); + } + + @Override + protected FeatureSet featureSet() { + return ES2018_MODULES; + } + }; + private static final PassFactory rewriteObjRestSpread = new HotSwapPassFactory("rewriteObjRestSpread") { @Override diff --git a/src/com/google/javascript/jscomp/parsing/IRFactory.java b/src/com/google/javascript/jscomp/parsing/IRFactory.java index 5f9c0a63cd5..df45f68ae1c 100644 --- a/src/com/google/javascript/jscomp/parsing/IRFactory.java +++ b/src/com/google/javascript/jscomp/parsing/IRFactory.java @@ -78,6 +78,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.ExportSpecifierTree; import com.google.javascript.jscomp.parsing.parser.trees.ExpressionStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.FinallyTree; +import com.google.javascript.jscomp.parsing.parser.trees.ForAwaitOfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForInStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForOfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForStatementTree; @@ -489,6 +490,7 @@ private static boolean isBreakTarget(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case WHILE: case DO: case SWITCH: @@ -503,6 +505,7 @@ private static boolean isContinueTarget(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case WHILE: case DO: return true; @@ -1355,6 +1358,16 @@ Node processForOf(ForOfStatementTree loopNode) { transformBlock(loopNode.body)); } + Node processForAwaitOf(ForAwaitOfStatementTree loopNode) { + maybeWarnForFeature(loopNode, Feature.FOR_AWAIT_OF); + Node initializer = transform(loopNode.initializer); + return newNode( + Token.FOR_AWAIT_OF, + initializer, + transform(loopNode.collection), + transformBlock(loopNode.body)); + } + Node processForLoop(ForStatementTree loopNode) { Node node = newNode( Token.FOR, @@ -1416,6 +1429,10 @@ Node processFunction(FunctionDeclarationTree functionTree) { maybeWarnForFeature(functionTree, Feature.ASYNC_FUNCTIONS); } + if (isGenerator && isAsync) { + maybeWarnForFeature(functionTree, Feature.ASYNC_GENERATORS); + } + IdentifierToken name = functionTree.name; Node newName; if (name != null) { @@ -2981,6 +2998,8 @@ public Node process(ParseTree node) { return processAwait(node.asAwaitExpression()); case FOR_OF_STATEMENT: return processForOf(node.asForOfStatement()); + case FOR_AWAIT_OF_STATEMENT: + return processForAwaitOf(node.asForAwaitOfStatement()); case EXPORT_DECLARATION: return processExportDecl(node.asExportDeclaration()); diff --git a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java index b1b3840ff5f..5b6a34907cc 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java +++ b/src/com/google/javascript/jscomp/parsing/parser/FeatureSet.java @@ -153,6 +153,10 @@ public enum Feature { OBJECT_LITERALS_WITH_SPREAD("object literals with spread", LangVersion.ES_NEXT), OBJECT_PATTERN_REST("object pattern rest", LangVersion.ES_NEXT), + // https://github.com/tc39/proposal-async-iteration + ASYNC_GENERATORS("async generator functions", LangVersion.ES2018), + FOR_AWAIT_OF("for-await-of loop", LangVersion.ES2018), + // 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 fbfc451f1a7..757f052a8e6 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/Parser.java +++ b/src/com/google/javascript/jscomp/parsing/parser/Parser.java @@ -57,6 +57,7 @@ import com.google.javascript.jscomp.parsing.parser.trees.ExportSpecifierTree; import com.google.javascript.jscomp.parsing.parser.trees.ExpressionStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.FinallyTree; +import com.google.javascript.jscomp.parsing.parser.trees.ForAwaitOfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForInStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForOfStatementTree; import com.google.javascript.jscomp.parsing.parser.trees.ForStatementTree; @@ -173,7 +174,18 @@ public class Parser { * Indicates the type of function currently being parsed. */ private enum FunctionFlavor { - NORMAL, GENERATOR, ASYNCHRONOUS; + NORMAL(false, false), + GENERATOR(true, false), + ASYNCHRONOUS(false, true), + ASYNCHRONOUS_GENERATOR(true, true); + + final boolean isGenerator; + final boolean isAsynchronous; + + FunctionFlavor(boolean isGenerator, boolean isAsynchronous) { + this.isGenerator = isGenerator; + this.isAsynchronous = isAsynchronous; + } } private final Scanner scanner; @@ -868,7 +880,8 @@ private ParseTree parseClassElement(PartialClassElement partialElement) { private boolean peekAsyncMethod() { return peekPredefinedString(ASYNC) && !peekImplicitSemiColon(1) - && peekPropertyNameOrComputedProp(1); + && (peekPropertyNameOrComputedProp(1) + || (peek(1, TokenType.STAR) && peekPropertyNameOrComputedProp(2))); } private ParseTree parseClassMemberDeclaration() { @@ -972,12 +985,17 @@ private ParseTree parseAsyncMethod() { private ParseTree parseAsyncMethod(PartialClassElement partial) { eatPredefinedString(ASYNC); + boolean generator = peek(TokenType.STAR); + if (generator) { + eat(TokenType.STAR); + } if (peekPropertyName(0)) { if (peekIdOrKeyword()) { IdentifierToken name = eatIdOrKeywordAsId(); FunctionDeclarationTree.Builder builder = FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.MEMBER) .setAsync(true) + .setGenerator(generator) .setStatic(partial.isStatic) .setName(name) .setAccess(partial.accessModifier); @@ -989,7 +1007,9 @@ private ParseTree parseAsyncMethod(PartialClassElement partial) { .setFunctionBody(new EmptyStatementTree(getTreeLocation(partial.start))); eatPossibleImplicitSemiColon(); } else { - parseFunctionTail(builder, FunctionFlavor.ASYNCHRONOUS); + parseFunctionTail( + builder, + generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS); } return builder.build(getTreeLocation(name.getStart())); @@ -1001,8 +1021,11 @@ private ParseTree parseAsyncMethod(PartialClassElement partial) { FunctionDeclarationTree.Builder builder = FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION) .setAsync(true) + .setGenerator(generator) .setStatic(partial.isStatic); - parseFunctionTail(builder, FunctionFlavor.ASYNCHRONOUS); + parseFunctionTail( + builder, + generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS); ParseTree function = builder.build(getTreeLocation(nameExpr.getStart())); return new ComputedPropertyMethodTree( @@ -1017,8 +1040,10 @@ private ParseTree parseAsyncMethod(PartialClassElement partial) { FunctionDeclarationTree.Builder builder = FunctionDeclarationTree.builder(FunctionDeclarationTree.Kind.EXPRESSION) .setAsync(true) + .setGenerator(generator) .setStatic(partial.isStatic); - parseFunctionTail(builder, FunctionFlavor.ASYNCHRONOUS); + parseFunctionTail( + builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS); ParseTree function = builder.build(getTreeLocation(nameExpr.getStart())); return new ComputedPropertyMethodTree( @@ -1070,7 +1095,7 @@ private void parseFunctionTail( FunctionDeclarationTree.Builder builder, FunctionFlavor functionFlavor) { functionContextStack.addLast(functionFlavor); builder - .setGenerator(functionFlavor == FunctionFlavor.GENERATOR) + .setGenerator(functionFlavor.isGenerator) .setGenerics(maybeParseGenericTypes()) .setFormalParameterList(parseFormalParameterList(ParamContext.IMPLEMENTATION)) .setReturnType(maybeParseColonType()) @@ -1253,9 +1278,8 @@ private ParseTree parseAsyncFunctionDeclaration() { SourcePosition start = getTreeStartLocation(); eatAsyncFunctionStart(); - if (peek(TokenType.STAR)) { - reportError("async functions cannot be generators"); - // ignore the star to see how much more we can parse for errors + boolean generator = peek(TokenType.STAR); + if (generator) { eat(TokenType.STAR); } @@ -1264,7 +1288,8 @@ private ParseTree parseAsyncFunctionDeclaration() { .setName(eatId()) .setAsync(true); - parseFunctionTail(builder, FunctionFlavor.ASYNCHRONOUS); + parseFunctionTail( + builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS); return builder.build(getTreeLocation(start)); } @@ -1272,9 +1297,8 @@ private ParseTree parseAsyncFunctionExpression() { SourcePosition start = getTreeStartLocation(); eatAsyncFunctionStart(); - if (peek(TokenType.STAR)) { - reportError("async functions cannot be generators"); - // ignore the star to see how much more we can parse for errors + boolean generator = peek(TokenType.STAR); + if (generator) { eat(TokenType.STAR); } @@ -1283,7 +1307,8 @@ private ParseTree parseAsyncFunctionExpression() { .setName(eatIdOpt()) .setAsync(true); - parseFunctionTail(builder, FunctionFlavor.ASYNCHRONOUS); + parseFunctionTail( + builder, generator ? FunctionFlavor.ASYNCHRONOUS_GENERATOR : FunctionFlavor.ASYNCHRONOUS); return builder.build(getTreeLocation(start)); } @@ -1862,13 +1887,21 @@ private ParseTree parseWhileStatement() { // 12.6.3 The for Statement // 12.6.4 The for-in Statement // The for-of Statement + // The for-await-of Statement private ParseTree parseForStatement() { SourcePosition start = getTreeStartLocation(); eat(TokenType.FOR); + boolean awaited = peekPredefinedString(AWAIT); + if (awaited) { + eatPredefinedString(AWAIT); + } eat(TokenType.OPEN_PAREN); if (peekVariableDeclarationList()) { VariableDeclarationListTree variables = parseVariableDeclarationListNoIn(); if (peek(TokenType.IN)) { + if (awaited) { + reportError("for-await-of is the only allowed asynchronous iteration"); + } // for-in: only one declaration allowed if (variables.declarations.size() > 1) { reportError("for-in statement may not have more than one variable declaration"); @@ -1890,15 +1923,27 @@ private ParseTree parseForStatement() { } else if (peekPredefinedString(PredefinedName.OF)) { // for-of: only one declaration allowed if (variables.declarations.size() > 1) { - reportError("for-of statement may not have more than one variable declaration"); + if (awaited) { + reportError("for-await-of statement may not have more than one variable declaration"); + } else { + reportError("for-of statement may not have more than one variable declaration"); + } } // for-of: initializer is illegal VariableDeclarationTree declaration = variables.declarations.get(0); if (declaration.initializer != null) { - reportError("for-of statement may not have initializer"); + if (awaited) { + reportError("for-await-of statement may not have initializer"); + } else { + reportError("for-of statement may not have initializer"); + } } - return parseForOfStatement(start, variables); + if (awaited) { + return parseForAwaitOfStatement(start, variables); + } else { + return parseForOfStatement(start, variables); + } } else { // "Vanilla" for statement: const/destructuring must have initializer checkVanillaForInitializers(variables); @@ -1924,7 +1969,12 @@ private ParseTree parseForStatement() { if (peek(TokenType.IN)) { return parseForInStatement(start, initializer); } else { - return parseForOfStatement(start, initializer); + // for {await}? ( _ of _ ) + if (awaited) { + return parseForAwaitOfStatement(start, initializer); + } else { + return parseForOfStatement(start, initializer); + } } } } @@ -1944,6 +1994,14 @@ private ParseTree parseForOfStatement( getTreeLocation(start), initializer, collection, body); } + private ParseTree parseForAwaitOfStatement(SourcePosition start, ParseTree initializer) { + eatPredefinedString(PredefinedName.OF); + ParseTree collection = parseExpression(); + eat(TokenType.CLOSE_PAREN); + ParseTree body = parseStatement(); + return new ForAwaitOfStatementTree(getTreeLocation(start), initializer, collection, body); + } + /** Checks variable declarations in for statements. */ private void checkVanillaForInitializers(VariableDeclarationListTree variables) { for (VariableDeclarationTree declaration : variables.declarations) { @@ -3074,7 +3132,7 @@ private boolean peekAssignmentOperator() { private boolean inGeneratorContext() { // disallow yield outside of generators - return functionContextStack.peekLast() == FunctionFlavor.GENERATOR; + return functionContextStack.peekLast().isGenerator; } // yield [no line terminator] (*)? AssignExpression @@ -3349,8 +3407,7 @@ private boolean peekAwaitExpression() { private ParseTree parseAwaitExpression() { SourcePosition start = getTreeStartLocation(); - if (functionContextStack.isEmpty() - || functionContextStack.peekLast() != FunctionFlavor.ASYNCHRONOUS) { + if (functionContextStack.isEmpty() || !functionContextStack.peekLast().isAsynchronous) { reportError("'await' used in a non-async function context"); } eatPredefinedString(AWAIT); diff --git a/src/com/google/javascript/rhino/IR.java b/src/com/google/javascript/rhino/IR.java index 9cc9df5bb4a..6aa4eb1f1e3 100644 --- a/src/com/google/javascript/rhino/IR.java +++ b/src/com/google/javascript/rhino/IR.java @@ -711,6 +711,7 @@ private static boolean mayBeStatementNoReturn(Node n) { case FOR: case FOR_IN: case FOR_OF: + case FOR_AWAIT_OF: case IF: case LABEL: case LET: diff --git a/test/com/google/javascript/jscomp/AstValidatorTest.java b/test/com/google/javascript/jscomp/AstValidatorTest.java index cabde3341b4..4ada23f9edc 100644 --- a/test/com/google/javascript/jscomp/AstValidatorTest.java +++ b/test/com/google/javascript/jscomp/AstValidatorTest.java @@ -115,6 +115,16 @@ public void testForOf() { valid("for(a of {});"); } + public void testForAwaitOf() { + setAcceptedLanguage(LanguageMode.ECMASCRIPT_2018); + valid("for await(var a of b);"); + valid("for await(let a of b);"); + valid("for await(const a of b);"); + valid("for await(a of b);"); + valid("for await(a of []);"); + valid("for await(a of {});"); + } + public void testQuestionableForIn() { setAcceptedLanguage(LanguageMode.ECMASCRIPT5); setExpectParseWarningsThisTest(); @@ -444,6 +454,10 @@ public void testFeatureValidation_forOf() { testFeatureValidation("for (const a of b) {}", Feature.FOR_OF); } + public void testFeatureValidation_forAwaitOf() { + testFeatureValidation("for await (const a of b) {}", Feature.FOR_AWAIT_OF); + } + public void testFeatureValidation_generatorFunctions() { testFeatureValidation("const f = function *() {}", Feature.GENERATORS); testFeatureValidation("function *f() {}", Feature.GENERATORS); @@ -496,6 +510,12 @@ public void testFeatureValidation_asyncFunctions() { testFeatureValidation("(async () => {})", Feature.ASYNC_FUNCTIONS); } + public void testFeatureValidation_asyncGeneratorFunctions() { + testFeatureValidation("const f = async function *() {}", Feature.ASYNC_GENERATORS); + testFeatureValidation("async function *f() {}", Feature.ASYNC_GENERATORS); + testFeatureValidation("class C { async *f() {} }", Feature.ASYNC_GENERATORS); + } + public void testFeatureValidation_objectLiteralsWithSpread() { testFeatureValidation("var obj = {...something};", Feature.OBJECT_LITERALS_WITH_SPREAD); } @@ -570,7 +590,7 @@ private void testFeatureValidation(String code, Feature feature) { } private Node parseScriptWithoutCheckingLanguageLevel(String code) { - setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); + setAcceptedLanguage(LanguageMode.ECMASCRIPT_2018); Node n = parseExpectedJs(code); Node script = n.getFirstChild(); assertNode(script).hasType(Token.SCRIPT); diff --git a/test/com/google/javascript/jscomp/CodePrinterTest.java b/test/com/google/javascript/jscomp/CodePrinterTest.java index 1a39b76a89a..c616b235a89 100644 --- a/test/com/google/javascript/jscomp/CodePrinterTest.java +++ b/test/com/google/javascript/jscomp/CodePrinterTest.java @@ -685,12 +685,30 @@ public void testForOfPretty() { assertPrettyPrintSame("for ([x, y] of [[1, 2]]) {\n c;\n}\n"); } + public void testForAwaitOf() { + languageMode = LanguageMode.ECMASCRIPT_2018; + + assertPrintSame("for await(a of b)c"); + assertPrintSame("for await(var a of b)c"); + } + + // In pretty-print mode, make sure there is a space before and after the 'of' in a for/of loop. + public void testForAwaitOfPretty() { + languageMode = LanguageMode.ECMASCRIPT_2018; + + assertPrettyPrintSame("for await ([x, y] of b) {\n c;\n}\n"); + assertPrettyPrintSame("for await (x of [[1, 2]]) {\n c;\n}\n"); + assertPrettyPrintSame("for await ([x, y] of [[1, 2]]) {\n c;\n}\n"); + } + public void testLetFor() { languageMode = LanguageMode.ECMASCRIPT_2015; assertPrintSame("for(let a=0;a<5;a++)b"); assertPrintSame("for(let a in b)c"); assertPrintSame("for(let a of b)c"); + languageMode = LanguageMode.ECMASCRIPT_2018; + assertPrintSame("for await(let a of b)c"); } public void testConstFor() { @@ -699,8 +717,12 @@ public void testConstFor() { assertPrintSame("for(const a=5;b1"); @@ -2461,6 +2493,16 @@ public void testAsyncMethod() { assertPrintSame("class C{static async[a+b](){}}"); } + public void testAsyncGeneratorMethod() { + languageMode = LanguageMode.ECMASCRIPT_2018; + assertPrintSame("o={async *m(){}}"); + assertPrintSame("o={async*[a+b](){}}"); + assertPrintSame("class C{async *m(){}}"); + assertPrintSame("class C{async*[a+b](){}}"); + assertPrintSame("class C{static async *m(){}}"); + assertPrintSame("class C{static async*[a+b](){}}"); + } + public void testAwaitExpression() { languageMode = LanguageMode.ECMASCRIPT_NEXT; assertPrintSame("async function f(promise){return await promise}"); diff --git a/test/com/google/javascript/jscomp/IntegrationTest.java b/test/com/google/javascript/jscomp/IntegrationTest.java index 99ca4fac059..af1822a815e 100644 --- a/test/com/google/javascript/jscomp/IntegrationTest.java +++ b/test/com/google/javascript/jscomp/IntegrationTest.java @@ -5230,6 +5230,25 @@ public void testDestructuringCannotConvert() { test(options, "for (const [x] = [], {y} = {}, z = 2;;) {}", Es6ToEs3Util.CANNOT_CONVERT_YET); } + public void testAsyncIter() { + CompilerOptions options = createCompilerOptions(); + options.setLanguageIn(LanguageMode.ECMASCRIPT_2018); + options.setLanguageOut(LanguageMode.ECMASCRIPT_2018); + testSame(options, "async function* foo() {}"); + testSame(options, "for await (a of b) {}"); + + options.setLanguageOut(LanguageMode.ECMASCRIPT_2017); + test( + options, + "async function* foo() {}", + RewriteAsyncIteration.CANNOT_CONVERT_ASYNC_ITERATION_YET); + + test( + options, + "for await (a of foo()) {}", + RewriteAsyncIteration.CANNOT_CONVERT_ASYNC_ITERATION_YET); + } + public void testDestructuringRest() { CompilerOptions options = createCompilerOptions(); options.setLanguageIn(LanguageMode.ECMASCRIPT_NEXT); diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index e6b36e29307..524b37fdf6d 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -3684,11 +3684,13 @@ public void testAsyncNamedFunction() { "let foo = async(5);")); } - public void testInvalidAsyncFunction() { - mode = LanguageMode.ECMASCRIPT8; + public void testAsyncGeneratorFunction() { + mode = LanguageMode.ECMASCRIPT2018; + expectFeatures(Feature.ASYNC_FUNCTIONS, Feature.GENERATORS, Feature.ASYNC_GENERATORS); strictMode = STRICT; - parseError("async function *f(){}", "async functions cannot be generators"); - parseError("f = async function *(){}", "async functions cannot be generators"); + parse("async function *f(){}"); + parse("f = async function *(){}"); + parse("class C { async *foo(){} }"); } public void testAsyncArrowFunction() { @@ -3935,6 +3937,53 @@ public void testForOf4() { parseError("for(a, b of c) d;", INVALID_ASSIGNMENT_TARGET); } + public void testValidForAwaitOf() { + mode = LanguageMode.ECMASCRIPT2018; + strictMode = SLOPPY; + + expectFeatures(Feature.FOR_AWAIT_OF); + parse("for await(a of b) c;"); + parse("for await(var a of b) c;"); + parse("for await (a.x of b) c;"); + parse("for await ([a1, a2, a3] of b) c;"); + parse("for await (const {x, y, z} of b) c;"); + // default value inside a pattern isn't an initializer + parse("for await (const {x, y = 2, z} of b) c;"); + expectFeatures(Feature.FOR_AWAIT_OF, Feature.LET_DECLARATIONS); + parse("for await(let a of b) c;"); + expectFeatures(Feature.FOR_AWAIT_OF, Feature.CONST_DECLARATIONS); + parse("for await(const a of b) c;"); + } + + public void testInvalidForAwaitOfInitializers() { + mode = LanguageMode.ECMASCRIPT2018; + strictMode = SLOPPY; + + parseError("for await (a=1 of b) c;", INVALID_ASSIGNMENT_TARGET); + parseError("for await (var a=1 of b) c;", "for-await-of statement may not have initializer"); + parseError("for await (let a=1 of b) c;", "for-await-of statement may not have initializer"); + parseError("for await (const a=1 of b) c;", "for-await-of statement may not have initializer"); + parseError( + "for await (let {a} = {} of b) c;", "for-await-of statement may not have initializer"); + } + + public void testInvalidForAwaitOfMultipleInitializerTargets() { + mode = LanguageMode.ECMASCRIPT2018; + strictMode = SLOPPY; + + parseError("for await (a, b of c) d;", INVALID_ASSIGNMENT_TARGET); + + parseError( + "for await (var a, b of c) d;", + "for-await-of statement may not have more than one variable declaration"); + parseError( + "for await (let a, b of c) d;", + "for-await-of statement may not have more than one variable declaration"); + parseError( + "for await (const a, b of c) d;", + "for-await-of statement may not have more than one variable declaration"); + } + public void testDestructuringInForLoops() { mode = LanguageMode.ECMASCRIPT6; strictMode = SLOPPY;