Skip to content

Commit

Permalink
Add basic ES6 class method support to TypedScopeCreator
Browse files Browse the repository at this point in the history
Methods are added to the type's property map, but no checking of overrides is done yet, and 'super' is not yet supported.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=202241474
  • Loading branch information
shicks authored and brad4d committed Jun 27, 2018
1 parent e8682d8 commit fecf27d
Show file tree
Hide file tree
Showing 5 changed files with 626 additions and 14 deletions.
5 changes: 4 additions & 1 deletion src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -5239,6 +5239,8 @@ public static Node getBestLValue(Node n) {
Node parent = n.getParent();
if (isFunctionDeclaration(n) || isClassDeclaration(n)) {
return n.getFirstChild();
} else if (n.isClassMembers()) {
return getBestLValue(parent);
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
Expand Down Expand Up @@ -5316,7 +5318,8 @@ static String getBestLValueName(@Nullable Node lValue) {
return null;
}
String methodName = lValue.getString();
return className + ".prototype." + methodName;
String maybePrototype = lValue.isStaticMember() ? "." : ".prototype.";
return className + maybePrototype + methodName;
}
// TODO(sdh): Tighten this to simply require !lValue.isQuotedString()
// Could get rid of the isJSIdentifier check, but may need to fix depot.
Expand Down
45 changes: 36 additions & 9 deletions src/com/google/javascript/jscomp/TypedScopeCreator.java
Expand Up @@ -1074,21 +1074,32 @@ private FunctionType createFunctionTypeFromNodes(
boolean isFnLiteral = rValue != null && rValue.isFunction();
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ? rValue.getSecondChild() : null;
Node classRoot =
lvalueNode != null && lvalueNode.getParent().isClassMembers()
? lvalueNode.getGrandparent()
: null;

// Find the type of any overridden function.
Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
TypedVar ownerVar = null;
String propName = null;
ObjectType ownerType = null;
if (ownerName != null) {
ownerVar = currentScope.getVar(ownerName);
if (ownerNode != null && classRoot != null) {
// Static members are owned by the constructor, non-statics are owned by the prototype.
ownerType = JSType.toMaybeFunctionType(classRoot.getJSType());
if (!lvalueNode.isStaticMember() && ownerType != null) {
ownerType = ((FunctionType) ownerType).getPrototype();
ownerName = ownerName + ".prototype";
}
} else if (ownerName != null) {
TypedVar ownerVar = currentScope.getVar(ownerName);
if (ownerVar != null) {
ownerType = ObjectType.cast(ownerVar.getType());
}
if (name != null) {
propName = name.substring(ownerName.length() + 1);
}
}

String propName = null;
if (ownerName != null && name != null) {
propName = name.substring(ownerName.length() + 1);
}

ObjectType prototypeOwner = getPrototypeOwnerType(ownerType);
Expand Down Expand Up @@ -1549,6 +1560,8 @@ JSType getDeclaredType(JSDocInfo info, Node lValue, @Nullable Node rValue) {
&& shouldUseFunctionLiteralType(
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (rValue != null && rValue.isClass()) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
Expand Down Expand Up @@ -2321,9 +2334,10 @@ void visitPreorder(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
if (parent.getString().equals("constructor")) {
// Constructor has already been analyzed, so pull that here.
n.setJSType(currentScope.getRootNode().getJSType());
setDeferredType(n, currentScope.getRootNode().getJSType());
} else {
defineFunctionLiteral(n);
}
// TODO(sdh): handle non-constructor methods.
}
}

Expand All @@ -2335,7 +2349,20 @@ void visitPostorder(NodeTraversal t, Node n, Node parent) {
// Declare bleeding class name in scope. Pull the type off the AST.
checkState(!n.getString().isEmpty()); // anonymous classes have EMPTY nodes, not NAME
defineSlot(n, parent.getJSType(), false);
} else if (n.isMemberFunctionDef() && !"constructor".equals(n.getString())) {
defineMemberFunction(n);
}
}

void defineMemberFunction(Node n) {
// MEMBER_FUNCTION_DEF -> CLASS_MEMBERS -> CLASS
Node ownerNode = n.getGrandparent();
checkState(ownerNode.isClass());
ObjectType ownerType = ownerNode.getJSType().toMaybeFunctionType();
if (!n.isStaticMember()) {
ownerType = ((FunctionType) ownerType).getPrototype();
}
ownerType.defineDeclaredProperty(n.getString(), n.getLastChild().getJSType(), n);
}
} // end ClassScopeBuilder

Expand Down
18 changes: 18 additions & 0 deletions test/com/google/javascript/jscomp/NodeUtilTest.java
Expand Up @@ -46,6 +46,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import junit.framework.TestCase;

Expand Down Expand Up @@ -2348,6 +2349,23 @@ public void testGetBestLValue() {
assertEquals("x", getFunctionLValue("var x = (y, function() {});"));
}

public void testGetBestLValueName() {
Function<String, String> getBestName =
(js) -> NodeUtil.getBestLValueName(NodeUtil.getBestLValue(getFunctionNode(js)));
assertEquals("x", getBestName.apply("var x = function() {};"));
assertEquals("x", getBestName.apply("x = function() {};"));
assertEquals("x", getBestName.apply("function x() {};"));
assertEquals("x", getBestName.apply("var x = y ? z : function() {};"));
assertEquals("x", getBestName.apply("var x = y ? function() {} : z;"));
assertEquals("x", getBestName.apply("var x = y && function() {};"));
assertEquals("x", getBestName.apply("var x = y || function() {};"));
assertEquals("x", getBestName.apply("var x = (y, function() {});"));
assertEquals("C.prototype.d", getBestName.apply("C.prototype.d = function() {};"));
assertEquals("C.prototype.d", getBestName.apply("class C { d() {} };"));
assertEquals("C.d", getBestName.apply("C.d = function() {};"));
assertEquals("C.d", getBestName.apply("class C { static d() {} };"));
}

public void testGetRValueOfLValue() {
assertTrue(functionIsRValueOfAssign("x = function() {};"));
assertTrue(functionIsRValueOfAssign("x += function() {};"));
Expand Down

0 comments on commit fecf27d

Please sign in to comment.