diff --git a/idea/resources/inspectionDescriptions/ReplacePutWithAssignment.html b/idea/resources/inspectionDescriptions/ReplacePutWithAssignment.html new file mode 100644 index 00000000000..7497d29f8eb --- /dev/null +++ b/idea/resources/inspectionDescriptions/ReplacePutWithAssignment.html @@ -0,0 +1,5 @@ + + +This inspection reports map.put function calls replaceable with the indexing operator ([]). + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 2bd8d003980..0bc77fe9c7e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2681,6 +2681,14 @@ language="kotlin" /> + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplacePutWithAssignmentInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplacePutWithAssignmentInspection.kt new file mode 100644 index 00000000000..bf6a52a8005 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplacePutWithAssignmentInspection.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.callExpression +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue +import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf + +class ReplacePutWithAssignmentInspection : AbstractKotlinInspection() { + private val compatibleNames = setOf("put") + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) { + super.visitDotQualifiedExpression(expression) + val callExpression = expression.callExpression ?: return + if (callExpression.valueArguments.size != 2) return + + val calleeExpression = callExpression.calleeExpression as? KtNameReferenceExpression ?: return + if (calleeExpression.getReferencedName() !in compatibleNames) return + + val context = expression.analyze() + if (expression.isUsedAsExpression(context)) return + val resolvedCall = expression.getResolvedCall(context) ?: return + val receiverType = resolvedCall.getExplicitReceiverValue()?.type ?: return + val receiverClass = receiverType.constructor.declarationDescriptor as? ClassDescriptor ?: return + if (receiverClass.isSubclassOf(DefaultBuiltIns.Instance.mutableMap)) { + val argumentOffset = expression.startOffset + val problemDescriptor = holder.manager.createProblemDescriptor( + calleeExpression, + TextRange(expression.startOffset - argumentOffset, + callExpression.endOffset - argumentOffset), + "map.put() can be converted to assignment", + ProblemHighlightType.WEAK_WARNING, + isOnTheFly, + ReplacePutWithAssignmentQuickfix() + ) + holder.registerProblem(problemDescriptor) + } + } + } + } +} + +class ReplacePutWithAssignmentQuickfix : LocalQuickFix { + override fun getName() = "Convert put to assignment" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement as KtNameReferenceExpression + val valueArguments = (element.parent as? KtCallExpression)?.valueArguments ?: return + val qualifiedExpression = element.parent.parent as? KtDotQualifiedExpression ?: return + qualifiedExpression.replace(KtPsiFactory(element).createExpressionByPattern("$0[$1] = $2", + qualifiedExpression.receiverExpression, + valueArguments[0]?.getArgumentExpression() ?: return, + valueArguments[1]?.getArgumentExpression() ?: return)) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/.inspection b/idea/testData/inspectionsLocal/replacePutWithAssignment/.inspection new file mode 100644 index 00000000000..5f8cc4fe6ed --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ReplacePutWithAssignmentInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt new file mode 100644 index 00000000000..0ea3eb9326a --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt @@ -0,0 +1,10 @@ +// PROBLEM: none + +class A { + fun put(x: Int, y: String) { + } +} + +fun foo() { + A().put(1, "foo") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putAsExpression.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/putAsExpression.kt new file mode 100644 index 00000000000..4c72160c011 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putAsExpression.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +val map = mutableMapOf(42 to "foo") + +fun foo() = map.put(60, "bar") diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt new file mode 100644 index 00000000000..e63456f84aa --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(map: MutableMap) { + map.put(42, "foo") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt.after b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt.after new file mode 100644 index 00000000000..ddbad2d0f33 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(map: MutableMap) { + map[42] = "foo" +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt new file mode 100644 index 00000000000..d10d0f28c82 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class MyMap() : HashMap() { + init { + this.put("foo", "bar") + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt.after b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt.after new file mode 100644 index 00000000000..41bbdde21f5 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class MyMap() : HashMap() { + init { + this["foo"] = "bar" + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt new file mode 100644 index 00000000000..de036175267 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + val map = mutableMapOf(42 to "foo") + map.put(60, "bar") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt.after b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt.after new file mode 100644 index 00000000000..9b3861057b6 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + val map = mutableMapOf(42 to "foo") + map[60] = "bar" +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt new file mode 100644 index 00000000000..62b3eb1bab4 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var map = mutableMapOf(42 to "foo") + map.put(60, "bar") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt.after b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt.after new file mode 100644 index 00000000000..db5ea610a89 --- /dev/null +++ b/idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var map = mutableMapOf(42 to "foo") + map[60] = "bar" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index d5a0114d5e1..b0a09390208 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2193,6 +2193,51 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplacePutWithAssignment extends AbstractLocalInspectionTest { + public void testAllFilesPresentInReplacePutWithAssignment() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replacePutWithAssignment"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("nonMap.kt") + public void testNonMap() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt"); + doTest(fileName); + } + + @TestMetadata("putAsExpression.kt") + public void testPutAsExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/putAsExpression.kt"); + doTest(fileName); + } + + @TestMetadata("putOnParameter.kt") + public void testPutOnParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/putOnParameter.kt"); + doTest(fileName); + } + + @TestMetadata("putOnThis.kt") + public void testPutOnThis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/putOnThis.kt"); + doTest(fileName); + } + + @TestMetadata("putOnVal.kt") + public void testPutOnVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVal.kt"); + doTest(fileName); + } + + @TestMetadata("putOnVar.kt") + public void testPutOnVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment/putOnVar.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/replaceRangeToWithUntil") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)