Skip to content

Commit d5b1004

Browse files
committed
Handle ES6 export/import name as alias syntax in VarCheck correctly for nonlocal names.
Examples: let a; export {a as b}; // b isn't and shouldn't be defined in the module or global scope import {b as a} from './foo.js'; // same ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=172157001
1 parent 2c0100b commit d5b1004

File tree

5 files changed

+88
-2
lines changed

5 files changed

+88
-2
lines changed

src/com/google/javascript/jscomp/MakeDeclaredNamesUnique.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
130130

131131
private void visitName(NodeTraversal t, Node n, Node parent) {
132132
// Don't rename the exported name foo in export {a as foo}; or import {foo as b};
133-
if (parent != null && ((parent.isImportSpec() && parent.getFirstChild() == n)
134-
|| (parent.isExportSpec() && parent.getLastChild() == n))) {
133+
if (NodeUtil.isNonlocalModuleExportName(n)) {
135134
return;
136135
}
137136
String newName = getReplacementName(n.getString());

src/com/google/javascript/jscomp/NodeUtil.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,6 +2684,19 @@ static boolean isReferenceName(Node n) {
26842684
return n.isName() && !n.getString().isEmpty();
26852685
}
26862686

2687+
/**
2688+
* @return Whether the name in an import or export spec is not defined within the module, but is
2689+
* an exported name from this or another module. e.g. nonlocal in "export {a as nonlocal}" or
2690+
* "import {nonlocal as a} from './foo.js'"
2691+
*/
2692+
static boolean isNonlocalModuleExportName(Node n) {
2693+
Node parent = n.getParent();
2694+
return (parent != null
2695+
&& n.isName()
2696+
&& ((parent.isExportSpec() && n != parent.getFirstChild())
2697+
|| (parent.isImportSpec() && n != parent.getLastChild())));
2698+
}
2699+
26872700
/** Whether the child node is the FINALLY block of a try. */
26882701
static boolean isTryFinallyNode(Node parent, Node child) {
26892702
return parent.isTry() && parent.hasXChildren(3)

src/com/google/javascript/jscomp/VarCheck.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ public void visit(NodeTraversal t, Node n, Node parent) {
196196
|| (NodeUtil.isClassExpression(parent) && n == parent.getFirstChild())) {
197197
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
198198
// current scope.
199+
} else if (NodeUtil.isNonlocalModuleExportName(n)) {
200+
// e.g. "export {a as b}" or "import {b as a} from './foo.js'
201+
// where b is defined in a module's export entries but not in any module scope.
199202
} else {
200203
boolean isArguments = scope.isFunctionScope() && ARGUMENTS.equals(varName);
201204
// The extern checks are stricter, don't report a second error.

test/com/google/javascript/jscomp/NodeUtilTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,61 @@ private void assertNodeNames(Set<String> nodeNames, Collection<Node> nodes) {
866866
assertEquals(nodeNames, actualNames);
867867
}
868868

869+
public void testIsNonlocalModuleExportNameOnExports1() {
870+
Node root = parse("export {localName as exportName};");
871+
Node moduleBody = root.getFirstChild();
872+
Node exportNode = moduleBody.getFirstChild();
873+
Node exportSpecs = exportNode.getFirstChild();
874+
Node exportSpec = exportSpecs.getFirstChild();
875+
876+
Node localName = exportSpec.getFirstChild();
877+
Node exportName = exportSpec.getSecondChild();
878+
879+
assertFalse(NodeUtil.isNonlocalModuleExportName(localName));
880+
assertTrue(NodeUtil.isNonlocalModuleExportName(exportName));
881+
882+
}
883+
884+
public void testIsNonlocalModuleExportNameOnExports2() {
885+
Node root = parse("let bar; export {bar};");
886+
Node moduleBody = root.getFirstChild();
887+
Node exportNode = moduleBody.getSecondChild();
888+
Node exportSpecs = exportNode.getFirstChild();
889+
Node exportSpec = exportSpecs.getFirstChild();
890+
891+
Node name = exportSpec.getFirstChild();
892+
893+
// bar is defined locally, so isNonlocalModuleExportName is false.
894+
assertFalse(NodeUtil.isNonlocalModuleExportName(name));
895+
}
896+
897+
public void testIsNonlocalModuleExportNameOnImports1() {
898+
Node root = parse("import {exportName as localName} from './foo.js';");
899+
Node moduleBody = root.getFirstChild();
900+
Node importNode = moduleBody.getFirstChild();
901+
Node importSpecs = importNode.getSecondChild();
902+
Node importSpec = importSpecs.getFirstChild();
903+
904+
Node exportName = importSpec.getFirstChild();
905+
Node localName = importSpec.getSecondChild();
906+
907+
assertTrue(NodeUtil.isNonlocalModuleExportName(exportName));
908+
assertFalse(NodeUtil.isNonlocalModuleExportName(localName));
909+
}
910+
911+
public void testIsNonlocalModuleExportNameOnImports2() {
912+
Node root = parse("import {bar} from './foo.js';");
913+
Node moduleBody = root.getFirstChild();
914+
Node importNode = moduleBody.getFirstChild();
915+
Node importSpecs = importNode.getSecondChild();
916+
Node importSpec = importSpecs.getFirstChild();
917+
918+
Node name = importSpec.getFirstChild();
919+
920+
// bar is defined locally so isNonlocalModuleExportName is false
921+
assertFalse(NodeUtil.isNonlocalModuleExportName(name));
922+
}
923+
869924
public void testIsControlStructureCodeBlock() {
870925
Node root = parse("if (x) foo(); else boo();");
871926
Node ifNode = root.getFirstChild();

test/com/google/javascript/jscomp/VarCheckTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,22 @@ public void testImportStar() {
680680
testSame("import * as foo from './foo.js';");
681681
}
682682

683+
public void testExportAsAlias() {
684+
testSame("let a = 1; export {a as b};");
685+
testError("let a = 1; export {b as a};", VarCheck.UNDEFINED_VAR_ERROR);
686+
testError("export {a as a};", VarCheck.UNDEFINED_VAR_ERROR);
687+
688+
// Make sure non-aliased exports still work correctly.
689+
testSame("let a = 1; export {a}");
690+
testError("let a = 1; export {b};", VarCheck.UNDEFINED_VAR_ERROR);
691+
}
692+
693+
public void testImportAsAlias() {
694+
testSame("import {b as a} from './foo.js'; let c = a;");
695+
testError("import {b as a} from './foo.js'; let c = b;", VarCheck.UNDEFINED_VAR_ERROR);
696+
testSame("import {a} from './foo.js'; let c = a;");
697+
}
698+
683699
private static final class VariableTestCheck implements CompilerPass {
684700

685701
final AbstractCompiler compiler;

0 commit comments

Comments
 (0)