From b51880332eb2e3b7215fb82af04c861cb9b4f183 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Tue, 26 Jun 2018 16:29:18 +0300 Subject: [PATCH] Migration inspection for fromClosedRange() call (KT-17176) #KT-17176 Open --- .../FromClosedRangeMigration.html | 8 ++ idea/src/META-INF/plugin.xml | 9 ++ idea/src/META-INF/plugin.xml.172 | 9 ++ idea/src/META-INF/plugin.xml.173 | 9 ++ idea/src/META-INF/plugin.xml.182 | 9 ++ idea/src/META-INF/plugin.xml.as31 | 9 ++ idea/src/META-INF/plugin.xml.as32 | 9 ++ .../FromClosedRangeMigrationInspection.kt | 107 ++++++++++++++++++ .../inspectionData/expected.xml | 22 ++++ .../inspectionData/inspections.test | 1 + .../migrationFromClosedRange/test.kt | 22 ++++ .../codeInsight/InspectionTestGenerated.java | 5 + 12 files changed, 219 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/FromClosedRangeMigration.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/migration/FromClosedRangeMigrationInspection.kt create mode 100644 idea/testData/inspections/migrationFromClosedRange/inspectionData/expected.xml create mode 100644 idea/testData/inspections/migrationFromClosedRange/inspectionData/inspections.test create mode 100644 idea/testData/inspections/migrationFromClosedRange/test.kt diff --git a/idea/resources/inspectionDescriptions/FromClosedRangeMigration.html b/idea/resources/inspectionDescriptions/FromClosedRangeMigration.html new file mode 100644 index 00000000000..932db285d0e --- /dev/null +++ b/idea/resources/inspectionDescriptions/FromClosedRangeMigration.html @@ -0,0 +1,8 @@ + + +

+ Since Kotlin 1.3 it's prohibited to call IntProgression.fromClosedRange() and LongProgression.fromClosedRange() with + MIN_VALUE step. All such calls should be checked during migration to Kotlin 1.3+. +

+ + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 98500f3e5f2..12200cc0362 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 49e8676d089..55def149811 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2905,6 +2905,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index faec942abe3..d609db88c74 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 31f698f7c4c..74767785abd 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2909,6 +2909,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 723fff3c575..12f01eed3df 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 115bf1b8046..b5d4ca3b7bb 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2908,6 +2908,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/FromClosedRangeMigrationInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/FromClosedRangeMigrationInspection.kt new file mode 100644 index 00000000000..c734617cfd0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/FromClosedRangeMigrationInspection.kt @@ -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()" + } +} diff --git a/idea/testData/inspections/migrationFromClosedRange/inspectionData/expected.xml b/idea/testData/inspections/migrationFromClosedRange/inspectionData/expected.xml new file mode 100644 index 00000000000..f493e15e6aa --- /dev/null +++ b/idea/testData/inspections/migrationFromClosedRange/inspectionData/expected.xml @@ -0,0 +1,22 @@ + + + test.kt + 6 + It's prohibited to call IntProgression.fromClosedRange() with MIN_VALUE step since 1.3 + + + test.kt + 7 + It's prohibited to call IntProgression.fromClosedRange() with MIN_VALUE step since 1.3 + + + test.kt + 14 + It's prohibited to call LongProgression.fromClosedRange() with MIN_VALUE step since 1.3 + + + test.kt + 15 + It's prohibited to call LongProgression.fromClosedRange() with MIN_VALUE step since 1.3 + + diff --git a/idea/testData/inspections/migrationFromClosedRange/inspectionData/inspections.test b/idea/testData/inspections/migrationFromClosedRange/inspectionData/inspections.test new file mode 100644 index 00000000000..3ddfde35744 --- /dev/null +++ b/idea/testData/inspections/migrationFromClosedRange/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.migration.FromClosedRangeMigrationInspection \ No newline at end of file diff --git a/idea/testData/inspections/migrationFromClosedRange/test.kt b/idea/testData/inspections/migrationFromClosedRange/test.kt new file mode 100644 index 00000000000..87a15178b47 --- /dev/null +++ b/idea/testData/inspections/migrationFromClosedRange/test.kt @@ -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 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index 7795fc663e0..36b86ddacfb 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -204,6 +204,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest { 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") public void testNaming_class_inspectionData_Inspections_test() throws Exception { runTest("idea/testData/inspections/naming/class/inspectionData/inspections.test");