mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[java-inspection] IDEA-324051 Inspections to migrate to sequenced collections (Java 21)
GitOrigin-RevId: bd008786ab983b8e8f3966d182da3be1ca9dcb55
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0f07763950
commit
79b66e1879
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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> → <code>list.getFirst();</code></li>
|
||||
<li><code>list.get(list.size() - 1)</code> → <code>list.getLast();</code></li>
|
||||
<li><code>list.remove(0)</code> → <code>list.removeFirst();</code></li>
|
||||
<li><code>list.remove(list.size() - 1)</code> → <code>list.removeLast();</code></li>
|
||||
<li><code>collection.iterator().next()</code> → <code>collection.getFirst();</code></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user