From a8b2d3b4e8fe57992051f5c74db2a1bb30bdddb7 Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Tue, 2 May 2017 21:05:26 +0200 Subject: [PATCH] Add inspection to detect copy() calls in a data class without named arguments So #KT-17660 Fixed --- .../CopyWithoutNamedArguments.html | 5 ++ idea/src/META-INF/plugin.xml | 8 +++ .../CopyWithoutNamedArgumentsInspection.kt | 63 +++++++++++++++++++ .../inspectionData/expected.xml | 10 +++ .../inspectionData/inspections.test | 1 + .../copyWithoutNamedArguments/test.kt | 15 +++++ .../copyWithoutNamedArguments/.inspection | 1 + .../copyWithoutNamedArguments/base.kt | 7 +++ .../copyWithoutNamedArguments/base.kt.after | 7 +++ .../copyWithoutNamedArguments/notAllNamed.kt | 5 ++ .../notAllNamed.kt.after | 5 ++ .../copyWithoutNamedArguments/this.kt | 3 + .../copyWithoutNamedArguments/this.kt.after | 3 + .../codeInsight/InspectionTestGenerated.java | 6 ++ .../LocalInspectionTestGenerated.java | 27 ++++++++ 15 files changed, 166 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/CopyWithoutNamedArguments.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/CopyWithoutNamedArgumentsInspection.kt create mode 100644 idea/testData/inspections/copyWithoutNamedArguments/inspectionData/expected.xml create mode 100644 idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test create mode 100644 idea/testData/inspections/copyWithoutNamedArguments/test.kt create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/.inspection create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt.after create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt.after create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt create mode 100644 idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt.after diff --git a/idea/resources/inspectionDescriptions/CopyWithoutNamedArguments.html b/idea/resources/inspectionDescriptions/CopyWithoutNamedArguments.html new file mode 100644 index 00000000000..101e063f60d --- /dev/null +++ b/idea/resources/inspectionDescriptions/CopyWithoutNamedArguments.html @@ -0,0 +1,5 @@ + + +This inspection reports calls to a data class' copy method without named arguments. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 014464e1dca..cc7c573c329 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2141,6 +2141,14 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/CopyWithoutNamedArgumentsInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/CopyWithoutNamedArgumentsInspection.kt new file mode 100644 index 00000000000..c8df6d2d802 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/CopyWithoutNamedArgumentsInspection.kt @@ -0,0 +1,63 @@ +/* + * 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.IntentionWrapper +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.AddNamesToCallArgumentsIntention +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.referenceExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class CopyWithoutNamedArgumentsInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + + val reference = expression.referenceExpression() as? KtNameReferenceExpression ?: return + if (reference.getReferencedNameAsName() != DataClassDescriptorResolver.COPY_METHOD_NAME) return + if (expression.valueArguments.all { it.isNamed() }) return + + val context = expression.analyze(BodyResolveMode.PARTIAL) + val call = expression.getResolvedCall(context) ?: return + val receiver = call.dispatchReceiver?.type?.constructor?.declarationDescriptor as? ClassDescriptor ?: return + + if (!receiver.isData) return + if (call.candidateDescriptor != context[BindingContext.DATA_CLASS_COPY_FUNCTION, receiver]) return + + holder.registerProblem( + expression.calleeExpression ?: return, + "'copy' method of data class is called without named arguments", + ProblemHighlightType.WEAK_WARNING, + IntentionWrapper(AddNamesToCallArgumentsIntention(), expression.containingKtFile) + ) + } + } + } + +} \ No newline at end of file diff --git a/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/expected.xml b/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/expected.xml new file mode 100644 index 00000000000..f890f37334a --- /dev/null +++ b/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/expected.xml @@ -0,0 +1,10 @@ + + + test.kt + 10 + light_idea_test_case + + 'copy' method of data class is called without named arguments + 'copy' method of data class is called without named arguments + + \ No newline at end of file diff --git a/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test b/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test new file mode 100644 index 00000000000..83220c320c9 --- /dev/null +++ b/idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.CopyWithoutNamedArgumentsInspection \ No newline at end of file diff --git a/idea/testData/inspections/copyWithoutNamedArguments/test.kt b/idea/testData/inspections/copyWithoutNamedArguments/test.kt new file mode 100644 index 00000000000..57071a46c93 --- /dev/null +++ b/idea/testData/inspections/copyWithoutNamedArguments/test.kt @@ -0,0 +1,15 @@ +data class Foo(val a: String) { + fun copy(i: Int) {} +} + +class Foo2() { + fun copy(i: Int) {} +} + +fun bar(f: Foo, f2: Foo2) { + f.copy("") + f.copy() + f.copy(a = "") + f.copy(1) + f2.copy(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/.inspection b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/.inspection new file mode 100644 index 00000000000..924d2b40150 --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.CopyWithoutNamedArgumentsInspection diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt new file mode 100644 index 00000000000..5f0fdd28e2b --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt @@ -0,0 +1,7 @@ +// PROBLEM: 'copy' method of data class is called without named arguments + +data class Foo(val a: String) + +fun bar(f: Foo) { + f.copy("") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt.after b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt.after new file mode 100644 index 00000000000..ad96deca4da --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt.after @@ -0,0 +1,7 @@ +// PROBLEM: 'copy' method of data class is called without named arguments + +data class Foo(val a: String) + +fun bar(f: Foo) { + f.copy(a = "") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt new file mode 100644 index 00000000000..3ed405b5889 --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt @@ -0,0 +1,5 @@ +data class SomeName(val a: Int, val b: Int, val c: String) + +fun foo(f: SomeName) { + f.copy(2, c = "") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt.after b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt.after new file mode 100644 index 00000000000..a92d57f0b0f --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt.after @@ -0,0 +1,5 @@ +data class SomeName(val a: Int, val b: Int, val c: String) + +fun foo(f: SomeName) { + f.copy(a = 2, c = "") +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt new file mode 100644 index 00000000000..6d0bfd842b1 --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt @@ -0,0 +1,3 @@ +data class SomeName(val a: Int, val b: Int, val c: String) + +fun SomeName.func() = copy(1, 0) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt.after b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt.after new file mode 100644 index 00000000000..3b893e1072d --- /dev/null +++ b/idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt.after @@ -0,0 +1,3 @@ +data class SomeName(val a: Int, val b: Int, val c: String) + +fun SomeName.func() = copy(a = 1, b = 0) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index 12596416c53..09f375314a1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -173,6 +173,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("copyWithoutNamedArguments/inspectionData/inspections.test") + public void testCopyWithoutNamedArguments_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/copyWithoutNamedArguments/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("dataClassPrivateConstructor/inspectionData/inspections.test") public void testDataClassPrivateConstructor_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/dataClassPrivateConstructor/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 291737a678c..068f192eef5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -36,6 +36,33 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CopyWithoutNamedArguments extends AbstractLocalInspectionTest { + public void testAllFilesPresentInCopyWithoutNamedArguments() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/copyWithoutNamedArguments"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("base.kt") + public void testBase() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments/base.kt"); + doTest(fileName); + } + + @TestMetadata("notAllNamed.kt") + public void testNotAllNamed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments/notAllNamed.kt"); + doTest(fileName); + } + + @TestMetadata("this.kt") + public void testThis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments/this.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)