[java-inspection] IDEA-324051 Inspections to migrate to sequenced collections (Java 21)

GitOrigin-RevId: bd008786ab983b8e8f3966d182da3be1ca9dcb55
This commit is contained in:
Tagir Valeev
2023-08-18 19:22:31 +02:00
committed by intellij-monorepo-bot
parent 0f07763950
commit 79b66e1879
8 changed files with 222 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.IndexedContainer;
import com.siyeh.ig.psiutils.MethodCallUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public final class SequencedCollectionMethodCanBeUsedInspection extends AbstractBaseJavaLocalInspectionTool {
private static final CallMatcher LIST_GET_REMOVE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "get", "remove")
.parameterTypes("int");
private static final CallMatcher ITERATOR_NEXT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "next")
.parameterCount(0);
private static final CallMatcher COLLECTION_ITERATOR = CallMatcher.instanceCall("java.util.Collection", "iterator")
.parameterCount(0);
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (PsiUtil.getLanguageLevel(holder.getFile()).isLessThan(LanguageLevel.JDK_21)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
if (LIST_GET_REMOVE.matches(call)) {
processListGetRemove(call);
}
if (ITERATOR_NEXT.matches(call)) {
processIteratorNext(call);
}
}
private void processIteratorNext(@NotNull PsiMethodCallExpression call) {
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
if (!COLLECTION_ITERATOR.matches(qualifierCall)) return;
PsiExpression collection = qualifierCall.getMethodExpression().getQualifierExpression();
if (collection == null || collection instanceof PsiThisExpression) return;
if (!InheritanceUtil.isInheritor(collection.getType(), "java.util.SequencedCollection")) return;
String name = "getFirst";
report(call, name);
}
private void report(@NotNull PsiMethodCallExpression call, String name) {
holder.registerProblem(
Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()),
JavaBundle.message("inspection.stream.api.migration.can.be.replaced.with.call", name + "()"),
new ReplaceWithCallFix(name));
}
private void processListGetRemove(@NotNull PsiMethodCallExpression call) {
PsiReferenceExpression methodExpr = call.getMethodExpression();
String name = methodExpr.getReferenceName();
PsiExpression list = PsiUtil.skipParenthesizedExprDown(methodExpr.getQualifierExpression());
if (list == null || list instanceof PsiThisExpression) return;
PsiExpression arg = PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]);
if (ExpressionUtils.isZero(arg)) {
report(call, name + "First");
}
if (arg instanceof PsiBinaryExpression binOp && binOp.getOperationTokenType().equals(JavaTokenType.MINUS) &&
ExpressionUtils.isOne(binOp.getROperand())) {
IndexedContainer container = IndexedContainer.fromLengthExpression(binOp.getLOperand());
if (container != null && container.isQualifierEquivalent(list)) {
report(call, name + "Last");
}
}
}
};
}
private static class ReplaceWithCallFix extends PsiUpdateModCommandQuickFix {
private final String myName;
private ReplaceWithCallFix(String methodName) {
myName = methodName;
}
@Override
public @NotNull String getName() {
return CommonQuickFixBundle.message("fix.replace.with.x", myName + "()");
}
@Override
public @NotNull String getFamilyName() {
return JavaBundle.message("intention.sequenced.collection.can.be.used.fix.name");
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (call == null) return;
CommentTracker ct = new CommentTracker();
ct.delete(call.getArgumentList().getExpressions());
ExpressionUtils.bindCallTo(call, myName);
PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call);
if (qualifier != null && InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_ITERATOR)) {
PsiExpression collection = qualifier.getMethodExpression().getQualifierExpression();
if (collection != null) {
ct.replace(qualifier, collection);
}
}
ct.insertCommentsBefore(call);
}
}
}

View File

@@ -1567,6 +1567,11 @@
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection"
key="inspection.simplify.stream.api.call.chains.display.name" bundle="messages.JavaBundle"/>
<localInspection groupPathKey="group.path.names.java.language.level.specific.issues.and.migration.aids" language="JAVA"
shortName="SequencedCollectionMethodCanBeUsed" enabledByDefault="true" level="WARNING"
groupBundle="messages.InspectionsBundle" groupKey="group.names.language.level.specific.issues.and.migration.aids21"
bundle="messages.JavaBundle" key="intention.sequenced.collection.can.be.used.display.name"
implementationClass="com.intellij.codeInspection.SequencedCollectionMethodCanBeUsedInspection"/>/>
<localInspection groupPath="Java" language="JAVA" shortName="ObviousNullCheck"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.verbose.or.redundant.code.constructs" enabledByDefault="true" level="WARNING"

View File

@@ -0,0 +1,13 @@
<html>
<body>
Reports collection API method calls that can be simplified using Java 21 <code>SequencedCollection</code> methods.
<p>The following conversions are supported:</p>
<ul>
<li><code>list.get(0)</code> &rarr; <code>list.getFirst();</code></li>
<li><code>list.get(list.size() - 1)</code> &rarr; <code>list.getLast();</code></li>
<li><code>list.remove(0)</code> &rarr; <code>list.removeFirst();</code></li>
<li><code>list.remove(list.size() - 1)</code> &rarr; <code>list.removeLast();</code></li>
<li><code>collection.iterator().next()</code> &rarr; <code>collection.getFirst();</code></li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,20 @@
// "Fix all 'SequencedCollection method can be used' problems in file" "true"
package java.util;
interface SequencedCollection<E> extends Collection<E> {
}
interface Foo extends SequencedCollection<String> {}
public class Test {
public static void main(Foo foo, String[] args) {
List<String> list = List.of(args);
var e1 = foo.getFirst();
var e2 = list.getFirst();
var e3 = list.getLast();
var e4 = list.removeFirst();
var e5 = list.removeLast();
list.remove("e");
list.get(1);
}
}

View File

@@ -0,0 +1,20 @@
// "Fix all 'SequencedCollection method can be used' problems in file" "true"
package java.util;
interface SequencedCollection<E> extends Collection<E> {
}
interface Foo extends SequencedCollection<String> {}
public class Test {
public static void main(Foo foo, String[] args) {
List<String> list = List.of(args);
var e1 = foo.iterator().n<caret>ext();
var e2 = list.get(0);
var e3 = list.get(list.size() - 1);
var e4 = list.remove(0);
var e5 = list.remove(list.size() - 1);
list.remove("e");
list.get(1);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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.intellij.java.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ReplaceNullCheckInspection;
import com.intellij.codeInspection.SequencedCollectionMethodCanBeUsedInspection;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21;
public class SequencedCollectionMethodCanBeUsedInspectionTest extends LightQuickFixParameterizedTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new SequencedCollectionMethodCanBeUsedInspection()};
}
@Override
protected String getBasePath() {
return "/inspection/sequencedCollectionMethod";
}
}

View File

@@ -1874,3 +1874,5 @@ intention.text.replace.this.occurrence.keep.import=Replace this occurrence and k
popup.title.choose.target.class=Choose Target Class
tooltip.incorrect.file.template=Incorrect ''{0}'' file template
label.ignored.exceptions=Ignored exceptions:
intention.sequenced.collection.can.be.used.display.name=SequencedCollection method can be used
intention.sequenced.collection.can.be.used.fix.name=Replace with SequencedCollection method call

View File

@@ -211,6 +211,7 @@ group.names.language.level.specific.issues.and.migration.aids15=Java 15
group.names.language.level.specific.issues.and.migration.aids16=Java 16
group.names.language.level.specific.issues.and.migration.aids19=Java 19
group.names.language.level.specific.issues.and.migration.aids20=Java 20
group.names.language.level.specific.issues.and.migration.aids21=Java 21
group.names.javabeans.issues=JavaBeans issues
group.names.inheritance.issues=Inheritance issues
group.names.data.flow.issues=Data flow