ExplicitArrayFillingInspection: check array content before suggesting to remove explicit filling loop (IDEA-222533)

GitOrigin-RevId: 8e5cfd1973a0152893fac37d7d50837fc92ca53a
This commit is contained in:
Artemiy Sartakov
2019-09-16 13:33:35 +00:00
committed by intellij-monorepo-bot
parent 8b6907e608
commit 47b823ffda
13 changed files with 241 additions and 7 deletions
@@ -1,28 +1,34 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.JavaFeature;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import static com.intellij.util.ObjectUtils.tryCast;
public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(ExplicitArrayFillingInspection.class);
@@ -55,10 +61,11 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
PsiExpression rValue = assignment.getRExpression();
if (rValue == null) return;
if (!isChangedInLoop(loop, rValue)) {
Object constValue = ExpressionUtils.computeConstantExpression(rValue);
if (constValue != null && constValue.equals(PsiTypesUtil.getDefaultValue(assignment.getType()))) {
if (!ControlFlowUtils.isInLoop(statement) &&
isDefaultValueAssigned(assignment, rValue) &&
isFilledWithDefaultValues(container.getQualifier(), statement)) {
holder.registerProblem(statement, getRange(statement, ProblemHighlightType.WARNING),
QuickFixBundle.message("delete.element.fix.text"),
InspectionsBundle.message("inspection.explicit.array.filling.redundant.loop.description"),
QuickFixFactory.getInstance().createDeleteFix(statement));
return;
}
@@ -81,6 +88,90 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
.anyMatch(call -> !ClassUtils.isImmutable(call.getType()) && !ConstructionUtils.isEmptyArrayInitializer(call));
}
private boolean isDefaultValueAssigned(@NotNull PsiAssignmentExpression assignment, @NotNull PsiExpression rhs) {
Object defaultValue = PsiTypesUtil.getDefaultValue(assignment.getType());
if (ExpressionUtils.isNullLiteral(rhs) && defaultValue == null) return true;
Object constantValue = ExpressionUtils.computeConstantExpression(rhs);
return constantValue != null && constantValue.equals(defaultValue);
}
private boolean isFilledWithDefaultValues(@NotNull PsiExpression expression, @NotNull PsiForStatement forStatement) {
PsiReferenceExpression arrayRef = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiReferenceExpression.class);
if (arrayRef == null) return false;
PsiVariable arrayVar = tryCast(arrayRef.resolve(), PsiVariable.class);
if (arrayVar == null) return false;
PsiCodeBlock block = tryCast(PsiUtil.getVariableCodeBlock(arrayVar, null), PsiCodeBlock.class);
if (block == null) return false;
Set<PsiStatement> defs = getDefsStatements(DefUseUtil.getDefs(block, arrayVar, arrayRef));
if (defs == null) return false;
return !isUsedBetween(forStatement, defs, arrayVar, block);
}
private boolean isUsedBetween(@NotNull PsiElement ref, @NotNull Set<PsiStatement> defs,
@NotNull PsiVariable arrayVar, @NotNull PsiCodeBlock block) {
Ref<Boolean> isUsed = Ref.create(false);
block.accept(new JavaRecursiveElementWalkingVisitor() {
boolean inContext;
@Override
public void visitStatement(PsiStatement statement) {
if (defs.contains(statement)) {
inContext = true;
}
else {
if (statement == ref) {
inContext = false;
stopWalking();
}
super.visitStatement(statement);
}
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
if (inContext && reference.isReferenceTo(arrayVar)) {
isUsed.set(true);
stopWalking();
}
super.visitReferenceElement(reference);
}
});
return isUsed.get();
}
@Nullable
private Set<PsiStatement> getDefsStatements(@NotNull PsiElement[] defs) {
Set<PsiStatement> statements = new HashSet<>();
for (PsiElement def : defs) {
PsiVariable variable = tryCast(def, PsiVariable.class);
if (variable != null) {
PsiExpression initializer = variable.getInitializer();
if (initializer == null || !isNewArrayCreation(initializer)) return null;
PsiDeclarationStatement declaration = PsiTreeUtil.getParentOfType(initializer, PsiDeclarationStatement.class);
if (declaration == null) return null;
statements.add(declaration);
continue;
}
PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(def, PsiAssignmentExpression.class);
if (assignment != null) {
if (!isNewArrayCreation(assignment.getRExpression())) return null;
PsiExpressionStatement expressionStatement = PsiTreeUtil.getParentOfType(assignment, PsiExpressionStatement.class);
if (expressionStatement == null) return null;
statements.add(expressionStatement);
continue;
}
return null;
}
return statements;
}
private boolean isNewArrayCreation(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
return expression == null || expression instanceof PsiNewExpression;
}
private void registerProblem(@NotNull PsiForStatement statement, boolean isSetAll) {
String message = InspectionsBundle.message("inspection.explicit.array.filling.description", isSetAll ? "setAll" : "fill");
ReplaceWithArraysCallFix fix = new ReplaceWithArraysCallFix(!isSetAll);
@@ -138,7 +229,7 @@ public class ExplicitArrayFillingInspection extends AbstractBaseJavaLocalInspect
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiForStatement statement = ObjectUtils.tryCast(descriptor.getStartElement(), PsiForStatement.class);
PsiForStatement statement = tryCast(descriptor.getStartElement(), PsiForStatement.class);
if (statement == null) return;
CountingLoop loop = CountingLoop.from(statement);
if (loop == null) return;
@@ -1,6 +1,6 @@
<html>
<body>
Reports loops which could be replaced with the <code>Arrays.setAll()</code> or <code>Arrays.fill()</code> calls.
Reports loops which could be replaced with <code>Arrays.setAll()</code> or <code>Arrays.fill()</code> calls.
This inspection suggests replacing loops with <code>Arrays.setAll()</code> if the language level of the project or module is 8 or higher.
Replacing loops with <code>Arrays.fill()</code> is possible with any language level.
<p>For example:</p>
@@ -0,0 +1,18 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
import java.util.Arrays;
public class Test {
public static String[] init(int n) {
String[] lines = new String[n];
String[] copy = getCopy(lines);
copy[0] = "foo";
Arrays.fill(lines, null);
return lines;
}
private static String[] getCopy(String[] original) {
return original;
}
}
@@ -0,0 +1,14 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
import java.util.Arrays;
public class Test {
public static int[] init(int[] arr, boolean b) {
if (b) {
arr = new int[10];
}
Arrays.fill(arr, 0);
return arr;
}
}
@@ -0,0 +1,17 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
import java.util.Arrays;
public class Test {
public static int[] init(int n) {
int[] data = new int[n];
int i = 0;
while (i < 3) {
Arrays.fill(data, 0);
if (i < 2) data[n - 1] = 6;
i++;
}
return data;
}
}
@@ -0,0 +1,9 @@
// "Delete element" "true"
public class Test {
public static int[] init(int[] arr) {
arr = new int[10];
return arr;
}
}
@@ -0,0 +1,10 @@
// "Delete element" "true"
public class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
data[n - 1] = 6;
return data;
}
}
@@ -0,0 +1,18 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
public class Test {
public static String[] init(int n) {
String[] lines = new String[n];
String[] copy = getCopy(lines);
copy[0] = "foo";
for (<caret>int i = 0; i < lines.length; i++) {
lines[i] = null;
}
return lines;
}
private static String[] getCopy(String[] original) {
return original;
}
}
@@ -0,0 +1,14 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
public class Test {
public static int[] init(int[] arr, boolean b) {
if (b) {
arr = new int[10];
}
for (<caret>int i = 0; i < arr.length; i++) {
arr[i] = 0;
}
return arr;
}
}
@@ -0,0 +1,17 @@
// "Replace loop with 'Arrays.fill()' method call" "true"
public class Test {
public static int[] init(int n) {
int[] data = new int[n];
int i = 0;
while (i < 3) {
for (<caret>int j = 0; j < data.length; j++) {
data[j] = 0;
}
if (i < 2) data[n - 1] = 6;
i++;
}
return data;
}
}
@@ -0,0 +1,12 @@
// "Delete element" "true"
public class Test {
public static int[] init(int[] arr) {
arr = new int[10];
for (<caret>int i = 0; i < arr.length; i++) {
arr[i] = 0;
}
return arr;
}
}
@@ -0,0 +1,13 @@
// "Delete element" "true"
public class Test {
public static int[] init(int n, boolean b) {
int[] data = new int[n];
for (<caret>int j = 0; j < data.length; j++) {
data[j] = 0;
}
data[n - 1] = 6;
return data;
}
}
@@ -842,6 +842,7 @@ inspection.replace.with.bulk.fix.name=Replace iteration with bulk ''{0}'' call
inspection.replace.with.bulk.fix.family.name=Replace with bulk method call
inspection.replace.with.bulk.wrap.arrays=Use Arrays.asList() to wrap arrays
inspection.explicit.array.filling.fix.family.name=Replace loop with ''Arrays.{0}()'' method call
inspection.explicit.array.filling.redundant.loop.description=Redundant initialization of a newly created array
inspection.explicit.array.filling.description=Can be replaced with single ''Arrays.{0}()'' method call
inspection.explicit.array.filling.suggest.set.all=Suggest 'Arrays.setAll()'
inspection.explicit.array.filling.no.suggestion.for.set.all=Do not suggest to use 'Arrays.setAll()'