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.
/*
* Copyright 2021 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.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.util.ASTHelpers.canBeRemoved;
import static com.google.errorprone.util.ASTHelpers.getSymbol;
import static com.google.errorprone.util.ASTHelpers.getType;
import static com.google.errorprone.util.ASTHelpers.isConsideredFinal;
import static com.google.errorprone.util.ASTHelpers.isSameType;
import com.google.auto.value.AutoValue;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewArrayTree;
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 com.sun.tools.javac.code.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.lang.model.element.ElementKind;
/**
* Helper for strongly typing fields. Fields that are declared as a weaker type but only used when
* wrapped in a stronger type will be refactored to the stronger type.
*
* @see com.google.errorprone.bugpatterns.time.StronglyTypeTime
*/
@AutoValue
public abstract class StronglyType {
abstract Function renameFunction();
abstract ImmutableSet primitiveTypesToReplace();
abstract Matcher factoryMatcher();
abstract BugChecker bugChecker();
public static Builder forCheck(BugChecker bugChecker) {
return new AutoValue_StronglyType.Builder()
.setBugChecker(bugChecker)
.setRenameFunction(name -> name);
}
/** Builder for {@link StronglyType} */
@AutoValue.Builder
public abstract static class Builder {
/**
* Set a mapping function that maps from the original name to a new name more befitting the
* strong type.
*/
public abstract Builder setRenameFunction(Function renameFn);
/** Set the matcher used to check if an expression is a factory creating a stronger type. */
public abstract Builder setFactoryMatcher(Matcher matcher);
abstract Builder setBugChecker(BugChecker bugChecker);
abstract ImmutableSet.Builder primitiveTypesToReplaceBuilder();
/** Add a type that can be replaced with a stronger type. */
public final Builder addType(Type type) {
primitiveTypesToReplaceBuilder().add(type);
return this;
}
public abstract StronglyType build();
}
public final Description match(CompilationUnitTree tree, VisitorState state) {
Map fields =
new HashMap<>(findPathToPotentialFields(state, primitiveTypesToReplace()));
SetMultimap usages = HashMultimap.create();
new TreePathScanner() {
@Override
public Void visitMemberSelect(MemberSelectTree memberSelectTree, Void unused) {
handle(memberSelectTree);
return super.visitMemberSelect(memberSelectTree, null);
}
@Override
public Void visitIdentifier(IdentifierTree identifierTree, Void unused) {
handle(identifierTree);
return null;
}
private void handle(Tree tree) {
Symbol symbol = getSymbol(tree);
if (!fields.containsKey(symbol)) {
return;
}
Tree parent = getCurrentPath().getParentPath().getLeaf();
if (!(parent instanceof ExpressionTree)
|| !factoryMatcher().matches((ExpressionTree) parent, state)) {
fields.remove(symbol);
return;
}
usages.put((VarSymbol) symbol, (ExpressionTree) parent);
}
}.scan(tree, null);
for (Map.Entry entry : fields.entrySet()) {
state.reportMatch(match(entry.getValue(), entry.getKey(), usages.get(entry.getKey()), state));
}
return NO_MATCH;
}
private Description match(
TreePath variableTreePath,
VarSymbol replacedSymbol,
Set invocationTrees,
VisitorState state) {
if (invocationTrees.stream().map(ASTHelpers::getSymbol).distinct().count() != 1) {
return NO_MATCH;
}
VariableTree variableTree = (VariableTree) variableTreePath.getLeaf();
ExpressionTree factory = invocationTrees.iterator().next();
String newName = renameFunction().apply(variableTree.getName().toString());
SuggestedFix.Builder fix = SuggestedFix.builder();
Type targetType = getType(factory);
String typeName = SuggestedFixes.qualifyType(state.withPath(variableTreePath), fix, targetType);
fix.replace(
variableTree,
String.format(
"%s %s %s = %s(%s);",
state.getSourceForNode(variableTree.getModifiers()),
typeName,
newName,
getMethodSelectOrNewClass(factory, state),
getWeakTypeIntitializerCode(variableTree, state)));
for (ExpressionTree expressionTree : invocationTrees) {
fix.replace(expressionTree, newName);
}
Type replacedType = state.getTypes().unboxedTypeOrType(replacedSymbol.type);
return bugChecker()
.buildDescription(variableTree)
.setMessage(
String.format(
"This %s is only used to construct %s instances. It would be"
+ " clearer to strongly type the field instead.",
buildStringForType(replacedType, state), targetType.tsym.getSimpleName()))
.addFix(fix.build())
.build();
}
private static String buildStringForType(Type type, VisitorState state) {
return SuggestedFixes.prettyType(type, state);
}
/**
* Get the source code for the initializer. If the initializer is an array literal without a type,
* prefix with new .
*/
private static String getWeakTypeIntitializerCode(VariableTree weakType, VisitorState state) {
// If the new array type is missing, we need to add it.
String prefix =
(weakType.getInitializer().getKind() == Kind.NEW_ARRAY
&& ((NewArrayTree) weakType.getInitializer()).getType() == null)
? String.format("new %s ", state.getSourceForNode(weakType.getType()))
: "";
return prefix + state.getSourceForNode(weakType.getInitializer());
}
private static String getMethodSelectOrNewClass(ExpressionTree tree, VisitorState state) {
switch (tree.getKind()) {
case METHOD_INVOCATION:
return state.getSourceForNode(((MethodInvocationTree) tree).getMethodSelect());
case NEW_CLASS:
return "new " + state.getSourceForNode(((NewClassTree) tree).getIdentifier());
default:
throw new AssertionError();
}
}
/** Finds the path to potential fields that we might want to strongly type. */
// TODO(b/147006492): Consider extracting a helper to find all fields that match a Matcher.
private ImmutableMap findPathToPotentialFields(
VisitorState state, Set potentialTypes) {
ImmutableMap.Builder fields = ImmutableMap.builder();
bugChecker().new SuppressibleTreePathScanner() {
@Override
public Void visitVariable(VariableTree variableTree, Void unused) {
VarSymbol symbol = getSymbol(variableTree);
Type type = state.getTypes().unboxedTypeOrType(symbol.type);
if (symbol.getKind() == ElementKind.FIELD
&& canBeRemoved(symbol)
&& isConsideredFinal(symbol)
&& variableTree.getInitializer() != null
&& potentialTypes.stream()
.anyMatch(potentialType -> isSameType(type, potentialType, state))
&& !bugChecker().isSuppressed(variableTree)) {
fields.put(symbol, getCurrentPath());
}
return super.visitVariable(variableTree, null);
}
}.scan(state.getPath().getCompilationUnit(), null);
return fields.buildOrThrow();
}
}