Skip to content

Commit

Permalink
Separated Es6ToEs3Converter into two passes, one intended to run befo…
Browse files Browse the repository at this point in the history
…re NTI and the other

after. Currently both passes still run before NTI.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=164281442
  • Loading branch information
EatingW authored and dimvar committed Aug 4, 2017
1 parent 71b36e8 commit 851a4a4
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 270 deletions.
4 changes: 3 additions & 1 deletion src/com/google/javascript/jscomp/DefaultPassConfig.java
Expand Up @@ -184,6 +184,7 @@ protected List<PassFactory> getTranspileOnlyPasses() {
if (options.needsTranspilationFrom(ES6) || options.needsTranspilationFrom(ES7)) { if (options.needsTranspilationFrom(ES6) || options.needsTranspilationFrom(ES7)) {
TranspilationPasses.addEs6EarlyPasses(passes); TranspilationPasses.addEs6EarlyPasses(passes);
TranspilationPasses.addEs6LatePasses(passes); TranspilationPasses.addEs6LatePasses(passes);
TranspilationPasses.addEs6PassesAfterNTI(passes);
TranspilationPasses.addPostCheckPasses(passes); TranspilationPasses.addPostCheckPasses(passes);
if (options.rewritePolyfills) { if (options.rewritePolyfills) {
TranspilationPasses.addRewritePolyfillPass(passes); TranspilationPasses.addRewritePolyfillPass(passes);
Expand Down Expand Up @@ -375,6 +376,7 @@ protected List<PassFactory> getChecks() {


if (options.needsTranspilationFrom(ES6)) { if (options.needsTranspilationFrom(ES6)) {
TranspilationPasses.addEs6LatePasses(checks); TranspilationPasses.addEs6LatePasses(checks);
TranspilationPasses.addEs6PassesAfterNTI(checks);
if (options.rewritePolyfills) { if (options.rewritePolyfills) {
TranspilationPasses.addRewritePolyfillPass(checks); TranspilationPasses.addRewritePolyfillPass(checks);
} }
Expand All @@ -391,7 +393,7 @@ protected List<PassFactory> getChecks() {
checks.add(convertStaticInheritance); checks.add(convertStaticInheritance);
} }


// End of ES6 transpilation passes. // End of ES6 transpilation passes before NTI.


if (!options.skipNonTranspilationPasses) { if (!options.skipNonTranspilationPasses) {
addNonTranspilationCheckPasses(checks); addNonTranspilationCheckPasses(checks);
Expand Down
Expand Up @@ -17,9 +17,8 @@


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;


import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.IR; import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder; import com.google.javascript.rhino.JSDocInfoBuilder;
Expand All @@ -28,33 +27,21 @@
import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Token;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;


/** /**
* Converts ES6 code to valid ES5 code. This class does most of the transpilation, and * Converts ES6 code to valid ES5 code. This class does transpilation for Rest, Spread, and Symbols,
* https://github.com/google/closure-compiler/wiki/ECMAScript6 lists which ES6 features are * which should be transpiled before NTI.
* supported. Other classes that start with "Es6" do other parts of the transpilation. * Other classes that start with "Es6" do other parts of the transpilation.
* *
* <p>In most cases, the output is valid as ES3 (hence the class name) but in some cases, if * <p>In most cases, the output is valid as ES3 (hence the class name) but in some cases, if
* the output language is set to ES5, we rely on ES5 features such as getters, setters, * the output language is set to ES5, we rely on ES5 features such as getters, setters,
* and Object.defineProperties. * and Object.defineProperties.
* *
* @author tbreisacher@google.com (Tyler Breisacher) * @author tbreisacher@google.com (Tyler Breisacher)
*/ */
// TODO(tbreisacher): This class does too many things. Break it into smaller passes. public final class EarlyEs6ToEs3Converter implements Callback, HotSwapCompilerPass {
public final class Es6ToEs3Converter implements NodeTraversal.Callback, HotSwapCompilerPass {
private final AbstractCompiler compiler; private final AbstractCompiler compiler;


static final DiagnosticType CANNOT_CONVERT = DiagnosticType.error(
"JSC_CANNOT_CONVERT",
"This code cannot be converted from ES6. {0}");

// TODO(tbreisacher): Remove this once we have implemented transpilation for all the features
// we intend to support.
static final DiagnosticType CANNOT_CONVERT_YET = DiagnosticType.error(
"JSC_CANNOT_CONVERT_YET",
"ES6 transpilation of ''{0}'' is not yet implemented.");

static final DiagnosticType BAD_REST_PARAMETER_ANNOTATION = DiagnosticType.warning( static final DiagnosticType BAD_REST_PARAMETER_ANNOTATION = DiagnosticType.warning(
"BAD_REST_PARAMETER_ANNOTATION", "BAD_REST_PARAMETER_ANNOTATION",
"Missing \"...\" in type annotation for rest parameter."); "Missing \"...\" in type annotation for rest parameter.");
Expand All @@ -67,13 +54,7 @@ public final class Es6ToEs3Converter implements NodeTraversal.Callback, HotSwapC


private static final String FRESH_SPREAD_VAR = "$jscomp$spread$args"; private static final String FRESH_SPREAD_VAR = "$jscomp$spread$args";


private static final String FRESH_COMP_PROP_VAR = "$jscomp$compprop"; public EarlyEs6ToEs3Converter(AbstractCompiler compiler) {

private static final String ITER_BASE = "$jscomp$iter$";

private static final String ITER_RESULT = "$jscomp$key$";

public Es6ToEs3Converter(AbstractCompiler compiler) {
this.compiler = compiler; this.compiler = compiler;
} }


Expand Down Expand Up @@ -102,7 +83,8 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
case GETTER_DEF: case GETTER_DEF:
case SETTER_DEF: case SETTER_DEF:
if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) { if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) {
cannotConvert(n, "ES5 getters/setters (consider using --language_out=ES5)"); Es6ToEs3Util.cannotConvert(
compiler, n, "ES5 getters/setters (consider using --language_out=ES5)");
return false; return false;
} }
break; break;
Expand Down Expand Up @@ -130,20 +112,6 @@ public void visit(NodeTraversal t, Node n, Node parent) {
visitGetprop(t, n); visitGetprop(t, n);
} }
break; break;
case OBJECTLIT:
visitObject(n);
break;
case MEMBER_FUNCTION_DEF:
if (parent.isObjectLit()) {
visitMemberFunctionDefInObjectLit(n, parent);
}
break;
case FOR_OF:
visitForOf(n, parent);
break;
case STRING_KEY:
visitStringKey(n);
break;
case ARRAYLIT: case ARRAYLIT:
case NEW: case NEW:
case CALL: case CALL:
Expand All @@ -154,14 +122,6 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }
} }
break; break;
case TAGGED_TEMPLATELIT:
Es6TemplateLiterals.visitTaggedTemplateLiteral(t, n);
break;
case TEMPLATELIT:
if (!parent.isTaggedTemplateLit()) {
Es6TemplateLiterals.visitTemplateLiteral(t, n);
}
break;
default: default:
break; break;
} }
Expand Down Expand Up @@ -203,86 +163,6 @@ private void visitGetprop(NodeTraversal t, Node n) {
} }
} }


/**
* Converts a member definition in an object literal to an ES3 key/value pair.
* Member definitions in classes are handled in {@link #Es6RewriteClass}.
*/
private void visitMemberFunctionDefInObjectLit(Node n, Node parent) {
String name = n.getString();
Node nameNode = n.getFirstFirstChild();
Node stringKey = IR.stringKey(name, n.getFirstChild().detach());
stringKey.setJSDocInfo(n.getJSDocInfo());
parent.replaceChild(n, stringKey);
stringKey.useSourceInfoFrom(nameNode);
compiler.reportChangeToEnclosingScope(stringKey);
}

/**
* Converts extended object literal {a} to {a:a}.
*/
// TODO(blickly): Separate this so it can be part of the normalization early transpilation passes.
private void visitStringKey(Node n) {
if (!n.hasChildren()) {
Node name = IR.name(n.getString());
name.useSourceInfoIfMissingFrom(n);
n.addChildToBack(name);
compiler.reportChangeToEnclosingScope(name);
}
}

private void visitForOf(Node node, Node parent) {
Node variable = node.removeFirstChild();
Node iterable = node.removeFirstChild();
Node body = node.removeFirstChild();
JSDocInfo varJSDocInfo = variable.getJSDocInfo();

Node iterName = IR.name(ITER_BASE + compiler.getUniqueNameIdSupplier().get());
iterName.makeNonIndexable();
Node getNext = IR.call(IR.getprop(iterName.cloneTree(), IR.string("next")));
String variableName;
Token declType;
if (variable.isName()) {
declType = Token.NAME;
variableName = variable.getQualifiedName();
} else {
Preconditions.checkState(NodeUtil.isNameDeclaration(variable),
"Expected var, let, or const. Got %s", variable);
declType = variable.getToken();
variableName = variable.getFirstChild().getQualifiedName();
}
Node iterResult = IR.name(ITER_RESULT + variableName);
iterResult.makeNonIndexable();

Node init = IR.var(iterName.cloneTree(), makeIterator(compiler, iterable));
Node initIterResult = iterResult.cloneTree();
initIterResult.addChildToFront(getNext.cloneTree());
init.addChildToBack(initIterResult);

Node cond = IR.not(IR.getprop(iterResult.cloneTree(), IR.string("done")));
Node incr = IR.assign(iterResult.cloneTree(), getNext.cloneTree());

Node declarationOrAssign;
if (declType == Token.NAME) {
declarationOrAssign = IR.assign(
IR.name(variableName).useSourceInfoFrom(variable),
IR.getprop(iterResult.cloneTree(), IR.string("value")));
declarationOrAssign.setJSDocInfo(varJSDocInfo);
declarationOrAssign = IR.exprResult(declarationOrAssign);
} else {
declarationOrAssign = new Node(
declType,
IR.name(variableName).useSourceInfoFrom(variable.getFirstChild()));
declarationOrAssign.getFirstChild().addChildToBack(
IR.getprop(iterResult.cloneTree(), IR.string("value")));
declarationOrAssign.setJSDocInfo(varJSDocInfo);
}
Node newBody = IR.block(declarationOrAssign, body).useSourceInfoFrom(body);
Node newFor = IR.forNode(init, cond, incr, newBody);
newFor.useSourceInfoIfMissingFromForTree(node);
parent.replaceChild(node, newFor);
compiler.reportChangeToEnclosingScope(newFor);
}

/** /**
* Processes a rest parameter * Processes a rest parameter
*/ */
Expand Down Expand Up @@ -382,7 +262,7 @@ private void visitArrayLitOrCallWithSpread(Node node, Node parent) {
groups.add(currGroup); groups.add(currGroup);
currGroup = null; currGroup = null;
} }
groups.add(arrayFromIterable(compiler, currElement.removeFirstChild())); groups.add(Es6ToEs3Util.arrayFromIterable(compiler, currElement.removeFirstChild()));
} else { } else {
if (currGroup == null) { if (currGroup == null) {
currGroup = IR.arraylit(); currGroup = IR.arraylit();
Expand Down Expand Up @@ -422,7 +302,8 @@ private void visitArrayLitOrCallWithSpread(Node node, Node parent) {
} else { } else {
if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) { if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) {
// TODO(tbreisacher): Support this in ES3 too by not relying on Function.bind. // TODO(tbreisacher): Support this in ES3 too by not relying on Function.bind.
cannotConvert(node, "\"...\" passed to a constructor (consider using --language_out=ES5)"); Es6ToEs3Util.cannotConvert(
compiler, node, "\"...\" passed to a constructor (consider using --language_out=ES5)");
} }
Node bindApply = NodeUtil.newQName(compiler, Node bindApply = NodeUtil.newQName(compiler,
"Function.prototype.bind.apply"); "Function.prototype.bind.apply");
Expand All @@ -432,110 +313,4 @@ private void visitArrayLitOrCallWithSpread(Node node, Node parent) {
parent.replaceChild(node, result); parent.replaceChild(node, result);
compiler.reportChangeToEnclosingScope(result); compiler.reportChangeToEnclosingScope(result);
} }

private void visitObject(Node obj) {
for (Node child : obj.children()) {
if (child.isComputedProp()) {
visitObjectWithComputedProperty(obj);
return;
}
}
}

private void visitObjectWithComputedProperty(Node obj) {
checkArgument(obj.isObjectLit());
List<Node> props = new ArrayList<>();
Node currElement = obj.getFirstChild();

while (currElement != null) {
if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER)
|| currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) {
cannotConvertYet(currElement, "computed getter/setter in an object literal");
return;
} else if (currElement.isGetterDef() || currElement.isSetterDef()) {
currElement = currElement.getNext();
} else {
Node nextNode = currElement.getNext();
obj.removeChild(currElement);
props.add(currElement);
currElement = nextNode;
}
}

String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get();

props = Lists.reverse(props);
Node result = IR.name(objName);
for (Node propdef : props) {
if (propdef.isComputedProp()) {
Node propertyExpression = propdef.removeFirstChild();
Node value = propdef.removeFirstChild();
result = IR.comma(
IR.assign(
IR.getelem(
IR.name(objName),
propertyExpression),
value),
result);
} else {
if (!propdef.hasChildren()) {
Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef);
propdef.addChildToBack(name);
}
Node val = propdef.removeFirstChild();
propdef.setToken(Token.STRING);
Token type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP;
Node access = new Node(type, IR.name(objName), propdef);
result = IR.comma(IR.assign(access, val), result);
}
}

Node statement = obj;
while (!NodeUtil.isStatement(statement)) {
statement = statement.getParent();
}

result.useSourceInfoIfMissingFromForTree(obj);
obj.replaceWith(result);

Node var = IR.var(IR.name(objName), obj);
var.useSourceInfoIfMissingFromForTree(statement);
statement.getParent().addChildBefore(var, statement);
compiler.reportChangeToEnclosingScope(var);
}

private void cannotConvert(Node n, String message) {
compiler.report(JSError.make(n, CANNOT_CONVERT, message));
}

/**
* Warns the user that the given ES6 feature cannot be converted to ES3
* because the transpilation is not yet implemented. A call to this method
* is essentially a "TODO(tbreisacher): Implement {@code feature}" comment.
*/
private void cannotConvertYet(Node n, String feature) {
compiler.report(JSError.make(n, CANNOT_CONVERT_YET, feature));
}

/**
* Returns a call to {@code $jscomp.makeIterator} with {@code iterable} as its argument.
*/
static Node makeIterator(AbstractCompiler compiler, Node iterable) {
return callEs6RuntimeFunction(compiler, iterable, "makeIterator");
}

/**
* Returns a call to $jscomp.arrayFromIterable with {@code iterable} as its argument.
*/
private static Node arrayFromIterable(AbstractCompiler compiler, Node iterable) {
return callEs6RuntimeFunction(compiler, iterable, "arrayFromIterable");
}

private static Node callEs6RuntimeFunction(
AbstractCompiler compiler, Node iterable, String function) {
compiler.ensureLibraryInjected("es6/util/" + function.toLowerCase(Locale.US), false);
return IR.call(
NodeUtil.newQName(compiler, "$jscomp." + function),
iterable);
}
} }
4 changes: 2 additions & 2 deletions src/com/google/javascript/jscomp/Es6ConvertSuper.java
Expand Up @@ -16,7 +16,7 @@
package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT_YET; import static com.google.javascript.jscomp.Es6ToEs3Util.CANNOT_CONVERT_YET;


import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
Expand All @@ -30,7 +30,7 @@
/** /**
* Converts {@code super.method()} calls and adds constructors to any classes that lack them. * Converts {@code super.method()} calls and adds constructors to any classes that lack them.
* *
* <p>This has to run before the main {@link Es6ToEs3Converter} pass. The super() constructor calls * <p>This has to run before the main {@link Es6RewriteClass} pass. The super() constructor calls
* are not converted here, but rather in {@link Es6ConvertSuperConstructorCalls}, which runs later. * are not converted here, but rather in {@link Es6ConvertSuperConstructorCalls}, which runs later.
*/ */
public final class Es6ConvertSuper extends NodeTraversal.AbstractPostOrderCallback public final class Es6ConvertSuper extends NodeTraversal.AbstractPostOrderCallback
Expand Down
Expand Up @@ -18,7 +18,7 @@
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT; import static com.google.javascript.jscomp.Es6ToEs3Util.CANNOT_CONVERT;


import com.google.javascript.jscomp.GlobalNamespace.Name; import com.google.javascript.jscomp.GlobalNamespace.Name;
import com.google.javascript.jscomp.GlobalNamespace.Ref; import com.google.javascript.jscomp.GlobalNamespace.Ref;
Expand Down
4 changes: 2 additions & 2 deletions src/com/google/javascript/jscomp/Es6ExtractClasses.java
Expand Up @@ -17,7 +17,7 @@
package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.javascript.jscomp.Es6ToEs3Converter.CANNOT_CONVERT; import static com.google.javascript.jscomp.Es6ToEs3Util.CANNOT_CONVERT;


import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType; import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType;
import com.google.javascript.jscomp.deps.ModuleNames; import com.google.javascript.jscomp.deps.ModuleNames;
Expand All @@ -42,7 +42,7 @@
* foo($jscomp$classdecl$var0); * foo($jscomp$classdecl$var0);
* </code> * </code>
* <p> * <p>
* This must be done before {@link Es6ToEs3Converter}, because that pass only handles classes * This must be done before {@link Es6RewriteClass}, because that pass only handles classes
* that are declarations or simple assignments. * that are declarations or simple assignments.
* @see Es6RewriteClass#visitClass(NodeTraversal, Node, Node) * @see Es6RewriteClass#visitClass(NodeTraversal, Node, Node)
*/ */
Expand Down

0 comments on commit 851a4a4

Please sign in to comment.