mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-inspections] IDEA-298718 Simplify array/list/string creation and immediate access
GitOrigin-RevId: 60df8ffa9aee53abf897e4fa6b42da4909a0e40b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
04d9657b40
commit
f19b3cd370
@@ -0,0 +1,6 @@
|
||||
// "Replace with '3'" "true-preview"
|
||||
class Test {
|
||||
void test() {
|
||||
int x = 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Replace with ''l''" "true-preview"
|
||||
class Test {
|
||||
void test() {
|
||||
char x = 'l';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with '"a"'" "true-preview"
|
||||
import java.util.List;
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
String s = "a";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Replace with '3'" "true-preview"
|
||||
class Test {
|
||||
void test() {
|
||||
int x = new int[]{1,2,3,4,5}<caret>[2];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Replace with ''l''" "true-preview"
|
||||
class Test {
|
||||
void test() {
|
||||
char x = "Hello".<caret>charAt(2);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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/";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user