All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.RewriteAsyncFunctions Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2016 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Optional;
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.Node;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Converts async functions to valid ES6 generator functions code.
 *
 * 

This pass must run before the passes that transpile let declarations, arrow functions, and * generators. * *

An async function, foo(a, b), will be rewritten as: * *

 {@code
 * function foo(a, b) {
 *   let $jscomp$async$this = this;
 *   let $jscomp$async$arguments = arguments;
 *   let $jscomp$async$super$get$x = () => super.x;
 *   function* $jscomp$async$generator() {
 *     // original body of foo() with:
 *     // - await (x) replaced with yield (x)
 *     // - arguments replaced with $jscomp$async$arguments
 *     // - this replaced with $jscomp$async$this
 *     // - super.x replaced with $jscomp$async$super$get$x()
 *     // - super.x(5) replaced with $jscomp$async$super$get$x().call($jscomp$async$this, 5)
 *   }
 *   return $jscomp.executeAsyncGenerator($jscomp$async$generator());
 * }}
*/ public final class RewriteAsyncFunctions implements NodeTraversal.Callback, HotSwapCompilerPass { private static final String ASYNC_GENERATOR_NAME = "$jscomp$async$generator"; private static final String ASYNC_ARGUMENTS = "$jscomp$async$arguments"; private static final String ASYNC_THIS = "$jscomp$async$this"; private static final String ASYNC_SUPER_PROP_GETTER_PREFIX = "$jscomp$async$super$get$"; /** * Keeps track of whether we're examining nodes within an async function & what changes are needed * for the function currently in context. */ private static final class LexicalContext { final Optional function; // absent for top level final LexicalContext thisAndArgumentsContext; final Set replacedSuperProperties = new LinkedHashSet<>(); boolean mustAddAsyncThisVariable = false; boolean mustAddAsyncArgumentsVariable = false; /** Creates root-level context. */ LexicalContext() { this.function = Optional.absent(); this.thisAndArgumentsContext = this; } LexicalContext(LexicalContext outer, Node function) { this.function = Optional.of(function); // An arrow function shares 'this' and 'arguments' with its outer scope. this.thisAndArgumentsContext = function.isArrowFunction() ? outer.thisAndArgumentsContext : this; } boolean isAsyncContext() { return function.isPresent() && function.get().isAsyncFunction(); } boolean mustReplaceThisAndArguments() { return isAsyncContext() || thisAndArgumentsContext.isAsyncContext(); } LexicalContext getAsyncThisAndArgumentsContext() { if (thisAndArgumentsContext.isAsyncContext()) { return thisAndArgumentsContext; } // The current context is an async arrow function within a non-async function, // so it must define its own replacement variables. checkState(isAsyncContext()); return this; } void recordAsyncThisReplacementWasDone() { getAsyncThisAndArgumentsContext().mustAddAsyncThisVariable = true; } void recordAsyncSuperReplacementWasDone(String superFunctionName) { getAsyncThisAndArgumentsContext().replacedSuperProperties.add(superFunctionName); } void recordAsyncArgumentsReplacementWasDone() { getAsyncThisAndArgumentsContext().mustAddAsyncArgumentsVariable = true; } } private final Deque contextStack; private final AbstractCompiler compiler; private static final FeatureSet transpiledFeatures = FeatureSet.BARE_MINIMUM.with(Feature.ASYNC_FUNCTIONS); public RewriteAsyncFunctions(AbstractCompiler compiler) { checkNotNull(compiler); this.compiler = compiler; this.contextStack = new ArrayDeque<>(); this.contextStack.addFirst(new LexicalContext()); } @Override public void process(Node externs, Node root) { TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this); TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, transpiledFeatures, this); } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (n.isFunction()) { contextStack.addFirst(new LexicalContext(contextStack.getFirst(), n)); if (n.isAsyncFunction()) { compiler.ensureLibraryInjected("es6/execute_async_generator", /* force */ false); } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { LexicalContext context = contextStack.getFirst(); if (n.isFunction()) { checkState( context.function.isPresent() && context.function.get() == n, "unexpected function context:\nexpected: %s\nactual: %s", n, context.function); contextStack.removeFirst(); } switch (n.getToken()) { case FUNCTION: if (context.isAsyncContext()) { convertAsyncFunction(context); } break; case NAME: if (context.mustReplaceThisAndArguments() && n.matchesQualifiedName("arguments")) { n.setString(ASYNC_ARGUMENTS); context.recordAsyncArgumentsReplacementWasDone(); compiler.reportChangeToChangeScope(context.function.get()); } break; case THIS: if (context.mustReplaceThisAndArguments()) { parent.replaceChild(n, IR.name(ASYNC_THIS).useSourceInfoIfMissingFrom(n)); context.recordAsyncThisReplacementWasDone(); compiler.reportChangeToChangeScope(context.function.get()); } break; case SUPER: if (context.mustReplaceThisAndArguments()) { if (!parent.isGetProp()) { compiler.report( JSError.make(parent, Es6ToEs3Util.CANNOT_CONVERT_YET, "super expression")); } Node medhodName = n.getNext(); String superPropertyName = ASYNC_SUPER_PROP_GETTER_PREFIX + medhodName.getString(); // super.x => $super$get$x() Node getPropReplacement = NodeUtil.newCallNode(IR.name(superPropertyName)); Node grandparent = parent.getParent(); if (grandparent.isCall() && grandparent.getFirstChild() == parent) { // $super$get$x()(...) => $super$get$x().call($this, ...) getPropReplacement = IR.getprop(getPropReplacement, IR.string("call")); grandparent.addChildAfter(IR.name(ASYNC_THIS), parent); context.recordAsyncThisReplacementWasDone(); } getPropReplacement.useSourceInfoFrom(parent); grandparent.replaceChild(parent, getPropReplacement); context.recordAsyncSuperReplacementWasDone(medhodName.getString()); compiler.reportChangeToChangeScope(context.function.get()); } break; case AWAIT: checkState(context.isAsyncContext(), "await found within non-async function body"); checkState(n.hasOneChild(), "await should have 1 operand, but has %s", n.getChildCount()); // Awaits become yields in the converted async function's inner generator function. parent.replaceChild(n, IR.yield(n.removeFirstChild()).useSourceInfoIfMissingFrom(n)); break; default: break; } } private void convertAsyncFunction(LexicalContext functionContext) { Node originalFunction = functionContext.function.get(); originalFunction.setIsAsyncFunction(false); Node originalBody = originalFunction.getLastChild(); Node newBody = IR.block().useSourceInfoIfMissingFrom(originalBody); originalFunction.replaceChild(originalBody, newBody); if (functionContext.mustAddAsyncThisVariable) { // const this$ = this; newBody.addChildToBack(IR.constNode(IR.name(ASYNC_THIS), IR.thisNode())); } if (functionContext.mustAddAsyncArgumentsVariable) { // const arguments$ = arguments; newBody.addChildToBack(IR.constNode(IR.name(ASYNC_ARGUMENTS), IR.name("arguments"))); } for (String replacedMethodName : functionContext.replacedSuperProperties) { // const super$get$x = () => super.x; Node arrowFunction = IR.arrowFunction( IR.name(""), IR.paramList(), IR.getprop(IR.superNode(), IR.string(replacedMethodName))); compiler.reportChangeToChangeScope(arrowFunction); String superReplacementName = ASYNC_SUPER_PROP_GETTER_PREFIX + replacedMethodName; newBody.addChildToBack(IR.constNode(IR.name(superReplacementName), arrowFunction)); } // Normalize arrow function short body to block body if (!originalBody.isNormalBlock()) { originalBody = IR.block(IR.returnNode(originalBody)).useSourceInfoFromForTree(originalBody); } // NOTE: visit() will already have made appropriate replacements in originalBody so it may // be used as the generator function body. Node newFunctionName = IR.name(ASYNC_GENERATOR_NAME); Node originalName = originalFunction.getFirstChild(); // Use the source info from the function name. Without this line, we would use the source info // from originalBody for the name node near the end of this method. newFunctionName.useSourceInfoIfMissingFromForTree(originalName); Node generatorFunction = IR.function(newFunctionName, IR.paramList(), originalBody); compiler.reportChangeToChangeScope(generatorFunction); generatorFunction.setIsGeneratorFunction(true); // function* $jscomp$async$generator() { ... } newBody.addChildToBack(generatorFunction); // return $jscomp.executeAsyncGenerator($jscomp$async$generator()); Node executeAsyncGenerator = IR.getprop(IR.name("$jscomp"), IR.string("executeAsyncGenerator")); newBody.addChildToBack( IR.returnNode( IR.call(executeAsyncGenerator, NodeUtil.newCallNode(IR.name(ASYNC_GENERATOR_NAME))))); newBody.useSourceInfoIfMissingFromForTree(originalBody); compiler.reportChangeToEnclosingScope(newBody); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy