Refactoring: move each problem to a separate class

This commit is contained in:
Nikolay Krasko
2018-09-18 13:39:59 +03:00
parent 86141be61f
commit 082801efb4
@@ -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
)
}
}
}