diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt index 04ba03e62a1..4074d864a04 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt @@ -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 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 { + 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 diff --git a/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt b/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt new file mode 100644 index 00000000000..ed16d288749 --- /dev/null +++ b/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt @@ -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) { + val one = buildSequence { + yield(1) + } + + val two = buildIterator { + yield(1) + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt.after b/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt.after new file mode 100644 index 00000000000..12a0b3c2d70 --- /dev/null +++ b/idea/testData/quickfix/obsoleteCoroutines/inProjectFix.kt.after @@ -0,0 +1,13 @@ +// "Fix experimental coroutines usages in the project" "true" +// WITH_RUNTIME +package migrate + +fun main(args: Array) { + val one = sequence { + yield(1) + } + + val two = iterator { + yield(1) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index cef6349c444..1e1bf0f3aa8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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");