From 79b66e1879f8bb8a4239ecd6527ba4fd3499bdb1 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 18 Aug 2023 19:22:31 +0200 Subject: [PATCH] [java-inspection] IDEA-324051 Inspections to migrate to sequenced collections (Java 21) GitOrigin-RevId: bd008786ab983b8e8f3966d182da3be1ca9dcb55 --- ...edCollectionMethodCanBeUsedInspection.java | 117 ++++++++++++++++++ java/java-impl/src/META-INF/JavaPlugin.xml | 5 + .../SequencedCollectionMethodCanBeUsed.html | 13 ++ .../sequencedCollectionMethod/afterAll.java | 20 +++ .../sequencedCollectionMethod/beforeAll.java | 20 +++ ...llectionMethodCanBeUsedInspectionTest.java | 44 +++++++ .../resources/messages/JavaBundle.properties | 2 + .../messages/InspectionsBundle.properties | 1 + 8 files changed, 222 insertions(+) create mode 100644 java/java-impl-inspections/src/com/intellij/codeInspection/SequencedCollectionMethodCanBeUsedInspection.java create mode 100644 java/java-impl/src/inspectionDescriptions/SequencedCollectionMethodCanBeUsed.html create mode 100644 java/java-tests/testData/inspection/sequencedCollectionMethod/afterAll.java create mode 100644 java/java-tests/testData/inspection/sequencedCollectionMethod/beforeAll.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInspection/SequencedCollectionMethodCanBeUsedInspectionTest.java diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/SequencedCollectionMethodCanBeUsedInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/SequencedCollectionMethodCanBeUsedInspection.java new file mode 100644 index 000000000000..0b959f474b93 --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/SequencedCollectionMethodCanBeUsedInspection.java @@ -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); + } + } +} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 41e79bea7644..fc8a83c786a1 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -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"/> + /> + +Reports collection API method calls that can be simplified using Java 21 SequencedCollection methods. +

The following conversions are supported:

+
    +
  • list.get(0)list.getFirst();
  • +
  • list.get(list.size() - 1)list.getLast();
  • +
  • list.remove(0)list.removeFirst();
  • +
  • list.remove(list.size() - 1)list.removeLast();
  • +
  • collection.iterator().next()collection.getFirst();
  • +
+ + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/sequencedCollectionMethod/afterAll.java b/java/java-tests/testData/inspection/sequencedCollectionMethod/afterAll.java new file mode 100644 index 000000000000..41f6561be924 --- /dev/null +++ b/java/java-tests/testData/inspection/sequencedCollectionMethod/afterAll.java @@ -0,0 +1,20 @@ +// "Fix all 'SequencedCollection method can be used' problems in file" "true" +package java.util; + +interface SequencedCollection extends Collection { +} +interface Foo extends SequencedCollection {} + +public class Test { + public static void main(Foo foo, String[] args) { + List 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); + } +} diff --git a/java/java-tests/testData/inspection/sequencedCollectionMethod/beforeAll.java b/java/java-tests/testData/inspection/sequencedCollectionMethod/beforeAll.java new file mode 100644 index 000000000000..12e390770705 --- /dev/null +++ b/java/java-tests/testData/inspection/sequencedCollectionMethod/beforeAll.java @@ -0,0 +1,20 @@ +// "Fix all 'SequencedCollection method can be used' problems in file" "true" +package java.util; + +interface SequencedCollection extends Collection { +} +interface Foo extends SequencedCollection {} + +public class Test { + public static void main(Foo foo, String[] args) { + List list = List.of(args); + + var e1 = foo.iterator().next(); + 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); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/SequencedCollectionMethodCanBeUsedInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/SequencedCollectionMethodCanBeUsedInspectionTest.java new file mode 100644 index 000000000000..0bedde2e40d4 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/SequencedCollectionMethodCanBeUsedInspectionTest.java @@ -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"; + } +} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 63c04dbed786..1b8f51e308aa 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -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 diff --git a/platform/analysis-api/resources/messages/InspectionsBundle.properties b/platform/analysis-api/resources/messages/InspectionsBundle.properties index 44b0fa5179ed..06e2e217447e 100644 --- a/platform/analysis-api/resources/messages/InspectionsBundle.properties +++ b/platform/analysis-api/resources/messages/InspectionsBundle.properties @@ -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