From 082801efb477053800311c86dda1515b3eba39ab Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Tue, 18 Sep 2018 13:39:59 +0300 Subject: [PATCH] Refactoring: move each problem to a separate class --- ...bsoleteExperimentalCoroutinesInspection.kt | 229 ++++++++++-------- 1 file changed, 129 insertions(+), 100 deletions(-) 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 2b7377523ca..982c99a888b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt @@ -42,64 +42,122 @@ class ObsoleteExperimentalCoroutinesInspection : AbstractKotlinInspection(), Cle } } - when (simpleNameExpression.text) { - RESUME_MARKER, RESUME_WITH_EXCEPTION_MARKER -> { - if (simpleNameExpression.parent !is KtCallExpression) return - - val descriptor = simpleNameExpression.resolveMainReferenceToDescriptors().firstOrNull() ?: return - val callableDescriptor = descriptor as? CallableDescriptor ?: return - - val resolvedToFqName = callableDescriptor.fqNameOrNull()?.asString() ?: return - val fixFqName = when (resolvedToFqName) { - RESUME_FUNCTION_FQNAME -> RESUME_FUNCTION_FIX_FQNAME - RESUME_WITH_EXCEPTION_FQNAME -> RESUME_WITH_EXCEPTION_FIX_FQNAME - else -> null - } ?: return - - val problemDescriptor = holder.manager.createProblemDescriptor( - simpleNameExpression, - simpleNameExpression, - "Methods are absent in coroutines class since 1.3", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - ObsoleteCoroutineUsageFix.createExtensionFix(fixFqName) - ) - - holder.registerProblem(problemDescriptor) - } - - EXPERIMENTAL_COROUTINES_MARKER -> { - val parent = simpleNameExpression.parent as? KtExpression ?: return - val reportExpression = parent as? KtDotQualifiedExpression ?: simpleNameExpression - - findBinding(simpleNameExpression) ?: return - - val problemDescriptor = holder.manager.createProblemDescriptor( - reportExpression, - reportExpression, - "Experimental coroutines usages are obsolete since 1.3", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - ObsoleteCoroutineUsageFix.createImportFix() - ) - - holder.registerProblem(problemDescriptor) + for (registeredProblem in PROBLEMS) { + if (registeredProblem.report(holder, isOnTheFly, simpleNameExpression)) { + return } } }) } companion object { - private const val EXPERIMENTAL_COROUTINES_MARKER = "experimental" + private val PROBLEMS = listOf( + ObsoleteExtensionFunctionUsage( + "resume", + "kotlin.coroutines.experimental.Continuation.resume", + "kotlin.coroutines.resume"), + ObsoleteExtensionFunctionUsage( + "resumeWithException", + "kotlin.coroutines.experimental.Continuation.resumeWithException", + "kotlin.coroutines.resumeWithException" + ), + ExperimentalImportUsage() + ) + } +} - private const val RESUME_MARKER = "resume" - private const val RESUME_FUNCTION_FQNAME = "kotlin.coroutines.experimental.Continuation.resume" - private const val RESUME_FUNCTION_FIX_FQNAME = "kotlin.coroutines.resume" +private interface CoroutineMigrationProblem { + fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean +} - private const val RESUME_WITH_EXCEPTION_MARKER = "resumeWithException" - private const val RESUME_WITH_EXCEPTION_FQNAME = "kotlin.coroutines.experimental.Continuation.resumeWithException" - private const val RESUME_WITH_EXCEPTION_FIX_FQNAME = "kotlin.coroutines.resumeWithException" +/** + * 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. + */ +private class ObsoleteCoroutineUsageFix(val delegate: CoroutineFix) : LocalQuickFix { + override fun getFamilyName(): String = "Fix experimental coroutines usage" + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + delegate.applyFix(project, descriptor) + } + + companion object { + interface CoroutineFix { + fun applyFix(project: Project, descriptor: ProblemDescriptor) + } + } +} + +private class ObsoleteExtensionFunctionUsage( + val textMarker: String, val oldFqName: String, newFqName: String +) : CoroutineMigrationProblem { + override fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean { + if (simpleNameExpression.text != textMarker) return false + + if (simpleNameExpression.parent !is KtCallExpression) return false + + val descriptor = simpleNameExpression.resolveMainReferenceToDescriptors().firstOrNull() ?: return false + val callableDescriptor = descriptor as? CallableDescriptor ?: return false + + val resolvedToFqName = callableDescriptor.fqNameOrNull()?.asString() ?: return false + if (resolvedToFqName != oldFqName) return false + + val problemDescriptor = holder.manager.createProblemDescriptor( + simpleNameExpression, + simpleNameExpression, + "Methods are absent in coroutines class since 1.3", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + ObsoleteCoroutineUsageFix(fix) + ) + + holder.registerProblem(problemDescriptor) + return true + } + + private val fix = ImportExtensionFunctionFix(newFqName) + + companion object { + private class ImportExtensionFunctionFix(val fqName: String) : ObsoleteCoroutineUsageFix.Companion.CoroutineFix { + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement + if (element !is KtSimpleNameExpression) return + + val importFun = + KotlinTopLevelFunctionFqnNameIndex.getInstance() + .get(fqName, element.project, GlobalSearchScope.allScope(element.project)) + .asSequence() + .map { it.resolveToDescriptorIfAny() } + .find { it != null && it.importableFqName?.asString() == fqName } ?: return + + ImportInsertHelper.getInstance(element.project).importDescriptor(element.containingKtFile, importFun, false) + } + } + } +} + +private class ExperimentalImportUsage : CoroutineMigrationProblem { + override fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean { + if (simpleNameExpression.text != EXPERIMENTAL_COROUTINES_MARKER) return false + + val parent = simpleNameExpression.parent as? KtExpression ?: return false + val reportExpression = parent as? KtDotQualifiedExpression ?: simpleNameExpression + + findBinding(simpleNameExpression) ?: return false + + val problemDescriptor = holder.manager.createProblemDescriptor( + reportExpression, + reportExpression, + "Experimental coroutines usages are obsolete since 1.3", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + ObsoleteCoroutineUsageFix(ObsoleteCoroutineImportFix) + ) + + holder.registerProblem(problemDescriptor) + return true + } + + companion object { private class Binding( val bindTo: FqName, val shouldRemove: Boolean, @@ -116,6 +174,8 @@ class ObsoleteExperimentalCoroutinesInspection : AbstractKotlinInspection(), Cle "kotlin.coroutines.experimental.buildSequence" ) + private const val EXPERIMENTAL_COROUTINES_MARKER = "experimental" + private fun findBinding(simpleNameExpression: KtSimpleNameExpression): Binding? { if (simpleNameExpression.text != EXPERIMENTAL_COROUTINES_MARKER) return null @@ -129,61 +189,30 @@ class ObsoleteExperimentalCoroutinesInspection : AbstractKotlinInspection(), Cle fqNameStr.startsWith(affectedImportPrefix) } ?: return null - return Binding(FqName(bindEntry.value), fqNameStr in IMPORTS_TO_REMOVE, importDirective) - } - } - - private class ObsoleteCoroutineUsageFix(val delegate: LocalQuickFix) : LocalQuickFix { - override fun getFamilyName(): String = QUICK_FIX_NAME - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - delegate.applyFix(project, descriptor) + return Binding( + FqName(bindEntry.value), + fqNameStr in IMPORTS_TO_REMOVE, + importDirective + ) } - companion object { - private const val QUICK_FIX_NAME = "Fix experimental coroutines usage" + private object ObsoleteCoroutineImportFix : ObsoleteCoroutineUsageFix.Companion.CoroutineFix { + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement + val simpleNameExpression = when (element) { + is KtSimpleNameExpression -> element + is KtDotQualifiedExpression -> element.selectorExpression as? KtSimpleNameExpression + else -> null + } ?: return - fun createImportFix() = ObsoleteCoroutineUsageFix(ObsoleteCoroutineImportFix()) - fun createExtensionFix(fqName: String) = ObsoleteCoroutineUsageFix(ImportExtensionFunctionFix(fqName)) + val binding = findBinding(simpleNameExpression) ?: return - private class ObsoleteCoroutineImportFix : LocalQuickFix { - override fun getFamilyName() = QUICK_FIX_NAME - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val element = descriptor.psiElement - val simpleNameExpression = when (element) { - is KtSimpleNameExpression -> element - is KtDotQualifiedExpression -> element.selectorExpression as? KtSimpleNameExpression - else -> null - } ?: return - - val binding = ObsoleteExperimentalCoroutinesInspection.findBinding(simpleNameExpression) ?: return - - if (binding.shouldRemove) { - binding.importDirective.delete() - } else { - simpleNameExpression.mainReference.bindToFqName( - binding.bindTo, shorteningMode = KtSimpleNameReference.ShorteningMode.NO_SHORTENING - ) - } - } - } - - private class ImportExtensionFunctionFix(val fqName: String) : LocalQuickFix { - override fun getFamilyName() = QUICK_FIX_NAME - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val element = descriptor.psiElement - if (element !is KtSimpleNameExpression) return - - val importFun = - KotlinTopLevelFunctionFqnNameIndex.getInstance() - .get(fqName, element.project, GlobalSearchScope.allScope(element.project)) - .asSequence() - .map { it.resolveToDescriptorIfAny() } - .find { it != null && it.importableFqName?.asString() == fqName } ?: return - - ImportInsertHelper.getInstance(element.project).importDescriptor(element.containingKtFile, importFun, false) + if (binding.shouldRemove) { + binding.importDirective.delete() + } else { + simpleNameExpression.mainReference.bindToFqName( + binding.bindTo, shorteningMode = KtSimpleNameReference.ShorteningMode.NO_SHORTENING + ) } } }