com.google.javascript.jscomp.RewriteAsyncIteration Maven / Gradle / Ivy
/*
* 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Supplier;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import java.util.ArrayDeque;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Converts async generator functions into a function returning a new $jscomp.AsyncGenWrapper around
* the original block and awaits/yields converted to yields of ActionRecords.
*
* {@code
* async function* foo() {
* let res = await myPromise;
* yield res + 1;
* }
* }
*
* becomes (prefixes trimmed for clarity)
*
*
{@code
* function foo() {
* return new $jscomp.AsyncGeneratorWrapper((function*(){
* let res = yield new $ActionRecord($ActionEnum.AWAIT_VALUE, myPromise);
* yield new $ActionRecord($ActionEnum.YIELD_VALUE, res + 1);
* })());
* }
* }
*/
public final class RewriteAsyncIteration implements NodeTraversal.Callback, HotSwapCompilerPass {
private static final FeatureSet transpiledFeatures =
FeatureSet.BARE_MINIMUM.with(Feature.ASYNC_GENERATORS, Feature.FOR_AWAIT_OF);
static final DiagnosticType CANNOT_CONVERT_ASYNCGEN =
DiagnosticType.error("JSC_CANNOT_CONVERT_ASYNCGEN", "Cannot convert async generator. {0}");
private static final String ACTION_RECORD_NAME = "$jscomp.AsyncGeneratorWrapper$ActionRecord";
private static final String ACTION_ENUM_AWAIT =
"$jscomp.AsyncGeneratorWrapper$ActionEnum.AWAIT_VALUE";
private static final String ACTION_ENUM_YIELD =
"$jscomp.AsyncGeneratorWrapper$ActionEnum.YIELD_VALUE";
private static final String ACTION_ENUM_YIELD_STAR =
"$jscomp.AsyncGeneratorWrapper$ActionEnum.YIELD_STAR";
private static final String FOR_AWAIT_ITERATOR_TEMP_NAME = "$jscomp$forAwait$tempIterator";
private static final String FOR_AWAIT_RESULT_TEMP_NAME = "$jscomp$forAwait$tempResult";
private int nextForAwaitId = 0;
private final AbstractCompiler compiler;
private final ArrayDeque contextStack;
private final String thisVarName = "$jscomp$asyncIter$this";
private final String argumentsVarName = "$jscomp$asyncIter$arguments";
private final String superPropGetterPrefix = "$jscomp$asyncIter$super$get$";
private final JSTypeRegistry registry;
private final AstFactory astFactory;
private final JSType unknownType;
/**
* If this option is set to true, then this pass will rewrite references to properties using super
* (e.g. `super.method()`) to avoid using `super` within an arrow function.
*
* This option exists due to a bug in MS Edge 17 which causes it to fail to access super
* properties correctly from within arrow functions.
*
*
See https://github.com/Microsoft/ChakraCore/issues/5784
*
*
If the final compiler output will not include ES6 classes, this option should not be set. It
* isn't needed since the `super` references will be transpiled away anyway. Also, when this
* option is set it uses `Object.getPrototypeOf()` to rewrite `super`, which may not exist in
* pre-ES6 JS environments.
*/
private final boolean rewriteSuperPropertyReferencesWithoutSuper;
/**
* Tracks a function and its context of this/arguments/super, if such a context exists.
*/
private static final class LexicalContext {
// Node that creates the context
private final Node contextRoot;
// The current function, or null if root scope where we are not in a function.
private final Node function;
// The context of the most recent definition of this/super/arguments
private final ThisSuperArgsContext thisSuperArgsContext;
// Represents the global/root scope. Should only exist on the bottom of the contextStack.
private LexicalContext(Node contextRoot) {
this.contextRoot = checkNotNull(contextRoot);
this.function = null;
this.thisSuperArgsContext = null;
}
/**
* Represents the context of a function or its parameter list.
*
* @param parent enclosing context
* @param contextRoot FUNCTION or PARAM_LIST node
* @param function same as contextRoot or the FUNCTION containing the PARAM_LIST
*/
private LexicalContext(LexicalContext parent, Node contextRoot, Node function) {
checkNotNull(parent);
checkNotNull(contextRoot);
checkArgument(contextRoot == function || contextRoot.isParamList(), contextRoot);
checkNotNull(function);
checkArgument(function.isFunction(), function);
this.contextRoot = contextRoot;
this.function = function;
if (function.isArrowFunction()) {
// Use the parent context to inherit this, arguments, and super for an arrow function or its
// parameter list.
this.thisSuperArgsContext = parent.thisSuperArgsContext;
} else if (contextRoot.isFunction()) {
// Non-arrow function gets its own context defining `this`, `arguments`, and `super`.
this.thisSuperArgsContext = new ThisSuperArgsContext(this);
} else {
// contextRoot is a parameter list.
// Never alias `this`, `arguments`, or `super` for normal function parameter lists.
// They are implicitly defined there.
this.thisSuperArgsContext = null;
}
}
static LexicalContext newGlobalContext(Node contextRoot) {
return new LexicalContext(contextRoot);
}
static LexicalContext newContextForFunction(LexicalContext parent, Node function) {
// Functions need their own context because:
// - async generator functions must be transpiled
// - non-async generator functions must NOT be transpiled
// - arrow functions inside of async generator functions need to have
// `this`, `arguments`, and `super` references aliased, including in their
// parameter lists
return new LexicalContext(parent, function, function);
}
static LexicalContext newContextForParamList(LexicalContext parent, Node paramList) {
// Parameter lists need their own context because `this`, `arguments`, and `super` must NOT be
// aliased for non-arrow function parameter lists, even for async generator functions.
return new LexicalContext(parent, paramList, parent.function);
}
Node getFunctionDeclaringThisArgsSuper() {
return thisSuperArgsContext.ctx.function;
}
/** Is it necessary to replace `this`, `super`, and `arguments` with aliases in this context? */
boolean mustReplaceThisSuperArgs() {
return thisSuperArgsContext != null
&& getFunctionDeclaringThisArgsSuper().isAsyncGeneratorFunction();
}
}
/**
* Tracks how this/arguments/super were used in the function so declarations of replacement
* variables can be prepended
*/
private static final class ThisSuperArgsContext {
/** The LexicalContext representing the function that declared this/super/args */
private final LexicalContext ctx;
private final Set usedSuperProperties = new LinkedHashSet<>();
private boolean usedThis = false;
private boolean usedArguments = false;
ThisSuperArgsContext(LexicalContext ctx) {
this.ctx = ctx;
}
}
private RewriteAsyncIteration(Builder builder) {
this.compiler = builder.compiler;
this.contextStack = new ArrayDeque<>();
this.rewriteSuperPropertyReferencesWithoutSuper =
builder.rewriteSuperPropertyReferencesWithoutSuper;
this.registry = builder.registry;
this.astFactory = builder.astFactory;
this.unknownType = createType(() -> registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
private T createType(Supplier fn) {
if (astFactory.isAddingTypes()) {
return fn.get();
}
return null;
}
private JSType createGenericType(JSTypeNative typeName, JSType typeArg) {
return Es6ToEs3Util.createGenericType(astFactory.isAddingTypes(), registry, typeName, typeArg);
}
static class Builder {
private final AbstractCompiler compiler;
private boolean rewriteSuperPropertyReferencesWithoutSuper = false;
private JSTypeRegistry registry;
private AstFactory astFactory;
Builder(AbstractCompiler compiler) {
checkNotNull(compiler);
this.compiler = compiler;
}
Builder rewriteSuperPropertyReferencesWithoutSuper(boolean value) {
rewriteSuperPropertyReferencesWithoutSuper = value;
return this;
}
RewriteAsyncIteration build() {
astFactory = compiler.createAstFactory();
registry = compiler.getTypeRegistry();
return new RewriteAsyncIteration(this);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
process(scriptRoot, /* hotSwap= */ true);
}
@Override
public void process(Node externs, Node root) {
process(root, /* hotSwap= */ false);
}
/**
* Helper function for both HotSwapCompilerPass#hotSwapScript and CompilerPass#process.
*
* @param root Root of AST to rewrite
*/
private void process(Node root, boolean hotSwap) {
checkState(contextStack.isEmpty());
contextStack.push(LexicalContext.newGlobalContext(root));
if (hotSwap) {
TranspilationPasses.hotSwapTranspile(compiler, root, transpiledFeatures, this);
} else {
TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this);
}
TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures);
checkState(contextStack.element().function == null);
contextStack.remove();
checkState(contextStack.isEmpty());
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
if (n.isFunction()) {
contextStack.push(LexicalContext.newContextForFunction(contextStack.element(), n));
} else if (n.isParamList()) {
contextStack.push(LexicalContext.newContextForParamList(contextStack.element(), n));
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
LexicalContext ctx = contextStack.element();
switch (n.getToken()) {
// Async Generators (and popping contexts)
case PARAM_LIST:
// Done handling parameter list, so pop its context
checkState(n.equals(ctx.contextRoot), n);
contextStack.pop();
break;
case FUNCTION:
checkState(n.equals(ctx.contextRoot));
if (n.isAsyncGeneratorFunction()) {
convertAsyncGenerator(t, n);
prependTempVarDeclarations(ctx, t);
}
// Done handling function, so pop its context
contextStack.pop();
break;
case AWAIT:
checkNotNull(ctx.function);
if (ctx.function.isAsyncGeneratorFunction()) {
convertAwaitOfAsyncGenerator(t, ctx, n);
}
break;
case YIELD: // Includes yield*
checkNotNull(ctx.function);
if (ctx.function.isAsyncGeneratorFunction()) {
convertYieldOfAsyncGenerator(t, ctx, n);
}
break;
// For-Await-Of loops
case FOR_AWAIT_OF:
checkNotNull(ctx.function);
checkState(ctx.function.isAsyncFunction());
replaceForAwaitOf(t, ctx, n);
NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.CONST_DECLARATIONS, compiler);
break;
// Maintaining references to this/arguments/super
case THIS:
if (ctx.mustReplaceThisSuperArgs()) {
replaceThis(t, ctx, n);
}
break;
case NAME:
if (ctx.mustReplaceThisSuperArgs() && n.matchesName("arguments")) {
replaceArguments(t, ctx, n);
}
break;
case SUPER:
if (ctx.mustReplaceThisSuperArgs()) {
replaceSuper(t, ctx, n, parent);
}
break;
default:
break;
}
}
/**
* Moves the body of an async generator function into a nested generator function and removes the
* async and generator props from the original function.
*
* {@code
* async function* foo() {
* bar();
* }
* }
*
* becomes
*
*
{@code
* function foo() {
* return new $jscomp.AsyncGeneratorWrapper((function*(){
* bar();
* })())
* }
* }
*
* @param originalFunction the original AsyncGeneratorFunction Node to be converted.
*/
private void convertAsyncGenerator(NodeTraversal t, Node originalFunction) {
checkNotNull(originalFunction);
checkState(originalFunction.isAsyncGeneratorFunction());
Node asyncGeneratorWrapperRef =
astFactory.createAsyncGeneratorWrapperReference(originalFunction.getJSType(), t.getScope());
Node innerFunction =
astFactory.createEmptyAsyncGeneratorWrapperArgument(asyncGeneratorWrapperRef.getJSType());
Node innerBlock = originalFunction.getLastChild();
originalFunction.removeChild(innerBlock);
innerFunction.replaceChild(innerFunction.getLastChild(), innerBlock);
// Body should be:
// return new $jscomp.AsyncGeneratorWrapper((new function with original block here)());
Node outerBlock =
astFactory.createBlock(
astFactory.createReturn(
astFactory.createNewNode(
asyncGeneratorWrapperRef, astFactory.createCall(innerFunction))));
originalFunction.addChildToBack(outerBlock);
originalFunction.setIsAsyncFunction(false);
originalFunction.setIsGeneratorFunction(false);
originalFunction.useSourceInfoIfMissingFromForTree(originalFunction);
// Both the inner and original functions should be marked as changed.
compiler.reportChangeToChangeScope(originalFunction);
compiler.reportChangeToChangeScope(innerFunction);
}
/**
* Converts an await into a yield of an ActionRecord to perform "AWAIT".
*
* {@code await myPromise}
*
* becomes
*
*
{@code yield new ActionRecord(ActionEnum.AWAIT_VALUE, myPromise)}
*
* @param awaitNode the original await Node to be converted
*/
private void convertAwaitOfAsyncGenerator(NodeTraversal t, LexicalContext ctx, Node awaitNode) {
checkNotNull(awaitNode);
checkState(awaitNode.isAwait());
checkState(ctx != null && ctx.function != null);
checkState(ctx.function.isAsyncGeneratorFunction());
Node expression = awaitNode.removeFirstChild();
checkNotNull(expression, "await needs an expression");
Node newActionRecord =
astFactory.createNewNode(
astFactory.createQName(t.getScope(), ACTION_RECORD_NAME),
astFactory.createQName(t.getScope(), ACTION_ENUM_AWAIT),
expression);
newActionRecord.useSourceInfoIfMissingFromForTree(awaitNode);
awaitNode.addChildToFront(newActionRecord);
awaitNode.setToken(Token.YIELD);
}
/**
* Converts a yield into a yield of an ActionRecord to perform "YIELD" or "YIELD_STAR".
*
* {@code
* yield;
* yield first;
* yield* second;
* }
*
* becomes
*
*
{@code
* yield new ActionRecord(ActionEnum.YIELD_VALUE, undefined);
* yield new ActionRecord(ActionEnum.YIELD_VALUE, first);
* yield new ActionRecord(ActionEnum.YIELD_STAR, second);
* }
*
* @param yieldNode the Node to be converted
*/
private void convertYieldOfAsyncGenerator(NodeTraversal t, LexicalContext ctx, Node yieldNode) {
checkNotNull(yieldNode);
checkState(yieldNode.isYield());
checkState(ctx != null && ctx.function != null);
checkState(ctx.function.isAsyncGeneratorFunction());
Node expression = yieldNode.removeFirstChild();
Node newActionRecord =
astFactory.createNewNode(astFactory.createQName(t.getScope(), ACTION_RECORD_NAME));
if (yieldNode.isYieldAll()) {
checkNotNull(expression);
// yield* expression becomes new ActionRecord(YIELD_STAR, expression)
newActionRecord.addChildToBack(astFactory.createQName(t.getScope(), ACTION_ENUM_YIELD_STAR));
newActionRecord.addChildToBack(expression);
} else {
if (expression == null) {
expression = NodeUtil.newUndefinedNode(null);
}
// yield expression becomes new ActionRecord(YIELD, expression)
newActionRecord.addChildToBack(astFactory.createQName(t.getScope(), ACTION_ENUM_YIELD));
newActionRecord.addChildToBack(expression);
}
newActionRecord.useSourceInfoIfMissingFromForTree(yieldNode);
yieldNode.addChildToFront(newActionRecord);
yieldNode.removeProp(Node.YIELD_ALL);
}
/**
* for await (lhs of rhs) { block(); }
*
* ...becomes...
*
*
{@code
* for (const tmpIterator = makeAsyncIterator(rhs);;) {
* const tmpRes = await tmpIterator.next();
* if (tmpRes.done) {
* break;
* }
* lhs = $tmpRes.value;
* {
* block(); // Wrapped in a block in case block re-declares lhs variable.
* }
* }
* }
*
* @param forAwaitOf
*/
private void replaceForAwaitOf(NodeTraversal t, LexicalContext ctx, Node forAwaitOf) {
int forAwaitId = nextForAwaitId++;
String iteratorTempName = FOR_AWAIT_ITERATOR_TEMP_NAME + forAwaitId;
String resultTempName = FOR_AWAIT_RESULT_TEMP_NAME + forAwaitId;
checkState(forAwaitOf.getParent() != null, "Cannot replace parentless for-await-of");
Node lhs = forAwaitOf.removeFirstChild();
Node rhs = forAwaitOf.removeFirstChild();
Node originalBody = forAwaitOf.removeFirstChild();
JSType typeParam =
createType(
() ->
JsIterables.maybeBoxIterableOrAsyncIterable(rhs.getJSType(), registry)
.orElse(unknownType));
Node initializer =
astFactory
.createSingleConstNameDeclaration(
iteratorTempName, astFactory.createJSCompMakeAsyncIteratorCall(rhs, t.getScope()))
.useSourceInfoIfMissingFromForTree(rhs);
// IIterableResult
JSType iterableResultType = createGenericType(JSTypeNative.I_ITERABLE_RESULT_TYPE, typeParam);
// const tmpRes = await tmpIterator.next()
Node resultDeclaration =
astFactory.createSingleConstNameDeclaration(
resultTempName,
constructAwaitNextResult(
t,
ctx,
iteratorTempName,
initializer.getFirstChild().getJSType(),
iterableResultType));
Node breakIfDone =
astFactory.createIf(
astFactory.createGetProp(
astFactory.createName(resultTempName, iterableResultType), "done"),
astFactory.createBlock(astFactory.createBreak()));
// Assignment statement to be moved from lhs into body of new for-loop
Node lhsAssignment;
if (lhs.isValidAssignmentTarget()) {
// In case of "for await (x of _)" just assign into the lhs.
lhsAssignment =
astFactory.exprResult(
astFactory.createAssign(
lhs,
astFactory.createGetProp(
astFactory.createName(resultTempName, iterableResultType), "value")));
} else if (NodeUtil.isNameDeclaration(lhs)) {
// In case of "for await (let x of _)" add a rhs to the let, becoming "let x = res.value"
lhs.getFirstChild()
.addChildToBack(
astFactory.createGetProp(
astFactory.createName(resultTempName, iterableResultType), "value"));
lhsAssignment = lhs;
} else {
throw new AssertionError("unexpected for-await-of lhs");
}
lhsAssignment.useSourceInfoIfMissingFromForTree(lhs);
Node newForLoop =
astFactory.createFor(
initializer,
astFactory.createEmpty(),
astFactory.createEmpty(),
astFactory.createBlock(
resultDeclaration, breakIfDone, lhsAssignment, ensureBlock(originalBody)));
forAwaitOf.replaceWith(newForLoop);
newForLoop.useSourceInfoIfMissingFromForTree(forAwaitOf);
compiler.reportChangeToEnclosingScope(newForLoop);
}
private Node ensureBlock(Node possiblyBlock) {
return possiblyBlock.isBlock()
? possiblyBlock
: astFactory.createBlock(possiblyBlock).useSourceInfoFrom(possiblyBlock);
}
private Node constructAwaitNextResult(
NodeTraversal t,
LexicalContext ctx,
String iteratorTempName,
JSType iteratorType,
JSType iterableResultType) {
checkNotNull(ctx.function);
Node result;
Node iteratorTemp = astFactory.createName(iteratorTempName, iteratorType);
if (ctx.function.isAsyncGeneratorFunction()) {
// We are in an AsyncGenerator and must instead yield an "await" ActionRecord
result =
astFactory.createYield(
iterableResultType,
astFactory.createNewNode(
astFactory.createQName(t.getScope(), ACTION_RECORD_NAME),
astFactory.createQName(t.getScope(), ACTION_ENUM_AWAIT),
astFactory.createCall(astFactory.createGetProp(iteratorTemp, "next"))));
} else {
result =
astFactory.createAwait(
iterableResultType,
astFactory.createCall(astFactory.createGetProp(iteratorTemp, "next")));
}
return result;
}
private void replaceThis(NodeTraversal t, LexicalContext ctx, Node n) {
checkArgument(n.isThis());
checkArgument(ctx != null && ctx.mustReplaceThisSuperArgs());
checkArgument(ctx.function != null, "Cannot prepend declarations to root scope");
checkNotNull(ctx.thisSuperArgsContext);
n.replaceWith(astFactory.createName(t.getScope(), thisVarName).useSourceInfoFrom(n));
ctx.thisSuperArgsContext.usedThis = true;
compiler.reportChangeToChangeScope(ctx.function);
}
private void replaceArguments(NodeTraversal t, LexicalContext ctx, Node n) {
checkArgument(n.isName() && "arguments".equals(n.getString()));
checkArgument(ctx != null && ctx.mustReplaceThisSuperArgs());
checkArgument(ctx.function != null, "Cannot prepend declarations to root scope");
checkNotNull(ctx.thisSuperArgsContext);
n.replaceWith(astFactory.createName(t.getScope(), argumentsVarName).useSourceInfoFrom(n));
ctx.thisSuperArgsContext.usedArguments = true;
compiler.reportChangeToChangeScope(ctx.function);
}
private void replaceSuper(NodeTraversal t, LexicalContext ctx, Node n, Node parent) {
if (!parent.isGetProp()) {
compiler.report(
JSError.make(
parent,
CANNOT_CONVERT_ASYNCGEN,
"super only allowed with getprop (like super.foo(), not super['foo']())"));
return;
}
checkArgument(n.isSuper());
checkArgument(ctx != null && ctx.mustReplaceThisSuperArgs());
checkArgument(ctx.function != null, "Cannot prepend declarations to root scope");
checkNotNull(ctx.thisSuperArgsContext);
Node propertyName = n.getNext();
String propertyReplacementNameText = superPropGetterPrefix + propertyName.getString();
// super.x => $super$get$x()
Node getPropReplacement =
astFactory.createCall(astFactory.createName(t.getScope(), propertyReplacementNameText));
Node grandparent = parent.getParent();
if (grandparent.isCall() && grandparent.getFirstChild() == parent) {
// super.x(...) => super.x.call($this, ...)
getPropReplacement = astFactory.createGetProp(getPropReplacement, "call");
grandparent.addChildAfter(
astFactory.createName(t.getScope(), thisVarName).useSourceInfoFrom(parent), parent);
ctx.thisSuperArgsContext.usedThis = true;
}
getPropReplacement.useSourceInfoFromForTree(parent);
grandparent.replaceChild(parent, getPropReplacement);
ctx.thisSuperArgsContext.usedSuperProperties.add(propertyName.getString());
compiler.reportChangeToChangeScope(ctx.function);
}
/**
* Prepends this/super/argument replacement variables to the top of the context's block
*
* {@code
* function() {
* return new AsyncGenWrapper(function*() {
* // code using replacements for this and super.foo
* }())
* }
* }
*
* will be converted to
*
* {@code
* function() {
* const $jscomp$asyncIter$this = this;
* const $jscomp$asyncIter$super$get$foo = () => super.foo;
* return new AsyncGenWrapper(function*() {
* // code using replacements for this and super.foo
* }())
* }
* }
*/
private void prependTempVarDeclarations(LexicalContext ctx, NodeTraversal t) {
checkArgument(ctx != null);
checkArgument(ctx.function != null, "Cannot prepend declarations to root scope");
checkNotNull(ctx.thisSuperArgsContext);
ThisSuperArgsContext thisSuperArgsCtx = ctx.thisSuperArgsContext;
Node function = ctx.function;
Node block = function.getLastChild();
checkNotNull(block, function);
Node prefixBlock = astFactory.createBlock(); // Temporary block to hold all declarations
if (thisSuperArgsCtx.usedThis) {
// { // prefixBlock
// const $jscomp$asyncIter$this = this;
// }
prefixBlock.addChildToBack(
astFactory
.createThisAliasDeclarationForFunction(thisVarName, function)
.useSourceInfoFromForTree(block));
}
if (thisSuperArgsCtx.usedArguments) {
// { // prefixBlock
// const $jscomp$asyncIter$this = this;
// const $jscomp$asyncIter$arguments = arguments;
// }
prefixBlock.addChildToBack(
astFactory
.createSingleConstNameDeclaration(
argumentsVarName, astFactory.createName(t.getScope(), "arguments"))
.useSourceInfoFromForTree(block));
}
for (String replacedMethodName : thisSuperArgsCtx.usedSuperProperties) {
// const super$get$x = () => super.x;
// OR avoid super for static method (class object -> superclass object)
// const super$get$x = () => Object.getPrototypeOf(this).x
// OR avoid super for instance method (instance -> prototype -> super prototype)
// const super$get$x = () => Object.getPrototypeOf(Object.getPrototypeOf(this)).x
Node superReference;
if (rewriteSuperPropertyReferencesWithoutSuper) {
// Rewrite to avoid using `super` within an arrow function.
// See more information on definition of this option.
// TODO(bradfordcsmith): RewriteAsyncIteration and RewriteAsyncFunctions have the
// same logic for dealing with super references. Consider having them share
// it from a common place instead of duplicating.
// static super: Object.getPrototypeOf(this);
superReference =
astFactory.createObjectGetPrototypeOfCall(astFactory.createThisForFunction(function));
if (!ctx.function.getParent().isStaticMember()) {
// instance super: Object.getPrototypeOf(Object.getPrototypeOf(this))
superReference = astFactory.createObjectGetPrototypeOfCall(superReference);
}
} else {
superReference = astFactory.createSuperForFunction(function);
}
Node arrowFunction =
astFactory.createZeroArgArrowFunctionForExpression(
astFactory.createGetProp(superReference, replacedMethodName));
compiler.reportChangeToChangeScope(arrowFunction);
NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.ARROW_FUNCTIONS, compiler);
String superReplacementName = superPropGetterPrefix + replacedMethodName;
prefixBlock.addChildToBack(
astFactory.createSingleConstNameDeclaration(superReplacementName, arrowFunction));
}
prefixBlock.useSourceInfoIfMissingFromForTree(block);
// Pulls all declarations out of prefixBlock and prepends in block
// block: {
// // declarations
// // code using this/super/args
// }
block.addChildrenToFront(prefixBlock.removeChildren());
if (thisSuperArgsCtx.usedThis
|| thisSuperArgsCtx.usedArguments
|| !thisSuperArgsCtx.usedSuperProperties.isEmpty()) {
compiler.reportChangeToChangeScope(function);
NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.CONST_DECLARATIONS, compiler);
}
}
}