diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties index 12b9c3d30486..e1770dc41e58 100644 --- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties +++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties @@ -1496,7 +1496,8 @@ replace.int.range.end.inclusive.with.last.quick.fix.text=Replace 'endInclusive' replace.int.range.start.with.first.quick.fix.text=Replace 'start' with 'first' could.be.replaced.with.unboxed.last=Could be replaced with unboxed 'last' could.be.replaced.with.unboxed.first=Could be replaced with unboxed 'first' -replace.with.until.quick.fix.text=Replace with until +replace.with.until.quick.fix.text=Replace with 'until' +replace.with.rangeUntil.quick.fix.text=Replace with '..<' replace.with.string.literal.fix.family.name=Replace with string templates replace.tostring.with.string.template=Replace 'toString' with string template replace.to.with.infix.form.quickfix.text=Replace 'to' with infix form @@ -2120,6 +2121,7 @@ inspection.wrap.unary.operator.display.name=Ambiguous unary operator use with nu inspection.nullable.boolean.elvis.display.name=Equality check can be used instead of elvis for nullable boolean check inspection.member.visibility.can.be.private.display.name=Class member can have 'private' visibility inspection.replace.range.to.with.until.display.name='rangeTo' or the '..' call should be replaced with 'until' +inspection.replace.range.to.with.rangeUntil.display.name='rangeTo' or the '..' call should be replaced with '..<' inspection.recursive.property.accessor.display.name=Recursive property accessor inspection.replace.array.of.with.literal.display.name='arrayOf' call can be replaced with array literal [...] inspection.copy.without.named.arguments.display.name='copy' method of data class is called without named arguments diff --git a/plugins/kotlin/code-insight/descriptions/resources-en/inspectionDescriptions/ReplaceRangeToWithRangeUntil.html b/plugins/kotlin/code-insight/descriptions/resources-en/inspectionDescriptions/ReplaceRangeToWithRangeUntil.html new file mode 100644 index 000000000000..edb138cea076 --- /dev/null +++ b/plugins/kotlin/code-insight/descriptions/resources-en/inspectionDescriptions/ReplaceRangeToWithRangeUntil.html @@ -0,0 +1,24 @@ + +
+Reports calls torangeTo or the .. operator instead of calls to ..<.
+Using corresponding functions makes your code simpler.
+The quick-fix replaces rangeTo or the .. call with ..<.
Example:
+
+ fun foo(a: Int) {
+ for (i in 0..a - 1) {
+
+ }
+ }
+
+After the quick-fix is applied:
+
+ fun foo(a: Int) {
+ for (i in 0..
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt
index 711af1410f35..c0ea65da1fd8 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt
@@ -48,7 +48,7 @@ class ConvertTwoComparisonsToRangeCheckInspection :
val rangeData = generateRangeExpressionData(element) ?: return
val replaced = element.replace(rangeData.createExpression())
(replaced as? KtBinaryExpression)?.right?.let {
- ReplaceRangeToWithUntilInspection.applyFixIfApplicable(it)
+ AbstractReplaceRangeToWithRangeUntilInspection.applyFixIfApplicable(it)
}
}
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt
index 8a6fca20638d..1d0fa9fa7d42 100644
--- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt
@@ -7,26 +7,32 @@ import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.config.LanguageFeature
+import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.getArguments
import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.typeUtil.isDouble
import org.jetbrains.kotlin.types.typeUtil.isFloat
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType
+import org.jetbrains.kotlin.resolve.checkers.OptInUsageChecker.Companion.isOptInAllowed
-class ReplaceRangeToWithUntilInspection : AbstractRangeInspection() {
+sealed class AbstractReplaceRangeToWithRangeUntilInspection : AbstractRangeInspection() {
override fun visitRangeTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
- if (!isApplicable(expression, context)) return
- holder.registerProblem(
- expression,
- KotlinBundle.message("inspection.replace.range.to.with.until.display.name"),
- ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
- ReplaceWithUntilQuickFix()
- )
+ val useRangeUntil = expression.possibleToUseRangeUntil()
+ if (!isApplicable(expression, context, useRangeUntil)) return
+ if (useRangeUntil xor (this is ReplaceRangeToWithRangeUntilInspection)) {
+ return
+ }
+ val desc =
+ if (useRangeUntil) KotlinBundle.message("inspection.replace.range.to.with.rangeUntil.display.name")
+ else KotlinBundle.message("inspection.replace.range.to.with.until.display.name")
+ holder.registerProblem(expression, desc, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, ReplaceWithUntilQuickFix(useRangeUntil))
}
override fun visitUntilOrRangeUntil(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
@@ -35,35 +41,50 @@ class ReplaceRangeToWithUntilInspection : AbstractRangeInspection() {
override fun visitDownTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
}
- class ReplaceWithUntilQuickFix : LocalQuickFix {
- override fun getName() = KotlinBundle.message("replace.with.until.quick.fix.text")
+ private class ReplaceWithUntilQuickFix(private val useRangeUntil: Boolean) : LocalQuickFix {
+ override fun getName(): String =
+ if (useRangeUntil) KotlinBundle.message("replace.with.rangeUntil.quick.fix.text")
+ else KotlinBundle.message("replace.with.until.quick.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement as KtExpression
- applyFix(element)
+ applyFix(element, useRangeUntil)
}
}
companion object {
fun applyFixIfApplicable(expression: KtExpression) {
- if (isApplicable(expression, expression.analyze(BodyResolveMode.PARTIAL_NO_ADDITIONAL))) applyFix(expression)
+ val useRangeUntil = expression.possibleToUseRangeUntil()
+ if (isApplicable(expression, expression.analyze(BodyResolveMode.PARTIAL_NO_ADDITIONAL), useRangeUntil)) {
+ applyFix(expression, useRangeUntil)
+ }
}
- private fun isApplicable(expression: KtExpression, context: BindingContext): Boolean {
+ private fun KtElement.possibleToUseRangeUntil(): Boolean =
+ languageVersionSettings.supportsFeature(LanguageFeature.RangeUntilOperator) &&
+ isOptInAllowed(
+ FqName("kotlin.ExperimentalStdlibApi"),
+ languageVersionSettings,
+ analyze(BodyResolveMode.PARTIAL_NO_ADDITIONAL)
+ )
+
+ private fun isApplicable(expression: KtExpression, context: BindingContext, useRangeUntil: Boolean): Boolean {
val (left, right) = expression.getArguments() ?: return false
// `until` isn't available for floating point numbers
- fun KtExpression.isIntegerType() = context.getType(this)
- ?.let { it.isPrimitiveNumberType() && !it.isDouble() && !it.isFloat() }
- return right?.deparenthesize()?.isMinusOne() == true && left?.isIntegerType() == true && right.isIntegerType() == true
+ fun KtExpression.isRangeUntilOrUntilApplicable() = context.getType(this)
+ ?.let { it.isPrimitiveNumberType() && (useRangeUntil || !it.isDouble() && !it.isFloat()) }
+ return right?.deparenthesize()?.isMinusOne() == true &&
+ left?.isRangeUntilOrUntilApplicable() == true && right.isRangeUntilOrUntilApplicable() == true
}
- private fun applyFix(element: KtExpression) {
+ private fun applyFix(element: KtExpression, useRangeUntil: Boolean) {
val args = element.getArguments() ?: return
+ val operator = if (useRangeUntil) "..<" else " until "
element.replace(
KtPsiFactory(element).createExpressionByPattern(
- "$0 until $1",
+ "$0$operator$1",
args.first ?: return,
(args.second?.deparenthesize() as? KtBinaryExpression)?.left ?: return
)
@@ -81,4 +102,10 @@ class ReplaceRangeToWithUntilInspection : AbstractRangeInspection() {
}
}
+/**
+ * Tests: [org.jetbrains.kotlin.idea.inspections.LocalInspectionTestGenerated.ReplaceRangeToWithUntil]
+ */
+class ReplaceRangeToWithUntilInspection : AbstractReplaceRangeToWithRangeUntilInspection()
+class ReplaceRangeToWithRangeUntilInspection : AbstractReplaceRangeToWithRangeUntilInspection()
+
private fun KtExpression.deparenthesize() = KtPsiUtil.safeDeparenthesize(this)
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index be66efd38b38..57d4545bdfbd 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -13003,6 +13003,54 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
}
}
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("testData/inspectionsLocal/replaceRangeToWithRangeUntil")
+ public static class ReplaceRangeToWithRangeUntil extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("closedRange.kt")
+ public void testClosedRange() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/closedRange.kt");
+ }
+
+ @TestMetadata("minusTwo.kt")
+ public void testMinusTwo() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/minusTwo.kt");
+ }
+
+ @TestMetadata("notMinusOne.kt")
+ public void testNotMinusOne() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/notMinusOne.kt");
+ }
+
+ @TestMetadata("operator.kt")
+ public void testOperator() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/operator.kt");
+ }
+
+ @TestMetadata("operatorLong.kt")
+ public void testOperatorLong() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/operatorLong.kt");
+ }
+
+ @TestMetadata("parentheses.kt")
+ public void testParentheses() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/parentheses.kt");
+ }
+
+ @TestMetadata("plusOne.kt")
+ public void testPlusOne() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/plusOne.kt");
+ }
+
+ @TestMetadata("rangeTo.kt")
+ public void testRangeTo() throws Exception {
+ runTest("testData/inspectionsLocal/replaceRangeToWithRangeUntil/rangeTo.kt");
+ }
+ }
+
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal/replaceRangeToWithUntil")
public static class ReplaceRangeToWithUntil extends AbstractLocalInspectionTest {
diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/.inspection b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/.inspection
new file mode 100644
index 000000000000..8db066829073
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.ReplaceRangeToWithRangeUntilInspection
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/closedRange.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/closedRange.kt
new file mode 100644
index 000000000000..ff036631c323
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceRangeToWithRangeUntil/closedRange.kt
@@ -0,0 +1,7 @@
+// WITH_STDLIB
+// LANGUAGE_VERSION: 1.8
+
+@OptIn(ExperimentalStdlibApi::class)
+fun foo(a: Float) {
+ 1f