Shortcut quick fix for migrating all coroutines in the project (KT-27164)

#KT-27164 Fixed
This commit is contained in:
Nikolay Krasko
2018-09-28 15:08:20 +03:00
parent 282407c4a7
commit b09ec3cbb3
4 changed files with 98 additions and 3 deletions
@@ -5,9 +5,15 @@
package org.jetbrains.kotlin.idea.inspections.migration
import com.intellij.analysis.AnalysisScope
import com.intellij.codeInspection.*
import com.intellij.codeInspection.actions.RunInspectionIntention
import com.intellij.codeInspection.ex.InspectionManagerEx
import com.intellij.codeInspection.ex.InspectionProfileImpl
import com.intellij.codeInspection.ex.InspectionToolWrapper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.profile.codeInspection.InspectionProjectProfileManager
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.descriptors.CallableDescriptor
@@ -22,6 +28,7 @@ import org.jetbrains.kotlin.idea.quickfix.migration.MigrationFix
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.name.FqName
@@ -82,6 +89,52 @@ private interface CoroutineMigrationProblem {
fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean
}
// Shortcut quick fix for running inspection in the project scope.
// Should work like RunInspectionAction.runInspection.
private class ObsoleteCoroutineUsageInWholeFix : LocalQuickFix {
override fun getFamilyName(): String = "Fix experimental coroutines usages in the project"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val toolWrapper = InspectionProjectProfileManager.getInstance(project).currentProfile.getInspectionTool(
ObsoleteExperimentalCoroutinesInspection().shortName,
project
)!!
runToolInProject(project, toolWrapper)
}
override fun startInWriteAction(): Boolean = false
companion object {
val INSTANCE = ObsoleteCoroutineUsageInWholeFix()
private fun runToolInProject(project: Project, toolWrapper: InspectionToolWrapper<*, *>) {
val managerEx = InspectionManager.getInstance(project) as InspectionManagerEx
val kotlinSourcesScope: GlobalSearchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.allScope(project), project)
val cleanupScope = AnalysisScope(kotlinSourcesScope, project)
val cleanupToolProfile = runInInspectionProfileInitMode { RunInspectionIntention.createProfile(toolWrapper, managerEx, null) }
managerEx.createNewGlobalContext(false)
.codeCleanup(cleanupScope, cleanupToolProfile, "Apply in the project: " + toolWrapper.displayName, null, false)
}
// Overcome failure during profile createin because of absent tools in tests
inline fun <T> runInInspectionProfileInitMode(runnable: () -> T): T {
return if (!ApplicationManager.getApplication().isUnitTestMode) {
runnable()
} else {
val old = InspectionProfileImpl.INIT_INSPECTIONS
try {
InspectionProfileImpl.INIT_INSPECTIONS = true
runnable()
} finally {
InspectionProfileImpl.INIT_INSPECTIONS = old
}
}
}
}
}
/**
* There should be a single fix class with the same family name, this way it can be executed for all found coroutines problems from UI.
*/
@@ -117,6 +170,14 @@ private fun isTopLevelCallForReplace(simpleNameExpression: KtSimpleNameExpressio
return !isInIndex
}
private fun fixesWithWholeProject(isOnTheFly: Boolean, fix: LocalQuickFix): Array<LocalQuickFix> {
if (!isOnTheFly) {
return arrayOf(fix)
}
return arrayOf(fix, ObsoleteCoroutineUsageInWholeFix.INSTANCE)
}
private class ObsoleteTopLevelFunctionUsage(
val textMarker: String, val oldFqName: String, val newFqName: String
) : CoroutineMigrationProblem {
@@ -131,7 +192,7 @@ private class ObsoleteTopLevelFunctionUsage(
simpleNameExpression,
"`$newFqName` is expected to be used since Kotlin 1.3",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
ObsoleteCoroutineUsageFix(fix)
*fixesWithWholeProject(isOnTheFly, ObsoleteCoroutineUsageFix(fix))
)
return true
@@ -167,7 +228,7 @@ private class ObsoleteExtensionFunctionUsage(
simpleNameExpression,
"Methods are absent in coroutines class since 1.3",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
ObsoleteCoroutineUsageFix(fix)
*fixesWithWholeProject(isOnTheFly, ObsoleteCoroutineUsageFix(fix))
)
return true
@@ -207,7 +268,7 @@ private class ExperimentalImportUsage : CoroutineMigrationProblem {
reportExpression,
"Experimental coroutines usages are obsolete since 1.3",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
ObsoleteCoroutineUsageFix(ObsoleteCoroutineImportFix)
*fixesWithWholeProject(isOnTheFly, ObsoleteCoroutineUsageFix(ObsoleteCoroutineImportFix))
)
return true
@@ -0,0 +1,16 @@
// "Fix experimental coroutines usages in the project" "true"
// WITH_RUNTIME
package migrate
import kotlin.coroutines.experimental.buildIterator
import kotlin.coroutines.experimental.buildSequence
fun main(args: Array<String>) {
val one = <caret>buildSequence {
yield(1)
}
val two = buildIterator {
yield(1)
}
}
@@ -0,0 +1,13 @@
// "Fix experimental coroutines usages in the project" "true"
// WITH_RUNTIME
package migrate
fun main(args: Array<String>) {
val one = sequence {
yield(1)
}
val two = iterator {
yield(1)
}
}
@@ -8347,6 +8347,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/obsoleteCoroutines/buildSequenceImport.kt");
}
@TestMetadata("inProjectFix.kt")
public void testInProjectFix() throws Exception {
runTest("idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt");
}
@TestMetadata("resume.kt")
public void testResume() throws Exception {
runTest("idea/testData/quickfix/obsoleteCoroutines/resume.kt");