[java-inspections] IDEA-298718 Simplify array/list/string creation and immediate access

GitOrigin-RevId: 60df8ffa9aee53abf897e4fa6b42da4909a0e40b
This commit is contained in:
Tagir Valeev
2022-07-27 14:38:39 +02:00
committed by intellij-monorepo-bot
parent 04d9657b40
commit f19b3cd370
13 changed files with 259 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
// "Replace with '3'" "true-preview"
class Test {
void test() {
int x = 3;
}
}

View File

@@ -0,0 +1,6 @@
// "Replace with ''l''" "true-preview"
class Test {
void test() {
char x = 'l';
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with '"a"'" "true-preview"
import java.util.List;
class Test {
void test() {
String s = "a";
}
}

View File

@@ -0,0 +1,6 @@
// "Replace with '3'" "true-preview"
class Test {
void test() {
int x = new int[]{1,2,3,4,5}<caret>[2];
}
}

View File

@@ -0,0 +1,6 @@
// "Replace with ''l''" "true-preview"
class Test {
void test() {
char x = "Hello".<caret>charAt(2);
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with '"a"'" "true-preview"
import java.util.List;
class Test {
void test() {
String s = List.of("a", "b", "c").<caret>get(0);
}
}

View File

@@ -0,0 +1,21 @@
import java.util.List;
class Demo {
private static final int CONST = 5;
public static void main(String[] args) {
if (<warning descr="Only one array element is used">new boolean[]{true, false, false, true}[3]</warning>) {
}
System.out.println((new boolean[10])[3]);
System.out.println(<warning descr="Only one list element is used">(List.of(1,2,3)).get(1)</warning>);
System.out.println(<warning descr="Only one list element is used">(List.of(1,2,3,4,5,6,7,8,9,10,11)).get(10)</warning>);
Integer[] integers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
System.out.println((List.of(integers)).get(0));
System.out.println(<warning descr="Only one list element is used">(List.of(integers[0])).get(0)</warning>);
System.out.println(<warning descr="Only one string character is used">"Hello World".charAt((10))</warning>);
System.out.println("Hello World".charAt(11));
System.out.println("Hello World".charAt(-1));
System.out.println("Hello World".charAt(CONST));
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2017 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.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.ig.redundancy.OnlyOneElementUsedInspection;
import com.siyeh.ig.redundancy.RedundantStringOperationInspection;
import org.jetbrains.annotations.NotNull;
public class OnlyOneElementUsedInspectionFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new OnlyOneElementUsedInspection()};
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/onlyOneElementUsed";
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_11;
}
}

View File

@@ -0,0 +1,32 @@
// Copyright 2000-2021 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.java.codeInsight.daemon.quickFix;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.siyeh.ig.LightJavaInspectionTestCase;
import com.siyeh.ig.redundancy.OnlyOneElementUsedInspection;
import com.siyeh.ig.redundancy.RedundantStringOperationInspection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OnlyOneElementUsedInspectionTest extends LightJavaInspectionTestCase {
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new OnlyOneElementUsedInspection();
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_11;
}
public void testOnlyOneElementUsed() {doTest();}
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/onlyOneElementUsed/";
}
}

View File

@@ -2441,3 +2441,8 @@ negative.int.constant.in.long.context.display.name=Negative int hexadecimal cons
negative.int.constant.in.long.context.fix.add.suffix=Add 'L' suffix (changes semantics)
negative.int.constant.in.long.context.fix.convert=Convert to long constant (preserves semantics)
add.to.ignore.annotation.preview=Configures inspection to ignore elements with the given annotation
inspection.only.one.element.used.display.name=Only one element is used
inspection.only.one.element.used.array=Only one array element is used
inspection.only.one.element.used.string=Only one string character is used
inspection.only.one.element.used.list=Only one list element is used
inspection.only.one.element.used.fix.family=Replace with an accessed element

View File

@@ -2653,6 +2653,9 @@
<localInspection groupPath="Java" language="JAVA" shortName="StringOperationCanBeSimplified" bundle="messages.InspectionGadgetsBundle" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
key="inspection.redundant.string.operation.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantStringOperationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="OnlyOneElementUsed" bundle="messages.InspectionGadgetsBundle"
key="inspection.only.one.element.used.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.OnlyOneElementUsedInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="ReplaceOnLiteralHasNoEffect" bundle="messages.InspectionGadgetsBundle" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
key="inspection.replace.on.literal.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" implementationClass="com.siyeh.ig.redundancy.ReplaceOnLiteralHasNoEffectInspection"/>

View File

@@ -0,0 +1,114 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.redundancy;
import com.intellij.codeInspection.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.MethodCallUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
public class OnlyOneElementUsedInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool {
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new RedundantElementAccessVisitor(holder);
}
private static class RedundantElementAccessVisitor extends JavaElementVisitor {
private static final CallMatcher LIST_GET = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "get")
.parameterTypes(PsiKeyword.INT);
private static final CallMatcher LIST_CONSTRUCTOR = CallMatcher.anyOf(
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_LIST, "of"),
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList")
);
private static final CallMatcher STRING_CHAR_AT = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "charAt")
.parameterTypes(PsiKeyword.INT);
private final @NotNull ProblemsHolder myHolder;
private RedundantElementAccessVisitor(@NotNull ProblemsHolder holder) { myHolder = holder; }
@Override
public void visitArrayAccessExpression(@NotNull PsiArrayAccessExpression expression) {
PsiNewExpression arrayExpression =
tryCast(PsiUtil.skipParenthesizedExprDown(expression.getArrayExpression()), PsiNewExpression.class);
if (arrayExpression == null || !arrayExpression.isArrayCreation()) return;
PsiArrayInitializerExpression initializer = arrayExpression.getArrayInitializer();
if (initializer == null) return;
Integer value = getIndex(expression.getIndexExpression());
if (value == null) return;
PsiExpression[] initializers = initializer.getInitializers();
if (value >= initializers.length) return;
myHolder.registerProblem(expression, InspectionGadgetsBundle.message("inspection.only.one.element.used.array"),
new InlineSingleElementAccessFix(initializers[value].getText()));
}
@Nullable
private static Integer getIndex(PsiExpression indexExpression) {
PsiLiteralExpression literal =
tryCast(PsiUtil.skipParenthesizedExprDown(indexExpression), PsiLiteralExpression.class);
if (literal == null) return null;
Integer value = tryCast(literal.getValue(), Integer.class);
if (value == null || value < 0) return null;
return value;
}
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
if (STRING_CHAR_AT.test(call)) {
PsiLiteralExpression qualifier =
tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiLiteralExpression.class);
if (qualifier == null) return;
Integer index = getIndex(call.getArgumentList().getExpressions()[0]);
if (index == null) return;
String value = tryCast(qualifier.getValue(), String.class);
if (value == null || index >= value.length()) return;
myHolder.registerProblem(call, InspectionGadgetsBundle.message("inspection.only.one.element.used.string"),
new InlineSingleElementAccessFix("'" + StringUtil.escapeCharCharacters(String.valueOf(value.charAt(index)))+"'"));
}
else if (LIST_GET.test(call)) {
PsiMethodCallExpression qualifier =
tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiMethodCallExpression.class);
if (!LIST_CONSTRUCTOR.test(qualifier)) return;
PsiMethod method = qualifier.resolveMethod();
if (method == null) return;
if (method.isVarArgs() && !MethodCallUtils.isVarArgCall(qualifier)) return;
Integer index = getIndex(call.getArgumentList().getExpressions()[0]);
if (index == null) return;
PsiExpression[] expressions = qualifier.getArgumentList().getExpressions();
if (index >= expressions.length) return;
myHolder.registerProblem(call, InspectionGadgetsBundle.message("inspection.only.one.element.used.list"),
new InlineSingleElementAccessFix(expressions[index].getText()));
}
}
}
private static class InlineSingleElementAccessFix implements LocalQuickFix {
final String myInitializer;
private InlineSingleElementAccessFix(String initializer) {
myInitializer = initializer;
}
@Override
public @NotNull String getName() {
return CommonQuickFixBundle.message("fix.replace.with.x", myInitializer);
}
@Override
public @NotNull String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.only.one.element.used.fix.family");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
new CommentTracker().replaceAndRestoreComments(descriptor.getStartElement(), myInitializer);
}
}
}

View File

@@ -0,0 +1,16 @@
<html>
<body>
Reports lists, arrays, and strings where exactly one element is queried right upon the creation.
Such expressions may appear after refactoring and usually could be replaced with an accessed element.
<p>Example:</p>
<pre><code>
System.out.println(new int[] {1,2,3,4,5}[2]);
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
System.out.println(3);
</code></pre>
<!-- tooltip end -->
<p><small>New in 2022.3</small></p>
</body>
</html>