Skip to content

Commit

Permalink
Make the lint warning for sorting goog.requires, and the suggested fi…
Browse files Browse the repository at this point in the history
…x that adds new ones, understand the sorting rules for goog.modules, which have "shorthand" goog.requires.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=136210403
  • Loading branch information
tbreisacher authored and blickly committed Oct 17, 2016
1 parent 0052786 commit 073f024
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -2291,7 +2291,7 @@ static boolean isBlockScopedDeclaration(Node n) {
* @return True if {@code n} is VAR, LET or CONST * @return True if {@code n} is VAR, LET or CONST
*/ */
public static boolean isNameDeclaration(Node n) { public static boolean isNameDeclaration(Node n) {
return n.isVar() || n.isLet() || n.isConst(); return n != null && (n.isVar() || n.isLet() || n.isConst());
} }


/** /**
Expand Down
Expand Up @@ -53,6 +53,7 @@ public final class CheckRequiresAndProvidesSorted extends AbstractShallowCallbac


private List<Node> requires; private List<Node> requires;
private List<Node> provides; private List<Node> provides;
private boolean containsStandaloneRequire = false;
private boolean containsShorthandRequire = false; private boolean containsShorthandRequire = false;


private final AbstractCompiler compiler; private final AbstractCompiler compiler;
Expand All @@ -73,30 +74,45 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverseEs6(compiler, scriptRoot, this); NodeTraversal.traverseEs6(compiler, scriptRoot, this);
} }


private final Function<Node, String> getNamespace = public static final Function<Node, String> getSortKey =
new Function<Node, String>() { new Function<Node, String>() {
@Override @Override
public String apply(Node n) { public String apply(Node n) {
Preconditions.checkState(n.isCall(), n); if (n.isExprResult()) {
return n.getLastChild().getString(); // Case 1: goog.require('a.b.c');
return n.getFirstChild().getLastChild().getString();
} else if (NodeUtil.isNameDeclaration(n)) {
if (n.getFirstChild().isName()) {
// Case 2: var x = goog.require('w.x');
return n.getFirstChild().getString();
} else if (n.getFirstChild().isDestructuringLhs()) {
// Case 3: var {y} = goog.require('w.x');
// All case 3 nodes should come after all case 2 nodes.
Node pattern = n.getFirstFirstChild();
Preconditions.checkState(pattern.isObjectPattern(), pattern);
return "{" + pattern.getFirstChild().getString();
}
}
throw new IllegalArgumentException("Unexpected node " + n);
} }
}; };


private final Ordering<Node> alphabetical = Ordering.natural().onResultOf(getNamespace); private final Ordering<Node> alphabetical = Ordering.natural().onResultOf(getSortKey);


@Override @Override
public void visit(NodeTraversal t, Node n, Node parent) { public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) { switch (n.getToken()) {
case SCRIPT: case SCRIPT:
// For now, don't report for requires being out of order if there are // For now, don't report for requires being out of order if there both
// "var x = goog.require('goog.x');" style requires. // "const x = goog.require('goog.x');" and "goog.require('goog.y')" style requires.
if (!containsShorthandRequire) { // TODO(tbreisacher): Put all the shorthand ones first, and then all the standalone ones.
reportIfOutOfOrder(requires, REQUIRES_NOT_SORTED); if (containsShorthandRequire && containsStandaloneRequire) {
return;
} }

reportIfOutOfOrder(requires, REQUIRES_NOT_SORTED);
reportIfOutOfOrder(provides, PROVIDES_NOT_SORTED); reportIfOutOfOrder(provides, PROVIDES_NOT_SORTED);
requires.clear(); reset();
provides.clear();
containsShorthandRequire = false;
break; break;
case CALL: case CALL:
Node callee = n.getFirstChild(); Node callee = n.getFirstChild();
Expand All @@ -112,18 +128,20 @@ public void visit(NodeTraversal t, Node n, Node parent) {
return; return;
} }
if (callee.matchesQualifiedName("goog.require")) { if (callee.matchesQualifiedName("goog.require")) {
requires.add(n); requires.add(parent);
containsStandaloneRequire = true;
} else { } else {
if (!requires.isEmpty()) { if (!requires.isEmpty()) {
t.report(n, PROVIDES_AFTER_REQUIRES); t.report(n, PROVIDES_AFTER_REQUIRES);
} }
if (callee.matchesQualifiedName("goog.provide")) { if (callee.matchesQualifiedName("goog.provide")) {
provides.add(n); provides.add(parent);
} }
} }
} else if (NodeUtil.isNameDeclaration(parent.getParent()) } else if (NodeUtil.isNameDeclaration(parent.getParent())
&& callee.matchesQualifiedName("goog.require")) { && callee.matchesQualifiedName("goog.require")) {
containsShorthandRequire = true; containsShorthandRequire = true;
requires.add(parent.getParent());
} }
break; break;
default: default:
Expand All @@ -146,4 +164,11 @@ private void reportIfOutOfOrder(List<Node> requiresOrProvides, DiagnosticType wa
JSError.make(requiresOrProvides.get(0), warning, Joiner.on('\n').join(correctOrder))); JSError.make(requiresOrProvides.get(0), warning, Joiner.on('\n').join(correctOrder)));
} }
} }

private void reset() {
requires.clear();
provides.clear();
containsShorthandRequire = false;
containsStandaloneRequire = false;
}
} }
13 changes: 6 additions & 7 deletions src/com/google/javascript/refactoring/ErrorToFixMapper.java
Expand Up @@ -22,6 +22,7 @@
import com.google.javascript.jscomp.JSError; import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.NodeTraversal; import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeUtil; import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted;
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.Node; import com.google.javascript.rhino.Node;
Expand Down Expand Up @@ -310,11 +311,6 @@ private static SuggestedFix getFixForUnsortedRequiresOrProvides(
return fix.replaceRange(first, last, newContent).build(); return fix.replaceRange(first, last, newContent).build();
} }


private static String getNamespaceFromClosureNode(Node exprResult) {
Preconditions.checkState(exprResult.isExprResult());
return exprResult.getFirstChild().getLastChild().getString();
}

private static class RequireProvideSorter extends NodeTraversal.AbstractShallowCallback private static class RequireProvideSorter extends NodeTraversal.AbstractShallowCallback
implements Comparator<Node> { implements Comparator<Node> {
private final String closureFunction; private final String closureFunction;
Expand All @@ -330,6 +326,9 @@ public final void visit(NodeTraversal nodeTraversal, Node n, Node parent) {
&& parent.isExprResult() && parent.isExprResult()
&& n.getFirstChild().matchesQualifiedName(closureFunction)) { && n.getFirstChild().matchesQualifiedName(closureFunction)) {
calls.add(parent); calls.add(parent);
} else if (NodeUtil.isNameDeclaration(parent)
&& n.getFirstFirstChild().matchesQualifiedName(closureFunction)) {
calls.add(parent);
} }
} }


Expand All @@ -339,8 +338,8 @@ public void sortCallsAlphabetically() {


@Override @Override
public int compare(Node n1, Node n2) { public int compare(Node n1, Node n2) {
String namespace1 = getNamespaceFromClosureNode(n1); String namespace1 = CheckRequiresAndProvidesSorted.getSortKey.apply(n1);
String namespace2 = getNamespaceFromClosureNode(n2); String namespace2 = CheckRequiresAndProvidesSorted.getSortKey.apply(n2);
return namespace1.compareTo(namespace2); return namespace1.compareTo(namespace2);
} }
} }
Expand Down
11 changes: 11 additions & 0 deletions src/com/google/javascript/refactoring/Matchers.java
Expand Up @@ -210,6 +210,17 @@ public static Matcher functionCall(final String name) {
}; };
} }


public static Matcher googRequire(final String namespace) {
return new Matcher() {
@Override
public boolean matches(Node node, NodeMetadata metadata) {
return functionCall("goog.require").matches(node, metadata)
&& node.getSecondChild().isString()
&& node.getSecondChild().getString().equals(namespace);
}
};
}

public static Matcher googRequire() { public static Matcher googRequire() {
return functionCall("goog.require"); return functionCall("goog.require");
} }
Expand Down
44 changes: 31 additions & 13 deletions src/com/google/javascript/refactoring/SuggestedFix.java
Expand Up @@ -553,9 +553,6 @@ public Builder addGoogRequire(Match m, String namespace) {
if (existingNode != null) { if (existingNode != null) {
return this; return this;
} }
Node googRequireNode = IR.exprResult(IR.call(
IR.getprop(IR.name("goog"), IR.string("require")),
IR.string(namespace)));


// Find the right goog.require node to insert this after. // Find the right goog.require node to insert this after.
Node script = NodeUtil.getEnclosingScript(node); Node script = NodeUtil.getEnclosingScript(node);
Expand All @@ -565,6 +562,21 @@ public Builder addGoogRequire(Match m, String namespace) {
if (script.getFirstChild().isModuleBody()) { if (script.getFirstChild().isModuleBody()) {
script = script.getFirstChild(); script = script.getFirstChild();
} }

Node googRequireNode = IR.call(
IR.getprop(IR.name("goog"), IR.string("require")),
IR.string(namespace));

// The name that will be used on the LHS, if the require is added using the shorthand form.
String shortName = namespace.substring(namespace.lastIndexOf('.') + 1);

if (script.isModuleBody()) {
// TODO(tbreisacher): Switch to IR.const() once ES6+ is on by default everywhere.
googRequireNode = IR.var(IR.name(shortName), googRequireNode);
} else {
googRequireNode = IR.exprResult(googRequireNode);
}

Node lastModuleOrProvideNode = null; Node lastModuleOrProvideNode = null;
Node lastGoogRequireNode = null; Node lastGoogRequireNode = null;
Node nodeToInsertBefore = null; Node nodeToInsertBefore = null;
Expand All @@ -584,6 +596,12 @@ public Builder addGoogRequire(Match m, String namespace) {
break; break;
} }
} }
} else if (NodeUtil.isNameDeclaration(child)
&& Matchers.googRequire().matches(child.getFirstFirstChild(), metadata)) {
if (shortName.compareTo(child.getFirstChild().getString()) < 0) {
nodeToInsertBefore = child;
break;
}
} }
child = child.getNext(); child = child.getNext();
} }
Expand Down Expand Up @@ -630,19 +648,19 @@ public Builder removeGoogRequire(Match m, String namespace) {


private Node findGoogRequireNode(Node n, NodeMetadata metadata, String namespace) { private Node findGoogRequireNode(Node n, NodeMetadata metadata, String namespace) {
Node script = NodeUtil.getEnclosingScript(n); Node script = NodeUtil.getEnclosingScript(n);
if (script.getFirstChild().isModuleBody()) {
script = script.getFirstChild();
}


if (script != null) { if (script != null) {
Node child = script.getFirstChild(); Node child = script.getFirstChild();
while (child != null) { while (child != null) {
if (child.isExprResult() && child.getFirstChild().isCall()) { if ((NodeUtil.isExprCall(child)
// TODO(mknichel): Replace this logic with a function argument && Matchers.googRequire(namespace).matches(child.getFirstChild(), metadata))
// Matcher when it exists. || (NodeUtil.isNameDeclaration(child)
Node grandchild = child.getFirstChild(); && Matchers.googRequire(namespace)
if (Matchers.googRequire().matches(child.getFirstChild(), metadata) .matches(child.getFirstFirstChild(), metadata))) {
&& grandchild.getLastChild().isString() return child;
&& namespace.equals(grandchild.getLastChild().getString())) {
return child;
}
} }
child = child.getNext(); child = child.getNext();
} }
Expand Down Expand Up @@ -687,7 +705,7 @@ private static boolean isInClosurizedFile(Node node, NodeMetadata metadata) {


Node child = script.getFirstChild(); Node child = script.getFirstChild();
while (child != null) { while (child != null) {
if (child.isExprResult() && child.getFirstChild().isCall()) { if (NodeUtil.isExprCall(child)) {
if (Matchers.googRequire().matches(child.getFirstChild(), metadata)) { if (Matchers.googRequire().matches(child.getFirstChild(), metadata)) {
return true; return true;
} }
Expand Down
Expand Up @@ -106,30 +106,47 @@ public void testB3473189() {
} }


/** /**
* If a goog.module uses the "var x = goog.require('x')" form, don't warn. * Contains both the standalone and the shorthand style of goog.require: No warning.
*/ */
public void testGoogModuleWithShorthand() { public void testGoogModule_bothStyles() {
testSame( testSameEs6(
LINE_JOINER.join( LINE_JOINER.join(
"goog.module('m');", "goog.module('m');",
"", "",
"goog.require('a.c');", "goog.require('a.c');",
"var d = goog.require('a.b.d');", "const d = goog.require('a.b.d');",
"goog.require('a.b.c');", "goog.require('a.b.c');",
"", "",
"alert(1);")); "alert(1);"));


testSame( }

public void testGoogModule_shorthand() {
testWarning(
LINE_JOINER.join( LINE_JOINER.join(
"goog.module('m');", "goog.module('m');",
"", "",
"var d = goog.require('a.b.d');", "var d = goog.require('a.b.d');",
"var c = goog.require('a.c');", "var c = goog.require('a.c');",
"", "",
"alert(1);")); "alert(1);"),
REQUIRES_NOT_SORTED);
}

public void testGoogModule_shorthand_destructuring() {
testWarningEs6(
LINE_JOINER.join(
"goog.module('m');",
"",
"var a = goog.require('a.b.d');",
"var {b} = goog.require('a.a.a');",
"var c = goog.require('a.c');",
"",
"alert(1);"),
REQUIRES_NOT_SORTED);
} }


public void testGoogModuleNoShorthand() { public void testGoogModule_standalone() {
testWarning( testWarning(
LINE_JOINER.join( LINE_JOINER.join(
"goog.module('m');", "goog.module('m');",
Expand Down

0 comments on commit 073f024

Please sign in to comment.