Add inspection to detect boxes *Range.start & endInclusive
These properties can be replaced with equivalent collections first & last. #KT-15537 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
401d8c2d70
commit
162de77b71
@@ -3306,6 +3306,15 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ReplaceRangeStartEndInclusiveWithFirstLastInspection"
|
||||
displayName="Replace Range 'start' or 'endInclusive' with 'first' or 'last'"
|
||||
groupPath="Kotlin"
|
||||
groupName="Style issues"
|
||||
enabledByDefault="true"
|
||||
level="WEAK WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantRequireNotNullCallInspection"
|
||||
displayName="Redundant 'requireNotNull' or 'checkNotNull' call"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports usages of <b>boxed</b> <code>Range.start</code> and <code>Range.endInclusive</code> properties.
|
||||
These properties can be replaced with <b>unboxed</b> <code>first</code> and <code>last</code> properties.
|
||||
Example: <b>range.start</b> can be replaced with <b>range.first</b>.
|
||||
</body>
|
||||
</html>
|
||||
+81
@@ -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"))
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.ReplaceRangeStartEndInclusiveWithFirstLastInspection
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// PROBLEM: none
|
||||
|
||||
class MyRange : ClosedRange<String> {
|
||||
override val start: String get() = "a"
|
||||
override val endInclusive: String = "z"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val range = MyRange()
|
||||
val start = range.<caret>endInclusive
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : CharRange = 'a' .. 'z'
|
||||
range.<caret>endInclusive
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : CharRange = 'a' .. 'z'
|
||||
range.last
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : IntRange = 1..2
|
||||
range.<caret>endInclusive
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : IntRange = 1..2
|
||||
range.last
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : LongRange = 1L..2L
|
||||
range.<caret>endInclusive
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : LongRange = 1L..2L
|
||||
range.last
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : CharRange = 'a' .. 'z'
|
||||
range.<caret>start
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : CharRange = 'a' .. 'z'
|
||||
range.first
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : IntRange = 1..2
|
||||
range.<caret>start
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : IntRange = 1..2
|
||||
range.first
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : LongRange = 1L..2L
|
||||
range.<caret>start
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var range : LongRange = 1L..2L
|
||||
range.first
|
||||
}
|
||||
+48
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user