Skip to content

Commit

Permalink
RemoveUnusedVars: remove unused properties in prototype literals
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=178190913
  • Loading branch information
brad4d authored and blickly committed Dec 7, 2017
1 parent 34c125f commit e8f7a77
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 43 deletions.
66 changes: 52 additions & 14 deletions src/com/google/javascript/jscomp/RemoveUnusedVars.java
Expand Up @@ -384,13 +384,22 @@ private void traverseRest(Node restNode, Scope scope) {
} }


private void traverseObjectLiteral(Node objectLiteral, Scope scope) { private void traverseObjectLiteral(Node objectLiteral, Scope scope) {
checkArgument(objectLiteral.isObjectLit(), objectLiteral);
// Is this an object literal that is assigned directly to a 'prototype' property?
if (isAssignmentToPrototype(objectLiteral.getParent())) {
traversePrototypeLiteral(objectLiteral, scope);
} else {
traverseNonPrototypeObjectLiteral(objectLiteral, scope);
}
}

private void traverseNonPrototypeObjectLiteral(Node objectLiteral, Scope scope) {
for (Node propertyNode = objectLiteral.getFirstChild(); for (Node propertyNode = objectLiteral.getFirstChild();
propertyNode != null; propertyNode != null;
propertyNode = propertyNode.getNext()) { propertyNode = propertyNode.getNext()) {
if (propertyNode.isStringKey() && !propertyNode.isQuotedString()) { if (propertyNode.isStringKey() && !propertyNode.isQuotedString()) {
// An unquoted property name in an object literal counts as a reference to that property // An unquoted property name in an object literal counts as a reference to that property
// name, because of some reflection patterns. // name, because of some reflection patterns.
// TODO(bradfordcsmith): Handle this better for `Foo.prototype = {a: 1, b: 2}`
markPropertyNameReferenced(propertyNode.getString()); markPropertyNameReferenced(propertyNode.getString());
traverseNode(propertyNode.getFirstChild(), scope); traverseNode(propertyNode.getFirstChild(), scope);
} else { } else {
Expand All @@ -399,6 +408,31 @@ private void traverseObjectLiteral(Node objectLiteral, Scope scope) {
} }
} }


private void traversePrototypeLiteral(Node objectLiteral, Scope scope) {
for (Node propertyNode = objectLiteral.getFirstChild();
propertyNode != null;
propertyNode = propertyNode.getNext()) {
if (propertyNode.isComputedProp() || propertyNode.isQuotedString()) {
traverseChildren(propertyNode, scope);
} else {
// If we've come this far, we already know we're keeping the prototype literal itself,
// but we may be able to remove unreferenced properties in it.
considerForIndependentRemoval(
new RemovableBuilder()
.addContinuation(new Continuation(propertyNode.getOnlyChild(), scope))
.buildClassOrPrototypeNamedProperty(propertyNode));
}
}
}

private boolean isAssignmentToPrototype(Node n) {
return n.isAssign() && isPrototypeGetProp(n.getFirstChild());
}

private boolean isPrototypeGetProp(Node n) {
return n.isGetProp() && n.getLastChild().getString().equals("prototype");
}

private void traverseCatch(Node catchNode, Scope scope) { private void traverseCatch(Node catchNode, Scope scope) {
Node exceptionNameNode = catchNode.getFirstChild(); Node exceptionNameNode = catchNode.getFirstChild();
Node block = exceptionNameNode.getNext(); Node block = exceptionNameNode.getNext();
Expand Down Expand Up @@ -804,7 +838,7 @@ private void traverseClassMembers(Node node, Scope scope) {
considerForIndependentRemoval( considerForIndependentRemoval(
new RemovableBuilder() new RemovableBuilder()
.addContinuation(new Continuation(member, scope)) .addContinuation(new Continuation(member, scope))
.buildMethodDefinition(member)); .buildClassOrPrototypeNamedProperty(member));
} else { } else {
checkState(member.isComputedProp()); checkState(member.isComputedProp());
traverseChildren(member, scope); traverseChildren(member, scope);
Expand Down Expand Up @@ -1231,12 +1265,12 @@ boolean isPrototypeObjectNamedPropertyAssignment() {
return isPrototypeObjectPropertyAssignment && isNamedPropertyAssignment(); return isPrototypeObjectPropertyAssignment && isNamedPropertyAssignment();
} }


boolean isMethodDeclaration() { boolean isClassOrPrototypeNamedProperty() {
return false; return false;
} }


boolean isIndependentlyRemovableNamedProperty() { boolean isIndependentlyRemovableNamedProperty() {
return isPrototypeObjectNamedPropertyAssignment() || isMethodDeclaration(); return isPrototypeObjectNamedPropertyAssignment() || isClassOrPrototypeNamedProperty();
} }
} }


Expand Down Expand Up @@ -1275,10 +1309,14 @@ NamedClassExpression buildNamedClassExpression(Node classNode) {
return new NamedClassExpression(this, classNode); return new NamedClassExpression(this, classNode);
} }


MethodDefinition buildMethodDefinition(Node methodNode) { ClassOrPrototypeNamedProperty buildClassOrPrototypeNamedProperty(Node propertyNode) {
checkArgument(methodNode.isMemberFunctionDef() || NodeUtil.isGetOrSetKey(methodNode)); checkArgument(
this.propertyName = methodNode.getString(); propertyNode.isMemberFunctionDef()
return new MethodDefinition(this, methodNode); || NodeUtil.isGetOrSetKey(propertyNode)
|| (propertyNode.isStringKey() && !propertyNode.isQuotedString()),
propertyNode);
this.propertyName = propertyNode.getString();
return new ClassOrPrototypeNamedProperty(this, propertyNode);
} }


FunctionDeclaration buildFunctionDeclaration(Node functionNode) { FunctionDeclaration buildFunctionDeclaration(Node functionNode) {
Expand Down Expand Up @@ -1443,22 +1481,22 @@ public void removeInternal(AbstractCompiler compiler) {
} }
} }


private class MethodDefinition extends Removable { private class ClassOrPrototypeNamedProperty extends Removable {
final Node methodNode; final Node propertyNode;


MethodDefinition(RemovableBuilder builder, Node methodNode) { ClassOrPrototypeNamedProperty(RemovableBuilder builder, Node propertyNode) {
super(builder); super(builder);
this.methodNode = methodNode; this.propertyNode = propertyNode;
} }


@Override @Override
boolean isMethodDeclaration() { boolean isClassOrPrototypeNamedProperty() {
return true; return true;
} }


@Override @Override
void removeInternal(AbstractCompiler compiler) { void removeInternal(AbstractCompiler compiler) {
NodeUtil.deleteNode(methodNode, compiler); NodeUtil.deleteNode(propertyNode, compiler);
} }
} }


Expand Down
Expand Up @@ -84,8 +84,7 @@ public void testAnalyzePrototypeProperties() {
"var x = new e; x.a()"); "var x = new e; x.a()");
} }


public void disabledTestObjectLiteralPrototype() { public void testObjectLiteralPrototype() {
// TODO(bradfordcsmith): handle properties in object literal assigned to prototype
test("function e(){}" + test("function e(){}" +
"e.prototype = {a: function(){}, b: function(){}};" + "e.prototype = {a: function(){}, b: function(){}};" +
"var x=new e; x.a()", "var x=new e; x.a()",
Expand Down Expand Up @@ -463,8 +462,7 @@ public void testGlobalFunctionsInGraph8() {
""); "");
} }


public void disabledTestGetterBaseline() { public void testGetterBaseline() {
// TODO(bradfordcsmith): remove unused getters
keepGlobals = true; keepGlobals = true;
test( test(
"function Foo() {}" + "function Foo() {}" +
Expand All @@ -481,37 +479,41 @@ public void disabledTestGetterBaseline() {
"function x() { (new Foo).methodA(); }"); "function x() { (new Foo).methodA(); }");
} }


public void disabledTestGetter1() { public void testGetter1() {
// TODO(bradfordcsmith): implement getters and setters
test( test(
"function Foo() {}" + lines(
"Foo.prototype = { " + "function Foo() {}",
" get methodA() {}," + "Foo.prototype = { ",
" get methodB() { x(); }" + " get methodA() {},",
"};" + " get methodB() { x(); }",
"function x() { (new Foo).methodA; }", "};",

"function x() { (new Foo).methodA; }",
"function Foo() {}" + "new Foo();"),
"Foo.prototype = {};"); lines(
"function Foo() {}",
// x() and all methods of Foo removed.
"Foo.prototype = {};",
"new Foo();"));


keepGlobals = true; keepGlobals = true;
test( test(
"function Foo() {}" + lines(
"Foo.prototype = { " + "function Foo() {}",
" get methodA() {}," + "Foo.prototype = { ",
" get methodB() { x(); }" + " get methodA() {},",
"};" + " get methodB() { x(); }",
"function x() { (new Foo).methodA; }", "};",

"function x() { (new Foo).methodA; }"),
"function Foo() {}" + lines(
"Foo.prototype = { " + "function Foo() {}",
" get methodA() {}" + "Foo.prototype = { ",
"};" + " get methodA() {}",
"function x() { (new Foo).methodA; }"); "};",
// x() keeps methodA alive
"function x() { (new Foo).methodA; }"));
} }


public void disabledTestGetter2() { public void testGetter2() {
// TODO(bradfordcsmith): remove unused getters
keepGlobals = true; keepGlobals = true;
test( test(
"function Foo() {}" + "function Foo() {}" +
Expand Down

0 comments on commit e8f7a77

Please sign in to comment.