mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-67323 (New inspection: StringBuffer/StringBuilder is modified but never queried)
This commit is contained in:
@@ -1878,3 +1878,6 @@ arrays.hash.code.quickfix=Replace with 'Arrays.hashCode()'
|
||||
method.can.be.variable.arity.method.display.name=Method can be variable arity method
|
||||
method.can.be.variable.arity.method.problem.descriptor=<code>#ref()</code> can be converted to variable arity method
|
||||
convert.to.variable.arity.method.quickfix=Convert to variable arity method
|
||||
mismatched.string.builder.query.update.display.name=Mismatched query and update of StringBuilder
|
||||
mismatched.string.builder.updated.problem.descriptor=Contents of {0} <code>#ref</code> are updated, but never queried #loc
|
||||
mismatched.string.builder.queried.problem.descriptor=Contents of {0} <code>#ref</code> are queried, but never updated #loc
|
||||
|
||||
@@ -552,6 +552,7 @@ public class InspectionGadgetsPlugin implements ApplicationComponent,
|
||||
}
|
||||
m_inspectionClasses.add(MismatchedArrayReadWriteInspection.class);
|
||||
m_inspectionClasses.add(MismatchedCollectionQueryUpdateInspection.class);
|
||||
m_inspectionClasses.add(MismatchedStringBuilderQueryUpdateInspection.class);
|
||||
m_inspectionClasses.add(MisspelledCompareToInspection.class);
|
||||
m_inspectionClasses.add(MisspelledHashcodeInspection.class);
|
||||
m_inspectionClasses.add(MisspelledEqualsInspection.class);
|
||||
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright 2011 Bas Leijdekkers
|
||||
*
|
||||
* 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.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MismatchedStringBuilderQueryUpdateInspection extends BaseInspection {
|
||||
|
||||
@NonNls
|
||||
private static final Set<String> returnSelfNames = new HashSet();
|
||||
static {
|
||||
returnSelfNames.add("append");
|
||||
returnSelfNames.add("appendCodePoint");
|
||||
returnSelfNames.add("delete");
|
||||
returnSelfNames.add("deleteCharAt");
|
||||
returnSelfNames.add("insert");
|
||||
returnSelfNames.add("replace");
|
||||
returnSelfNames.add("reverse");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getID(){
|
||||
return "MismatchedQueryAndUpdateOfStringBuilder";
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"mismatched.string.builder.query.update.display.name");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String buildErrorString(Object... infos) {
|
||||
final boolean updated = ((Boolean)infos[0]).booleanValue();
|
||||
final PsiType type = (PsiType)infos[1]; //"StringBuilder";
|
||||
if(updated){
|
||||
return InspectionGadgetsBundle.message(
|
||||
"mismatched.string.builder.updated.problem.descriptor",
|
||||
type.getPresentableText());
|
||||
} else{
|
||||
return InspectionGadgetsBundle.message(
|
||||
"mismatched.string.builder.queried.problem.descriptor",
|
||||
type.getPresentableText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseInspectionVisitor buildVisitor() {
|
||||
return new MismatchedQueryAndUpdateOfStringBuilderVisitor();
|
||||
}
|
||||
|
||||
private static class MismatchedQueryAndUpdateOfStringBuilderVisitor
|
||||
extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitField(PsiField field) {
|
||||
super.visitField(field);
|
||||
if (!field.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = PsiUtil.getTopLevelClass(field);
|
||||
if (!checkVariable(field, containingClass)) {
|
||||
return;
|
||||
}
|
||||
final boolean queried =
|
||||
stringBuilderContentsAreQueried(field, containingClass);
|
||||
final boolean updated =
|
||||
stringBuilderContentsAreUpdated(field, containingClass);
|
||||
if (queried == updated) {
|
||||
return;
|
||||
}
|
||||
registerFieldError(field, Boolean.valueOf(updated),
|
||||
field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(PsiLocalVariable variable) {
|
||||
super.visitLocalVariable(variable);
|
||||
final PsiCodeBlock codeBlock =
|
||||
PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
|
||||
if (!checkVariable(variable, codeBlock)) {
|
||||
return;
|
||||
}
|
||||
final boolean queried =
|
||||
stringBuilderContentsAreQueried(variable, codeBlock);
|
||||
final boolean updated =
|
||||
stringBuilderContentsAreUpdated(variable, codeBlock);
|
||||
if (queried == updated) {
|
||||
return;
|
||||
}
|
||||
registerVariableError(variable, Boolean.valueOf(updated),
|
||||
variable.getType());
|
||||
}
|
||||
|
||||
private static boolean checkVariable(PsiVariable variable,
|
||||
PsiElement context) {
|
||||
if(context == null){
|
||||
return false;
|
||||
}
|
||||
if (!TypeUtils.variableHasTypeOrSubtype(variable,
|
||||
"java.lang.AbstractStringBuilder")) {
|
||||
return false;
|
||||
}
|
||||
if(VariableAccessUtils.variableIsAssigned(variable, context)){
|
||||
return false;
|
||||
}
|
||||
if(VariableAccessUtils.variableIsAssignedFrom(variable, context)){
|
||||
return false;
|
||||
}
|
||||
if(VariableAccessUtils.variableIsReturned(variable, context)){
|
||||
return false;
|
||||
}
|
||||
return !VariableAccessUtils.variableIsUsedInArrayInitializer(
|
||||
variable, context);
|
||||
}
|
||||
|
||||
private static boolean stringBuilderContentsAreUpdated(
|
||||
PsiVariable variable, PsiElement context) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null && !isDefaultConstructorCall(initializer)) {
|
||||
return true;
|
||||
}
|
||||
return isStringBuilderUpdated(variable, context);
|
||||
}
|
||||
|
||||
private static boolean stringBuilderContentsAreQueried(
|
||||
PsiVariable variable, PsiElement context) {
|
||||
return isStringBuilderQueried(variable, context);
|
||||
}
|
||||
|
||||
private static boolean isDefaultConstructorCall(
|
||||
PsiExpression initializer) {
|
||||
if (!(initializer instanceof PsiNewExpression)) {
|
||||
return false;
|
||||
}
|
||||
final PsiNewExpression newExpression =
|
||||
(PsiNewExpression) initializer;
|
||||
final PsiJavaCodeReferenceElement classReference =
|
||||
newExpression.getClassReference();
|
||||
if (classReference == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiElement target = classReference.resolve();
|
||||
if (!(target instanceof PsiClass)) {
|
||||
return false;
|
||||
}
|
||||
final PsiClass aClass = (PsiClass) target;
|
||||
final String qualifiedName = aClass.getQualifiedName();
|
||||
if (!"java.lang.StringBuilder".equals(qualifiedName) &&
|
||||
!"java.lang.StringBuffer".equals(qualifiedName)) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpressionList argumentList =
|
||||
newExpression.getArgumentList();
|
||||
if (argumentList == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiExpression[] arguments = argumentList.getExpressions();
|
||||
if (arguments.length == 0) {
|
||||
return true;
|
||||
}
|
||||
final PsiExpression argument = arguments[0];
|
||||
final PsiType argumentType = argument.getType();
|
||||
return PsiType.INT.equals(argumentType);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isStringBuilderUpdated(PsiVariable variable,
|
||||
PsiElement context) {
|
||||
final StringBuilderUpdateCalledVisitor visitor =
|
||||
new StringBuilderUpdateCalledVisitor(variable);
|
||||
context.accept(visitor);
|
||||
return visitor.isUpdated();
|
||||
}
|
||||
|
||||
private static class StringBuilderUpdateCalledVisitor
|
||||
extends JavaRecursiveElementVisitor {
|
||||
|
||||
@NonNls
|
||||
private static final Set<String> updateNames = new HashSet();
|
||||
static {
|
||||
updateNames.add("append");
|
||||
updateNames.add("appendCodePoint");
|
||||
updateNames.add("delete");
|
||||
updateNames.add("delete");
|
||||
updateNames.add("deleteCharAt");
|
||||
updateNames.add("insert");
|
||||
updateNames.add("replace");
|
||||
updateNames.add("setCharAt");
|
||||
}
|
||||
|
||||
private final PsiVariable variable;
|
||||
boolean updated = false;
|
||||
|
||||
public StringBuilderUpdateCalledVisitor(PsiVariable variable) {
|
||||
this.variable = variable;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(
|
||||
PsiMethodCallExpression expression) {
|
||||
super.visitMethodCallExpression(expression);
|
||||
if (updated) {
|
||||
return;
|
||||
}
|
||||
super.visitMethodCallExpression(expression);
|
||||
final PsiReferenceExpression methodExpression =
|
||||
expression.getMethodExpression();
|
||||
final String name = methodExpression.getReferenceName();
|
||||
if (!updateNames.contains(name)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpression qualifierExpression =
|
||||
methodExpression.getQualifierExpression();
|
||||
if (hasReferenceToVariable(variable, qualifierExpression)) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isStringBuilderQueried(PsiVariable variable,
|
||||
PsiElement context) {
|
||||
final StringBuilderQueryCalledVisitor visitor =
|
||||
new StringBuilderQueryCalledVisitor(variable);
|
||||
context.accept(visitor);
|
||||
return visitor.isQueried();
|
||||
}
|
||||
|
||||
private static class StringBuilderQueryCalledVisitor
|
||||
extends JavaRecursiveElementVisitor {
|
||||
|
||||
@NonNls
|
||||
private static final Set<String> queryNames = new HashSet();
|
||||
static {
|
||||
queryNames.add("toString");
|
||||
queryNames.add("indexOf");
|
||||
queryNames.add("lastIndexOf");
|
||||
queryNames.add("capacity");
|
||||
queryNames.add("charAt");
|
||||
queryNames.add("codePointAt");
|
||||
queryNames.add("codePointBefore");
|
||||
queryNames.add("codePointCount");
|
||||
queryNames.add("equals");
|
||||
queryNames.add("getChars");
|
||||
queryNames.add("hashCode");
|
||||
queryNames.add("length");
|
||||
queryNames.add("offsetByCodePoints");
|
||||
queryNames.add("subSequence");
|
||||
queryNames.add("substring");
|
||||
}
|
||||
|
||||
private final PsiVariable variable;
|
||||
private boolean queried = false;
|
||||
|
||||
private StringBuilderQueryCalledVisitor(PsiVariable variable) {
|
||||
this.variable = variable;
|
||||
}
|
||||
|
||||
public boolean isQueried() {
|
||||
return queried;
|
||||
}
|
||||
|
||||
@Override public void visitElement(@NotNull PsiElement element){
|
||||
if (queried) {
|
||||
return;
|
||||
}
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(
|
||||
PsiMethodCallExpression expression) {
|
||||
if (queried) {
|
||||
return;
|
||||
}
|
||||
super.visitMethodCallExpression(expression);
|
||||
final PsiReferenceExpression methodExpression =
|
||||
expression.getMethodExpression();
|
||||
final String name = methodExpression.getReferenceName();
|
||||
if (!queryNames.contains(name)) {
|
||||
return;
|
||||
}
|
||||
final PsiExpression qualifierExpression =
|
||||
methodExpression.getQualifierExpression();
|
||||
if (hasReferenceToVariable(variable, qualifierExpression)) {
|
||||
queried = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasReferenceToVariable(PsiVariable variable,
|
||||
PsiElement element) {
|
||||
if (element instanceof PsiReferenceExpression) {
|
||||
final PsiReferenceExpression referenceExpression =
|
||||
(PsiReferenceExpression) element;
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
if (variable.equals(target)) {
|
||||
return true;
|
||||
}
|
||||
} else if (element instanceof PsiParenthesizedExpression) {
|
||||
final PsiParenthesizedExpression parenthesizedExpression =
|
||||
(PsiParenthesizedExpression) element;
|
||||
final PsiExpression expression =
|
||||
parenthesizedExpression.getExpression();
|
||||
return hasReferenceToVariable(variable, expression);
|
||||
} else if (element instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCallExpression =
|
||||
(PsiMethodCallExpression) element;
|
||||
final PsiReferenceExpression methodExpression =
|
||||
methodCallExpression.getMethodExpression();
|
||||
final String name = methodExpression.getReferenceName();
|
||||
if (returnSelfNames.contains(name)) {
|
||||
return hasReferenceToVariable(variable,
|
||||
methodExpression.getQualifierExpression());
|
||||
}
|
||||
} else if (element instanceof PsiConditionalExpression) {
|
||||
final PsiConditionalExpression conditionalExpression =
|
||||
(PsiConditionalExpression) element;
|
||||
final PsiExpression thenExpression =
|
||||
conditionalExpression.getThenExpression();
|
||||
if (hasReferenceToVariable(variable, thenExpression)) {
|
||||
return true;
|
||||
}
|
||||
final PsiExpression elseExpression =
|
||||
conditionalExpression.getElseExpression();
|
||||
return hasReferenceToVariable(variable, elseExpression);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports any StringBuilder or StringBuffer fields or variables whose contents are read but not written,
|
||||
or written but not read. Such mismatched reads and writes are pointless, and probably indicate
|
||||
dead, incomplete or erroneous code.
|
||||
<p>
|
||||
<small>New in 10.5, Powered by InspectionGadgets</small>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user