Skip to content

Commit

Permalink
Run the checks from ClosureCheckModule inside bundled goog.modules.
Browse files Browse the repository at this point in the history
In addition, create a new callback type for traversing module scopes,
including both file-level modules and bundled goog.modules.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=125824641
  • Loading branch information
blickly committed Jun 27, 2016
1 parent a912078 commit 95af284
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 43 deletions.
48 changes: 26 additions & 22 deletions src/com/google/javascript/jscomp/ClosureCheckModule.java
Expand Up @@ -16,7 +16,7 @@
package com.google.javascript.jscomp; package com.google.javascript.jscomp;


import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.jscomp.NodeTraversal.AbstractModuleCallback;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Token;
Expand All @@ -27,10 +27,11 @@
/** /**
* Checks that goog.module() is used correctly. * Checks that goog.module() is used correctly.
* *
* Note that this file only does checks that can be done per-file. Whole program * <p>Note that this file only does checks that can be done per-file. Whole program checks happen
* checks happen during goog.module rewriting, in {@link ClosureRewriteModule}. * during goog.module rewriting, in {@link ClosureRewriteModule}.
*/ */
public final class ClosureCheckModule implements Callback, HotSwapCompilerPass { public final class ClosureCheckModule extends AbstractModuleCallback
implements HotSwapCompilerPass {
static final DiagnosticType AT_EXPORT_IN_GOOG_MODULE = static final DiagnosticType AT_EXPORT_IN_GOOG_MODULE =
DiagnosticType.error( DiagnosticType.error(
"JSC_AT_EXPORT_IN_GOOG_MODULE", "JSC_AT_EXPORT_IN_GOOG_MODULE",
Expand Down Expand Up @@ -122,28 +123,36 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) {
} }


@Override @Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { public void enterModule(NodeTraversal t, Node scopeRoot) {
if (n.isScript()) { Node firstStatement = scopeRoot.getFirstChild();
if (NodeUtil.isGoogModuleFile(n)) { if (NodeUtil.isExprCall(firstStatement)) {
n.putBooleanProp(Node.GOOG_MODULE, true); Node call = firstStatement.getFirstChild();
return true; Node callee = call.getFirstChild();
if (callee.matchesQualifiedName("goog.module")) {
Preconditions.checkState(currentModuleName == null);
currentModuleName = extractFirstArgumentName(call);
} }
return false;
} }
return true; }

@Override
public void exitModule(NodeTraversal t, Node scopeRoot) {
currentModuleName = null;
shortRequiredNamespaces.clear();
defaultExportNode = null;
} }


@Override @Override
public void visit(NodeTraversal t, Node n, Node parent) { public void visit(NodeTraversal t, Node n, Node parent) {
if (currentModuleName == null) {
return;
}
switch (n.getType()) { switch (n.getType()) {
case CALL: case CALL:
Node callee = n.getFirstChild(); Node callee = n.getFirstChild();
if (callee.matchesQualifiedName("goog.module")) { if (callee.matchesQualifiedName("goog.module")
if (currentModuleName == null) { && !currentModuleName.equals(extractFirstArgumentName(n))) {
currentModuleName = extractFirstArgumentName(n); t.report(n, MULTIPLE_MODULES_IN_FILE);
} else {
t.report(n, MULTIPLE_MODULES_IN_FILE);
}
} else if (callee.matchesQualifiedName("goog.provide")) { } else if (callee.matchesQualifiedName("goog.provide")) {
t.report(n, MODULE_AND_PROVIDES); t.report(n, MODULE_AND_PROVIDES);
} else if (callee.matchesQualifiedName("goog.require")) { } else if (callee.matchesQualifiedName("goog.require")) {
Expand Down Expand Up @@ -198,11 +207,6 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }
} }
break; break;
case SCRIPT:
currentModuleName = null;
shortRequiredNamespaces.clear();
defaultExportNode = null;
break;
} }
} }


Expand Down
1 change: 1 addition & 0 deletions src/com/google/javascript/jscomp/ClosureRewriteModule.java
Expand Up @@ -251,6 +251,7 @@ private class ScriptRecorder extends AbstractPreOrderCallback {
@Override @Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isGoogModuleFile(n)) { if (NodeUtil.isGoogModuleFile(n)) {
n.putBooleanProp(Node.GOOG_MODULE, true);
inlineModuleIntoGlobal(n); inlineModuleIntoGlobal(n);
compiler.reportCodeChange(); compiler.reportCodeChange();
checkAndSetStrictModeDirective(t, n); checkAndSetStrictModeDirective(t, n);
Expand Down
78 changes: 57 additions & 21 deletions src/com/google/javascript/jscomp/NodeTraversal.java
Expand Up @@ -150,14 +150,10 @@ public abstract static class AbstractPreOrderCallback implements Callback {
public void visit(NodeTraversal t, Node n, Node parent) {} public void visit(NodeTraversal t, Node n, Node parent) {}
} }


/** /** Abstract scoped callback to visit all nodes in postorder. */
* Abstract scoped callback to visit all nodes in postorder. public abstract static class AbstractScopedCallback implements ScopedCallback {
*/
public abstract static class AbstractScopedCallback
implements ScopedCallback {
@Override @Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
Node parent) {
return true; return true;
} }


Expand All @@ -174,26 +170,61 @@ public void exitScope(NodeTraversal t) {}
*/ */
public abstract static class AbstractShallowCallback implements Callback { public abstract static class AbstractShallowCallback implements Callback {
@Override @Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
Node parent) {
// We do want to traverse the name of a named function, but we don't // We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body. // want to traverse the arguments or body.
return parent == null || !parent.isFunction() || return parent == null || !parent.isFunction() || n == parent.getFirstChild();
n == parent.getFirstChild();
} }
} }


/** /**
* Abstract callback to visit all structure and statement nodes but doesn't * Abstract callback to visit all structure and statement nodes but doesn't traverse into
* traverse into functions or expressions. * functions or expressions.
*/ */
public abstract static class AbstractShallowStatementCallback public abstract static class AbstractShallowStatementCallback implements Callback {
implements Callback {
@Override @Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
Node parent) { return parent == null
return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent); || NodeUtil.isStatementBlock(parent);
}
}

/**
* Abstract callback that knows when goog.modules (and in the future ES6 modules) are entered
* and exited. This includes both whole file modules and bundled modules.
*/
public abstract static class AbstractModuleCallback implements ScopedCallback {

/**
* Called immediately after entering a module.
*/
public abstract void enterModule(NodeTraversal t, Node scopeRoot);

/**
* Called immediately before exiting a module.
*/
public abstract void exitModule(NodeTraversal t, Node scopeRoot);

@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}

@Override
public final void enterScope(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
if (NodeUtil.isModuleScopeRoot(scopeRoot)) {
enterModule(t, scopeRoot);
}
}

@Override
public final void exitScope(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
if (NodeUtil.isModuleScopeRoot(scopeRoot)) {
exitModule(t, scopeRoot);
}
} }
} }


Expand Down Expand Up @@ -863,14 +894,19 @@ public boolean inGlobalHoistScope() {
* in a global block scope. * in a global block scope.
*/ */
public boolean inModuleScope() { public boolean inModuleScope() {
return getScopeRoot().isModuleBody(); return NodeUtil.isModuleScopeRoot(getScopeRoot());
} }


/** /**
* Determines whether the hoist scope of the current traversal is global. * Determines whether the hoist scope of the current traversal is global.
*/ */
public boolean inModuleHoistScope() { public boolean inModuleHoistScope() {
return getCfgRoot().isModuleBody(); Node moduleRoot = getCfgRoot();
if (moduleRoot.isFunction()) {
// For wrapped modules, the function block is the module scope root.
moduleRoot = moduleRoot.getLastChild();
}
return NodeUtil.isModuleScopeRoot(moduleRoot);
} }


int getScopeDepth() { int getScopeDepth() {
Expand Down
24 changes: 24 additions & 0 deletions src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -4491,6 +4491,30 @@ private static boolean isGoogModuleCall(Node n) {
return false; return false;
} }


static boolean isModuleScopeRoot(Node n) {
return n.isModuleBody() || isBundledGoogModuleScopeRoot(n);
}

private static boolean isBundledGoogModuleScopeRoot(Node n) {
if (!n.isBlock() || !n.hasChildren() || !isGoogModuleCall(n.getFirstChild())) {
return false;
}
Node function = n.getParent();
if (function == null
|| !function.isFunction()
|| getFunctionParameters(function).getChildCount() != 1
|| !getFunctionParameters(function).getFirstChild().matchesQualifiedName("exports")) {
return false;
}
Node call = function.getParent();
if (!call.isCall()
|| call.getChildCount() != 2
|| !call.getFirstChild().matchesQualifiedName("goog.loadModule")) {
return false;
}
return call.getParent().isExprResult() && call.getGrandparent().isScript();
}

private static boolean isGoogModuleDeclareLegacyNamespaceCall(Node n) { private static boolean isGoogModuleDeclareLegacyNamespaceCall(Node n) {
if (isExprCall(n)) { if (isExprCall(n)) {
Node target = n.getFirstFirstChild(); Node target = n.getFirstFirstChild();
Expand Down
49 changes: 49 additions & 0 deletions test/com/google/javascript/jscomp/ClosureCheckModuleTest.java
Expand Up @@ -97,6 +97,55 @@ public void testMultipleGoogModules() {
MULTIPLE_MODULES_IN_FILE); MULTIPLE_MODULES_IN_FILE);
} }


public void testBundledGoogModules() {
testError(
LINE_JOINER.join(
"goog.loadModule(function(exports){",
" 'use strict';",
" goog.module('xyz');",
" foo.call(this, 1, 2, 3);",
" return exports;",
"});"),
GOOG_MODULE_REFERENCES_THIS);

testError(
LINE_JOINER.join(
"goog.loadModule(function(exports){",
" 'use strict';",
" goog.module('foo.example.ClassName');",
" /** @constructor @export */ function ClassName() {}",
" exports = ClassName;",
" return exports;",
"});"),
ClosureCheckModule.AT_EXPORT_IN_GOOG_MODULE);

testSameEs6(
LINE_JOINER.join(
"goog.loadModule(function(exports){",
" 'use strict';",
" goog.module('xyz');",
" exports = class {}",
" return exports;",
"});",
"goog.loadModule(function(exports){",
" goog.module('abc');",
" var Foo = goog.require('xyz');",
" var x = new Foo;",
" return exports;",
"});"));

testError(
LINE_JOINER.join(
"goog.loadModule(function(exports){",
" 'use strict';",
" goog.module('xyz');",
" goog.module('abc');",
" var x = goog.require('other.x');",
" return exports;",
"});"),
MULTIPLE_MODULES_IN_FILE);
}

public void testGoogModuleReferencesGlobalName() { public void testGoogModuleReferencesGlobalName() {
testError("goog.module('x.y.z');\nx.y.z = function() {};", REFERENCE_TO_MODULE_GLOBAL_NAME); testError("goog.module('x.y.z');\nx.y.z = function() {};", REFERENCE_TO_MODULE_GLOBAL_NAME);
} }
Expand Down

0 comments on commit 95af284

Please sign in to comment.