Skip to content

Commit

Permalink
Extend ProcessClosurePrimitives to handle goog.requireType calls.
Browse files Browse the repository at this point in the history
From the perspective of ProcessClosurePrimitives, goog.requireType and goog.require are identical, except that a goog.requireType is allowed to appear before the matching goog.provide.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=213056346
  • Loading branch information
tjgq committed Sep 18, 2018
1 parent 0ffed17 commit 32086bf
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 30 deletions.
54 changes: 33 additions & 21 deletions src/com/google/javascript/jscomp/ProcessClosurePrimitives.java
Expand Up @@ -43,14 +43,13 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;


/** /**
* Replaces goog.provide calls, removes goog.require calls, verifies that * Replaces goog.provide calls, removes goog.{require,requireType} calls, verifies that each
* goog.require has a corresponding goog.provide and some closure specific * goog.{require,requireType} has a corresponding goog.provide, and performs some Closure-pecific
* simplifications. * simplifications.
* *
* @author chrisn@google.com (Chris Nokleberg) * @author chrisn@google.com (Chris Nokleberg)
*/ */
class ProcessClosurePrimitives extends AbstractPostOrderCallback class ProcessClosurePrimitives extends AbstractPostOrderCallback implements HotSwapCompilerPass {
implements HotSwapCompilerPass {


static final DiagnosticType NULL_ARGUMENT_ERROR = DiagnosticType.error( static final DiagnosticType NULL_ARGUMENT_ERROR = DiagnosticType.error(
"JSC_NULL_ARGUMENT_ERROR", "JSC_NULL_ARGUMENT_ERROR",
Expand Down Expand Up @@ -208,17 +207,7 @@ public void process(Node externs, Node root) {


if (requiresLevel.isOn()) { if (requiresLevel.isOn()) {
for (UnrecognizedRequire r : unrecognizedRequires) { for (UnrecognizedRequire r : unrecognizedRequires) {
DiagnosticType error; checkForLateOrMissingProvide(r);
ProvidedName expectedName = providedNames.get(r.namespace);
if (expectedName != null && expectedName.firstNode != null) {
// The namespace ended up getting provided after it was required.
error = LATE_PROVIDE_ERROR;
} else {
error = MISSING_PROVIDE_ERROR;
}

compiler.report(JSError.make(
r.requireNode, requiresLevel, error, r.namespace));
} }
} }


Expand All @@ -231,6 +220,23 @@ public void process(Node externs, Node root) {
} }
} }


private void checkForLateOrMissingProvide(UnrecognizedRequire r) {
// Both goog.require and goog.requireType must have a matching goog.provide.
// However, goog.require must match an earlier goog.provide, while goog.requireType is allowed
// to match a later goog.provide.
DiagnosticType error;
ProvidedName expectedName = providedNames.get(r.namespace);
if (expectedName != null && expectedName.firstNode != null) {
if (r.isRequireType) {
return;
}
error = LATE_PROVIDE_ERROR;
} else {
error = MISSING_PROVIDE_ERROR;
}
compiler.report(JSError.make(r.requireNode, requiresLevel, error, r.namespace));
}

private Node getAnyValueOfType(JSDocInfo jsdoc) { private Node getAnyValueOfType(JSDocInfo jsdoc) {
checkArgument(jsdoc.hasType()); checkArgument(jsdoc.hasType());
Node typeAst = jsdoc.getType().getRoot(); Node typeAst = jsdoc.getType().getRoot();
Expand Down Expand Up @@ -296,6 +302,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }
break; break;
case "require": case "require":
case "requireType":
if (validPrimitiveCall(t, n)) { if (validPrimitiveCall(t, n)) {
processRequireCall(t, n, parent); processRequireCall(t, n, parent);
} }
Expand Down Expand Up @@ -440,17 +447,16 @@ static boolean isValidDefineValue(Node val) {
} }
} }


/** /** Handles a goog.require or goog.requireType call. */
* Handles a goog.require call.
*/
private void processRequireCall(NodeTraversal t, Node n, Node parent) { private void processRequireCall(NodeTraversal t, Node n, Node parent) {
Node left = n.getFirstChild(); Node left = n.getFirstChild();
Node arg = left.getNext(); Node arg = left.getNext();
String method = left.getFirstChild().getNext().getString();
if (verifyLastArgumentIsString(t, left, arg)) { if (verifyLastArgumentIsString(t, left, arg)) {
String ns = arg.getString(); String ns = arg.getString();
ProvidedName provided = providedNames.get(ns); ProvidedName provided = providedNames.get(ns);
if (provided == null || !provided.isExplicitlyProvided()) { if (provided == null || !provided.isExplicitlyProvided()) {
unrecognizedRequires.add(new UnrecognizedRequire(n, ns)); unrecognizedRequires.add(new UnrecognizedRequire(n, ns, method.equals("requireType")));
} else { } else {
JSModule providedModule = provided.explicitModule; JSModule providedModule = provided.explicitModule;


Expand All @@ -459,7 +465,11 @@ private void processRequireCall(NodeTraversal t, Node n, Node parent) {
checkNotNull(providedModule, n); checkNotNull(providedModule, n);


JSModule module = t.getModule(); JSModule module = t.getModule();
if (module != providedModule && !moduleGraph.dependsOn(module, providedModule)) { // A cross-chunk goog.require must match a goog.provide in an earlier chunk. However, a
// cross-chunk goog.requireType is allowed to match a goog.provide in a later chunk.
if (module != providedModule
&& !moduleGraph.dependsOn(module, providedModule)
&& !method.equals("requireType")) {
compiler.report( compiler.report(
t.makeError(n, XMODULE_REQUIRE_ERROR, ns, t.makeError(n, XMODULE_REQUIRE_ERROR, ns,
providedModule.getName(), providedModule.getName(),
Expand Down Expand Up @@ -1686,10 +1696,12 @@ private void maybeAddToSymbolTable(Node n) {
private static class UnrecognizedRequire { private static class UnrecognizedRequire {
final Node requireNode; final Node requireNode;
final String namespace; final String namespace;
final boolean isRequireType;


UnrecognizedRequire(Node requireNode, String namespace) { UnrecognizedRequire(Node requireNode, String namespace, boolean isRequireType) {
this.requireNode = requireNode; this.requireNode = requireNode;
this.namespace = namespace; this.namespace = namespace;
this.isRequireType = isRequireType;
} }
} }
} }
105 changes: 96 additions & 9 deletions test/com/google/javascript/jscomp/ProcessClosurePrimitivesTest.java
Expand Up @@ -446,7 +446,7 @@ public void testProvideValidObjectType() {
"/** @type {Object<string>} */ var foo = {};"); "/** @type {Object<string>} */ var foo = {};");
} }


public void testRemovalOfRequires() { public void testRemovalOfRequire() {
test("goog.provide('foo'); goog.require('foo');", "/** @const */ var foo={};"); test("goog.provide('foo'); goog.require('foo');", "/** @const */ var foo={};");
test( test(
"goog.provide('foo.bar'); goog.require('foo.bar');", "goog.provide('foo.bar'); goog.require('foo.bar');",
Expand All @@ -458,7 +458,21 @@ public void testRemovalOfRequires() {
testSame("foo.require('foo.bar');"); testSame("foo.require('foo.bar');");
} }


public void testPreserveGoogRequires() { public void testRemovalOfRequireType() {
test("goog.provide('foo'); goog.requireType('foo');", "/** @const */ var foo={};");
test(
"goog.provide('foo.bar'); goog.requireType('foo.bar');",
"/** @const */ var foo={}; /** @const */ foo.bar={};");
test(
"goog.provide('foo.bar.baz'); goog.requireType('foo.bar.baz');",
"/** @const */ var foo={}; /** @const */ foo.bar={}; /** @const */ foo.bar.baz={};");
test(
"goog.provide('foo'); var x = 3; goog.requireType('foo'); something();",
"/** @const */ var foo={}; var x = 3; something();");
testSame("foo.requireType('foo.bar');");
}

public void testPreserveGoogRequire() {
preserveGoogProvidesAndRequires = true; preserveGoogProvidesAndRequires = true;
test( test(
"goog.provide('foo'); goog.require('foo');", "goog.provide('foo'); goog.require('foo');",
Expand All @@ -468,25 +482,51 @@ public void testPreserveGoogRequires() {
"/** @const */ var foo = {}; goog.provide('foo'); goog.require('foo'); var a = {};"); "/** @const */ var foo = {}; goog.provide('foo'); goog.require('foo'); var a = {};");
} }


public void testRequireErrorCases() { public void testPreserveGoogRequireType() {
preserveGoogProvidesAndRequires = true;

test(
"goog.provide('foo'); goog.requireType('foo');",
"/** @const */ var foo={}; goog.provide('foo'); goog.requireType('foo');");
test(
"goog.provide('foo'); goog.requireType('foo'); var a = {};",
"/** @const */ var foo = {}; goog.provide('foo'); goog.requireType('foo'); var a = {};");
}

public void testRequireBadArguments() {
testError("goog.require();", NULL_ARGUMENT_ERROR); testError("goog.require();", NULL_ARGUMENT_ERROR);
testError("goog.require(5);", INVALID_ARGUMENT_ERROR); testError("goog.require(5);", INVALID_ARGUMENT_ERROR);
testError("goog.require([]);", INVALID_ARGUMENT_ERROR); testError("goog.require([]);", INVALID_ARGUMENT_ERROR);
testError("goog.require({});", INVALID_ARGUMENT_ERROR); testError("goog.require({});", INVALID_ARGUMENT_ERROR);

testError("goog.require(`template`);", INVALID_ARGUMENT_ERROR); testError("goog.require(`template`);", INVALID_ARGUMENT_ERROR);
testError("goog.require(tagged`template`);", INVALID_ARGUMENT_ERROR); testError("goog.require(tagged`template`);", INVALID_ARGUMENT_ERROR);
testError("goog.require(`${template}Sub`);", INVALID_ARGUMENT_ERROR); testError("goog.require(`${template}Sub`);", INVALID_ARGUMENT_ERROR);
} }


public void testLateProvides() { public void testRequireTypeBadArguments() {
testError("goog.requireType();", NULL_ARGUMENT_ERROR);
testError("goog.requireType(5);", INVALID_ARGUMENT_ERROR);
testError("goog.requireType([]);", INVALID_ARGUMENT_ERROR);
testError("goog.requireType({});", INVALID_ARGUMENT_ERROR);
testError("goog.requireType(`template`);", INVALID_ARGUMENT_ERROR);
testError("goog.requireType(tagged`template`);", INVALID_ARGUMENT_ERROR);
testError("goog.requireType(`${template}Sub`);", INVALID_ARGUMENT_ERROR);
}

public void testLateProvideForRequire() {
testError("goog.require('foo'); goog.provide('foo');", LATE_PROVIDE_ERROR); testError("goog.require('foo'); goog.provide('foo');", LATE_PROVIDE_ERROR);
testError("goog.require('foo.bar'); goog.provide('foo.bar');", LATE_PROVIDE_ERROR); testError("goog.require('foo.bar'); goog.provide('foo.bar');", LATE_PROVIDE_ERROR);
testError("goog.provide('foo.bar'); goog.require('foo'); goog.provide('foo');", testError("goog.provide('foo.bar'); goog.require('foo'); goog.provide('foo');",
LATE_PROVIDE_ERROR); LATE_PROVIDE_ERROR);
} }


public void testMissingProvides() { public void testLateProvideForRequireType() {
testNoWarning("goog.requireType('foo'); goog.provide('foo');");
testNoWarning("goog.requireType('foo.bar'); goog.provide('foo.bar');");
testNoWarning("goog.provide('foo.bar'); goog.requireType('foo'); goog.provide('foo');");
}

public void testMissingProvideForRequire() {
testError("goog.require('foo');", MISSING_PROVIDE_ERROR); testError("goog.require('foo');", MISSING_PROVIDE_ERROR);
testError("goog.provide('foo'); goog.require('Foo');", MISSING_PROVIDE_ERROR); testError("goog.provide('foo'); goog.require('Foo');", MISSING_PROVIDE_ERROR);
testError("goog.provide('foo'); goog.require('foo.bar');", MISSING_PROVIDE_ERROR); testError("goog.provide('foo'); goog.require('foo.bar');", MISSING_PROVIDE_ERROR);
Expand All @@ -495,6 +535,16 @@ public void testMissingProvides() {
MISSING_PROVIDE_ERROR); MISSING_PROVIDE_ERROR);
} }


public void testMissingProvideForRequireType() {
testError("goog.requireType('foo');", MISSING_PROVIDE_ERROR);
testError("goog.provide('foo'); goog.requireType('Foo');", MISSING_PROVIDE_ERROR);
testError("goog.provide('foo'); goog.requireType('foo.bar');", MISSING_PROVIDE_ERROR);
testError(
"goog.provide('foo'); var EXPERIMENT_FOO = true; "
+ "if (EXPERIMENT_FOO) {goog.requireType('foo.bar');}",
MISSING_PROVIDE_ERROR);
}

public void testProvideInExterns() { public void testProvideInExterns() {
allowExternsChanges(); allowExternsChanges();


Expand Down Expand Up @@ -628,6 +678,15 @@ public void testGoodCrossModuleRequire2() {
}); });
} }


public void testCrossModuleRequireType() {
test(
createModuleStar("goog.requireType('goog.ui');", "", "goog.provide('goog.ui')"),
new String[] {"", "", "/** @const */ goog.ui = {};"});
test(
createModuleStar("", "goog.provide('goog.ui');", "goog.requireType('goog.ui');"),
new String[] {"", "/** @const */ goog.ui = {};", ""});
}

// Tests providing additional code with non-overlapping var namespace. // Tests providing additional code with non-overlapping var namespace.
public void testSimpleAdditionalProvide() { public void testSimpleAdditionalProvide() {
additionalCode = "goog.provide('b.B'); b.B = {};"; additionalCode = "goog.provide('b.B'); b.B = {};";
Expand Down Expand Up @@ -693,19 +752,39 @@ public void testRequireOfAdditionalProvide() {
"/** @const */ var b={};b.B={}; /** @const */ var a={};a.A={};"); "/** @const */ var b={};b.B={}; /** @const */ var a={};a.A={};");
} }


// Tests that a requireType of additional code generates no error.
public void testRequireTypeOfAdditionalProvide() {
additionalCode = "goog.provide('b.B'); b.B = {};";
test(
"goog.requireType('b.B'); goog.provide('a.A'); a.A = {};",
"/** @const */ var b={};b.B={}; /** @const */ var a={};a.A={};");
}

// Tests that a require not in additional code generates (only) one error. // Tests that a require not in additional code generates (only) one error.
public void testMissingRequireWithAdditionalProvide() { public void testMissingRequireWithAdditionalProvide() {
additionalCode = "goog.provide('b.B'); b.B = {};"; additionalCode = "goog.provide('b.B'); b.B = {};";
testError("goog.require('b.C'); goog.provide('a.A'); a.A = {};", testError("goog.require('b.C'); goog.provide('a.A'); a.A = {};",
MISSING_PROVIDE_ERROR); MISSING_PROVIDE_ERROR);
} }


// Tests that a requireType not in additional code generates (only) one error.
public void testMissingRequireTypeWithAdditionalProvide() {
additionalCode = "goog.provide('b.B'); b.B = {};";
testError("goog.requireType('b.C'); goog.provide('a.A'); a.A = {};", MISSING_PROVIDE_ERROR);
}

// Tests that a require in additional code generates no error. // Tests that a require in additional code generates no error.
public void testLateRequire() { public void testLateRequire() {
additionalEndCode = "goog.require('a.A');"; additionalEndCode = "goog.require('a.A');";
test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={}; a.A={};"); test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={}; a.A={};");
} }


// Tests that a requireType in additional code generates no error.
public void testLateRequireType() {
additionalEndCode = "goog.requireType('a.A');";
test("goog.provide('a.A'); a.A = {};", "/** @const */ var a={}; a.A={};");
}

// Tests a case where code is reordered after processing provides and then // Tests a case where code is reordered after processing provides and then
// provides are processed again. // provides are processed again.
public void testReorderedProvides() { public void testReorderedProvides() {
Expand Down Expand Up @@ -868,9 +947,6 @@ public void testInvalidProvide() {
} }


public void testInvalidRequire() { public void testInvalidRequire() {
test(
"goog.provide('a.b'); goog.require('a.b');",
"/** @const */ var a = {}; /** @const */ a.b = {};");
testError("goog.provide('a.b'); var x = x || goog.require('a.b');", testError("goog.provide('a.b'); var x = x || goog.require('a.b');",
INVALID_CLOSURE_CALL_ERROR); INVALID_CLOSURE_CALL_ERROR);
testError("goog.provide('a.b'); x = goog.require('a.b');", testError("goog.provide('a.b'); x = goog.require('a.b');",
Expand All @@ -879,6 +955,15 @@ public void testInvalidRequire() {
INVALID_CLOSURE_CALL_ERROR); INVALID_CLOSURE_CALL_ERROR);
} }


public void testInvalidRequireType() {
testError(
"goog.provide('a.b'); var x = x || goog.requireType('a.b');", INVALID_CLOSURE_CALL_ERROR);
testError("goog.provide('a.b'); x = goog.requireType('a.b');", INVALID_CLOSURE_CALL_ERROR);
testError(
"goog.provide('a.b'); function f() { goog.requireType('a.b'); }",
INVALID_CLOSURE_CALL_ERROR);
}

public void testValidGoogMethod() { public void testValidGoogMethod() {
testSame("function f() { goog.isDef('a.b'); }"); testSame("function f() { goog.isDef('a.b'); }");
testSame("function f() { goog.inherits(a, b); }"); testSame("function f() { goog.inherits(a, b); }");
Expand Down Expand Up @@ -1261,6 +1346,7 @@ public void testProvideInIndependentModules4() {


public void testRequireOfBaseGoog() { public void testRequireOfBaseGoog() {
testError("goog.require('goog');", MISSING_PROVIDE_ERROR); testError("goog.require('goog');", MISSING_PROVIDE_ERROR);
testError("goog.requireType('goog');", MISSING_PROVIDE_ERROR);
} }


public void testSourcePositionPreservation() { public void testSourcePositionPreservation() {
Expand Down Expand Up @@ -1303,6 +1389,7 @@ public void testNoStubForProvidedTypedef4() {


public void testProvideRequireSameFile() { public void testProvideRequireSameFile() {
test("goog.provide('x');\ngoog.require('x');", "/** @const */ var x = {};"); test("goog.provide('x');\ngoog.require('x');", "/** @const */ var x = {};");
test("goog.provide('x');\ngoog.requireType('x');", "/** @const */ var x = {};");
} }


public void testDefineCases() { public void testDefineCases() {
Expand Down

0 comments on commit 32086bf

Please sign in to comment.