Skip to content

Commit

Permalink
Handle goog.requireType correctly in Es6RewriteModules.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=215422628
  • Loading branch information
tjgq authored and lauraharker committed Oct 2, 2018
1 parent 43616e7 commit eb521a4
Show file tree
Hide file tree
Showing 6 changed files with 436 additions and 54 deletions.
5 changes: 5 additions & 0 deletions src/com/google/javascript/jscomp/ClosurePrimitiveErrors.java
Expand Up @@ -46,6 +46,11 @@ private ClosurePrimitiveErrors() {}
"JSC_GOOG_MODULE_INVALID_REQUIRE_NAMESPACE",
"goog.require parameter must be a string literal.");

static final DiagnosticType INVALID_REQUIRE_TYPE_NAMESPACE =
DiagnosticType.error(
"JSC_GOOG_MODULE_INVALID_REQUIRE_TYPE_NAMESPACE",
"goog.requireType parameter must be a string literal.");

static final DiagnosticType MISSING_MODULE_OR_PROVIDE =
DiagnosticType.error(
"JSC_MISSING_MODULE_OR_PROVIDE", "Required namespace \"{0}\" never defined.");
Expand Down
20 changes: 18 additions & 2 deletions src/com/google/javascript/jscomp/ClosureRewriteModule.java
Expand Up @@ -22,6 +22,7 @@
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_CALL_SCOPE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_TYPE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MISSING_MODULE_OR_PROVIDE;

import com.google.common.base.Joiner;
Expand Down Expand Up @@ -376,7 +377,7 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
} else if (method.matchesQualifiedName(GOOG_REQUIRE)) {
recordGoogRequire(t, n, /* mustBeOrdered= */ true);
} else if (method.matchesQualifiedName(GOOG_REQUIRETYPE)) {
recordGoogRequire(t, n, /* mustBeOrdered= */ false);
recordGoogRequireType(t, n);
} else if (method.matchesQualifiedName(GOOG_FORWARDDECLARE) && !parent.isExprResult()) {
recordGoogForwardDeclare(t, n);
} else if (method.matchesQualifiedName(GOOG_MODULE_GET)) {
Expand Down Expand Up @@ -864,6 +865,21 @@ private void recordGoogRequire(NodeTraversal t, Node call, boolean mustBeOrdered
}
}

private void recordGoogRequireType(NodeTraversal t, Node call) {
Node legacyNamespaceNode = call.getLastChild();
if (!legacyNamespaceNode.isString()) {
t.report(legacyNamespaceNode, INVALID_REQUIRE_TYPE_NAMESPACE);
return;
}

// A goog.requireType call is not required to appear after the corresponding namespace
// definition.
boolean mustBeOrdered = false;

// For purposes of import collection, goog.requireType is the same as goog.require.
recordGoogRequire(t, call, mustBeOrdered);
}

private void recordGoogForwardDeclare(NodeTraversal t, Node call) {
Node namespaceNode = call.getLastChild();
if (!call.hasTwoChildren() || !namespaceNode.isString()) {
Expand All @@ -876,7 +892,7 @@ private void recordGoogForwardDeclare(NodeTraversal t, Node call) {
// goog.module.get(). To avoid reporting the error twice suppress it here.
boolean mustBeOrdered = false;

// For purposes of import collection goog.forwardDeclare is the same as goog.require;
// For purposes of import collection, goog.forwardDeclare is the same as goog.require.
recordGoogRequire(t, call, mustBeOrdered);
}

Expand Down
10 changes: 6 additions & 4 deletions src/com/google/javascript/jscomp/Es6RenameReferences.java
Expand Up @@ -47,7 +47,7 @@ final class Es6RenameReferences extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!typesOnly && NodeUtil.isReferenceName(n)) {
renameReference(t, n);
renameReference(t, n, false);
}

JSDocInfo info = n.getJSDocInfo();
Expand All @@ -59,13 +59,13 @@ public void visit(NodeTraversal t, Node n, Node parent) {
private void renameTypeNode(NodeTraversal t, Iterable<Node> typeNodes) {
for (Node type : typeNodes) {
if (type.isString()) {
renameReference(t, type);
renameReference(t, type, true);
}
renameTypeNode(t, type.children());
}
}

private void renameReference(NodeTraversal t, Node n) {
private void renameReference(NodeTraversal t, Node n, boolean isType) {
String fullName = n.getString();
List<String> split = SPLIT_ON_DOT.splitToList(fullName);
String oldName = split.get(0);
Expand All @@ -75,7 +75,9 @@ private void renameReference(NodeTraversal t, Node n) {
if (newName != null) {
String rest = split.size() == 2 ? "." + split.get(1) : "";
n.setString(newName + rest);
t.reportCodeChange();
if (!isType) {
t.reportCodeChange();
}
return;
} else if (current.hasOwnSlot(oldName)) {
return;
Expand Down
120 changes: 80 additions & 40 deletions src/com/google/javascript/jscomp/Es6RewriteModules.java
Expand Up @@ -24,6 +24,7 @@
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_CALL_SCOPE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_TYPE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MISSING_MODULE_OR_PROVIDE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MODULE_USES_GOOG_MODULE_GET;
import static com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature.MODULES;
Expand Down Expand Up @@ -66,7 +67,8 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback
static final DiagnosticType LHS_OF_GOOG_REQUIRE_MUST_BE_CONST =
DiagnosticType.error(
"JSC_LHS_OF_GOOG_REQUIRE_MUST_BE_CONST",
"The left side of a goog.require() must use ''const'' (not ''let'' or ''var'')");
"The left side of a goog.require() or goog.requireType() "
+ "must use ''const'' (not ''let'' or ''var'')");

static final DiagnosticType NAMESPACE_IMPORT_CANNOT_USE_STAR =
DiagnosticType.error(
Expand All @@ -77,6 +79,11 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback
static final DiagnosticType DUPLICATE_EXPORT =
DiagnosticType.error("JSC_DUPLICATE_EXPORT", "Duplicate export ''{0}''.");

static final DiagnosticType REQUIRE_TYPE_FOR_ES6_SHOULD_BE_CONST =
DiagnosticType.error(
"JSC_REQUIRE_TYPE_FOR_ES6_SHOULD_BE_CONST",
"goog.requireType alias for ES6 module should be const.");

static final DiagnosticType FORWARD_DECLARE_FOR_ES6_SHOULD_BE_CONST =
DiagnosticType.error(
"JSC_FORWARD_DECLARE_FOR_ES6_SHOULD_BE_CONST",
Expand Down Expand Up @@ -108,9 +115,10 @@ public final class Es6RewriteModules extends AbstractPostOrderCallback
private Map<String, ModuleOriginalNamePair> importMap;

/**
* Local variable names that were goog.require'd to qualified name we need to line. We need to
* inline all required names since there are certain well-known Closure symbols (like
* goog.asserts) that later stages of the compiler check for and cannot handle aliases.
* Local variable names that were goog.require'd to qualified name we need to line.
*
* <p>We need to inline all required names since there are certain well-known Closure symbols
* (like goog.asserts) that later stages of the compiler check for and cannot handle aliases.
*
* <p>We use this to rewrite something like:
*
Expand Down Expand Up @@ -208,27 +216,28 @@ public void clearState() {
}

/**
* Checks for goog.require + goog.module.get + goog.forwardDeclare calls in non-ES6 modules that
* Checks for goog.require, goog.requireType, goog.module.get and goog.forwardDeclare calls that
* are meant to import ES6 modules and rewrites them.
*/
private class RewriteRequiresForEs6Modules extends AbstractPostOrderCallback {
private boolean transpiled = false;
private Table<Node, String, String> forwardDeclareRenameTable;
// An (s, old, new) entry indicates that occurrences of `old` in scope `s` should be rewritten
// as `new`. This is used to rewrite namespaces that appear in calls to goog.requireType and
// goog.forwardDeclare.
private Table<Node, String, String> renameTable;

void rewrite(Node scriptNode) {
transpiled = false;
forwardDeclareRenameTable = HashBasedTable.create();
renameTable = HashBasedTable.create();
NodeTraversal.traverse(compiler, scriptNode, this);

if (transpiled) {
scriptNode.putBooleanProp(Node.TRANSPILED, true);
}

if (!forwardDeclareRenameTable.isEmpty()) {
if (!renameTable.isEmpty()) {
NodeTraversal.traverse(
compiler,
scriptNode,
new Es6RenameReferences(forwardDeclareRenameTable, /* typesOnly= */ true));
compiler, scriptNode, new Es6RenameReferences(renameTable, /* typesOnly= */ true));
}
}

Expand All @@ -239,16 +248,19 @@ public void visit(NodeTraversal t, Node n, Node parent) {
}

boolean isRequire = n.getFirstChild().matchesQualifiedName("goog.require");
boolean isRequireType = n.getFirstChild().matchesQualifiedName("goog.requireType");
boolean isGet = n.getFirstChild().matchesQualifiedName("goog.module.get");
boolean isForwardDeclare = n.getFirstChild().matchesQualifiedName("goog.forwardDeclare");

if (!isRequire && !isGet && !isForwardDeclare) {
if (!isRequire && !isRequireType && !isGet && !isForwardDeclare) {
return;
}

if (!n.hasTwoChildren() || !n.getLastChild().isString()) {
if (isRequire) {
t.report(n, INVALID_REQUIRE_NAMESPACE);
} else if (isRequireType) {
t.report(n, INVALID_REQUIRE_TYPE_NAMESPACE);
} else if (isGet) {
t.report(n, INVALID_GET_NAMESPACE);
} else {
Expand Down Expand Up @@ -282,41 +294,62 @@ public void visit(NodeTraversal t, Node n, Node parent) {

Node statementNode = NodeUtil.getEnclosingStatement(n);
boolean importHasAlias = NodeUtil.isNameDeclaration(statementNode);

if (importHasAlias) {
if (statementNode.getFirstChild().isDestructuringLhs()) {
if (isForwardDeclare) {
// const {a, c:b} = goog.forwardDeclare('an.es6.namespace');
t.report(n, INVALID_DESTRUCTURING_FORWARD_DECLARE);
return;
}
// Work around a bug in the type checker where destructing can create
// too many layers of aliases and confuse the type checker. b/112061124.

// const {a, c:b} = goog.require('an.es6.namespace');
// const a = module$es6.a;
// const b = module$es6.c;
for (Node child : statementNode.getFirstFirstChild().children()) {
checkState(child.isStringKey());
checkState(child.getFirstChild().isName());
Node constNode =
IR.constNode(
IR.name(child.getFirstChild().getString()),
IR.getprop(
IR.name(ModuleRenaming.getGlobalName(moduleMetadata, name)),
IR.string(child.getString())));
constNode.useSourceInfoFromForTree(child);
statementNode.getParent().addChildBefore(constNode, statementNode);
if (isRequireType) {
if (!statementNode.isConst()) {
t.report(statementNode, REQUIRE_TYPE_FOR_ES6_SHOULD_BE_CONST);
return;
}
// const {a, c:b} = goog.requireType('an.es6.namespace');
for (Node child : statementNode.getFirstFirstChild().children()) {
checkState(child.isStringKey());
checkState(child.getFirstChild().isName());
renameTable.put(
t.getScopeRoot(),
child.getFirstChild().getString(),
ModuleRenaming.getGlobalName(moduleMetadata, name) + "." + child.getString());
}
} else {
// Work around a bug in the type checker where destructing can create
// too many layers of aliases and confuse the type checker. b/112061124.

// const {a, c:b} = goog.require('an.es6.namespace');
// const a = module$es6.a;
// const b = module$es6.c;
for (Node child : statementNode.getFirstFirstChild().children()) {
checkState(child.isStringKey());
checkState(child.getFirstChild().isName());
Node constNode =
IR.constNode(
IR.name(child.getFirstChild().getString()),
IR.getprop(
IR.name(ModuleRenaming.getGlobalName(moduleMetadata, name)),
IR.string(child.getString())));
constNode.useSourceInfoFromForTree(child);
statementNode.getParent().addChildBefore(constNode, statementNode);
}
}
statementNode.detach();
t.reportCodeChange();
} else {
if (isForwardDeclare) {
if (isForwardDeclare || isRequireType) {
if (!statementNode.isConst()) {
t.report(statementNode, FORWARD_DECLARE_FOR_ES6_SHOULD_BE_CONST);
DiagnosticType diagnostic =
isForwardDeclare
? FORWARD_DECLARE_FOR_ES6_SHOULD_BE_CONST
: REQUIRE_TYPE_FOR_ES6_SHOULD_BE_CONST;
t.report(statementNode, diagnostic);
return;
}
// const namespace = goog.forwardDeclare('an.es6.namespace');
forwardDeclareRenameTable.put(
// const namespace = goog.requireType('an.es6.namespace');
renameTable.put(
t.getScopeRoot(),
statementNode.getFirstChild().getString(),
ModuleRenaming.getGlobalName(moduleMetadata, name));
Expand All @@ -332,9 +365,10 @@ public void visit(NodeTraversal t, Node n, Node parent) {
}
}
} else {
if (isForwardDeclare) {
if (isForwardDeclare || isRequireType) {
// goog.forwardDeclare('an.es6.namespace')
forwardDeclareRenameTable.put(
// goog.requireType('an.es6.namespace')
renameTable.put(
t.getScopeRoot(), name, ModuleRenaming.getGlobalName(moduleMetadata, name));
statementNode.detach();
} else {
Expand Down Expand Up @@ -714,9 +748,15 @@ private void rewriteRequires(Node script) {
script,
(NodeTraversal t, Node n, Node parent) -> {
if (n.isCall()) {
if (n.getFirstChild().matchesQualifiedName("goog.require")) {
Node fn = n.getFirstChild();
if (fn.matchesQualifiedName("goog.require")
|| fn.matchesQualifiedName("goog.requireType")) {
// TODO(tjgq): This will rewrite both type references and code references. For
// goog.requireType, the latter are potentially broken because the symbols aren't
// guaranteed to be available at run time. A separate pass needs to be added to
// detect these incorrect uses of goog.requireType.
visitRequireOrGet(t, n, parent, /* isRequire= */ true);
} else if (n.getFirstChild().matchesQualifiedName("goog.module.get")) {
} else if (fn.matchesQualifiedName("goog.module.get")) {
visitGoogModuleGet(t, n, parent);
}
}
Expand All @@ -728,7 +768,7 @@ private void rewriteRequires(Node script) {
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
for (Node typeNode : info.getTypeNodes()) {
inlineRequiredType(t, typeNode);
inlineAliasedTypes(t, typeNode);
}
}

Expand All @@ -744,7 +784,7 @@ private void rewriteRequires(Node script) {
});
}

private void inlineRequiredType(NodeTraversal t, Node typeNode) {
private void inlineAliasedTypes(NodeTraversal t, Node typeNode) {
if (typeNode.isString()) {
String name = typeNode.getString();
List<String> split = Splitter.on('.').limit(2).splitToList(name);
Expand All @@ -764,7 +804,7 @@ private void inlineRequiredType(NodeTraversal t, Node typeNode) {
}
}
for (Node child : typeNode.children()) {
inlineRequiredType(t, child);
inlineAliasedTypes(t, child);
}
}

Expand Down
Expand Up @@ -19,6 +19,7 @@
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_CALL_SCOPE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_GET_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.INVALID_REQUIRE_TYPE_NAMESPACE;
import static com.google.javascript.jscomp.ClosurePrimitiveErrors.MISSING_MODULE_OR_PROVIDE;
import static com.google.javascript.jscomp.ClosureRewriteModule.DUPLICATE_MODULE;
import static com.google.javascript.jscomp.ClosureRewriteModule.DUPLICATE_NAMESPACE;
Expand Down Expand Up @@ -1043,7 +1044,7 @@ public void testInvalidRequire() {

@Test
public void testInvalidRequireType() {
testError("goog.module('ns.a');" + "goog.requireType(a);", INVALID_REQUIRE_NAMESPACE);
testError("goog.module('ns.a');" + "goog.requireType(a);", INVALID_REQUIRE_TYPE_NAMESPACE);
}

@Test
Expand Down

0 comments on commit eb521a4

Please sign in to comment.