From 162de77b71a21f50118765d877b67600b50adf42 Mon Sep 17 00:00:00 2001 From: Jan Gerling Date: Fri, 1 Mar 2019 17:23:40 +0300 Subject: [PATCH] Add inspection to detect boxes *Range.start & endInclusive These properties can be replaced with equivalent collections first & last. #KT-15537 Fixed --- idea/resources/META-INF/plugin-common.xml | 9 +++ ...ceRangeStartEndInclusiveWithFirstLast.html | 7 ++ ...tartEndInclusiveWithFirstLastInspection.kt | 81 +++++++++++++++++++ .../.inspection | 1 + .../customRange.kt | 11 +++ .../endInclusiveCharRange.kt | 6 ++ .../endInclusiveCharRange.kt.after | 6 ++ .../endInclusiveIntRange.kt | 6 ++ .../endInclusiveIntRange.kt.after | 6 ++ .../endInclusiveLongRange.kt | 6 ++ .../endInclusiveLongRange.kt.after | 6 ++ .../startCharRange.kt | 6 ++ .../startCharRange.kt.after | 6 ++ .../startIntRange.kt | 6 ++ .../startIntRange.kt.after | 6 ++ .../startLongRange.kt | 6 ++ .../startLongRange.kt.after | 6 ++ .../LocalInspectionTestGenerated.java | 48 +++++++++++ 18 files changed, 229 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/ReplaceRangeStartEndInclusiveWithFirstLast.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeStartEndInclusiveWithFirstLastInspection.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/.inspection create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/customRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt.after create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt.after create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt.after create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt.after create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt.after create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt create mode 100644 idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt.after diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index c8ffd481409..0e013d6ab8e 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3306,6 +3306,15 @@ language="kotlin" /> + + + +This inspection reports usages of boxed Range.start and Range.endInclusive properties. +These properties can be replaced with unboxed first and last properties. +Example: range.start can be replaced with range.first. + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeStartEndInclusiveWithFirstLastInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeStartEndInclusiveWithFirstLastInspection.kt new file mode 100644 index 00000000000..5c9d0fb8956 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeStartEndInclusiveWithFirstLastInspection.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.dotQualifiedExpressionVisitor +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe + +class ReplaceRangeStartEndInclusiveWithFirstLastInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return dotQualifiedExpressionVisitor(fun(expression: KtDotQualifiedExpression) { + val selectorExpression = expression.selectorExpression ?: return + if (selectorExpression.text != "start" && selectorExpression.text != "endInclusive") return + + val resolvedCall = expression.resolveToCall() ?: return + val containing = resolvedCall.resultingDescriptor.containingDeclaration as? ClassDescriptor ?: return + if (!containing.isRange()) return + + if (selectorExpression.text == "start") { + holder.registerProblem( + expression, + "Could be replaced with unboxed `first`", + ReplaceIntRangeStartWithFirstQuickFix() + ) + } else if (selectorExpression.text == "endInclusive") { + holder.registerProblem( + expression, + "Could be replaced with unboxed `last`", + ReplaceIntRangeEndInclusiveWithLastQuickFix() + ) + } + }) + } +} + +private val rangeTypes = setOf( + "kotlin.ranges.IntRange", + "kotlin.ranges.CharRange", + "kotlin.ranges.LongRange", + "kotlin.ranges.UIntRange", + "kotlin.ranges.ULongRange" +) + +private fun ClassDescriptor.isRange(): Boolean { + return rangeTypes.any { this.fqNameUnsafe.asString() == it } +} + +class ReplaceIntRangeStartWithFirstQuickFix : LocalQuickFix { + override fun getName() = "Replace 'start' with 'first'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement as KtDotQualifiedExpression + val selector = element.selectorExpression ?: return + selector.replace(KtPsiFactory(element).createExpression("first")) + } +} + +class ReplaceIntRangeEndInclusiveWithLastQuickFix : LocalQuickFix { + override fun getName() = "Replace 'endInclusive' with 'last'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement as KtDotQualifiedExpression + val selector = element.selectorExpression ?: return + selector.replace(KtPsiFactory(element).createExpression("last")) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/.inspection b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/.inspection new file mode 100644 index 00000000000..4b8d971dc88 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ReplaceRangeStartEndInclusiveWithFirstLastInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/customRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/customRange.kt new file mode 100644 index 00000000000..7565c1e6505 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/customRange.kt @@ -0,0 +1,11 @@ +// PROBLEM: none + +class MyRange : ClosedRange { + override val start: String get() = "a" + override val endInclusive: String = "z" +} + +fun main() { + val range = MyRange() + val start = range.endInclusive +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt new file mode 100644 index 00000000000..a964eb9b4ef --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : CharRange = 'a' .. 'z' + range.endInclusive +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt.after new file mode 100644 index 00000000000..a792f6a3177 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : CharRange = 'a' .. 'z' + range.last +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt new file mode 100644 index 00000000000..fe8e131b541 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : IntRange = 1..2 + range.endInclusive +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt.after new file mode 100644 index 00000000000..66fa20f8700 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : IntRange = 1..2 + range.last +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt new file mode 100644 index 00000000000..0d945e0cb4d --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : LongRange = 1L..2L + range.endInclusive +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt.after new file mode 100644 index 00000000000..63f231ecbbc --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : LongRange = 1L..2L + range.last +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt new file mode 100644 index 00000000000..17d1810221a --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : CharRange = 'a' .. 'z' + range.start +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt.after new file mode 100644 index 00000000000..ea4a8eae57b --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : CharRange = 'a' .. 'z' + range.first +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt new file mode 100644 index 00000000000..7a14abcb266 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : IntRange = 1..2 + range.start +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt.after new file mode 100644 index 00000000000..9fd69b9f511 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : IntRange = 1..2 + range.first +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt new file mode 100644 index 00000000000..28da7f961fc --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : LongRange = 1L..2L + range.start +} diff --git a/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt.after b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt.after new file mode 100644 index 00000000000..9cc298baac5 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + var range : LongRange = 1L..2L + range.first +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index ddf3fc1b84a..83a52a39c57 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -7388,6 +7388,54 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceRangeStartEndInclusiveWithFirstLast extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInReplaceRangeStartEndInclusiveWithFirstLast() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("customRange.kt") + public void testCustomRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/customRange.kt"); + } + + @TestMetadata("endInclusiveCharRange.kt") + public void testEndInclusiveCharRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveCharRange.kt"); + } + + @TestMetadata("endInclusiveIntRange.kt") + public void testEndInclusiveIntRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveIntRange.kt"); + } + + @TestMetadata("endInclusiveLongRange.kt") + public void testEndInclusiveLongRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/endInclusiveLongRange.kt"); + } + + @TestMetadata("startCharRange.kt") + public void testStartCharRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startCharRange.kt"); + } + + @TestMetadata("startIntRange.kt") + public void testStartIntRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startIntRange.kt"); + } + + @TestMetadata("startLongRange.kt") + public void testStartLongRange() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceRangeStartEndInclusiveWithFirstLast/startLongRange.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/replaceRangeToWithUntil") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)