Merge branch 'issue-282'

This commit is contained in:
Andreas Dangel
2017-03-18 11:05:06 +01:00
3 changed files with 69 additions and 1 deletions

View File

@ -14,10 +14,13 @@ import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
import net.sourceforge.pmd.lang.java.ast.AccessNode;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.lang.symboltable.Scope;
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
public class UnnecessaryLocalBeforeReturnRule extends AbstractJavaRule {
@ -58,13 +61,56 @@ public class UnnecessaryLocalBeforeReturnRule extends AbstractJavaRule {
if (var.indexOf('.') != -1) {
var = var.substring(0, var.indexOf('.'));
}
addViolation(data, rtn, var);
// Is the variable initialized with another member that is later used?
if (!isInitDataModifiedAfterInit(variableDeclaration, rtn)) {
addViolation(data, rtn, var);
}
}
}
}
return data;
}
private boolean isInitDataModifiedAfterInit(final VariableNameDeclaration variableDeclaration,
final ASTReturnStatement rtn) {
final ASTVariableInitializer initializer = variableDeclaration.getAccessNodeParent()
.getFirstDescendantOfType(ASTVariableInitializer.class);
if (initializer != null) {
final List<ASTName> referencedNames = initializer.findDescendantsOfType(ASTName.class);
for (final ASTName refName : referencedNames) {
// TODO : Shouldn't the scope allow us to search for a var name occurrences directly, moving up through parent scopes?
Scope scope = refName.getScope();
do {
final Map<VariableNameDeclaration, List<NameOccurrence>> declarations = scope
.getDeclarations(VariableNameDeclaration.class);
for (final Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : declarations
.entrySet()) {
if (entry.getKey().getName().equals(refName.getImage())) {
// Variable found! Check usage locations
for (final NameOccurrence occ : entry.getValue()) {
final ScopedNode location = occ.getLocation();
// Is it used after initializing our "unnecessary" local but before the return statement?
// TODO : should node define isAfter / isBefore helper methods?
if ((location.getBeginLine() > initializer.getEndLine()
|| (location.getBeginLine() == initializer.getEndLine() && location.getBeginColumn() >= initializer.getEndColumn()))
&& (location.getEndLine() < rtn.getBeginLine()
|| (location.getEndLine() == rtn.getBeginLine()
&& location.getEndColumn() <= rtn.getEndColumn()))) {
return true;
}
}
return false;
}
}
scope = scope.getParent();
} while (scope != null);
}
}
return false;
}
private boolean isNotAnnotated(VariableNameDeclaration variableDeclaration) {
AccessNode accessNodeParent = variableDeclaration.getAccessNodeParent();
return !accessNodeParent.hasDescendantOfType(ASTAnnotation.class);

View File

@ -143,6 +143,27 @@ public class UnnecessaryLocalBeforeReturnCase {
T result = (T) criteria.uniqueResult();
return result;
}
}
]]></code>
</test-code>
<test-code>
<description>#282 UnnecessaryLocalBeforeReturn false positive when cloning Maps</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class CustomerErrorCollector {
private final ConcurrentHashMap<String, String> customerErrors = new ConcurrentHashMap<>();
public void error(String customerNr, String errorMsg) {
customerErrors.put(customerNr, errorMsg);
}
public Map<String, String> getAndReset() {
final Map<String, String> copy = new HashMap<>(customerErrors);
customerErrors.clear();
return copy; // PMD complains that variable could be avoided
}
}
]]></code>
</test-code>