Migration inspection for fromClosedRange() call (KT-17176)
#KT-17176 Open
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Since Kotlin 1.3 it's prohibited to call <i>IntProgression.fromClosedRange()</i> and <i>LongProgression.fromClosedRange()</i> with
|
||||||
|
<i>MIN_VALUE</i> step. All such calls should be checked during migration to Kotlin 1.3+.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -2905,6 +2905,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -2909,6 +2909,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection"
|
||||||
|
displayName="MIN_VALUE step in fromClosedRange() since 1.3"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Migration"
|
||||||
|
enabledByDefault="false"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 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.migration
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import org.jetbrains.kotlin.config.LanguageVersion
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||||
|
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
import org.jetbrains.kotlin.psi.simpleNameExpressionVisitor
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
class FromClosedRangeMigrationInspection : AbstractKotlinInspection() {
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
|
||||||
|
simpleNameExpressionVisitor(fun(simpleNameExpression) {
|
||||||
|
val callExpression = simpleNameExpression.parent as? KtCallExpression ?: return
|
||||||
|
if (simpleNameExpression.text != SHORT_NAME) return
|
||||||
|
|
||||||
|
run {
|
||||||
|
val versionAtLeast13 = simpleNameExpression.languageVersionSettings.languageVersion >= LanguageVersion.KOTLIN_1_3
|
||||||
|
if (!versionAtLeast13 && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val descriptor = simpleNameExpression.resolveMainReferenceToDescriptors().firstOrNull() ?: return
|
||||||
|
val callableDescriptor = descriptor as? CallableDescriptor ?: return
|
||||||
|
|
||||||
|
val resolvedToFqName = callableDescriptor.fqNameOrNull()?.asString() ?: return
|
||||||
|
if (resolvedToFqName != INT_FROM_CLOSE_RANGE_FQNAME && resolvedToFqName != LONG_FROM_CLOSE_RANGE_FQNAME) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val stepParameter = callableDescriptor.valueParameters.getOrNull(2) ?: return
|
||||||
|
if (stepParameter.name.asString() != "step") return
|
||||||
|
|
||||||
|
val resolvedCall = callExpression.resolveToCall() ?: return
|
||||||
|
val resolvedValueArgument = resolvedCall.valueArguments[stepParameter] as? ExpressionValueArgument ?: return
|
||||||
|
val argumentExpression = resolvedValueArgument.valueArgument?.getArgumentExpression() ?: return
|
||||||
|
|
||||||
|
val constant = ConstantExpressionEvaluator.getConstant(argumentExpression, argumentExpression.analyze(BodyResolveMode.PARTIAL))
|
||||||
|
if (constant != null) {
|
||||||
|
val value = (constant as? TypedCompileTimeConstant<*>)?.constantValue?.value
|
||||||
|
if (value != null) {
|
||||||
|
if ((resolvedToFqName == INT_FROM_CLOSE_RANGE_FQNAME && Int.MIN_VALUE == value) ||
|
||||||
|
(resolvedToFqName == LONG_FROM_CLOSE_RANGE_FQNAME && Long.MIN_VALUE == value)
|
||||||
|
) {
|
||||||
|
report(holder, simpleNameExpression, resolvedToFqName, isOnTheFly, isError = true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't report for constants other than MIN_VALUE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
report(holder, simpleNameExpression, resolvedToFqName, isOnTheFly, isError = false)
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun report(
|
||||||
|
holder: ProblemsHolder,
|
||||||
|
simpleNameExpression: KtSimpleNameExpression,
|
||||||
|
resolvedToFqName: String,
|
||||||
|
isOnTheFly: Boolean,
|
||||||
|
isError: Boolean
|
||||||
|
) {
|
||||||
|
val desc = when (resolvedToFqName) {
|
||||||
|
INT_FROM_CLOSE_RANGE_FQNAME -> INT_FROM_CLOSE_RANGE_DESC
|
||||||
|
LONG_FROM_CLOSE_RANGE_FQNAME -> LONG_FROM_CLOSE_RANGE_DESC
|
||||||
|
else -> throw IllegalArgumentException("Can't process $resolvedToFqName")
|
||||||
|
}
|
||||||
|
|
||||||
|
val problemDescriptor = holder.manager.createProblemDescriptor(
|
||||||
|
simpleNameExpression,
|
||||||
|
simpleNameExpression,
|
||||||
|
"It's prohibited to call $desc with MIN_VALUE step since 1.3",
|
||||||
|
if (isError) ProblemHighlightType.ERROR else ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||||
|
isOnTheFly
|
||||||
|
)
|
||||||
|
|
||||||
|
holder.registerProblem(problemDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SHORT_NAME = "fromClosedRange"
|
||||||
|
|
||||||
|
private const val INT_FROM_CLOSE_RANGE_FQNAME = "kotlin.ranges.IntProgression.Companion.fromClosedRange"
|
||||||
|
private const val INT_FROM_CLOSE_RANGE_DESC = "IntProgression.fromClosedRange()"
|
||||||
|
|
||||||
|
private const val LONG_FROM_CLOSE_RANGE_FQNAME = "kotlin.ranges.LongProgression.Companion.fromClosedRange"
|
||||||
|
private const val LONG_FROM_CLOSE_RANGE_DESC = "LongProgression.fromClosedRange()"
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<problems>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>6</line>
|
||||||
|
<description>It's prohibited to call IntProgression.fromClosedRange() with MIN_VALUE step since 1.3</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>7</line>
|
||||||
|
<description>It's prohibited to call IntProgression.fromClosedRange() with MIN_VALUE step since 1.3</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>14</line>
|
||||||
|
<description>It's prohibited to call LongProgression.fromClosedRange() with MIN_VALUE step since 1.3</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>15</line>
|
||||||
|
<description>It's prohibited to call LongProgression.fromClosedRange() with MIN_VALUE step since 1.3</description>
|
||||||
|
</problem>
|
||||||
|
</problems>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package fromClosedRange
|
||||||
|
|
||||||
|
import kotlin.ranges.IntProgression.Companion.fromClosedRange
|
||||||
|
|
||||||
|
fun testInt(rangeStart: Int, rangeEnd: Int, step: Int) {
|
||||||
|
fromClosedRange(12, 143, step) // report
|
||||||
|
fromClosedRange(12, 143, Int.MIN_VALUE) // report
|
||||||
|
|
||||||
|
fromClosedRange(step = 12, rangeEnd = rangeEnd, rangeStart = rangeStart) // do not report
|
||||||
|
fromClosedRange(12, 143, 2) // do not report
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLong(rangeStart: Long, rangeEnd: Long, step: Long) {
|
||||||
|
LongProgression.fromClosedRange(12, 143, step) // report
|
||||||
|
LongProgression.fromClosedRange(12, 143, Long.MIN_VALUE) // report
|
||||||
|
|
||||||
|
LongProgression.fromClosedRange(12, 143, 2.toLong()) // do no report
|
||||||
|
LongProgression.fromClosedRange(step = 12, rangeEnd = rangeEnd, rangeStart = rangeStart) // do not report
|
||||||
|
LongProgression.fromClosedRange(12, 143, 2) // do not report
|
||||||
|
LongProgression.fromClosedRange(12, 143, 2L) // do not report
|
||||||
|
LongProgression.fromClosedRange(12, 143, Int.MIN_VALUE) // do not report
|
||||||
|
}
|
||||||
+5
@@ -204,6 +204,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
|
|||||||
runTest("idea/testData/inspections/memberVisibilityCanBePrivate/inspectionData/inspections.test");
|
runTest("idea/testData/inspections/memberVisibilityCanBePrivate/inspectionData/inspections.test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("migrationFromClosedRange/inspectionData/inspections.test")
|
||||||
|
public void testMigrationFromClosedRange_inspectionData_Inspections_test() throws Exception {
|
||||||
|
runTest("idea/testData/inspections/migrationFromClosedRange/inspectionData/inspections.test");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("naming/class/inspectionData/inspections.test")
|
@TestMetadata("naming/class/inspectionData/inspections.test")
|
||||||
public void testNaming_class_inspectionData_Inspections_test() throws Exception {
|
public void testNaming_class_inspectionData_Inspections_test() throws Exception {
|
||||||
runTest("idea/testData/inspections/naming/class/inspectionData/inspections.test");
|
runTest("idea/testData/inspections/naming/class/inspectionData/inspections.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user