From ac41550386bba040264054e9baadb1da9b4994cd Mon Sep 17 00:00:00 2001 From: blickly Date: Wed, 20 Dec 2017 15:17:15 -0800 Subject: [PATCH] A few more code cleanups to ths ijs package ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=179740352 --- .../javascript/jscomp/ijs/ClassUtil.java | 91 ++++++++++++++ .../jscomp/ijs/ConvertToTypedInterface.java | 111 ++++-------------- .../javascript/jscomp/ijs/FileInfo.java | 3 +- .../javascript/jscomp/ijs/JsdocUtil.java | 3 + .../jscomp/ijs/PotentialDeclaration.java | 58 ++++++--- 5 files changed, 163 insertions(+), 103 deletions(-) create mode 100644 src/com/google/javascript/jscomp/ijs/ClassUtil.java diff --git a/src/com/google/javascript/jscomp/ijs/ClassUtil.java b/src/com/google/javascript/jscomp/ijs/ClassUtil.java new file mode 100644 index 00000000000..af61c9b0b97 --- /dev/null +++ b/src/com/google/javascript/jscomp/ijs/ClassUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 The Closure Compiler Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.javascript.jscomp.ijs; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.javascript.jscomp.NodeUtil; +import com.google.javascript.rhino.JSDocInfo; +import com.google.javascript.rhino.Node; + +/** + * Static utility methods for dealing with classes. The primary benefit is for papering over + * the differences between ES6 class and goog.defineClass syntax. + */ +final class ClassUtil { + private ClassUtil() {} + + static boolean isThisProp(Node getprop) { + return getprop.isGetProp() && getprop.getFirstChild().isThis(); + } + + static String getPrototypeNameOfThisProp(Node getprop) { + checkArgument(isThisProp(getprop)); + Node function = NodeUtil.getEnclosingFunction(getprop); + String className = getClassName(function); + checkState(className != null && !className.isEmpty()); + return className + ".prototype." + getprop.getLastChild().getString(); + } + + static String getPrototypeNameOfMethod(Node function) { + checkArgument(isClassMethod(function)); + String className = getClassName(function); + checkState(className != null && !className.isEmpty()); + return className + ".prototype." + function.getParent().getString(); + } + + static boolean isClassMethod(Node functionNode) { + checkArgument(functionNode.isFunction()); + Node parent = functionNode.getParent(); + if (parent.isMemberFunctionDef() + && parent.getParent().isClassMembers()) { + // ES6 class + return true; + } + // goog.defineClass + return parent.isStringKey() + && parent.getParent().isObjectLit() + && parent.getGrandparent().isCall() + && parent.getGrandparent().getFirstChild().matchesQualifiedName("goog.defineClass"); + } + + static String getClassName(Node functionNode) { + if (isClassMethod(functionNode)) { + Node parent = functionNode.getParent(); + if (parent.isMemberFunctionDef()) { + // ES6 class + Node classNode = functionNode.getGrandparent().getParent(); + checkState(classNode.isClass()); + return NodeUtil.getName(classNode); + } + // goog.defineClass + checkState(parent.isStringKey()); + Node defineClassCall = parent.getGrandparent(); + checkState(defineClassCall.isCall()); + return NodeUtil.getBestLValue(defineClassCall).getQualifiedName(); + } + return NodeUtil.getName(functionNode); + } + + static boolean isConstructor(Node functionNode) { + if (isClassMethod(functionNode)) { + return "constructor".equals(functionNode.getParent().getString()); + } + JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(functionNode); + return jsdoc != null && jsdoc.isConstructor(); + } +} diff --git a/src/com/google/javascript/jscomp/ijs/ConvertToTypedInterface.java b/src/com/google/javascript/jscomp/ijs/ConvertToTypedInterface.java index 41a4d840e07..bd29bb6778d 100644 --- a/src/com/google/javascript/jscomp/ijs/ConvertToTypedInterface.java +++ b/src/com/google/javascript/jscomp/ijs/ConvertToTypedInterface.java @@ -111,7 +111,7 @@ private static class RemoveNonDeclarations implements NodeTraversal.Callback { public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case FUNCTION: - if (!isConstructor(n)) { + if (!ClassUtil.isConstructor(n)) { Node body = n.getLastChild(); if (!body.isNormalBlock() || body.hasChildren()) { t.reportCodeChange(body); @@ -276,7 +276,7 @@ static void splitNameDeclarationsAndRemoveDestructuring(Node n, NodeTraversal t) } private static class PropagateConstJsdoc extends NodeTraversal.AbstractPostOrderCallback { - FileInfo currentFile; + final FileInfo currentFile; PropagateConstJsdoc(FileInfo currentFile) { this.currentFile = currentFile; @@ -293,7 +293,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { case FUNCTION: if (NodeUtil.isStatementParent(parent)) { currentFile.recordNameDeclaration(n.getFirstChild(), t.getScope()); - } else if (isClassMethod(n)) { + } else if (ClassUtil.isClassMethod(n)) { currentFile.recordMethod(n, t.getScope()); } break; @@ -367,7 +367,7 @@ private void propagateJsdocAtName(NodeTraversal t, Node nameNode) { return; } JSDocInfo newJsdoc = JsdocUtil.getJSDocForRhs(rhs, jsdoc); - if (newJsdoc == null && isThisProp(nameNode)) { + if (newJsdoc == null && ClassUtil.isThisProp(nameNode)) { Var decl = findNameDeclaration(t.getScope(), rhs); newJsdoc = JsdocUtil.getJSDocForName(decl, jsdoc); } @@ -422,7 +422,7 @@ private void removeDuplicateDeclarations() { } } - public void simplifyAll() { + void simplifyAll() { // Remove duplicate assignments to the same symbol removeDuplicateDeclarations(); @@ -431,7 +431,7 @@ public void simplifyAll() { SHORT_TO_LONG.immutableSortedCopy(currentFile.getDeclarations().keySet()); for (String name : seenNames) { for (PotentialDeclaration decl : currentFile.getDeclarations().get(name)) { - processName(name, decl); + processDeclaration(decl); } } // Simplify all names inside constructors. @@ -440,17 +440,17 @@ public void simplifyAll() { } } - private void processName(String qname, PotentialDeclaration decl) { - if (NodeUtil.isCallTo(decl.lhs, "goog.define")) { - NodeUtil.deleteNode(decl.lhs.getLastChild(), compiler); + private void processDeclaration(PotentialDeclaration decl) { + if (NodeUtil.isCallTo(decl.getLhs(), "goog.define")) { + NodeUtil.deleteNode(decl.getLhs().getLastChild(), compiler); return; } - switch (shouldRemove(qname, decl)) { + switch (shouldRemove(decl)) { case PRESERVE_ALL: - if (decl.rhs != null && decl.rhs.isFunction()) { - processFunction(decl.rhs); - } else if (decl.rhs != null && isClass(decl.rhs)) { - processClass(decl.rhs); + if (decl.getRhs() != null && decl.getRhs().isFunction()) { + processFunction(decl.getRhs()); + } else if (decl.getRhs() != null && isClass(decl.getRhs())) { + processClass(decl.getRhs()); } break; case SIMPLIFY_RHS: @@ -476,7 +476,7 @@ private void processClass(Node n) { private void processFunction(Node n) { checkArgument(n.isFunction()); processFunctionParameters(n.getSecondChild()); - if (isConstructor(n) && n.getLastChild().hasChildren()) { + if (ClassUtil.isConstructor(n) && n.getLastChild().hasChildren()) { currentFile.markConstructorToProcess(n); } } @@ -494,7 +494,7 @@ private void processFunctionParameters(Node paramList) { } private void processConstructor(final Node function) { - if (getClassName(function) == null) { + if (ClassUtil.getClassName(function) == null) { return; } final Node insertionPoint = NodeUtil.getEnclosingStatement(function); @@ -507,8 +507,8 @@ public void visit(NodeTraversal t, Node n, Node parent) { if (n.isExprResult()) { Node expr = n.getFirstChild(); Node name = expr.isAssign() ? expr.getFirstChild() : expr; - if (isThisProp(name)) { - String fullyQualifiedName = getPrototypeNameOfThisProp(name); + if (ClassUtil.isThisProp(name)) { + String fullyQualifiedName = ClassUtil.getPrototypeNameOfThisProp(name); JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(name); Node newProtoAssignStmt = NodeUtil.newQNameDeclaration(compiler, fullyQualifiedName, null, jsdoc); @@ -546,14 +546,15 @@ private static String rootName(String qualifiedName) { return qualifiedName.substring(0, dotIndex); } - private RemovalType shouldRemove(String fullyQualifiedName, PotentialDeclaration decl) { + private RemovalType shouldRemove(PotentialDeclaration decl) { + String fullyQualifiedName = decl.getFullyQualifiedName(); if ("$jscomp".equals(rootName(fullyQualifiedName))) { // These are created by goog.scope processing, but clash with each other // and should not be depended on. return RemovalType.REMOVE_ALL; } - Node nameNode = decl.lhs; - Node rhs = decl.rhs; + Node nameNode = decl.getLhs(); + Node rhs = decl.getRhs(); Node jsdocNode = NodeUtil.getBestJSDocInfoNode(nameNode); JSDocInfo jsdoc = jsdocNode.getJSDocInfo(); boolean isExport = isExportLhs(nameNode); @@ -589,34 +590,16 @@ private RemovalType shouldRemove(String fullyQualifiedName, PotentialDeclaration } private boolean isAliasDefinition(PotentialDeclaration decl) { - boolean isExport = isExportLhs(decl.lhs); - if (isConstToBeInferred(decl.getJsDoc(), decl.lhs, isExport) && decl.rhs.isQualifiedName()) { - String aliasedName = decl.rhs.getQualifiedName(); + boolean isExport = isExportLhs(decl.getLhs()); + if (isConstToBeInferred(decl.getJsDoc(), decl.getLhs(), isExport) + && decl.getRhs().isQualifiedName()) { + String aliasedName = decl.getRhs().getQualifiedName(); return currentFile.isPrefixRequired(aliasedName) || currentFile.isNameDeclared(aliasedName); } return false; } } - static boolean isThisProp(Node getprop) { - return getprop.isGetProp() && getprop.getFirstChild().isThis(); - } - - static String getPrototypeNameOfThisProp(Node getprop) { - checkArgument(isThisProp(getprop)); - Node function = NodeUtil.getEnclosingFunction(getprop); - String className = getClassName(function); - checkState(className != null && !className.isEmpty()); - return className + ".prototype." + getprop.getLastChild().getString(); - } - - static String getPrototypeNameOfMethod(Node function) { - checkArgument(isClassMethod(function)); - String className = getClassName(function); - checkState(className != null && !className.isEmpty()); - return className + ".prototype." + function.getParent().getString(); - } - // TODO(blickly): Move to NodeUtil if it makes more sense there. private static boolean isDeclaration(Node nameNode) { checkArgument(nameNode.isQualifiedName()); @@ -644,47 +627,6 @@ static boolean isConstToBeInferred( && !NodeUtil.isNamespaceDecl(nameNode); } - static boolean isClassMethod(Node functionNode) { - checkArgument(functionNode.isFunction()); - Node parent = functionNode.getParent(); - if (parent.isMemberFunctionDef() - && parent.getParent().isClassMembers()) { - // ES6 class - return true; - } - // goog.defineClass - return parent.isStringKey() - && parent.getParent().isObjectLit() - && parent.getGrandparent().isCall() - && parent.getGrandparent().getFirstChild().matchesQualifiedName("goog.defineClass"); - } - - private static String getClassName(Node functionNode) { - if (isClassMethod(functionNode)) { - Node parent = functionNode.getParent(); - if (parent.isMemberFunctionDef()) { - // ES6 class - Node classNode = functionNode.getGrandparent().getParent(); - checkState(classNode.isClass()); - return NodeUtil.getName(classNode); - } - // goog.defineClass - checkState(parent.isStringKey()); - Node defineClassCall = parent.getGrandparent(); - checkState(defineClassCall.isCall()); - return NodeUtil.getBestLValue(defineClassCall).getQualifiedName(); - } - return NodeUtil.getName(functionNode); - } - - private static boolean isConstructor(Node functionNode) { - if (isClassMethod(functionNode)) { - return "constructor".equals(functionNode.getParent().getString()); - } - JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(functionNode); - return jsdoc != null && jsdoc.isConstructor(); - } - private static boolean isImportRhs(@Nullable Node rhs) { if (rhs == null || !rhs.isCall()) { return false; @@ -694,5 +636,4 @@ private static boolean isImportRhs(@Nullable Node rhs) { || callee.matchesQualifiedName("goog.forwardDeclare"); } - } diff --git a/src/com/google/javascript/jscomp/ijs/FileInfo.java b/src/com/google/javascript/jscomp/ijs/FileInfo.java index 440744ae8fb..81f81facb6c 100644 --- a/src/com/google/javascript/jscomp/ijs/FileInfo.java +++ b/src/com/google/javascript/jscomp/ijs/FileInfo.java @@ -66,7 +66,8 @@ boolean isNameDeclared(String fullyQualifiedName) { return declarations.containsKey(fullyQualifiedName); } - static boolean containsPrefix(String fullyQualifiedName, Iterable prefixNamespaces) { + private static boolean containsPrefix( + String fullyQualifiedName, Iterable prefixNamespaces) { for (String prefix : prefixNamespaces) { if (fullyQualifiedName.equals(prefix) || fullyQualifiedName.startsWith(prefix + ".")) { return true; diff --git a/src/com/google/javascript/jscomp/ijs/JsdocUtil.java b/src/com/google/javascript/jscomp/ijs/JsdocUtil.java index e06a72c7281..238b0018ba8 100644 --- a/src/com/google/javascript/jscomp/ijs/JsdocUtil.java +++ b/src/com/google/javascript/jscomp/ijs/JsdocUtil.java @@ -30,6 +30,9 @@ import com.google.javascript.rhino.Token; import javax.annotation.Nullable; +/** + * Static utility methods for dealing with inspecting and constructing JSDoc objects. + */ final class JsdocUtil { private JsdocUtil() {} diff --git a/src/com/google/javascript/jscomp/ijs/PotentialDeclaration.java b/src/com/google/javascript/jscomp/ijs/PotentialDeclaration.java index f013abd93cf..62813aa0ef7 100644 --- a/src/com/google/javascript/jscomp/ijs/PotentialDeclaration.java +++ b/src/com/google/javascript/jscomp/ijs/PotentialDeclaration.java @@ -26,15 +26,26 @@ import com.google.javascript.rhino.Node; import javax.annotation.Nullable; +/** + * Encapsulates something that could be a declaration. + * + * This includes: + * var/let/const declarations, + * function/class declarations, + * method declarations, + * assignments, + * goog.define calls, + * and even valueless property accesses (e.g. `/** @type {number} * / Foo.prototype.bar`) + */ class PotentialDeclaration { // The fully qualified name of the declaration. private final String fullyQualifiedName; // The LHS node of the declaration. - final Node lhs; + private final Node lhs; // The RHS node of the declaration, if it exists. - final @Nullable Node rhs; + private final @Nullable Node rhs; // The scope in which the declaration is defined. - final Scope scope; + private final Scope scope; private PotentialDeclaration(String fullyQualifiedName, Node lhs, Node rhs, Scope scope) { this.fullyQualifiedName = checkNotNull(fullyQualifiedName); @@ -47,15 +58,15 @@ static PotentialDeclaration fromName(Node nameNode, Scope scope) { checkArgument(nameNode.isQualifiedName(), nameNode); Node rhs = NodeUtil.getRValueOfLValue(nameNode); String name = - ConvertToTypedInterface.isThisProp(nameNode) - ? ConvertToTypedInterface.getPrototypeNameOfThisProp(nameNode) + ClassUtil.isThisProp(nameNode) + ? ClassUtil.getPrototypeNameOfThisProp(nameNode) : nameNode.getQualifiedName(); return new PotentialDeclaration(name, nameNode, rhs, scope); } static PotentialDeclaration fromMethod(Node functionNode, Scope scope) { checkArgument(functionNode.isFunction()); - String name = ConvertToTypedInterface.getPrototypeNameOfMethod(functionNode); + String name = ClassUtil.getPrototypeNameOfMethod(functionNode); return new PotentialDeclaration(name, functionNode.getParent(), functionNode, scope); } @@ -70,7 +81,7 @@ String getFullyQualifiedName() { return fullyQualifiedName; } - Node getStatement() { + private Node getStatement() { return NodeUtil.getEnclosingStatement(lhs); } @@ -88,7 +99,7 @@ void remove(AbstractCompiler compiler) { statement.removeChildren(); } - void removeStringKeyValue(Node stringKey) { + private void removeStringKeyValue(Node stringKey) { Node value = stringKey.getOnlyChild(); Node replacementValue = IR.number(0).srcrefTree(value); stringKey.replaceChild(value, replacementValue); @@ -99,23 +110,23 @@ void removeStringKeyValue(Node stringKey) { * Usually, this means removing the RHS and leaving a type annotation. */ void simplify(AbstractCompiler compiler) { - Node nameNode = lhs; + Node nameNode = getLhs(); JSDocInfo jsdoc = getJsDoc(); if (jsdoc != null && jsdoc.hasEnumParameterType()) { // Remove values from enums - if (rhs.isObjectLit() && rhs.hasChildren()) { - for (Node key : rhs.children()) { + if (getRhs().isObjectLit() && getRhs().hasChildren()) { + for (Node key : getRhs().children()) { removeStringKeyValue(key); } - compiler.reportChangeToEnclosingScope(rhs); + compiler.reportChangeToEnclosingScope(getRhs()); } return; } if (NodeUtil.isNamespaceDecl(nameNode)) { - Node objLit = rhs; - if (rhs.isOr()) { - objLit = rhs.getLastChild().detach(); - rhs.replaceWith(objLit); + Node objLit = getRhs(); + if (getRhs().isOr()) { + objLit = getRhs().getLastChild().detach(); + getRhs().replaceWith(objLit); compiler.reportChangeToEnclosingScope(nameNode); } if (objLit.hasChildren()) { @@ -131,7 +142,7 @@ void simplify(AbstractCompiler compiler) { } if (nameNode.matchesQualifiedName("exports")) { // Replace the RHS of a default goog.module export with Unknown - replaceRhsWithUnknown(rhs); + replaceRhsWithUnknown(getRhs()); compiler.reportChangeToEnclosingScope(nameNode); return; } @@ -155,4 +166,17 @@ static boolean isTypedRhs(Node rhs) { private static void replaceRhsWithUnknown(Node rhs) { rhs.replaceWith(IR.cast(IR.number(0), JsdocUtil.getQmarkTypeJSDoc()).srcrefTree(rhs)); } + + Node getLhs() { + return lhs; + } + + @Nullable + Node getRhs() { + return rhs; + } + + Scope getScope() { + return scope; + } }