All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.errorprone.bugpatterns.FieldCanBeLocal Maven / Gradle / Ivy
/*
* Copyright 2019 The Error Prone 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.errorprone.bugpatterns;
import static com.google.common.collect.Iterables.getLast;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.util.ASTHelpers.getAnnotation;
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
import static com.google.errorprone.util.ASTHelpers.getSymbol;
import static com.google.errorprone.util.ASTHelpers.shouldKeep;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ElementKind;
/** Flags fields which can be replaced with local variables. */
@BugPattern(
altNames = {"unused", "Unused"},
summary = "This field can be replaced with a local variable in the methods that use it.",
severity = SUGGESTION,
documentSuppression = false)
public final class FieldCanBeLocal extends BugChecker implements CompilationUnitTreeMatcher {
private static final ImmutableSet VALID_ON_LOCAL_VARIABLES =
Sets.immutableEnumSet(ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE);
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
Map potentialFields = new LinkedHashMap<>();
SetMultimap unconditionalAssignments =
MultimapBuilder.linkedHashKeys().linkedHashSetValues().build();
SetMultimap uses =
MultimapBuilder.linkedHashKeys().linkedHashSetValues().build();
new SuppressibleTreePathScanner(state) {
@Override
public Void visitVariable(VariableTree variableTree, Void unused) {
VarSymbol symbol = getSymbol(variableTree);
if (symbol.getKind() == ElementKind.FIELD
&& symbol.isPrivate()
&& canBeLocal(variableTree)
&& !shouldKeep(variableTree)
&& !symbol.getSimpleName().toString().startsWith("unused")) {
potentialFields.put(symbol, getCurrentPath());
}
return null;
}
private boolean canBeLocal(VariableTree variableTree) {
if (variableTree.getModifiers() == null) {
return true;
}
return variableTree.getModifiers().getAnnotations().stream()
.allMatch(this::canBeUsedOnLocalVariable);
}
private boolean canBeUsedOnLocalVariable(AnnotationTree annotationTree) {
// TODO(b/137842683): Should this (and all other places using getAnnotation with Target) be
// replaced with annotation mirror traversals?
// This is safe given we know that Target does not have Class fields.
Target target = getAnnotation(annotationTree, Target.class);
if (target == null) {
return true;
}
return !Sets.intersection(VALID_ON_LOCAL_VARIABLES, ImmutableSet.copyOf(target.value()))
.isEmpty();
}
}.scan(state.getPath(), null);
new TreePathScanner() {
boolean inMethod = false;
@Override
public Void visitClass(ClassTree classTree, Void unused) {
if (isSuppressed(classTree, state)) {
return null;
}
inMethod = false;
return super.visitClass(classTree, unused);
}
@Override
public Void visitMethod(MethodTree methodTree, Void unused) {
if (methodTree.getBody() == null) {
return null;
}
handleMethodLike(new TreePath(getCurrentPath(), methodTree.getBody()));
inMethod = true;
super.visitMethod(methodTree, null);
inMethod = false;
return null;
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree, Void unused) {
if (lambdaExpressionTree.getBody() == null) {
return null;
}
handleMethodLike(new TreePath(getCurrentPath(), lambdaExpressionTree.getBody()));
inMethod = true;
super.visitLambdaExpression(lambdaExpressionTree, null);
inMethod = false;
return null;
}
private void handleMethodLike(TreePath treePath) {
int depth = Iterables.size(getCurrentPath());
new TreePathScanner() {
Set unconditionallyAssigned = new HashSet<>();
@Override
public Void visitAssignment(AssignmentTree assignmentTree, Void unused) {
scan(assignmentTree.getExpression(), null);
Symbol symbol = getSymbol(assignmentTree.getVariable());
if (!(symbol instanceof VarSymbol)) {
return scan(assignmentTree.getVariable(), null);
}
VarSymbol varSymbol = (VarSymbol) symbol;
if (!potentialFields.containsKey(varSymbol)) {
return scan(assignmentTree.getVariable(), null);
}
// An unconditional assignment in a MethodTree is three levels deeper than the
// MethodTree itself.
if (Iterables.size(getCurrentPath()) == depth + 3) {
unconditionallyAssigned.add(varSymbol);
unconditionalAssignments.put(varSymbol, getCurrentPath());
}
return scan(assignmentTree.getVariable(), null);
}
@Override
public Void visitIdentifier(IdentifierTree identifierTree, Void unused) {
handleIdentifier(identifierTree);
return super.visitIdentifier(identifierTree, null);
}
@Override
public Void visitMemberSelect(MemberSelectTree memberSelectTree, Void unused) {
handleIdentifier(memberSelectTree);
return super.visitMemberSelect(memberSelectTree, null);
}
private void handleIdentifier(Tree tree) {
Symbol symbol = getSymbol(tree);
if (!(symbol instanceof VarSymbol)) {
return;
}
VarSymbol varSymbol = (VarSymbol) symbol;
uses.put(varSymbol, tree);
if (!unconditionallyAssigned.contains(varSymbol)) {
potentialFields.remove(varSymbol);
}
}
@Override
public Void visitNewClass(NewClassTree node, Void unused) {
unconditionallyAssigned.clear();
return super.visitNewClass(node, null);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
unconditionallyAssigned.clear();
return super.visitMethodInvocation(node, null);
}
@Override
public Void visitMethod(MethodTree methodTree, Void unused) {
return null;
}
@Override
public Void visitLambdaExpression(
LambdaExpressionTree lambdaExpressionTree, Void unused) {
return null;
}
}.scan(treePath, null);
}
@Override
public Void visitIdentifier(IdentifierTree identifierTree, Void unused) {
if (!inMethod) {
potentialFields.remove(getSymbol(identifierTree));
}
return null;
}
@Override
public Void visitMemberSelect(MemberSelectTree memberSelectTree, Void unused) {
if (!inMethod) {
potentialFields.remove(getSymbol(memberSelectTree));
}
return super.visitMemberSelect(memberSelectTree, null);
}
}.scan(state.getPath(), null);
for (Map.Entry entry : potentialFields.entrySet()) {
VarSymbol varSymbol = entry.getKey();
TreePath declarationSite = entry.getValue();
Collection assignmentLocations = unconditionalAssignments.get(varSymbol);
if (assignmentLocations.isEmpty()) {
continue;
}
SuggestedFix.Builder fix = SuggestedFix.builder();
VariableTree variableTree = (VariableTree) declarationSite.getLeaf();
String type = state.getSourceForNode(variableTree.getType());
String annotations = getAnnotationSource(state, variableTree);
fix.delete(declarationSite.getLeaf());
Set deletedTrees = new HashSet<>();
Set scopesDeclared = new HashSet<>();
for (TreePath assignmentSite : assignmentLocations) {
AssignmentTree assignmentTree = (AssignmentTree) assignmentSite.getLeaf();
Symbol rhsSymbol = getSymbol(assignmentTree.getExpression());
// If the RHS of the assignment is a variable with the same name as the field, just remove
// the assignment.
String assigneeName = getSymbol(assignmentTree.getVariable()).getSimpleName().toString();
if (rhsSymbol != null
&& assignmentTree.getExpression() instanceof IdentifierTree
&& rhsSymbol.getSimpleName().contentEquals(assigneeName)) {
deletedTrees.add(assignmentTree.getVariable());
fix.delete(assignmentSite.getParentPath().getLeaf());
} else {
Tree scope = assignmentSite.getParentPath().getParentPath().getLeaf();
if (scopesDeclared.add(scope)) {
fix.prefixWith(assignmentSite.getLeaf(), annotations + " " + type + " ");
}
}
}
// Strip "this." off any uses of the field.
for (Tree usage : uses.get(varSymbol)) {
if (deletedTrees.contains(usage)
|| usage.getKind() == Kind.IDENTIFIER
|| usage.getKind() != Kind.MEMBER_SELECT) {
continue;
}
ExpressionTree selected = ((MemberSelectTree) usage).getExpression();
if (!(selected instanceof IdentifierTree)) {
continue;
}
IdentifierTree ident = (IdentifierTree) selected;
if (ident.getName().contentEquals("this")) {
fix.replace(getStartPosition(ident), state.getEndPosition(ident) + 1, "");
}
}
state.reportMatch(describeMatch(declarationSite.getLeaf(), fix.build()));
}
return Description.NO_MATCH;
}
private static String getAnnotationSource(VisitorState state, VariableTree variableTree) {
List annotations = variableTree.getModifiers().getAnnotations();
if (annotations == null || annotations.isEmpty()) {
return "";
}
return state
.getSourceCode()
.subSequence(
getStartPosition(annotations.get(0)), state.getEndPosition(getLast(annotations)))
.toString();
}
}