Refactoring: move each problem to a separate class
This commit is contained in:
+129
-100
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user