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 2018 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.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.util.ASTHelpers.isConsideredFinal;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
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.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.util.Name;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A checker that suggests deduplicating literals with existing constant variables.
*
* @author [email protected] (Liam Miller-Cushon)
*/
@BugPattern(
summary =
"This expression was previously declared as a constant;"
+ " consider replacing this occurrence.",
severity = ERROR)
public class DeduplicateConstants extends BugChecker implements CompilationUnitTreeMatcher {
/** A lexical scope for constant declarations. */
static class Scope {
/** A map from string literals to constant declarations. */
private final HashMap values = new HashMap<>();
/** Declarations that are hidden in the current scope. */
private final Set hidden = new HashSet<>();
/** The parent of the current scope. */
private final Scope parent;
Scope(Scope parent) {
this.parent = parent;
}
/** Enters a new sub-scope. */
Scope enter() {
return new Scope(this);
}
/** Returns an in-scope constant variable with the given value. */
@Nullable
public VarSymbol get(String value) {
VarSymbol sym = getInternal(value);
if (sym == null) {
return null;
}
if (hidden.contains(sym.getSimpleName())) {
return null;
}
return sym;
}
@Nullable
private VarSymbol getInternal(String value) {
VarSymbol sym = values.get(value);
if (sym != null) {
return sym;
}
if (parent != null) {
sym = parent.get(value);
if (sym != null) {
return sym;
}
}
return null;
}
/** Adds a constant declaration with the given value to the current scope. */
public void put(String value, VarSymbol sym) {
hidden.remove(sym.getSimpleName());
values.put(value, sym);
}
/**
* Records a non-constant variable declaration that hides any previously declared constants of
* the same name.
*/
public void remove(VarSymbol sym) {
hidden.add(sym.getSimpleName());
}
}
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
Table fixes = HashBasedTable.create();
new TreeScanner() {
@Override
public Void visitBlock(BlockTree tree, Scope scope) {
// enter a new block scope (includes block trees for method and class bodies)
return super.visitBlock(tree, scope.enter());
}
@Override
public Void visitVariable(VariableTree tree, Scope scope) {
// record that this variables hides previous declarations before entering its initializer
scope.remove(ASTHelpers.getSymbol(tree));
scan(tree.getInitializer(), scope);
saveConstValue(tree, scope);
return null;
}
@Override
public Void visitLiteral(LiteralTree tree, Scope scope) {
replaceLiteral(tree, scope, state);
return super.visitLiteral(tree, scope);
}
private void replaceLiteral(LiteralTree tree, Scope scope, VisitorState state) {
Object value = ASTHelpers.constValue(tree);
if (value == null) {
return;
}
VarSymbol sym = scope.get(state.getSourceForNode(tree));
if (sym == null) {
return;
}
SuggestedFix fix = SuggestedFix.replace(tree, sym.getSimpleName().toString());
fixes.put(sym, tree, fix);
}
private void saveConstValue(VariableTree tree, Scope scope) {
VarSymbol sym = ASTHelpers.getSymbol(tree);
if (!isConsideredFinal(sym)) {
return;
}
// heuristic: long string constants are generally more interesting than short ones, or
// than non-string constants (e.g. `""`, `0`, or `false`).
String constValue = ASTHelpers.constValue(tree.getInitializer(), String.class);
if (constValue == null || constValue.length() <= 1) {
return;
}
scope.put(state.getSourceForNode(tree.getInitializer()), sym);
}
}.scan(tree, new Scope(null));
for (Map.Entry> entries : fixes.rowMap().entrySet()) {
Map occurrences = entries.getValue();
if (occurrences.size() < 2) {
// heuristic: only de-duplicate when there are two or more occurrences
continue;
}
// report the finding on each occurrence, but provide a fix for all related occurrences,
// so it works better on changed-lines only
SuggestedFix fix = mergeFix(occurrences.values());
occurrences.keySet().forEach(t -> state.reportMatch(describeMatch(t, fix)));
}
return Description.NO_MATCH;
}
private static SuggestedFix mergeFix(Collection fixes) {
SuggestedFix.Builder fix = SuggestedFix.builder();
fixes.forEach(fix::merge);
return fix.build();
}
}