Skip to content

Commit

Permalink
Split EarlyEs6ToEs3converter into 2 passes
Browse files Browse the repository at this point in the history
Es6InjectRuntimeLibraries is injects runtime code needed for
Symbol, Symbol.iterator, and for-of transpilation.

Es6RewriteRestAndSpread transpiles REST parameters and SPREAD
expressions.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=197006873
  • Loading branch information
brad4d authored and blickly committed May 17, 2018
1 parent 2008247 commit d01e47e
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 158 deletions.
136 changes: 136 additions & 0 deletions src/com/google/javascript/jscomp/Es6InjectRuntimeLibraries.java
@@ -0,0 +1,136 @@
/*
* 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.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;

/**
* Injects JS library code that may be needed by the transpiled form of the input source code.
*
* <p>The intention here is to add anything that could be needed and rely on `RemoveUnusedCode` to
* remove the parts that don't end up getting used. This pass should run before type checking so the
* type checking code can add type information to the injected JavaScript for checking and
* optimization purposes.
*
* This class also reports an error if it finds getters or setters are used and the language output
* level is too low to support them.
* TODO(bradfordcsmith): The getter/setter check should probably be done separately in an earlier
* pass that only runs when the output language level is ES3 and the input language level is
* ES5 or greater.
*/
public final class Es6InjectRuntimeLibraries implements Callback, HotSwapCompilerPass {
private final AbstractCompiler compiler;

// Since there's currently no Feature for Symbol, run this pass if the code has any ES6 features.
private static final FeatureSet requiredForFeatures = FeatureSet.ES6.without(FeatureSet.ES5);

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

@Override
public void process(Node externs, Node root) {
TranspilationPasses.processTranspile(compiler, root, requiredForFeatures, this);
}

@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, requiredForFeatures, this);
}

@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case FOR_OF:
// We will need this when we transpile for/of in LateEs6ToEs3Converter,
// but we want the runtime functions to be have JSType applied to it by the type checker.
Es6ToEs3Util.preloadEs6RuntimeFunction(compiler, "makeIterator");
break;
case GETTER_DEF:
case SETTER_DEF:
if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) {
Es6ToEs3Util.cannotConvert(
compiler, n, "ES5 getters/setters (consider using --language_out=ES5)");
}
break;
case FUNCTION:
if (n.isAsyncFunction()) {
throw new IllegalStateException("async functions should have already been converted");
}
if (n.isGeneratorFunction()) {
compiler.ensureLibraryInjected("es6/generator_engine", /* force= */ false);
}
break;
default:
break;
}
return true;
}

@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case NAME:
if (!n.isFromExterns() && isGlobalSymbol(t, n)) {
initSymbolBefore(n);
}
break;
case GETPROP:
if (!n.isFromExterns()) {
visitGetprop(t, n);
}
break;
default:
break;
}
}

/** @return Whether {@code n} is a reference to the global "Symbol" function. */
private boolean isGlobalSymbol(NodeTraversal t, Node n) {
if (!n.matchesQualifiedName("Symbol")) {
return false;
}
Var var = t.getScope().getVar("Symbol");
return var == null || var.isGlobal();
}

/** Inserts a call to $jscomp.initSymbol() before {@code n}. */
private void initSymbolBefore(Node n) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node initSymbol = IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbol")));
statement.getParent().addChildBefore(initSymbol.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(initSymbol);
}

// TODO(tbreisacher): Do this for all well-known symbols.
private void visitGetprop(NodeTraversal t, Node n) {
if (!n.matchesQualifiedName("Symbol.iterator")) {
return;
}
if (isGlobalSymbol(t, n.getFirstChild())) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node init = IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolIterator")));
statement.getParent().addChildBefore(init.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(init);
}
}
}
2 changes: 1 addition & 1 deletion src/com/google/javascript/jscomp/Es6RewriteGenerators.java
Expand Up @@ -62,7 +62,7 @@
* </ul>
* </ul>
*
* <p>{@code Es6RewriteGenerators} depends on {@link EarlyEs6ToEs3Converter} to inject
* <p>{@code Es6RewriteGenerators} depends on {@link Es6InjectRuntimeLibraries} to inject
* <code>generator_engine.js</code> template.
*/
final class Es6RewriteGenerators implements HotSwapCompilerPass {
Expand Down
Expand Up @@ -19,7 +19,6 @@
import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.NodeUtil.Visitor;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
Expand All @@ -32,23 +31,15 @@
import java.util.ArrayList;
import java.util.List;

/**
* Converts ES6 code to valid ES5 code. This class does transpilation for Rest, Spread, and Symbols,
* which should be transpiled before NTI.
* 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
* the output language is set to ES5, we rely on ES5 features such as getters, setters,
* and Object.defineProperties.
*
* @author tbreisacher@google.com (Tyler Breisacher)
*/
public final class EarlyEs6ToEs3Converter implements Callback, HotSwapCompilerPass {
/** Converts REST parameters and SPREAD expressions. */
public final class Es6RewriteRestAndSpread extends NodeTraversal.AbstractPostOrderCallback
implements HotSwapCompilerPass {
private final AbstractCompiler compiler;

static final DiagnosticType BAD_REST_PARAMETER_ANNOTATION = DiagnosticType.warning(
"BAD_REST_PARAMETER_ANNOTATION",
"Missing \"...\" in type annotation for rest parameter.");
static final DiagnosticType BAD_REST_PARAMETER_ANNOTATION =
DiagnosticType.warning(
"BAD_REST_PARAMETER_ANNOTATION",
"Missing \"...\" in type annotation for rest parameter.");

// The name of the index variable for populating the rest parameter array.
private static final String REST_INDEX = "$jscomp$restIndex";
Expand All @@ -57,81 +48,32 @@ public final class EarlyEs6ToEs3Converter implements Callback, HotSwapCompilerPa
private static final String REST_PARAMS = "$jscomp$restParams";

private static final String FRESH_SPREAD_VAR = "$jscomp$spread$args";
// Since there's currently no Feature for Symbol, run this pass if the code has any ES6 features.
private static final FeatureSet requiredForFeatures = FeatureSet.ES6.without(FeatureSet.ES5);
private static final FeatureSet featuresTranspiledAway =
FeatureSet.BARE_MINIMUM.with(
Feature.REST_PARAMETERS,
Feature.SPREAD_EXPRESSIONS);
private static final FeatureSet transpiledFeatures =
FeatureSet.BARE_MINIMUM.with(Feature.REST_PARAMETERS, Feature.SPREAD_EXPRESSIONS);

public EarlyEs6ToEs3Converter(AbstractCompiler compiler) {
public Es6RewriteRestAndSpread(AbstractCompiler compiler) {
this.compiler = compiler;
}

@Override
public void process(Node externs, Node root) {
TranspilationPasses.processTranspile(compiler, externs, requiredForFeatures, this);
TranspilationPasses.processTranspile(compiler, root, requiredForFeatures, this);
TranspilationPasses.markFeaturesAsTranspiledAway(compiler, featuresTranspiledAway);
TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this);
TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this);
TranspilationPasses.markFeaturesAsTranspiledAway(compiler, transpiledFeatures);
}

@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, requiredForFeatures, this);
TranspilationPasses.markFeaturesAsTranspiledAway(compiler, featuresTranspiledAway);
TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, transpiledFeatures, this);
TranspilationPasses.markFeaturesAsTranspiledAway(compiler, transpiledFeatures);
}

/**
* Some nodes must be visited pre-order in order to rewrite the
* references to {@code this} correctly.
* Everything else is translated post-order in {@link #visit}.
*/
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case REST:
visitRestParam(t, n, parent);
break;
case FOR_OF:
// We will need this when we transpile for/of in LateEs6ToEs3Converter,
// but we want the runtime functions to be have JSType applied to it by the type checker.
Es6ToEs3Util.preloadEs6RuntimeFunction(compiler, "makeIterator");
break;
case GETTER_DEF:
case SETTER_DEF:
if (compiler.getOptions().getLanguageOut() == LanguageMode.ECMASCRIPT3) {
Es6ToEs3Util.cannotConvert(
compiler, n, "ES5 getters/setters (consider using --language_out=ES5)");
return false;
}
break;
case FUNCTION:
if (n.isAsyncFunction()) {
throw new IllegalStateException("async functions should have already been converted");
}
if (n.isGeneratorFunction()) {
compiler.ensureLibraryInjected("es6/generator_engine", /* force= */ false);
}
break;
default:
break;
}
return true;
}

@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case NAME:
if (!n.isFromExterns() && isGlobalSymbol(t, n)) {
initSymbolBefore(n);
}
break;
case GETPROP:
if (!n.isFromExterns()) {
visitGetprop(t, n);
}
break;
case ARRAYLIT:
case NEW:
case CALL:
Expand All @@ -147,45 +89,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
}
}

/**
* @return Whether {@code n} is a reference to the global "Symbol" function.
*/
private boolean isGlobalSymbol(NodeTraversal t, Node n) {
if (!n.matchesQualifiedName("Symbol")) {
return false;
}
Var var = t.getScope().getVar("Symbol");
return var == null || var.isGlobal();
}

/**
* Inserts a call to $jscomp.initSymbol() before {@code n}.
*/
private void initSymbolBefore(Node n) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node initSymbol = IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbol")));
statement.getParent().addChildBefore(initSymbol.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(initSymbol);
}

// TODO(tbreisacher): Do this for all well-known symbols.
private void visitGetprop(NodeTraversal t, Node n) {
if (!n.matchesQualifiedName("Symbol.iterator")) {
return;
}
if (isGlobalSymbol(t, n.getFirstChild())) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node init = IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolIterator")));
statement.getParent().addChildBefore(init.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(init);
}
}

/**
* Processes a rest parameter
*/
/** Processes a rest parameter */
private void visitRestParam(NodeTraversal t, Node restParam, Node paramList) {
Node functionBody = paramList.getNext();
int restIndex = paramList.getIndexOfChild(restParam);
Expand Down Expand Up @@ -219,8 +123,7 @@ private void visitRestParam(NodeTraversal t, Node restParam, Node paramList) {

Node newBlock = IR.block().useSourceInfoFrom(functionBody);
Node name = IR.name(paramName);
Node let = IR.let(name, IR.name(REST_PARAMS))
.useSourceInfoIfMissingFromForTree(functionBody);
Node let = IR.let(name, IR.name(REST_PARAMS)).useSourceInfoIfMissingFromForTree(functionBody);
newBlock.addChildToFront(let);

for (Node child : functionBody.children()) {
Expand Down Expand Up @@ -251,11 +154,15 @@ private void visitRestParam(NodeTraversal t, Node restParam, Node paramList) {
Node init = IR.var(IR.name(REST_INDEX), IR.number(restIndex));
Node cond = IR.lt(IR.name(REST_INDEX), IR.getprop(IR.name("arguments"), IR.string("length")));
Node incr = IR.inc(IR.name(REST_INDEX), false);
Node body = IR.block(IR.exprResult(IR.assign(
IR.getelem(IR.name(REST_PARAMS), IR.sub(IR.name(REST_INDEX), IR.number(restIndex))),
IR.getelem(IR.name("arguments"), IR.name(REST_INDEX)))));
functionBody.addChildAfter(IR.forNode(init, cond, incr, body)
.useSourceInfoIfMissingFromForTree(restParam), newArr);
Node body =
IR.block(
IR.exprResult(
IR.assign(
IR.getelem(
IR.name(REST_PARAMS), IR.sub(IR.name(REST_INDEX), IR.number(restIndex))),
IR.getelem(IR.name("arguments"), IR.name(REST_INDEX)))));
functionBody.addChildAfter(
IR.forNode(init, cond, incr, body).useSourceInfoIfMissingFromForTree(restParam), newArr);
functionBody.addChildToBack(newBlock);
compiler.reportChangeToEnclosingScope(newBlock);

Expand All @@ -270,25 +177,27 @@ private Node replaceTypeVariablesWithUnknown(JSDocInfo functionJsdoc, Node typeA
if (typeVars.isEmpty()) {
return typeAst;
}
NodeUtil.visitPreOrder(typeAst, new Visitor(){
@Override
public void visit(Node n) {
if (n.isString() && n.getParent() != null && typeVars.contains(n.getString())) {
n.replaceWith(new Node(Token.QMARK));
}
}
});
NodeUtil.visitPreOrder(
typeAst,
new Visitor() {
@Override
public void visit(Node n) {
if (n.isString() && n.getParent() != null && typeVars.contains(n.getString())) {
n.replaceWith(new Node(Token.QMARK));
}
}
});
return typeAst;
}

/**
* Processes array literals or calls containing spreads. Examples:
* [1, 2, ...x, 4, 5] => [].concat([1, 2], $jscomp.arrayFromIterable(x), [4, 5])
* Processes array literals or calls containing spreads. Examples: [1, 2, ...x, 4, 5] =>
* [].concat([1, 2], $jscomp.arrayFromIterable(x), [4, 5])
*
* f(...arr) => f.apply(null, [].concat($jscomp.arrayFromIterable(arr)))
* <p>f(...arr) => f.apply(null, [].concat($jscomp.arrayFromIterable(arr)))
*
* new F(...args) =>
* new Function.prototype.bind.apply(F, [].concat($jscomp.arrayFromIterable(args)))
* <p>new F(...args) => new Function.prototype.bind.apply(F,
* [].concat($jscomp.arrayFromIterable(args)))
*/
private void visitArrayLitOrCallWithSpread(Node node, Node parent) {
if (node.isArrayLit()) {
Expand Down

0 comments on commit d01e47e

Please sign in to comment.