mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Make "ReplaceRangeToWithUntil" suggest using rangeUntil operator
instead of `until` Make it suggest only when it's possible (language level is 1.8 at least and OptIn is allowed) Tests are copied from ReplaceRangeToWithUntilInspection. (only closedRange.kt test is changed) ^KTIJ-22372 Fixed GitOrigin-RevId: dded3ea9395ffa9269be94c47545c18048fbe504
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e1de80a85b
commit
15ed569d36
@@ -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
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports calls to <code>rangeTo</code> or the <code>..</code> operator instead of calls to <code>..<</code>.
|
||||
<p>Using corresponding functions makes your code simpler.</p>
|
||||
<p>The quick-fix replaces <code>rangeTo</code> or the <code>..</code> call with <code>..<</code>.</p>
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..a - 1) {
|
||||
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>After the quick-fix is applied:</p>
|
||||
<pre><code>
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..<a) {
|
||||
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<!-- tooltip end -->
|
||||
</body>
|
||||
</html>
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-18
@@ -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)
|
||||
|
||||
+48
@@ -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 {
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.ReplaceRangeToWithRangeUntilInspection
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Float) {
|
||||
1f<caret>..a - 1
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Float) {
|
||||
1f..<a
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// PROBLEM: none
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..a - 2<caret>) {
|
||||
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// PROBLEM: none
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..a<caret>) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..<caret>a - 1) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..<a) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Long) {
|
||||
for (i in 1L<caret>..a - 1L) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Long) {
|
||||
for (i in 1L..<a) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun example() {
|
||||
val max = 5
|
||||
for (i in 0..<caret>(max - 1)) {
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun example() {
|
||||
val max = 5
|
||||
for (i in 0..<max) {
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// PROBLEM: none
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..a + 1<caret>) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in <caret>0.rangeTo(a - 1)) {
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// LANGUAGE_VERSION: 1.8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun foo(a: Int) {
|
||||
for (i in 0..<a) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1651,6 +1651,14 @@
|
||||
language="kotlin"
|
||||
key="inspection.replace.range.to.with.until.display.name" bundle="messages.KotlinBundle"/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ReplaceRangeToWithRangeUntilInspection"
|
||||
groupPath="Kotlin"
|
||||
groupBundle="messages.KotlinBundle" groupKey="group.names.style.issues"
|
||||
enabledByDefault="true"
|
||||
level="WEAK WARNING"
|
||||
language="kotlin"
|
||||
key="inspection.replace.range.to.with.rangeUntil.display.name" bundle="messages.KotlinBundle"/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.MemberVisibilityCanBePrivateInspection"
|
||||
groupPath="Kotlin"
|
||||
groupBundle="messages.KotlinBundle" groupKey="group.names.style.issues"
|
||||
|
||||
Reference in New Issue
Block a user