From 5bcd2410da8f6bf1c212eefd46140aa569bbc893 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 29 Nov 2016 13:19:20 +0700 Subject: [PATCH] Java8CollectionsApiInspection & Java8ReplaceMapGetInspection merged into Java8MapApiInspection; support method references in computeIfAbsent fix for IDEA-163932 Map.get suggested replacement with Map.getOrDefault is misleading --- .../Java8CollectionsApiInspection.java | 296 ----------- .../Java8ReplaceMapGetInspection.java | 277 ---------- .../ReplaceConditionalMapPutFix.java | 156 ------ .../java18api/Java8MapApiInspection.java | 492 ++++++++++++++++++ .../Java8MapApiInspectionMerger.java | 33 ++ .../afterComputeIfAbsent.java | 0 .../afterComputeIfAbsentSingleLine.java | 0 .../afterComputeIfAbsentSingleLineParens.java | 0 .../afterComputeIfAbsentUsedKey.java | 21 + .../afterComputeIfAbsentUsedKeyMr.java | 19 + .../afterComputeIfAbsentUsedKeyWrongType.java | 20 + .../afterComputeIfAbsentVarConflict.java | 0 .../afterConditional.java | 0 .../afterConditional2.java | 0 .../afterGetOrDefaultAssignment.java | 0 .../afterGetOrDefaultField.java | 0 .../java8MapApi/afterGetOrDefaultIf.java | 13 + .../afterGetOrDefaultLocal.java | 0 .../java8MapApi/afterGetOrDefaultTernary.java | 12 + .../afterIfCompute.java | 0 .../java8MapApi/afterPutBranch.java | 7 + .../afterPutIfAbsent.java | 0 .../afterSimpleIf.java | 0 .../afterSimpleIf2.java | 0 .../beforeComputeIfAbsent.java | 0 ...oreComputeIfAbsentAdditionalStatement.java | 0 .../beforeComputeIfAbsentChecked.java | 0 .../beforeComputeIfAbsentIncomplete.java | 0 .../beforeComputeIfAbsentNonFinal.java | 0 .../beforeComputeIfAbsentSingleLine.java | 0 ...beforeComputeIfAbsentSingleLineParens.java | 0 ...foreComputeIfAbsentSingleLineWrongVar.java | 0 .../beforeComputeIfAbsentUsedKey.java | 24 + .../beforeComputeIfAbsentUsedKeyMr.java | 22 + ...beforeComputeIfAbsentUsedKeyWrongType.java | 23 + .../beforeComputeIfAbsentVarConflict.java | 0 .../beforeConditional.java | 2 +- .../beforeConditional2.java | 0 .../beforeGetOrDefaultAssignment.java | 0 .../beforeGetOrDefaultExtends.java | 13 + .../beforeGetOrDefaultField.java | 2 +- .../beforeGetOrDefaultFieldOther.java | 0 .../beforeGetOrDefaultFieldOther2.java | 0 .../java8MapApi/beforeGetOrDefaultIf.java | 16 + .../beforeGetOrDefaultLocal.java | 0 .../beforeGetOrDefaultTernary.java | 12 + .../beforeIfComplexReturnNoCompute.java | 0 .../beforeIfComplexReturnNoPut.java | 0 .../beforeIfCompute.java | 0 .../java8MapApi/beforePutBranch.java | 9 + .../beforePutBranchContainsOtherCode.java | 0 .../beforePutIfAbsent.java | 0 .../beforePutIfAbsentVarUsed.java | 0 .../beforeSimpleIf.java | 0 .../beforeSimpleIf2.java | 0 .../Java8CollectionsApiInspectionTest.java | 48 -- ...st.java => Java8MapApiInspectionTest.java} | 10 +- ...va8ReplaceMapGet.html => Java8MapApi.html} | 0 .../src/messages/QuickFixBundle.properties | 8 +- resources/src/META-INF/IdeaPlugin.xml | 12 +- 60 files changed, 751 insertions(+), 796 deletions(-) delete mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8CollectionsApiInspection.java delete mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8ReplaceMapGetInspection.java delete mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18api/ReplaceConditionalMapPutFix.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspection.java create mode 100644 java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspectionMerger.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterComputeIfAbsent.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterComputeIfAbsentSingleLine.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterComputeIfAbsentSingleLineParens.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKey.java create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyMr.java create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyWrongType.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterComputeIfAbsentVarConflict.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/afterConditional.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/afterConditional2.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterGetOrDefaultAssignment.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterGetOrDefaultField.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultIf.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterGetOrDefaultLocal.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultTernary.java rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/afterIfCompute.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/afterPutBranch.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/afterPutIfAbsent.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/afterSimpleIf.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/afterSimpleIf2.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsent.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentAdditionalStatement.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentChecked.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentIncomplete.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentNonFinal.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentSingleLine.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentSingleLineParens.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentSingleLineWrongVar.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKey.java create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyMr.java create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyWrongType.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeComputeIfAbsentVarConflict.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeConditional.java (70%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeConditional2.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeGetOrDefaultAssignment.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultExtends.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeGetOrDefaultField.java (91%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeGetOrDefaultFieldOther.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeGetOrDefaultFieldOther2.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultIf.java rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforeGetOrDefaultLocal.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultTernary.java rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeIfComplexReturnNoCompute.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeIfComplexReturnNoPut.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeIfCompute.java (100%) create mode 100644 java/java-tests/testData/inspection/java8MapApi/beforePutBranch.java rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforePutBranchContainsOtherCode.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforePutIfAbsent.java (100%) rename java/java-tests/testData/inspection/{java8MapGet => java8MapApi}/beforePutIfAbsentVarUsed.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeSimpleIf.java (100%) rename java/java-tests/testData/inspection/{java8CollectionsApi => java8MapApi}/beforeSimpleIf2.java (100%) delete mode 100644 java/java-tests/testSrc/com/intellij/codeInspection/Java8CollectionsApiInspectionTest.java rename java/java-tests/testSrc/com/intellij/codeInspection/{Java8ReplaceMapGetInspectionTest.java => Java8MapApiInspectionTest.java} (73%) rename resources-en/src/inspectionDescriptions/{Java8ReplaceMapGet.html => Java8MapApi.html} (100%) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8CollectionsApiInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8CollectionsApiInspection.java deleted file mode 100644 index 55008d12b530..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8CollectionsApiInspection.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2000-2016 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.codeInspection.java18api; - -import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; -import com.intellij.codeInspection.util.LambdaGenerationUtil; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiUtil; -import com.siyeh.ig.psiutils.EquivalenceChecker; -import com.siyeh.ig.psiutils.ExpressionUtils; -import one.util.streamex.StreamEx; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * @author Dmitry Batkovich - */ -public class Java8CollectionsApiInspection extends BaseJavaBatchLocalInspectionTool { - private static final Logger LOG = Logger.getInstance(Java8CollectionsApiInspection.class); - - public boolean myReportContainsCondition; - public boolean mySuggestPutIfAbsentForComplexExpression; - - @Nullable - @Override - public JComponent createOptionsPanel() { - MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); - panel.addCheckbox("Report when \'containsKey\' is used in condition (may change semantics)", "myReportContainsCondition"); - panel.addCheckbox("Suggest to replace with \'putIfAbsent\' if value is complex expression (may change semantics)", "mySuggestPutIfAbsentForComplexExpression"); - return panel; - } - - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { - if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { - return PsiElementVisitor.EMPTY_VISITOR; - } - return new JavaElementVisitor() { - @Override - public void visitConditionalExpression(PsiConditionalExpression expression) { - final ConditionInfo conditionInfo = extractConditionInfo(expression.getCondition()); - if (conditionInfo == null) return; - final PsiExpression thenExpression = expression.getThenExpression(); - final PsiExpression elseExpression = expression.getElseExpression(); - if (thenExpression == null || elseExpression == null) return; - analyzeCorrespondenceOfPutAndGet(conditionInfo.isInverted() ? thenExpression : elseExpression, - conditionInfo.isInverted() ? elseExpression : thenExpression, - conditionInfo.getQualifier(), conditionInfo.getContainsKey(), - holder, expression); - } - - @Override - public void visitIfStatement(PsiIfStatement statement) { - final PsiExpression condition = statement.getCondition(); - final ConditionInfo conditionInfo = extractConditionInfo(condition); - if (conditionInfo == null) return; - PsiStatement maybeGetBranch = conditionInfo.isInverted() ? statement.getElseBranch() : statement.getThenBranch(); - if (maybeGetBranch instanceof PsiBlockStatement) { - final PsiStatement[] getBranchStatements = ((PsiBlockStatement)maybeGetBranch).getCodeBlock().getStatements(); - if (getBranchStatements.length > 1) return; - maybeGetBranch = getBranchStatements.length == 0 ? null : getBranchStatements[0]; - } - final PsiStatement branch = conditionInfo.isInverted() ? statement.getThenBranch() : statement.getElseBranch(); - final PsiStatement maybePutStatement; - if (branch instanceof PsiBlockStatement) { - final PsiStatement[] statements = ((PsiBlockStatement)branch).getCodeBlock().getStatements(); - if (statements.length != 1) return; - maybePutStatement = statements[statements.length - 1]; - } - else { - maybePutStatement = branch; - } - if (maybePutStatement != null) { - analyzeCorrespondenceOfPutAndGet(maybePutStatement, maybeGetBranch, conditionInfo.getQualifier(), conditionInfo.getContainsKey(), - holder, statement); - } - } - }; - } - - @Nullable - private ConditionInfo extractConditionInfo(PsiExpression condition) { - final ConditionInfo info = extractConditionInfoIfGet(condition); - if (info != null) { - return info; - } - return !myReportContainsCondition ? null : extractConditionInfoIfContains(condition); - } - - @Nullable - static PsiExpression getValueComparedWithNull(PsiBinaryExpression binOp) { - if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ) && - !binOp.getOperationTokenType().equals(JavaTokenType.NE)) return null; - PsiExpression left = binOp.getLOperand(); - PsiExpression right = binOp.getROperand(); - if(ExpressionUtils.isNullLiteral(right)) return left; - if(ExpressionUtils.isNullLiteral(left)) return right; - return null; - } - - @Nullable - private static ConditionInfo extractConditionInfoIfGet(PsiExpression condition) { - if(!(condition instanceof PsiBinaryExpression)) return null; - PsiBinaryExpression binOp = (PsiBinaryExpression)condition; - PsiExpression operand = getValueComparedWithNull(binOp); - if (!(operand instanceof PsiMethodCallExpression)) return null; - final PsiMethodCallExpression maybeGetCall = (PsiMethodCallExpression)operand; - if (!isJavaUtilMapMethodWithName(maybeGetCall, "get")) return null; - final PsiExpression[] arguments = maybeGetCall.getArgumentList().getExpressions(); - if (arguments.length != 1) return null; - PsiExpression getQualifier = maybeGetCall.getMethodExpression().getQualifierExpression(); - PsiExpression keyExpression = arguments[0]; - return new ConditionInfo(getQualifier, keyExpression, binOp.getOperationTokenType().equals(JavaTokenType.EQEQ)); - } - - @Nullable - private static ConditionInfo extractConditionInfoIfContains(PsiExpression condition) { - boolean inverted = false; - final PsiMethodCallExpression conditionMethodCall; - if (condition instanceof PsiPrefixExpression) { - final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)condition; - if (JavaTokenType.EXCL.equals(prefixExpression.getOperationSign().getTokenType()) && - prefixExpression.getOperand() instanceof PsiMethodCallExpression) { - conditionMethodCall = (PsiMethodCallExpression)prefixExpression.getOperand(); - inverted = true; - } - else { - return null; - } - } - else if (condition instanceof PsiMethodCallExpression) { - conditionMethodCall = (PsiMethodCallExpression)condition; - } - else { - return null; - } - if (!isJavaUtilMapMethodWithName(conditionMethodCall, "containsKey")) { - return null; - } - final PsiExpression containsQualifier = conditionMethodCall.getMethodExpression().getQualifierExpression(); - if (containsQualifier == null) { - return null; - } - final PsiExpression[] expressions = conditionMethodCall.getArgumentList().getExpressions(); - if (expressions.length != 1) { - return null; - } - PsiExpression containsKey = expressions[0]; - return new ConditionInfo(containsQualifier, containsKey, inverted); - } - - private void analyzeCorrespondenceOfPutAndGet(@NotNull PsiElement adjustedElseBranch, - @Nullable PsiElement adjustedThenBranch, - @Nullable PsiExpression containsQualifier, - @Nullable PsiExpression containsKey, - @NotNull ProblemsHolder holder, - @NotNull PsiElement context) { - final PsiElement maybePutMethodCall; - final PsiElement maybeGetMethodCall; - if (adjustedThenBranch == null) { - maybeGetMethodCall = null; - if (adjustedElseBranch instanceof PsiExpressionStatement) { - final PsiExpression expression = ((PsiExpressionStatement)adjustedElseBranch).getExpression(); - if (expression instanceof PsiMethodCallExpression && isJavaUtilMapMethodWithName((PsiMethodCallExpression)expression, "put")) { - maybePutMethodCall = expression; - } - else { - maybePutMethodCall = null; - } - } - else { - maybePutMethodCall = null; - } - } - else { - if (adjustedElseBranch instanceof PsiStatement && adjustedThenBranch instanceof PsiStatement) { - final EquivalenceChecker.Decision decision = EquivalenceChecker.getCanonicalPsiEquivalence().statementsAreEquivalentDecision((PsiStatement)adjustedElseBranch, - (PsiStatement)adjustedThenBranch); - maybePutMethodCall = decision.getLeftDiff(); - maybeGetMethodCall = decision.getRightDiff(); - } - else { - maybePutMethodCall = adjustedElseBranch; - maybeGetMethodCall = adjustedThenBranch; - } - } - if (maybePutMethodCall instanceof PsiMethodCallExpression && - (maybeGetMethodCall == null || maybeGetMethodCall instanceof PsiMethodCallExpression)) { - final PsiMethodCallExpression putMethodCall = (PsiMethodCallExpression)maybePutMethodCall; - final PsiMethodCallExpression getMethodCall = (PsiMethodCallExpression)maybeGetMethodCall; - final PsiExpression putQualifier = putMethodCall.getMethodExpression().getQualifierExpression(); - final PsiExpression getQualifier = getMethodCall == null ? null : getMethodCall.getMethodExpression().getQualifierExpression(); - if ((getMethodCall == null || EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(putQualifier, getQualifier)) && - EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(putQualifier, containsQualifier) && - isJavaUtilMapMethodWithName(putMethodCall, "put") && - (getMethodCall == null || isJavaUtilMapMethodWithName(getMethodCall, "get"))) { - - PsiExpression getArgument; - if (getMethodCall != null) { - final PsiExpression[] arguments = getMethodCall.getArgumentList().getExpressions(); - if (arguments.length != 1) { - return; - } - getArgument = arguments[0]; - } - else { - getArgument = null; - } - - final PsiExpression[] putArguments = putMethodCall.getArgumentList().getExpressions(); - if (putArguments.length != 2) { - return; - } - PsiExpression putKeyArgument = putArguments[0]; - PsiExpression putValueArgument = putArguments[1]; - - if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(containsKey, putKeyArgument) && - (getArgument == null || EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(getArgument, putKeyArgument))) { - LocalQuickFix fix = null; - if (ExpressionUtils.isSimpleExpression(putValueArgument)) { - fix = new ReplaceConditionalMapPutFix(putMethodCall, false); - } - else if ((maybePutMethodCall.getParent() instanceof PsiExpressionStatement) && // only if result of put is not used - LambdaGenerationUtil.canBeUncheckedLambda(putValueArgument)) { - fix = new ReplaceConditionalMapPutFix(putMethodCall, true); - } - else if (mySuggestPutIfAbsentForComplexExpression) { - fix = new ReplaceConditionalMapPutFix(putMethodCall, false); - } - if(fix != null) { - holder.registerProblem(context, QuickFixBundle.message("java.8.collections.api.inspection.description"), fix); - } - } - } - } - } - - static boolean isJavaUtilMapMethodWithName(@NotNull PsiMethodCallExpression methodCallExpression, @NotNull String expectedName) { - if (!expectedName.equals(methodCallExpression.getMethodExpression().getReferenceName())) { - return false; - } - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method == null) return false; - PsiMethod[] superMethods = method.findDeepestSuperMethods(); - if (superMethods.length == 0) { - superMethods = new PsiMethod[]{method}; - } - return StreamEx.of(superMethods).map(PsiMember::getContainingClass).nonNull().map(PsiClass::getQualifiedName) - .has(CommonClassNames.JAVA_UTIL_MAP); - } - - private static class ConditionInfo { - private final PsiExpression myQualifier; - private final PsiExpression myContainsKey; - private final boolean myInverted; - - private ConditionInfo(PsiExpression qualifier, PsiExpression containsKey, boolean inverted) { - myQualifier = qualifier; - myContainsKey = containsKey; - myInverted = inverted; - } - - public PsiExpression getQualifier() { - return myQualifier; - } - - public PsiExpression getContainsKey() { - return myContainsKey; - } - - public boolean isInverted() { - return myInverted; - } - } -} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8ReplaceMapGetInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8ReplaceMapGetInspection.java deleted file mode 100644 index 608b388d1ac9..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/Java8ReplaceMapGetInspection.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2000-2016 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.codeInspection.java18api; - -import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; -import com.intellij.codeInspection.util.LambdaGenerationUtil; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.siyeh.ig.psiutils.CommentTracker; -import com.siyeh.ig.psiutils.ControlFlowUtils; -import com.siyeh.ig.psiutils.EquivalenceChecker; -import com.siyeh.ig.psiutils.ExpressionUtils; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * @author Tagir Valeev - */ -public class Java8ReplaceMapGetInspection extends BaseJavaBatchLocalInspectionTool { - - public boolean mySuggestMapGetOrDefault = true; - public boolean mySuggestMapComputeIfAbsent = true; - public boolean mySuggestMapPutIfAbsent = true; - - @Nullable - @Override - public JComponent createOptionsPanel() { - MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); - panel.addCheckbox("Suggest conversion to Map.computeIfAbsent", "mySuggestMapComputeIfAbsent"); - panel.addCheckbox("Suggest conversion to Map.getOrDefault", "mySuggestMapGetOrDefault"); - panel.addCheckbox("Suggest conversion to Map.putIfAbsent", "mySuggestMapPutIfAbsent"); - return panel; - } - - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { - if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { - return PsiElementVisitor.EMPTY_VISITOR; - } - return new JavaElementVisitor() { - @Override - public void visitIfStatement(PsiIfStatement statement) { - if(statement.getElseBranch() != null) return; - PsiExpression condition = statement.getCondition(); - PsiReferenceExpression value = getReferenceComparedWithNull(condition); - if (value == null) return; - PsiElement previous = PsiTreeUtil.skipSiblingsBackward(statement, PsiWhiteSpace.class, PsiComment.class); - PsiMethodCallExpression getCall = tryExtractMapGetCall(value, previous); - if(getCall == null) return; - PsiExpression[] getArguments = getCall.getArgumentList().getExpressions(); - if(getArguments.length != 1) return; - PsiStatement thenBranch = ControlFlowUtils.stripBraces(statement.getThenBranch()); - PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(thenBranch); - EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence(); - if(assignment != null) { - /* - value = map.get(key); - if(value == null) { - value = ... - } - */ - if (!mySuggestMapGetOrDefault) return; - if (ExpressionUtils.isSimpleExpression(assignment.getRExpression()) && - equivalence.expressionsAreEquivalent(assignment.getLExpression(), value) && - !equivalence.expressionsAreEquivalent(assignment.getRExpression(), value)) { - holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"), - new ReplaceGetNullCheck("getOrDefault")); - } - } else { - /* - value = map.get(key); - if(value == null) { - value = ... - map.put(key, value); - } - */ - PsiExpression key = getArguments[0]; - PsiExpression mapExpression = getCall.getMethodExpression().getQualifierExpression(); - PsiExpression lambdaCandidate = extractLambdaCandidate(thenBranch, mapExpression, key, value); - if (lambdaCandidate != null && mySuggestMapComputeIfAbsent) { - holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"), - new ReplaceGetNullCheck("computeIfAbsent")); - } - if (lambdaCandidate == null && mySuggestMapPutIfAbsent) { - PsiExpression expression = extractPutValue(thenBranch, mapExpression, key); - if(ExpressionUtils.isSimpleExpression(expression) && !equivalence.expressionsAreEquivalent(expression, value)) { - holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"), - new ReplaceGetNullCheck("putIfAbsent")); - } - } - } - } - }; - } - - @Nullable - static PsiExpression extractLambdaCandidate(PsiStatement statement, PsiExpression mapExpression, - PsiExpression keyExpression, PsiReferenceExpression valueExpression) { - EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence(); - PsiAssignmentExpression assignment; - PsiExpression putValue = extractPutValue(statement, mapExpression, keyExpression); - if(putValue != null) { - // like map.put(key, val = new ArrayList<>()); - assignment = ExpressionUtils.getAssignment(putValue); - } - else { - if (!(statement instanceof PsiBlockStatement)) return null; - // like val = new ArrayList<>(); map.put(key, val); - PsiStatement[] statements = ((PsiBlockStatement)statement).getCodeBlock().getStatements(); - if (statements.length != 2) return null; - putValue = extractPutValue(statements[1], mapExpression, keyExpression); - if (!equivalence.expressionsAreEquivalent(valueExpression, putValue)) return null; - assignment = ExpressionUtils.getAssignment(statements[0]); - } - if (assignment == null) return null; - PsiExpression lambdaCandidate = assignment.getRExpression(); - if (lambdaCandidate == null || !equivalence.expressionsAreEquivalent(assignment.getLExpression(), valueExpression)) return null; - if (!LambdaGenerationUtil.canBeUncheckedLambda(lambdaCandidate)) return null; - return lambdaCandidate; - } - - @Contract("null -> null") - @Nullable - private static PsiMethodCallExpression extractPutCall(PsiStatement statement) { - if(!(statement instanceof PsiExpressionStatement)) return null; - PsiExpression expression = ((PsiExpressionStatement)statement).getExpression(); - if (!(expression instanceof PsiMethodCallExpression)) return null; - PsiMethodCallExpression putCall = (PsiMethodCallExpression)expression; - if (!Java8CollectionsApiInspection.isJavaUtilMapMethodWithName(putCall, "put")) return null; - return putCall; - } - - @Nullable - private static PsiExpression extractPutValue(PsiStatement statement, PsiExpression mapExpression, PsiExpression keyExpression) { - PsiMethodCallExpression putCall = extractPutCall(statement); - if (putCall == null) return null; - PsiExpression[] putArguments = putCall.getArgumentList().getExpressions(); - EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence(); - return putArguments.length == 2 && - equivalence.expressionsAreEquivalent(putCall.getMethodExpression().getQualifierExpression(), mapExpression) && - equivalence.expressionsAreEquivalent(keyExpression, putArguments[0]) ? putArguments[1] : null; - } - - @Contract("null -> null") - @Nullable - private static PsiReferenceExpression getReferenceComparedWithNull(PsiExpression condition) { - if(!(condition instanceof PsiBinaryExpression)) return null; - PsiBinaryExpression binOp = (PsiBinaryExpression)condition; - if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ)) return null; - PsiExpression value = Java8CollectionsApiInspection.getValueComparedWithNull(binOp); - if(!(value instanceof PsiReferenceExpression)) return null; - return (PsiReferenceExpression)value; - } - - @Nullable - @Contract("_, null -> null") - static PsiMethodCallExpression tryExtractMapGetCall(PsiReferenceExpression target, PsiElement element) { - if(element instanceof PsiDeclarationStatement) { - PsiDeclarationStatement declaration = (PsiDeclarationStatement)element; - PsiElement[] elements = declaration.getDeclaredElements(); - if(elements.length > 0) { - PsiElement lastDeclaration = elements[elements.length - 1]; - if(lastDeclaration instanceof PsiLocalVariable && target.isReferenceTo(lastDeclaration)) { - PsiLocalVariable var = (PsiLocalVariable)lastDeclaration; - PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(var.getInitializer()); - if (initializer instanceof PsiMethodCallExpression && - Java8CollectionsApiInspection.isJavaUtilMapMethodWithName((PsiMethodCallExpression)initializer, "get")) { - return (PsiMethodCallExpression)initializer; - } - } - } - } - PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(element); - if(assignment != null) { - PsiExpression lValue = assignment.getLExpression(); - if (lValue instanceof PsiReferenceExpression && - EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(target, lValue)) { - PsiExpression rValue = PsiUtil.skipParenthesizedExprDown(assignment.getRExpression()); - if (rValue instanceof PsiMethodCallExpression && - Java8CollectionsApiInspection.isJavaUtilMapMethodWithName((PsiMethodCallExpression)rValue, "get")) { - return (PsiMethodCallExpression)rValue; - } - } - } - return null; - } - - private static class ReplaceGetNullCheck implements LocalQuickFix { - private final String myMethodName; - - ReplaceGetNullCheck(String methodName) { - myMethodName = methodName; - } - - @Nls - @NotNull - @Override - public String getName() { - return QuickFixBundle.message("java.8.collections.api.inspection.fix.text", myMethodName); - } - - @Nls - @NotNull - @Override - public String getFamilyName() { - return QuickFixBundle.message("java.8.replace.map.get.inspection.fix.family.name"); - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - PsiElement element = descriptor.getStartElement(); - PsiIfStatement ifStatement = PsiTreeUtil.getParentOfType(element, PsiIfStatement.class); - if(ifStatement == null) return; - PsiReferenceExpression value = getReferenceComparedWithNull(ifStatement.getCondition()); - if(value == null) return; - PsiElement statement = PsiTreeUtil.skipSiblingsBackward(ifStatement, PsiWhiteSpace.class, PsiComment.class); - PsiMethodCallExpression getCall = tryExtractMapGetCall(value, statement); - if(getCall == null || !Java8CollectionsApiInspection.isJavaUtilMapMethodWithName(getCall, "get")) return; - PsiExpression[] args = getCall.getArgumentList().getExpressions(); - if(args.length != 1) return; - PsiStatement thenBranch = ControlFlowUtils.stripBraces(ifStatement.getThenBranch()); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(thenBranch); - CommentTracker ct = new CommentTracker(); - PsiReferenceExpression methodExpression = getCall.getMethodExpression(); - if(assignment != null) { - PsiExpression defaultValue = assignment.getRExpression(); - if (!ExpressionUtils.isSimpleExpression(defaultValue)) return; - methodExpression.handleElementRename(myMethodName); - getCall.getArgumentList().add(ct.markUnchanged(defaultValue)); - } else { - PsiExpression lambdaCandidate = extractLambdaCandidate(thenBranch, methodExpression.getQualifierExpression(), args[0], value); - if (lambdaCandidate == null) { - PsiExpression valueExpression = extractPutValue(thenBranch, methodExpression.getQualifierExpression(), args[0]); - if(ExpressionUtils.isSimpleExpression(valueExpression)) { - methodExpression.handleElementRename(myMethodName); - getCall.getArgumentList().add(ct.markUnchanged(valueExpression)); - } - } else { - methodExpression.handleElementRename(myMethodName); - String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("k", lambdaCandidate, true); - PsiExpression lambda = factory.createExpressionFromText(varName + " -> " + ct.text(lambdaCandidate), lambdaCandidate); - getCall.getArgumentList().add(lambda); - } - } - ct.deleteAndRestoreComments(ifStatement); - CodeStyleManager.getInstance(project).reformat(statement); - } - } -} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/ReplaceConditionalMapPutFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/ReplaceConditionalMapPutFix.java deleted file mode 100644 index 978c583af02c..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java18api/ReplaceConditionalMapPutFix.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2000-2016 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.codeInspection.java18api; - -import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Couple; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; - - -/** - * @author Dmitry Batkovich - */ -public class ReplaceConditionalMapPutFix implements LocalQuickFix { - private static final String COMPUTE_IF_ABSENT_METHOD = "computeIfAbsent"; - private static final String PUT_IF_ABSENT_METHOD = "putIfAbsent"; - - private final SmartPsiElementPointer myPutExpressionPointer; - private final String myMethodName; - - public ReplaceConditionalMapPutFix(PsiMethodCallExpression putExpression, boolean useComputeIfAbsent) { - final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(putExpression.getProject()); - myPutExpressionPointer = smartPointerManager.createSmartPsiElementPointer(putExpression); - myMethodName = useComputeIfAbsent ? COMPUTE_IF_ABSENT_METHOD : PUT_IF_ABSENT_METHOD; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiElement conditionalOperator = descriptor.getPsiElement(); - if (conditionalOperator == null) return; - final ConditionalOperatorHelper operatorHelper = getHelper(conditionalOperator); - - final PsiMethodCallExpression putExpression = myPutExpressionPointer.getElement(); - if (putExpression == null) return; - - PsiElement putContainingBranch = null; - for (PsiElement branch : operatorHelper.getBranches(conditionalOperator)) { - if (branch != null && PsiTreeUtil.isAncestor(branch, putExpression, false)) { - putContainingBranch = branch; - break; - } - } - if (putContainingBranch == null) return; - - final PsiExpression[] arguments = putExpression.getArgumentList().getExpressions(); - final PsiElement qualifier = putExpression.getMethodExpression().getQualifier(); - if (qualifier == null) { - return; - } - - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - - final PsiElement putContainingElement = operatorHelper.getPutContainingElement(putExpression); - final Couple boundText = getBoundText(putContainingElement, putExpression); - - String valueArgument = putExpression.getArgumentList().getExpressions()[1].getText(); - if(myMethodName.equals(COMPUTE_IF_ABSENT_METHOD)) { - String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("k", putExpression, true); - valueArgument = varName + " -> " + valueArgument; - } - final PsiStatement newStatement = - elementFactory.createStatementFromText(boundText.getFirst() + qualifier.getText() + "." + myMethodName + - "(" + arguments[0].getText() + "," + valueArgument + ")" + boundText.getSecond(), - conditionalOperator); - conditionalOperator.replace(newStatement); - } - - private static Couple getBoundText(@NotNull PsiElement parent, @NotNull PsiElement child) { - final TextRange childRange = child.getTextRange(); - final int parentStartOffset = parent.getTextRange().getStartOffset(); - final String parentText = parent.getText(); - return Couple.of(parentText.substring(0, childRange.getStartOffset() - parentStartOffset), - parentText.substring(childRange.getEndOffset() - parentStartOffset)); - } - - @Nls - @NotNull - @Override - public String getFamilyName() { - return QuickFixBundle.message("java.8.collections.api.inspection.fix.family.name"); - } - - @Nls - @NotNull - @Override - public String getName() { - return QuickFixBundle.message("java.8.collections.api.inspection.fix.text", myMethodName); - } - - private static ConditionalOperatorHelper getHelper(PsiElement element) { - return element instanceof PsiConditionalExpression ? new ConditionalExpressionHelper() : new IfStatementHelper(); - } - - interface ConditionalOperatorHelper { - @NotNull - PsiElement[] getBranches(PsiElement element); - - @NotNull - PsiElement getPutContainingElement(PsiElement putElement); - } - - private static class ConditionalExpressionHelper implements ConditionalOperatorHelper { - @NotNull - @Override - public PsiElement[] getBranches(PsiElement element) { - final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)element; - return new PsiElement[]{conditionalExpression.getThenExpression(), conditionalExpression.getElseExpression()}; - } - - @NotNull - @Override - public PsiElement getPutContainingElement(PsiElement putElement) { - for (PsiElement element : getBranches(PsiTreeUtil.getParentOfType(putElement, PsiConditionalExpression.class))) { - if (PsiTreeUtil.isAncestor(element, putElement, false)) { - return element; - } - } - throw new AssertionError(); - } - } - - private static class IfStatementHelper implements ConditionalOperatorHelper { - @NotNull - @Override - public PsiElement[] getBranches(PsiElement element) { - final PsiIfStatement ifStatement = (PsiIfStatement)element; - return new PsiElement[]{ifStatement.getThenBranch(), ifStatement.getElseBranch()}; - } - - @NotNull - @Override - public PsiElement getPutContainingElement(PsiElement putElement) { - return PsiTreeUtil.getParentOfType(putElement, PsiStatement.class); - } - } -} diff --git a/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspection.java b/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspection.java new file mode 100644 index 000000000000..7cada2682697 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspection.java @@ -0,0 +1,492 @@ +/* + * Copyright 2000-2016 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.codeInspection.java18api; + +import com.intellij.codeInsight.PsiEquivalenceUtil; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; +import com.intellij.codeInspection.util.LambdaGenerationUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.psiutils.*; +import one.util.streamex.IntStreamEx; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * @author Tagir Valeev + */ +public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool { + private static final Logger LOG = Logger.getInstance(Java8MapApiInspection.class); + public static final String SHORT_NAME = "Java8MapApi"; + + public boolean mySuggestMapGetOrDefault = true; + public boolean mySuggestMapComputeIfAbsent = true; + public boolean mySuggestMapPutIfAbsent = true; + public boolean myTreatGetNullAsContainsKey = false; + + @Nullable + @Override + public JComponent createOptionsPanel() { + MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); + panel.addCheckbox("Suggest conversion to Map.computeIfAbsent", "mySuggestMapComputeIfAbsent"); + panel.addCheckbox("Suggest conversion to Map.getOrDefault", "mySuggestMapGetOrDefault"); + panel.addCheckbox("Suggest conversion to Map.putIfAbsent", "mySuggestMapPutIfAbsent"); + panel.addCheckbox("Treat 'get(k) != null' the same as 'containsKey(k)' (may change semantics)", "myTreatGetNullAsContainsKey"); + return panel; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + @Override + public void visitConditionalExpression(PsiConditionalExpression expression) { + MapCheckCondition condition = fromTernary(expression); + if(condition == null || condition.hasVariable()) return; + PsiExpression existsBranch = condition.getExistsBranch(expression.getThenExpression(), expression.getElseExpression()); + PsiExpression noneBranch = condition.getNoneBranch(expression.getThenExpression(), expression.getElseExpression()); + processGetPut(condition, expression, existsBranch, existsBranch, noneBranch); + } + + @Override + public void visitIfStatement(PsiIfStatement statement) { + MapCheckCondition condition = fromIfStatement(statement); + if(condition == null) return; + PsiStatement existsBranch = ControlFlowUtils.stripBraces(condition.getExistsBranch(statement.getThenBranch(), statement.getElseBranch())); + PsiStatement noneBranch = ControlFlowUtils.stripBraces(condition.getNoneBranch(statement.getThenBranch(), statement.getElseBranch())); + if(existsBranch == null) { + processSingleBranch(statement, condition, noneBranch); + } else { + if(condition.hasVariable()) return; + EquivalenceChecker.Decision decision = + EquivalenceChecker.getCanonicalPsiEquivalence().statementsAreEquivalentDecision(noneBranch, existsBranch); + + processGetPut(condition, statement, existsBranch, decision.getRightDiff(), decision.getLeftDiff()); + } + } + + private void processGetPut(MapCheckCondition condition, PsiElement toRemove, PsiElement result, PsiElement exists, PsiElement none) { + if(!(exists instanceof PsiExpression)) return; + PsiMethodCallExpression getCall = extractMapMethodCall((PsiExpression)exists, "get"); + if (getCall == null || !condition.isMap(getCall.getMethodExpression().getQualifierExpression())) return; + PsiExpression[] getArgs = getCall.getArgumentList().getExpressions(); + if (getArgs.length != 1 || !condition.isKey(getArgs[0])) return; + + if(!(none instanceof PsiExpression)) return; + PsiExpression noneExpression = (PsiExpression)none; + PsiMethodCallExpression putCall = extractMapMethodCall(noneExpression, "put"); + if (mySuggestMapPutIfAbsent && + putCall != null && + condition.isGetNull() && + condition.isMap(putCall.getMethodExpression().getQualifierExpression())) { + PsiExpression[] putArgs = putCall.getArgumentList().getExpressions(); + if (putArgs.length != 2 || !condition.isKey(putArgs[0]) || !ExpressionUtils.isSimpleExpression(putArgs[1])) return; + condition.register(holder, new ReplaceWithSingleMapOperation("putIfAbsent", getCall, putArgs[1], toRemove, result)); + } + if (mySuggestMapGetOrDefault && condition.isContainsKey() && ExpressionUtils.isSimpleExpression(noneExpression) && + !(getCall.getType() instanceof PsiCapturedWildcardType)) { + condition.register(holder, new ReplaceWithSingleMapOperation("getOrDefault", getCall, noneExpression, toRemove, + result)); + } + } + + private void processSingleBranch(PsiIfStatement statement, MapCheckCondition condition, PsiStatement noneBranch) { + PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(noneBranch); + if(assignment != null && mySuggestMapGetOrDefault && condition.isContainsKey()) { + /* + value = map.get(key); + if(value == null) { + value = ... + } + */ + if (ExpressionUtils.isSimpleExpression(assignment.getRExpression()) && + condition.isValueReference(assignment.getLExpression()) && + !condition.isValueReference(assignment.getRExpression())) { + condition + .register(holder, ReplaceWithSingleMapOperation.fromIf("getOrDefault", condition, statement, assignment.getRExpression())); + } + } else if (condition.isGetNull()) { + /* + value = map.get(key); + if(value == null) { + value = ... + map.put(key, value); + } + */ + PsiExpression lambdaCandidate = extractLambdaCandidate(condition, noneBranch); + if (lambdaCandidate != null && mySuggestMapComputeIfAbsent) { + condition.register(holder, ReplaceWithSingleMapOperation.fromIf("computeIfAbsent", condition, statement, lambdaCandidate)); + } + if (lambdaCandidate == null) { + PsiExpression expression = extractPutValue(condition, noneBranch); + if(expression != null) { + String replacement = null; + if (mySuggestMapPutIfAbsent && ExpressionUtils.isSimpleExpression(expression) && !condition.isValueReference(expression)) { + replacement = "putIfAbsent"; + } + else if (mySuggestMapComputeIfAbsent && !condition.hasVariable()) { + replacement = "computeIfAbsent"; + } + if(replacement != null) { + if(condition.hasVariable()) { + condition.register(holder, ReplaceWithSingleMapOperation.fromIf(replacement, condition, statement, expression)); + } else { + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class); + LOG.assertTrue(call != null); + condition.register(holder, new ReplaceWithSingleMapOperation(replacement, call, expression, statement, noneBranch)); + } + } + } + } + } + } + }; + } + + @Nullable + static PsiExpression getValueComparedWithNull(PsiBinaryExpression binOp) { + if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ) && + !binOp.getOperationTokenType().equals(JavaTokenType.NE)) return null; + PsiExpression left = binOp.getLOperand(); + PsiExpression right = binOp.getROperand(); + if(ExpressionUtils.isNullLiteral(right)) return left; + if(ExpressionUtils.isNullLiteral(left)) return right; + return null; + } + + @Nullable + static PsiExpression extractLambdaCandidate(MapCheckCondition condition, PsiStatement statement) { + PsiAssignmentExpression assignment; + PsiExpression putValue = extractPutValue(condition, statement); + if(putValue != null) { + // like map.put(key, val = new ArrayList<>()); + assignment = ExpressionUtils.getAssignment(putValue); + } + else { + if (!(statement instanceof PsiBlockStatement)) return null; + // like val = new ArrayList<>(); map.put(key, val); + PsiStatement[] statements = ((PsiBlockStatement)statement).getCodeBlock().getStatements(); + if (statements.length != 2) return null; + putValue = extractPutValue(condition, statements[1]); + if (!condition.isValueReference(putValue)) return null; + assignment = ExpressionUtils.getAssignment(statements[0]); + } + if (assignment == null) return null; + PsiExpression lambdaCandidate = assignment.getRExpression(); + if (lambdaCandidate == null || !condition.isValueReference(assignment.getLExpression())) return null; + if (!LambdaGenerationUtil.canBeUncheckedLambda(lambdaCandidate)) return null; + return lambdaCandidate; + } + + @Contract("null, _ -> null") + static PsiMethodCallExpression extractMapMethodCall(PsiExpression expression, @NotNull String expectedName) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (!(expression instanceof PsiMethodCallExpression)) return null; + PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + if (!expectedName.equals(methodCallExpression.getMethodExpression().getReferenceName())) return null; + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) return null; + PsiMethod[] superMethods = method.findDeepestSuperMethods(); + if (superMethods.length == 0) { + superMethods = new PsiMethod[]{method}; + } + return StreamEx.of(superMethods).map(PsiMember::getContainingClass).nonNull().map(PsiClass::getQualifiedName) + .has(CommonClassNames.JAVA_UTIL_MAP) ? methodCallExpression : null; + } + + + @Contract("_, null -> null") + @Nullable + private static PsiExpression extractPutValue(MapCheckCondition condition, PsiStatement statement) { + if(!(statement instanceof PsiExpressionStatement)) return null; + PsiMethodCallExpression putCall = extractMapMethodCall(((PsiExpressionStatement)statement).getExpression(), "put"); + if (putCall == null) return null; + PsiExpression[] putArguments = putCall.getArgumentList().getExpressions(); + return putArguments.length == 2 && + condition.isMap(putCall.getMethodExpression().getQualifierExpression()) && + condition.isKey(putArguments[0]) ? putArguments[1] : null; + } + + @Nullable + @Contract("_, null -> null") + static PsiMethodCallExpression tryExtractMapGetCall(PsiReferenceExpression target, PsiElement element) { + if(element instanceof PsiDeclarationStatement) { + PsiDeclarationStatement declaration = (PsiDeclarationStatement)element; + PsiElement[] elements = declaration.getDeclaredElements(); + if(elements.length > 0) { + PsiElement lastDeclaration = elements[elements.length - 1]; + if(lastDeclaration instanceof PsiLocalVariable && target.isReferenceTo(lastDeclaration)) { + PsiLocalVariable var = (PsiLocalVariable)lastDeclaration; + return extractMapMethodCall(var.getInitializer(), "get"); + } + } + } + PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(element); + if(assignment != null) { + PsiExpression lValue = assignment.getLExpression(); + if (lValue instanceof PsiReferenceExpression && + EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(target, lValue)) { + return extractMapMethodCall(assignment.getRExpression(), "get"); + } + } + return null; + } + + @Nullable + MapCheckCondition fromIfStatement(PsiIfStatement ifStatement) { + return tryExtract(ifStatement.getCondition(), ifStatement); + } + + @Nullable + MapCheckCondition fromTernary(PsiConditionalExpression ternary) { + PsiElement parent = ternary.getParent().getParent(); + return tryExtract(ternary.getCondition(), parent instanceof PsiStatement ? (PsiStatement)parent : null); + } + + @Nullable + private MapCheckCondition tryExtract(PsiExpression fullCondition, @Nullable PsiStatement statement) { + PsiExpression condition = PsiUtil.skipParenthesizedExprDown(fullCondition); + boolean negated = false; + while(condition != null && BoolUtils.isNegation(condition)) { + negated ^= true; + condition = BoolUtils.getNegated(condition); + } + if(condition == null) return null; + PsiReferenceExpression valueReference = null; + boolean containsKey = false; + PsiMethodCallExpression call; + if(condition instanceof PsiBinaryExpression) { + negated ^= ((PsiBinaryExpression)condition).getOperationTokenType().equals(JavaTokenType.EQEQ); + PsiExpression value = getValueComparedWithNull((PsiBinaryExpression)condition); + if(value instanceof PsiReferenceExpression && statement != null) { + valueReference = (PsiReferenceExpression)value; + PsiElement previous = PsiTreeUtil.skipSiblingsBackward(statement, PsiWhiteSpace.class, PsiComment.class); + call = tryExtractMapGetCall(valueReference, previous); + } else { + call = extractMapMethodCall(value, "get"); + } + } else { + call = extractMapMethodCall(condition, "containsKey"); + containsKey = true; + } + if(call == null) return null; + PsiExpression mapExpression = call.getMethodExpression().getQualifierExpression(); + if(mapExpression == null) return null; + PsiExpression[] args = call.getArgumentList().getExpressions(); + if(args.length != 1) return null; + PsiExpression keyExpression = args[0]; + return new MapCheckCondition(valueReference, mapExpression, keyExpression, fullCondition, negated, containsKey); + } + + private static class ReplaceWithSingleMapOperation implements LocalQuickFix { + private final String myMethodName; + private final SmartPsiElementPointer myCallPointer; + private final SmartPsiElementPointer myValuePointer; + private final SmartPsiElementPointer myRemovedPointer; + private final SmartPsiElementPointer myResultPointer; + + ReplaceWithSingleMapOperation(String methodName, PsiMethodCallExpression call, PsiExpression value, PsiElement removed, PsiElement result) { + myMethodName = methodName; + SmartPointerManager manager = SmartPointerManager.getInstance(value.getProject()); + myCallPointer = manager.createSmartPsiElementPointer(call); + myValuePointer = manager.createSmartPsiElementPointer(value); + myRemovedPointer = manager.createSmartPsiElementPointer(removed); + myResultPointer = manager.createSmartPsiElementPointer(result); + } + + @NotNull + static ReplaceWithSingleMapOperation fromIf(String methodName, MapCheckCondition condition, PsiStatement ifStatement, PsiExpression value) { + PsiMethodCallExpression call = condition.getCheckCall(); + PsiStatement result = PsiTreeUtil.getParentOfType(call, PsiStatement.class); + LOG.assertTrue(result != null); + return new ReplaceWithSingleMapOperation(methodName, call, value, ifStatement, result); + } + + @Nls + @NotNull + @Override + public String getName() { + return QuickFixBundle.message("java.8.map.api.inspection.fix.text", myMethodName); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return QuickFixBundle.message("java.8.map.api.inspection.fix.family.name"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiMethodCallExpression call = myCallPointer.getElement(); + if (call == null) return; + PsiExpressionList argsList = call.getArgumentList(); + PsiExpression[] args = argsList.getExpressions(); + if(args.length == 0) return; + PsiExpression value = myValuePointer.getElement(); + if (value == null) return; + PsiElement removed = myRemovedPointer.getElement(); + if (removed == null) return; + PsiElement result = myResultPointer.getElement(); + if(result == null) return; + + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + CommentTracker ct = new CommentTracker(); + call.getMethodExpression().handleElementRename(myMethodName); + PsiExpression replacement; + if(myMethodName.equals("computeIfAbsent")) { + PsiExpression key = args[0]; + List refs = Collections.emptyList(); + String nameCandidate = "k"; + if(key instanceof PsiReferenceExpression && ((PsiReferenceExpression)key).getQualifier() == null) { + // try to use lambda parameter if key is simple reference and has the same type as map keys + PsiMethod method = call.resolveMethod(); + if(method != null) { + PsiType argType = method.getParameterList().getParameters()[0].getType(); + PsiType mapKeyType = call.resolveMethodGenerics().getSubstitutor().substitute(argType); + PsiType keyType = key.getType(); + + if(mapKeyType != null && keyType != null && keyType.isAssignableFrom(mapKeyType)) { + PsiElement element = ((PsiReferenceExpression)key).resolve(); + refs = StreamEx.of(PsiTreeUtil.collectElementsOfType(value, PsiReferenceExpression.class)) + .filter(ref -> ref.getQualifierExpression() == null && ref.isReferenceTo(element)).toList(); + if (!refs.isEmpty()) { + String name = ((PsiReferenceExpression)key).getReferenceName(); + // like "myVariableName" => "mvn" + nameCandidate = IntStreamEx.ofChars(name).mapFirst(Character::toUpperCase).filter(Character::isUpperCase).charsToString() + .toLowerCase(Locale.ENGLISH); + if (nameCandidate.isEmpty()) { + nameCandidate = "k"; + } + } + } + } + } + String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(nameCandidate, value, true); + for(PsiReferenceExpression ref : refs) { + ref.handleElementRename(varName); + } + replacement = factory.createExpressionFromText(varName + " -> " + ct.text(value), value); + } else { + replacement = ct.markUnchanged(value); + } + if(args.length == 2) { + ct.replace(args[1], replacement); + } else { + argsList.add(replacement); + } + PsiExpression expression = argsList.getExpressions()[1]; + if(expression instanceof PsiLambdaExpression) { + LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference((PsiLambdaExpression)expression); + } + if(PsiTreeUtil.isAncestor(removed, result, true)) { + result = ct.replaceAndRestoreComments(removed, ct.markUnchanged(result)); + } else { + ct.deleteAndRestoreComments(removed); + } + CodeStyleManager.getInstance(project).reformat(result); + } + } + + class MapCheckCondition { + private final @Nullable PsiReferenceExpression myValueReference; + private final PsiExpression myMapExpression; + private final PsiExpression myKeyExpression; + private final PsiExpression myFullCondition; + private final boolean myNegated; + private final boolean myContainsKey; + + private MapCheckCondition(@Nullable PsiReferenceExpression valueReference, + PsiExpression mapExpression, + PsiExpression keyExpression, + PsiExpression fullCondition, + boolean negated, + boolean containsKey) { + myValueReference = valueReference; + myMapExpression = mapExpression; + myKeyExpression = keyExpression; + myFullCondition = fullCondition; + myNegated = negated; + myContainsKey = containsKey; + } + + boolean isContainsKey() { + return myContainsKey || myTreatGetNullAsContainsKey; + } + + boolean isGetNull() { + return !myContainsKey || myTreatGetNullAsContainsKey; + } + + @Contract("null -> false") + boolean isMap(PsiExpression expression) { + return expression != null && PsiEquivalenceUtil.areElementsEquivalent(myMapExpression, expression); + } + + @Contract("null -> false") + boolean isKey(PsiExpression expression) { + return expression != null && PsiEquivalenceUtil.areElementsEquivalent(myKeyExpression, expression); + } + + @Contract("null -> false") + boolean isValueReference(PsiExpression expression) { + return expression != null && myValueReference != null && PsiEquivalenceUtil.areElementsEquivalent(expression, myValueReference); + } + + T getExistsBranch(T thenBranch, T elseBranch) { + return myNegated ? elseBranch : thenBranch; + } + + T getNoneBranch(T thenBranch, T elseBranch) { + return myNegated ? thenBranch : elseBranch; + } + + boolean hasVariable() { + return myValueReference != null; + } + + PsiMethodCallExpression getCheckCall() { + return PsiTreeUtil.getParentOfType(myMapExpression, PsiMethodCallExpression.class); + } + + public PsiExpression getFullCondition() { + return myFullCondition; + } + + public void register(ProblemsHolder holder, LocalQuickFix fix) { + //noinspection DialogTitleCapitalization + holder.registerProblem(getFullCondition(), QuickFixBundle.message("java.8.map.api.inspection.description"), fix); + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspectionMerger.java b/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspectionMerger.java new file mode 100644 index 000000000000..d2631f129cb5 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/java18api/Java8MapApiInspectionMerger.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2016 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.codeInspection.java18api; + +import com.intellij.codeInspection.ex.InspectionElementsMerger; + +public class Java8MapApiInspectionMerger extends InspectionElementsMerger { + private static final String COLLECTION_API_INSPECTION = "Java8CollectionsApi"; + private static final String REPLACE_MAP_GET_INSPECTION = "Java8ReplaceMapGet"; + + @Override + public String getMergedToolName() { + return Java8MapApiInspection.SHORT_NAME; + } + + @Override + public String[] getSourceToolNames() { + return new String[] {COLLECTION_API_INSPECTION, REPLACE_MAP_GET_INSPECTION}; + } +} diff --git a/java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsent.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsent.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsent.java rename to java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsent.java diff --git a/java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentSingleLine.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentSingleLine.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentSingleLine.java rename to java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentSingleLine.java diff --git a/java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentSingleLineParens.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentSingleLineParens.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentSingleLineParens.java rename to java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentSingleLineParens.java diff --git a/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKey.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKey.java new file mode 100644 index 000000000000..7167e9f3f07f --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKey.java @@ -0,0 +1,21 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + int i; + + MyItem(String k, int i) { + this.k = k; + this.i = i; + } + } + + public MyItem testMap(Map map, String token) { + MyItem item = map.computeIfAbsent(token, t -> new MyItem(t, 1)); + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyMr.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyMr.java new file mode 100644 index 000000000000..868499d5868a --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyMr.java @@ -0,0 +1,19 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + + MyItem(String k) { + this.k = k; + } + } + + public MyItem testMap(Map map, String token) { + MyItem item = map.computeIfAbsent(token, MyItem::new); + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyWrongType.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyWrongType.java new file mode 100644 index 000000000000..dbffd24c7a74 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentUsedKeyWrongType.java @@ -0,0 +1,20 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + + MyItem(String k) { + this.k = k; + } + } + + public MyItem testMap(Map map, String token) { + // Cannot create method reference here as MyItem wants a String, but lambda will receive a CharSequence + MyItem item = map.computeIfAbsent(token, k -> new MyItem(token)); + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentVarConflict.java b/java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentVarConflict.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterComputeIfAbsentVarConflict.java rename to java/java-tests/testData/inspection/java8MapApi/afterComputeIfAbsentVarConflict.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/afterConditional.java b/java/java-tests/testData/inspection/java8MapApi/afterConditional.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/afterConditional.java rename to java/java-tests/testData/inspection/java8MapApi/afterConditional.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/afterConditional2.java b/java/java-tests/testData/inspection/java8MapApi/afterConditional2.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/afterConditional2.java rename to java/java-tests/testData/inspection/java8MapApi/afterConditional2.java diff --git a/java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultAssignment.java b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultAssignment.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultAssignment.java rename to java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultAssignment.java diff --git a/java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultField.java b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultField.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultField.java rename to java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultField.java diff --git a/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultIf.java b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultIf.java new file mode 100644 index 000000000000..718784ed4512 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultIf.java @@ -0,0 +1,13 @@ +// "Replace with 'getOrDefault' method call" "true" +import java.util.Map; + +public class Main { + private static final String NONE = "none"; + + private String str; + + public void testGetOrDefault(Map map, String key, Main other) { + /* output none */ + System.out.println(/* output map value */ map.getOrDefault("k", NONE)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultLocal.java b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultLocal.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterGetOrDefaultLocal.java rename to java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultLocal.java diff --git a/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultTernary.java b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultTernary.java new file mode 100644 index 000000000000..c2f5d1927aa1 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterGetOrDefaultTernary.java @@ -0,0 +1,12 @@ +// "Replace with 'getOrDefault' method call" "true" +import java.util.Map; + +public class Main { + private static final String NONE = "none"; + + private String str; + + public String testGetOrDefault(Map map, String key, Main other) { + return map.getOrDefault(key, "oops"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/afterIfCompute.java b/java/java-tests/testData/inspection/java8MapApi/afterIfCompute.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/afterIfCompute.java rename to java/java-tests/testData/inspection/java8MapApi/afterIfCompute.java diff --git a/java/java-tests/testData/inspection/java8MapApi/afterPutBranch.java b/java/java-tests/testData/inspection/java8MapApi/afterPutBranch.java new file mode 100644 index 000000000000..f014378c9bc9 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/afterPutBranch.java @@ -0,0 +1,7 @@ +// "Replace with 'putIfAbsent' method call" "true" +import java.util.Map; +class Test { + void m1(Map map) { + map.putIfAbsent("ads", i); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/afterPutIfAbsent.java b/java/java-tests/testData/inspection/java8MapApi/afterPutIfAbsent.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/afterPutIfAbsent.java rename to java/java-tests/testData/inspection/java8MapApi/afterPutIfAbsent.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/afterSimpleIf.java b/java/java-tests/testData/inspection/java8MapApi/afterSimpleIf.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/afterSimpleIf.java rename to java/java-tests/testData/inspection/java8MapApi/afterSimpleIf.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/afterSimpleIf2.java b/java/java-tests/testData/inspection/java8MapApi/afterSimpleIf2.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/afterSimpleIf2.java rename to java/java-tests/testData/inspection/java8MapApi/afterSimpleIf2.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsent.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsent.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsent.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsent.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentAdditionalStatement.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentAdditionalStatement.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentAdditionalStatement.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentAdditionalStatement.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentChecked.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentChecked.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentChecked.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentChecked.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentIncomplete.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentIncomplete.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentIncomplete.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentIncomplete.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentNonFinal.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentNonFinal.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentNonFinal.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentNonFinal.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLine.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLine.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLine.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLine.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLineParens.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLineParens.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLineParens.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLineParens.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLineWrongVar.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLineWrongVar.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentSingleLineWrongVar.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentSingleLineWrongVar.java diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKey.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKey.java new file mode 100644 index 000000000000..3b0c3f0b3025 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKey.java @@ -0,0 +1,24 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + int i; + + MyItem(String k, int i) { + this.k = k; + this.i = i; + } + } + + public MyItem testMap(Map map, String token) { + MyItem item = map.get(token); + if(item == null) { + map.put(token, item = new MyItem(token, 1)); + } + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyMr.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyMr.java new file mode 100644 index 000000000000..b4147b58d616 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyMr.java @@ -0,0 +1,22 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + + MyItem(String k) { + this.k = k; + } + } + + public MyItem testMap(Map map, String token) { + MyItem item = map.get(token); + if(item == null) { + map.put(token, item = new MyItem(token)); + } + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyWrongType.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyWrongType.java new file mode 100644 index 000000000000..5be7b8d96a6c --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentUsedKeyWrongType.java @@ -0,0 +1,23 @@ +// "Replace with 'computeIfAbsent' method call" "true" +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Main { + static class MyItem { + String k; + + MyItem(String k) { + this.k = k; + } + } + + public MyItem testMap(Map map, String token) { + // Cannot create method reference here as MyItem wants a String, but lambda will receive a CharSequence + MyItem item = map.get(token); + if(item == null) { + map.put(token, item = new MyItem(token)); + } + return item; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentVarConflict.java b/java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentVarConflict.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeComputeIfAbsentVarConflict.java rename to java/java-tests/testData/inspection/java8MapApi/beforeComputeIfAbsentVarConflict.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeConditional.java b/java/java-tests/testData/inspection/java8MapApi/beforeConditional.java similarity index 70% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeConditional.java rename to java/java-tests/testData/inspection/java8MapApi/beforeConditional.java index c0ccf8ec1dd9..ef24bc87ce84 100644 --- a/java/java-tests/testData/inspection/java8CollectionsApi/beforeConditional.java +++ b/java/java-tests/testData/inspection/java8MapApi/beforeConditional.java @@ -2,6 +2,6 @@ import java.util.Map; class Test { void m1111(Map map) { - int i = !map.containsKey("asd") ? map.put("asd", 123) : map.get("asd"); + int i = !map.containsKey("asd") ? map.put("asd", 123) : map.get("asd"); } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeConditional2.java b/java/java-tests/testData/inspection/java8MapApi/beforeConditional2.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeConditional2.java rename to java/java-tests/testData/inspection/java8MapApi/beforeConditional2.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultAssignment.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultAssignment.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultAssignment.java rename to java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultAssignment.java diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultExtends.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultExtends.java new file mode 100644 index 000000000000..0c7220144b4e --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultExtends.java @@ -0,0 +1,13 @@ +// "Replace with 'getOrDefault' method call" "false" +import java.util.Map; + +public class Main { + public CharSequence test(Map map) { + // cannot replace as map.getOrDefault("xyz", "none") will result in compilation error + if(map.containsKey("xyz")) { + return map.get("xyz"); + } else { + return "none"; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultField.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultField.java similarity index 91% rename from java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultField.java rename to java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultField.java index bf4c9253a54c..73dbe0f7d842 100644 --- a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultField.java +++ b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultField.java @@ -8,7 +8,7 @@ public class Main { public void testGetOrDefault(Map map, String key, Main other) { str = map.get(key); - if(str == null) { + if(!(str != null)) { /* block comment */ diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultFieldOther.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultFieldOther.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultFieldOther.java rename to java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultFieldOther.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultFieldOther2.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultFieldOther2.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultFieldOther2.java rename to java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultFieldOther2.java diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultIf.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultIf.java new file mode 100644 index 000000000000..47ad58eb8a41 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultIf.java @@ -0,0 +1,16 @@ +// "Replace with 'getOrDefault' method call" "true" +import java.util.Map; + +public class Main { + private static final String NONE = "none"; + + private String str; + + public void testGetOrDefault(Map map, String key, Main other) { + if(map.containsKey("k")) { + System.out.println(/* output map value */ map.get("k")); + } else { + System.out.println(/* output none */ NONE); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultLocal.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultLocal.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforeGetOrDefaultLocal.java rename to java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultLocal.java diff --git a/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultTernary.java b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultTernary.java new file mode 100644 index 000000000000..2c205144d554 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforeGetOrDefaultTernary.java @@ -0,0 +1,12 @@ +// "Replace with 'getOrDefault' method call" "true" +import java.util.Map; + +public class Main { + private static final String NONE = "none"; + + private String str; + + public String testGetOrDefault(Map map, String key, Main other) { + return map.containsKey(key) ? map.get(key) : "oops"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeIfComplexReturnNoCompute.java b/java/java-tests/testData/inspection/java8MapApi/beforeIfComplexReturnNoCompute.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeIfComplexReturnNoCompute.java rename to java/java-tests/testData/inspection/java8MapApi/beforeIfComplexReturnNoCompute.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeIfComplexReturnNoPut.java b/java/java-tests/testData/inspection/java8MapApi/beforeIfComplexReturnNoPut.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeIfComplexReturnNoPut.java rename to java/java-tests/testData/inspection/java8MapApi/beforeIfComplexReturnNoPut.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeIfCompute.java b/java/java-tests/testData/inspection/java8MapApi/beforeIfCompute.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeIfCompute.java rename to java/java-tests/testData/inspection/java8MapApi/beforeIfCompute.java diff --git a/java/java-tests/testData/inspection/java8MapApi/beforePutBranch.java b/java/java-tests/testData/inspection/java8MapApi/beforePutBranch.java new file mode 100644 index 000000000000..fcce38e53c62 --- /dev/null +++ b/java/java-tests/testData/inspection/java8MapApi/beforePutBranch.java @@ -0,0 +1,9 @@ +// "Replace with 'putIfAbsent' method call" "true" +import java.util.Map; +class Test { + void m1(Map map) { + if (!map.containsKey("ads")) { + map.put("ads", i); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforePutBranchContainsOtherCode.java b/java/java-tests/testData/inspection/java8MapApi/beforePutBranchContainsOtherCode.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforePutBranchContainsOtherCode.java rename to java/java-tests/testData/inspection/java8MapApi/beforePutBranchContainsOtherCode.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforePutIfAbsent.java b/java/java-tests/testData/inspection/java8MapApi/beforePutIfAbsent.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforePutIfAbsent.java rename to java/java-tests/testData/inspection/java8MapApi/beforePutIfAbsent.java diff --git a/java/java-tests/testData/inspection/java8MapGet/beforePutIfAbsentVarUsed.java b/java/java-tests/testData/inspection/java8MapApi/beforePutIfAbsentVarUsed.java similarity index 100% rename from java/java-tests/testData/inspection/java8MapGet/beforePutIfAbsentVarUsed.java rename to java/java-tests/testData/inspection/java8MapApi/beforePutIfAbsentVarUsed.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeSimpleIf.java b/java/java-tests/testData/inspection/java8MapApi/beforeSimpleIf.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeSimpleIf.java rename to java/java-tests/testData/inspection/java8MapApi/beforeSimpleIf.java diff --git a/java/java-tests/testData/inspection/java8CollectionsApi/beforeSimpleIf2.java b/java/java-tests/testData/inspection/java8MapApi/beforeSimpleIf2.java similarity index 100% rename from java/java-tests/testData/inspection/java8CollectionsApi/beforeSimpleIf2.java rename to java/java-tests/testData/inspection/java8MapApi/beforeSimpleIf2.java diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/Java8CollectionsApiInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/Java8CollectionsApiInspectionTest.java deleted file mode 100644 index 966707ced91f..000000000000 --- a/java/java-tests/testSrc/com/intellij/codeInspection/Java8CollectionsApiInspectionTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2000-2016 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.codeInspection; - -import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; -import com.intellij.codeInspection.java18api.Java8CollectionsApiInspection; -import org.jetbrains.annotations.NotNull; - -/** - * @author Dmitry Batkovich - */ -public class Java8CollectionsApiInspectionTest extends LightQuickFixParameterizedTestCase { - private Java8CollectionsApiInspection myInspection; - - @NotNull - @Override - protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{myInspection}; - } - - public void setUp() throws Exception { - myInspection = new Java8CollectionsApiInspection(); - myInspection.myReportContainsCondition = true; - super.setUp(); - } - - public void test() throws Exception { - doAllTests(); - } - - @Override - protected String getBasePath() { - return "/inspection/java8CollectionsApi"; - } -} diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/Java8ReplaceMapGetInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/Java8MapApiInspectionTest.java similarity index 73% rename from java/java-tests/testSrc/com/intellij/codeInspection/Java8ReplaceMapGetInspectionTest.java rename to java/java-tests/testSrc/com/intellij/codeInspection/Java8MapApiInspectionTest.java index ac5aba706f7d..dac543d241f5 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/Java8ReplaceMapGetInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/Java8MapApiInspectionTest.java @@ -16,17 +16,19 @@ package com.intellij.codeInspection; import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; -import com.intellij.codeInspection.java18api.Java8ReplaceMapGetInspection; +import com.intellij.codeInspection.java18api.Java8MapApiInspection; import org.jetbrains.annotations.NotNull; /** * @author Tagir Valeev */ -public class Java8ReplaceMapGetInspectionTest extends LightQuickFixParameterizedTestCase { +public class Java8MapApiInspectionTest extends LightQuickFixParameterizedTestCase { @NotNull @Override protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{new Java8ReplaceMapGetInspection()}; + Java8MapApiInspection inspection = new Java8MapApiInspection(); + inspection.myTreatGetNullAsContainsKey = true; + return new LocalInspectionTool[]{inspection}; } public void test() throws Exception { @@ -35,6 +37,6 @@ public class Java8ReplaceMapGetInspectionTest extends LightQuickFixParameterized @Override protected String getBasePath() { - return "/inspection/java8MapGet"; + return "/inspection/java8MapApi"; } } diff --git a/resources-en/src/inspectionDescriptions/Java8ReplaceMapGet.html b/resources-en/src/inspectionDescriptions/Java8MapApi.html similarity index 100% rename from resources-en/src/inspectionDescriptions/Java8ReplaceMapGet.html rename to resources-en/src/inspectionDescriptions/Java8MapApi.html diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index 7161dbe0a4f1..4f3809911c97 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -283,11 +283,9 @@ wrap.long.with.math.to.int.parameter.multiple.text=Wrap {0, choice, 1#1st|2#2nd| add.exception.from.field.initializer.to.constructor.throws.text=Add exception to class {0, choice, 0#default constructor|1#constructor|2#constructors} signature add.exception.from.field.initializer.to.constructor.throws.family.text=Add exception to class constructors signature -java.8.collections.api.inspection.description=If statement could be replaced with single method -java.8.collections.api.inspection.fix.family.name=Replace with single method call -java.8.collections.api.inspection.fix.text=Replace with ''{0}'' method call -java.8.replace.map.get.inspection.description=Map.get and condition could be replaced with single method call -java.8.replace.map.get.inspection.fix.family.name=Replace Map.get and condition with single method call +java.8.map.api.inspection.fix.text=Replace with ''{0}'' method call +java.8.map.api.inspection.description=Can be replaced with single Map method call +java.8.map.api.inspection.fix.family.name=Replace with single Map method call java.8.collection.removeif.inspection.description=The loop could be replaced with Collection.removeIf java.8.collection.removeif.inspection.fix.name=Replace the loop with Collection.removeIf java.8.list.sort.inspection.description=Collections.sort could be replaced with List.sort diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 45fd98b420d5..58982593c684 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -589,6 +589,7 @@ + - - + implementationClass="com.intellij.codeInspection.java18api.Java8MapApiInspection" + displayName="Replace with single Map method"/>