diff --git a/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/after.kt.template new file mode 100644 index 00000000000..1efa896629b --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/after.kt.template @@ -0,0 +1 @@ +a until b \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/before.kt.template new file mode 100644 index 00000000000..e2c2168b033 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/before.kt.template @@ -0,0 +1 @@ +a..b-1 diff --git a/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/description.html b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/description.html new file mode 100644 index 00000000000..f7fcd47e779 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceRangeToWithUntilIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces calls to 'rangeTo' or the '..' operator with calls to 'until' and offsets the upper bound by 1. + + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/after.kt.template new file mode 100644 index 00000000000..e2c2168b033 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/after.kt.template @@ -0,0 +1 @@ +a..b-1 diff --git a/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/before.kt.template new file mode 100644 index 00000000000..1efa896629b --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/before.kt.template @@ -0,0 +1 @@ +a until b \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/description.html b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/description.html new file mode 100644 index 00000000000..227464b937e --- /dev/null +++ b/idea/resources/intentionDescriptions/ReplaceUntilWithRangeToIntention/description.html @@ -0,0 +1,5 @@ + + +This intention replaces calls to 'until' with the '..' operator and subtracts one from the upper bound. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index f2e95d85d40..d61728fd904 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1310,6 +1310,16 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ReplaceRangeToWithUntilIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ReplaceUntilWithRangeToIntention + Kotlin + + this.left to this.right + is KtDotQualifiedExpression -> this.receiverExpression to this.callExpression?.valueArguments?.singleOrNull()?.getArgumentExpression() + else -> null +} + +private val REGEX_RANGE_TO = """kotlin.(Char|Byte|Short|Int|Long).rangeTo""".toRegex() + +class ReplaceRangeToWithUntilIntention : SelfTargetingIntention( + KtExpression::class.java, + "Replace with 'until' function call" +) { + + override fun isApplicableTo(element: KtExpression, caretOffset: Int): Boolean { + if (element !is KtBinaryExpression && element !is KtDotQualifiedExpression) return false + + val fqName = element.getCallableDescriptor()?.fqNameUnsafe?.asString() ?: return false + if (!fqName.matches(REGEX_RANGE_TO)) return false + return element.getArguments()?.second?.isMinusOne() == true + } + + override fun applyTo(element: KtExpression, editor: Editor?) { + val args = element.getArguments() ?: return + element.replace(KtPsiFactory(element).createExpressionByPattern("$0 until $1", + args.first ?: return, + (args.second as? KtBinaryExpression)?.left ?: return)) + } + + private fun KtExpression.isMinusOne(): Boolean { + if (this !is KtBinaryExpression) return false + if (operationToken != KtTokens.MINUS) return false + + val right = right as? KtConstantExpression ?: return false + val context = right.analyze(BodyResolveMode.PARTIAL) + val constantValue = ConstantExpressionEvaluator.getConstant(right, context)?.toConstantValue(right.getType(context) ?: return false) + val rightValue = (constantValue?.value as? Number)?.toInt() ?: return false + return rightValue == 1 + } + +} + +class ReplaceUntilWithRangeToIntention : SelfTargetingIntention(KtExpression::class.java, "Replace with '..' operator") { + override fun isApplicableTo(element: KtExpression, caretOffset: Int): Boolean { + if (element !is KtBinaryExpression && element !is KtDotQualifiedExpression) return false + val fqName = element.getCallableDescriptor()?.fqNameUnsafe?.asString() ?: return false + return fqName == "kotlin.ranges.until" + } + + override fun applyTo(element: KtExpression, editor: Editor?) { + val args = element.getArguments() ?: return + element.replace(KtPsiFactory(element).createExpressionByPattern("$0..$1 - 1", args.first ?: return, args.second ?: return)) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/.intention b/idea/testData/intentions/replaceRangeToWithUntil/.intention new file mode 100644 index 00000000000..5faf679b8df --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceRangeToWithUntilIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/closedRange.kt b/idea/testData/intentions/replaceRangeToWithUntil/closedRange.kt new file mode 100644 index 00000000000..cd4ffc2c903 --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/closedRange.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(a: Float) { + 1f..a - 1 +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/minusTwo.kt b/idea/testData/intentions/replaceRangeToWithUntil/minusTwo.kt new file mode 100644 index 00000000000..fb48d5920e5 --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/minusTwo.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(a: Int) { + for (i in 0..a - 2) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/notMinusOne.kt b/idea/testData/intentions/replaceRangeToWithUntil/notMinusOne.kt new file mode 100644 index 00000000000..3ece06af44f --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/notMinusOne.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(a: Int) { + for (i in 0..a) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/operator.kt b/idea/testData/intentions/replaceRangeToWithUntil/operator.kt new file mode 100644 index 00000000000..fb00b189ad2 --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/operator.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0..a - 1) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/operator.kt.after b/idea/testData/intentions/replaceRangeToWithUntil/operator.kt.after new file mode 100644 index 00000000000..6109f8b93bd --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/operator.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0 until a) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt b/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt new file mode 100644 index 00000000000..135db06bf6b --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Long) { + for (i in 1L..a - 1L) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt.after b/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt.after new file mode 100644 index 00000000000..7faae7b6a3b --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Long) { + for (i in 1L until a) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/plusOne.kt b/idea/testData/intentions/replaceRangeToWithUntil/plusOne.kt new file mode 100644 index 00000000000..814ac76fb49 --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/plusOne.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(a: Int) { + for (i in 0..a + 1) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt b/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt new file mode 100644 index 00000000000..72fab0da864 --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0.rangeTo(a - 1)) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt.after b/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt.after new file mode 100644 index 00000000000..6109f8b93bd --- /dev/null +++ b/idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0 until a) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceUntilWithRangeTo/.intention b/idea/testData/intentions/replaceUntilWithRangeTo/.intention new file mode 100644 index 00000000000..bc5b75b9b88 --- /dev/null +++ b/idea/testData/intentions/replaceUntilWithRangeTo/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ReplaceUntilWithRangeToIntention \ No newline at end of file diff --git a/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt b/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt new file mode 100644 index 00000000000..6109f8b93bd --- /dev/null +++ b/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0 until a) { + + } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt.after b/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt.after new file mode 100644 index 00000000000..fb00b189ad2 --- /dev/null +++ b/idea/testData/intentions/replaceUntilWithRangeTo/simple.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(a: Int) { + for (i in 0..a - 1) { + + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 318e9d1eace..270fd38ab15 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -10070,6 +10070,57 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/replaceRangeToWithUntil") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceRangeToWithUntil extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceRangeToWithUntil() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceRangeToWithUntil"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("closedRange.kt") + public void testClosedRange() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/closedRange.kt"); + doTest(fileName); + } + + @TestMetadata("minusTwo.kt") + public void testMinusTwo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/minusTwo.kt"); + doTest(fileName); + } + + @TestMetadata("notMinusOne.kt") + public void testNotMinusOne() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/notMinusOne.kt"); + doTest(fileName); + } + + @TestMetadata("operator.kt") + public void testOperator() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/operator.kt"); + doTest(fileName); + } + + @TestMetadata("operatorLong.kt") + public void testOperatorLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/operatorLong.kt"); + doTest(fileName); + } + + @TestMetadata("plusOne.kt") + public void testPlusOne() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/plusOne.kt"); + doTest(fileName); + } + + @TestMetadata("rangeTo.kt") + public void testRangeTo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceRangeToWithUntil/rangeTo.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/replaceSubstringWithDropLast") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -10220,6 +10271,21 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/replaceUntilWithRangeTo") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceUntilWithRangeTo extends AbstractIntentionTest { + public void testAllFilesPresentInReplaceUntilWithRangeTo() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceUntilWithRangeTo"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUntilWithRangeTo/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/replaceWithOperatorAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)