com.google.javascript.jscomp.DartSuperAccessorsPass Maven / Gradle / Ivy
/*
* Copyright 2015 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.checkState;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
/**
* Converts {@code super} getter and setter calls in order to support the output
* of the Dart Dev Compiler (https://github.com/dart-lang/dev_compiler). This has
* to run before the {@link Es6ConvertSuper} pass.
*
* Note that the approach taken here doesn't lend itself to good optimizations of getters and
* setters. An alternative approach is needed to generate fully optimizable output before general
* ES6→ES5 lowering of super accessor expressions can be rolled out.
*
*
TODO(ochafik): Add support for `super.x++` (and --, pre/post variants).
*/
public final class DartSuperAccessorsPass implements NodeTraversal.Callback,
HotSwapCompilerPass {
static final String CALL_SUPER_GET = "$jscomp.superGet";
static final String CALL_SUPER_SET = "$jscomp.superSet";
private final AbstractCompiler compiler;
/**
* Whether JSCompiler_renameProperty can and should be used (i.e. if we think a RenameProperties
* pass will be run).
*/
private final boolean renameProperties;
public DartSuperAccessorsPass(AbstractCompiler compiler) {
this.compiler = compiler;
CompilerOptions options = compiler.getOptions();
this.renameProperties = options.propertyRenaming == PropertyRenamingPolicy.ALL_UNQUOTED;
checkState(
options.getOutputFeatureSet().contains(FeatureSet.ES5),
"Dart super accessors pass requires ES5+ output");
// We currently rely on JSCompiler_renameProperty, which is not type-aware.
// We would need something like goog.reflect.object (with the super class type),
// but right now this would yield much larger code.
checkState(
!options.shouldAmbiguateProperties() && !options.shouldDisambiguateProperties(),
"Dart super accessors pass is not compatible with property (dis)ambiguation yet");
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (isSuperGet(n)) {
visitSuperGet(n);
return false;
} else if (isSuperSet(n)) {
if (!n.isAssign()) {
n = normalizeAssignmentOp(n);
}
visitSuperSet(n);
return false;
}
return true;
}
private static boolean isCalled(Node n) {
Node parent = n.getParent();
return parent.isCall() && (n == parent.getFirstChild());
}
/** Transforms `a += b` to `a = a + b`. */
private static Node normalizeAssignmentOp(Node n) {
Node lhs = n.getFirstChild();
Node rhs = n.getLastChild();
Node newRhs = new Node(NodeUtil.getOpFromAssignmentOp(n),
lhs.cloneTree(), rhs.cloneTree()).srcrefTree(n);
return replace(n, IR.assign(lhs.cloneTree(), newRhs).srcrefTree(n));
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {}
private boolean isSuperGet(Node n) {
return (n.isGetProp() || n.isGetElem())
&& !isCalled(n)
&& n.getFirstChild().isSuper()
&& isInsideInstanceMember(n);
}
private boolean isSuperSet(Node n) {
return NodeUtil.isAssignmentOp(n) && isSuperGet(n.getFirstChild());
}
/**
* Returns true if this node is or is enclosed by an instance member definition
* (non-static method, getter or setter).
*/
private static boolean isInsideInstanceMember(Node n) {
while (n != null) {
if (n.isMemberFunctionDef()
|| n.isGetterDef()
|| n.isSetterDef()
|| n.isComputedProp()) {
return !n.isStaticMember();
}
if (n.isClass()) {
// Stop at the first enclosing class.
return false;
}
n = n.getParent();
}
return false;
}
private void visitSuperGet(Node superGet) {
Node name = superGet.getLastChild().cloneTree();
Node callSuperGet = IR.call(
NodeUtil.newQName(compiler, CALL_SUPER_GET).srcrefTree(superGet),
IR.thisNode().srcref(superGet),
superGet.isGetProp() ? renameProperty(name) : name);
replace(superGet, callSuperGet);
compiler.ensureLibraryInjected("es6_dart_runtime", false);
compiler.reportChangeToEnclosingScope(callSuperGet);
}
private void visitSuperSet(Node superSet) {
checkArgument(superSet.isAssign());
// First, recurse on the assignment's right-hand-side.
NodeTraversal.traverse(compiler, superSet.getLastChild(), this);
Node rhs = superSet.getLastChild();
Node superGet = superSet.getFirstChild();
Node name = superGet.getLastChild().cloneTree();
Node callSuperSet = IR.call(
NodeUtil.newQName(compiler, CALL_SUPER_SET).srcrefTree(superSet),
IR.thisNode().srcref(superSet),
superGet.isGetProp() ? renameProperty(name) : name,
rhs.cloneTree());
replace(superSet, callSuperSet);
compiler.ensureLibraryInjected("es6_dart_runtime", false);
compiler.reportChangeToEnclosingScope(callSuperSet);
}
private static Node replace(Node original, Node replacement) {
original.replaceWith(replacement);
return replacement;
}
/**
* Wraps a property string in a JSCompiler_renameProperty call.
*
*
Should only be called in phases running before {@link RenameProperties},
* if such a pass is even used (see {@link #renameProperties}).
*/
private Node renameProperty(Node propertyName) {
checkArgument(propertyName.isString());
if (!renameProperties) {
return propertyName;
}
Node call = IR.call(
IR.name(NodeUtil.JSC_PROPERTY_NAME_FN).srcref(propertyName),
propertyName);
call.srcref(propertyName);
call.putBooleanProp(Node.FREE_CALL, true);
call.putBooleanProp(Node.IS_CONSTANT_NAME, true);
return call;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, externs, this);
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
}