Skip to content

Commit

Permalink
Recognize class declarations in export default in NodeUtil.isClassExp…
Browse files Browse the repository at this point in the history
…ression.

e.g.
  export default class Foo {}
adds Foo to the module scope, although Foo is not an exported name from the module.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=169153915
  • Loading branch information
lauraharker authored and blickly committed Sep 18, 2017
1 parent 021ab75 commit 1dba5d2
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -3040,7 +3040,7 @@ static boolean isFunctionExpression(Node n) {
* @return Whether n is a class used within an expression. * @return Whether n is a class used within an expression.
*/ */
static boolean isClassExpression(Node n) { static boolean isClassExpression(Node n) {
return n.isClass() && !isStatement(n); return n.isClass() && (!isNamedClass(n) || !isDeclarationParent(n.getParent()));
} }


/** /**
Expand Down
Expand Up @@ -1084,6 +1084,18 @@ public void testClassExpressionInForLoopInitializer() {
assertTrue(classScope.isDeclared("Clazz", false)); assertTrue(classScope.isDeclared("Clazz", false));
} }


public void testClassDeclarationInExportDefault() {
String js = "export default class Clazz {}";
Node root = getRoot(js);
Scope globalScope = scopeCreator.createScope(root, null);
assertFalse(globalScope.isDeclared("Clazz", false));

Node moduleBody = root.getFirstChild();
checkState(moduleBody.isModuleBody(), moduleBody);
Scope moduleScope = scopeCreator.createScope(moduleBody, globalScope);
assertTrue(moduleScope.isDeclared("Clazz", false));
}

public void testVarsInModulesNotGlobal() { public void testVarsInModulesNotGlobal() {
Node root = getRoot("goog.module('example'); var x;"); Node root = getRoot("goog.module('example'); var x;");
Scope globalScope = scopeCreator.createScope(root, null); Scope globalScope = scopeCreator.createScope(root, null);
Expand Down
55 changes: 47 additions & 8 deletions test/com/google/javascript/jscomp/NodeUtilTest.java
Expand Up @@ -654,7 +654,8 @@ public void testMayEffectMutableState() {
public void testIsFunctionExpression() { public void testIsFunctionExpression() {
assertContainsAnonFunc(true, "(function(){})"); assertContainsAnonFunc(true, "(function(){})");
assertContainsAnonFunc(true, "[function a(){}]"); assertContainsAnonFunc(true, "[function a(){}]");
assertContainsAnonFunc(false, "{x: function a(){}}"); assertContainsAnonFunc(false, "{label: function a(){}}");
assertContainsAnonFunc(true, "({x: function a(){}})");
assertContainsAnonFunc(true, "(function a(){})()"); assertContainsAnonFunc(true, "(function a(){})()");
assertContainsAnonFunc(true, "x = function a(){};"); assertContainsAnonFunc(true, "x = function a(){};");
assertContainsAnonFunc(true, "var x = function a(){};"); assertContainsAnonFunc(true, "var x = function a(){};");
Expand All @@ -681,28 +682,66 @@ public void testIsFunctionExpression() {
} }


private void assertContainsAnonFunc(boolean expected, String js) { private void assertContainsAnonFunc(boolean expected, String js) {
Node funcParent = findParentOfFuncDescendant(parse(js)); Node funcParent = findParentOfFuncOrClassDescendant(parse(js), Token.FUNCTION);
assertNotNull("Expected function node in parse tree of: " + js, funcParent); assertNotNull("Expected function node in parse tree of: " + js, funcParent);
Node funcNode = getFuncChild(funcParent); Node funcNode = getFuncOrClassChild(funcParent, Token.FUNCTION);
assertEquals(expected, NodeUtil.isFunctionExpression(funcNode)); assertEquals(expected, NodeUtil.isFunctionExpression(funcNode));
} }


private Node findParentOfFuncDescendant(Node n) { public void testIsClassExpression() {
assertContainsAnonClass(true, "(class {})");
assertContainsAnonClass(true, "[class Clazz {}]");
assertContainsAnonClass(false, "{label: class Clazz {}}");
assertContainsAnonClass(true, "({x: class Clazz {}})");
assertContainsAnonClass(true, "x = class Clazz {};");
assertContainsAnonClass(true, "var x = class Clazz {};");
assertContainsAnonClass(true, "if (class Clazz {});");
assertContainsAnonClass(true, "while (class Clazz {});");
assertContainsAnonClass(true, "do; while (class Clazz {});");
assertContainsAnonClass(true, "for (class Clazz {};;);");
assertContainsAnonClass(true, "for (;class Clazz {};);");
assertContainsAnonClass(true, "for (;;class Clazz {});");
assertContainsAnonClass(true, "for (p in class Clazz {});");
assertContainsAnonClass(true, "with (class Clazz {}) {}");
assertContainsAnonClass(false, "class Clazz {}");
assertContainsAnonClass(false, "if (x) class Clazz {};");
assertContainsAnonClass(false, "if (x) { class Clazz {} }");
assertContainsAnonClass(false, "if (x); else class Clazz {};");
assertContainsAnonClass(false, "while (x) class Clazz {};");
assertContainsAnonClass(false, "do class Clazz {} while (0);");
assertContainsAnonClass(false, "for (;;) class Clazz {}");
assertContainsAnonClass(false, "for (p in o) class Clazz {};");
assertContainsAnonClass(false, "with (x) class Clazz {}");
assertContainsAnonClass(true, "export default class {};");
assertContainsAnonClass(false, "export default class Clazz {};");
assertContainsAnonClass(false, "export class Clazz {};");
}

private void assertContainsAnonClass(boolean expected, String js) {
Node classParent = findParentOfFuncOrClassDescendant(parse(js), Token.CLASS);
assertNotNull("Expected class node in parse tree of: " + js, classParent);
Node classNode = getFuncOrClassChild(classParent, Token.CLASS);
assertEquals(expected, NodeUtil.isClassExpression(classNode));
}

private Node findParentOfFuncOrClassDescendant(Node n, Token token) {
checkArgument(token.equals(Token.CLASS) || token.equals(Token.FUNCTION));
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.isFunction()) { if (c.getToken().equals(token)) {
return n; return n;
} }
Node result = findParentOfFuncDescendant(c); Node result = findParentOfFuncOrClassDescendant(c, token);
if (result != null) { if (result != null) {
return result; return result;
} }
} }
return null; return null;
} }


private Node getFuncChild(Node n) { private Node getFuncOrClassChild(Node n, Token token) {
checkArgument(token.equals(Token.CLASS) || token.equals(Token.FUNCTION));
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.isFunction()) { if (c.getToken().equals(token)) {
return c; return c;
} }
} }
Expand Down

0 comments on commit 1dba5d2

Please sign in to comment.