From ec087e8f3d2a663a9fdfc8627ef0c550c5fb0beb Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Tue, 30 Jun 2020 15:41:24 +0300 Subject: [PATCH] KT-39869 Move base classes to separate file --- .../ObsoleteCodeMigrationInspection.kt | 200 +++++++++++++++++ ...bsoleteExperimentalCoroutinesInspection.kt | 201 +----------------- 2 files changed, 211 insertions(+), 190 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteCodeMigrationInspection.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteCodeMigrationInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteCodeMigrationInspection.kt new file mode 100644 index 00000000000..1f97edfd41f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteCodeMigrationInspection.kt @@ -0,0 +1,200 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +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.idea.KotlinBundle +import org.jetbrains.kotlin.idea.configuration.MigrationInfo +import org.jetbrains.kotlin.idea.configuration.isLanguageVersionUpdate +import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.project.languageVersionSettings +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.stubindex.KotlinSourceFilterScope +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.parents + +internal abstract class ObsoleteCodeMigrationInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool, MigrationFix { + protected abstract val fromVersion: LanguageVersion + protected abstract val toVersion: LanguageVersion + protected abstract val problems: List + + final override fun isApplicable(migrationInfo: MigrationInfo): Boolean { + return migrationInfo.isLanguageVersionUpdate(fromVersion, toVersion) + } + + final override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid { + return simpleNameExpressionVisitor(fun(simpleNameExpression) { + run { + val versionIsSatisfied = simpleNameExpression.languageVersionSettings.languageVersion >= toVersion + if (!versionIsSatisfied && !ApplicationManager.getApplication().isUnitTestMode) { + return + } + } + + for (registeredProblem in problems) { + if (registeredProblem.report(holder, isOnTheFly, simpleNameExpression)) { + return + } + } + }) + } +} + +internal interface ObsoleteCodeMigrationProblem { + fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean +} + +internal interface ObsoleteCodeFix { + fun applyFix(project: Project, descriptor: ProblemDescriptor) +} + +// Shortcut quick fix for running inspection in the project scope. +// Should work like RunInspectionAction.runInspection. +internal abstract class ObsoleteCodeInWholeProjectFix : LocalQuickFix { + protected abstract val inspectionName: String + + final override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val toolWrapper = InspectionProjectProfileManager.getInstance(project).currentProfile.getInspectionTool(inspectionName, project)!! + runToolInProject(project, toolWrapper) + } + + final override fun startInWriteAction(): Boolean = false + + 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, + KotlinBundle.message("apply.in.the.project.0", toolWrapper.displayName), + null, + false + ) + } + + // Overcome failure during profile creating because of absent tools in tests + private 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. + */ +internal abstract class ObsoleteCodeFixDelegateQuickFix(private val delegate: ObsoleteCodeFix) : LocalQuickFix { + final override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + delegate.applyFix(project, descriptor) + } +} + +internal abstract class ObsoleteImportsUsage : ObsoleteCodeMigrationProblem { + /** + * Required to report the problem only on one psi element instead of the every single qualifier + * in the import statement. + */ + protected abstract val textMarker: String + + protected abstract val packageBindings: Map + protected open val importsToRemove: Set = emptySet() + + protected abstract val wholeProjectFix: LocalQuickFix + protected abstract fun problemMessage(): String + + protected abstract fun wrapFix(fix: ObsoleteCodeFix): LocalQuickFix + + final override fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean { + if (simpleNameExpression.text != textMarker) return false + + val parent = simpleNameExpression.parent as? KtExpression ?: return false + val reportExpression = parent as? KtDotQualifiedExpression ?: simpleNameExpression + + findBinding(simpleNameExpression) ?: return false + + holder.registerProblem( + reportExpression, + problemMessage(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + *fixesWithWholeProject(isOnTheFly, wrapFix(ObsoleteCoroutineImportFix()), wholeProjectFix) + ) + + return true + } + + private fun findBinding(simpleNameExpression: KtSimpleNameExpression): Binding? { + if (simpleNameExpression.text != textMarker) return null + + val importDirective = simpleNameExpression.parents + .takeWhile { it is KtDotQualifiedExpression || it is KtImportDirective } + .lastOrNull() as? KtImportDirective ?: return null + + val fqNameStr = importDirective.importedFqName?.asString() ?: return null + + val bindEntry = packageBindings.entries.find { (affectedImportPrefix, _) -> + fqNameStr.startsWith(affectedImportPrefix) + } ?: return null + + return Binding( + FqName(bindEntry.value), + fqNameStr in importsToRemove, + importDirective + ) + } + + private inner class ObsoleteCoroutineImportFix : ObsoleteCodeFix { + 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 = findBinding(simpleNameExpression) ?: return + + if (binding.shouldRemove) { + binding.importDirective.delete() + } else { + simpleNameExpression.mainReference.bindToFqName( + binding.bindTo, shorteningMode = KtSimpleNameReference.ShorteningMode.NO_SHORTENING + ) + } + } + } + + private class Binding( + val bindTo: FqName, + val shouldRemove: Boolean, + val importDirective: KtImportDirective + ) +} + 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 18c366c97c4..f8f5436467e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt @@ -5,73 +5,28 @@ 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.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder 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 import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests -import org.jetbrains.kotlin.idea.configuration.MigrationInfo -import org.jetbrains.kotlin.idea.configuration.isLanguageVersionUpdate import org.jetbrains.kotlin.idea.imports.importableFqName -import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection -import org.jetbrains.kotlin.idea.project.languageVersionSettings -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 -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull -internal abstract class ObsoleteCodeMigrationInspection() : AbstractKotlinInspection(), CleanupLocalInspectionTool, MigrationFix { - protected abstract val fromVersion: LanguageVersion - protected abstract val toVersion: LanguageVersion - protected abstract val problems: List - - final override fun isApplicable(migrationInfo: MigrationInfo): Boolean { - return migrationInfo.isLanguageVersionUpdate(fromVersion, toVersion) - } - - final override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid { - return simpleNameExpressionVisitor(fun(simpleNameExpression) { - run { - val versionIsSatisfied = simpleNameExpression.languageVersionSettings.languageVersion >= toVersion - if (!versionIsSatisfied && !ApplicationManager.getApplication().isUnitTestMode) { - return - } - } - - for (registeredProblem in problems) { - if (registeredProblem.report(holder, isOnTheFly, simpleNameExpression)) { - return - } - } - }) - } -} - -internal interface ObsoleteCodeMigrationProblem { - fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean -} - -private interface ObsoleteCodeFix { - fun applyFix(project: Project, descriptor: ProblemDescriptor) -} - internal class ObsoleteExperimentalCoroutinesInspection : ObsoleteCodeMigrationInspection() { override val fromVersion: LanguageVersion = LanguageVersion.KOTLIN_1_2 override val toVersion: LanguageVersion = LanguageVersion.KOTLIN_1_3 @@ -101,64 +56,11 @@ internal class ObsoleteExperimentalCoroutinesInspection : ObsoleteCodeMigrationI ) } -// Shortcut quick fix for running inspection in the project scope. -// Should work like RunInspectionAction.runInspection. -private abstract class ObsoleteCodeInWholeProjectFix : LocalQuickFix { - protected abstract val inspectionName: String - - final override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val toolWrapper = InspectionProjectProfileManager.getInstance(project).currentProfile.getInspectionTool(inspectionName, project)!! - runToolInProject(project, toolWrapper) - } - - final override fun startInWriteAction(): Boolean = false - - 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, - KotlinBundle.message("apply.in.the.project.0", toolWrapper.displayName), - null, - false - ) - } - - // Overcome failure during profile creating because of absent tools in tests - private 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 - } - } - } -} - -private object ObsoleteCoroutineUsageInWholeProjectFix : ObsoleteCodeInWholeProjectFix() { +private object ObsoleteCoroutinesUsageInWholeProjectFix : ObsoleteCodeInWholeProjectFix() { override val inspectionName = ObsoleteExperimentalCoroutinesInspection().shortName override fun getFamilyName(): String = KotlinBundle.message("obsolete.coroutine.usage.in.whole.fix.family.name") } -/** - * 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 abstract class ObsoleteCodeFixDelegateQuickFix(private val delegate: ObsoleteCodeFix) : LocalQuickFix { - final override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - delegate.applyFix(project, descriptor) - } -} - private class ObsoleteCoroutinesDelegateQuickFix(delegate: ObsoleteCodeFix) : ObsoleteCodeFixDelegateQuickFix(delegate) { override fun getFamilyName(): String = KotlinBundle.message("obsolete.coroutine.usage.fix.family.name") } @@ -181,7 +83,7 @@ private fun isTopLevelCallForReplace(simpleNameExpression: KtSimpleNameExpressio return !isInIndex } -private fun fixesWithWholeProject(isOnTheFly: Boolean, fix: LocalQuickFix, wholeProjectFix: LocalQuickFix): Array { +internal fun fixesWithWholeProject(isOnTheFly: Boolean, fix: LocalQuickFix, wholeProjectFix: LocalQuickFix): Array { if (!isOnTheFly) { return arrayOf(fix) } @@ -203,7 +105,7 @@ private class ObsoleteTopLevelFunctionUsage( simpleNameExpression, KotlinBundle.message("0.is.expected.to.be.used.since.kotlin.1.3", newFqName), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - *fixesWithWholeProject(isOnTheFly, ObsoleteCoroutinesDelegateQuickFix(fix), ObsoleteCoroutineUsageInWholeProjectFix) + *fixesWithWholeProject(isOnTheFly, ObsoleteCoroutinesDelegateQuickFix(fix), ObsoleteCoroutinesUsageInWholeProjectFix) ) return true @@ -239,7 +141,7 @@ private class ObsoleteExtensionFunctionUsage( simpleNameExpression, KotlinBundle.message("methods.are.absent.in.coroutines.class.since.1.3"), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - *fixesWithWholeProject(isOnTheFly, ObsoleteCoroutinesDelegateQuickFix(fix), ObsoleteCoroutineUsageInWholeProjectFix) + *fixesWithWholeProject(isOnTheFly, ObsoleteCoroutinesDelegateQuickFix(fix), ObsoleteCoroutinesUsageInWholeProjectFix) ) return true @@ -270,87 +172,6 @@ private class ObsoleteExtensionFunctionUsage( } } -private abstract class ObsoleteImportsUsage : ObsoleteCodeMigrationProblem { - /** - * Required to report the problem only on one psi element instead of the every single qualifier - * in the import statement. - */ - protected abstract val textMarker: String - - protected abstract val packageBindings: Map - protected open val importsToRemove: Set = emptySet() - - protected abstract val wholeProjectFix: LocalQuickFix - protected abstract fun problemMessage(): String - - protected abstract fun wrapFix(fix: ObsoleteCodeFix): LocalQuickFix - - final override fun report(holder: ProblemsHolder, isOnTheFly: Boolean, simpleNameExpression: KtSimpleNameExpression): Boolean { - if (simpleNameExpression.text != textMarker) return false - - val parent = simpleNameExpression.parent as? KtExpression ?: return false - val reportExpression = parent as? KtDotQualifiedExpression ?: simpleNameExpression - - findBinding(simpleNameExpression) ?: return false - - holder.registerProblem( - reportExpression, - problemMessage(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - *fixesWithWholeProject(isOnTheFly, wrapFix(ObsoleteCoroutineImportFix()), wholeProjectFix) - ) - - return true - } - - private fun findBinding(simpleNameExpression: KtSimpleNameExpression): Binding? { - if (simpleNameExpression.text != textMarker) return null - - val importDirective = simpleNameExpression.parents - .takeWhile { it is KtDotQualifiedExpression || it is KtImportDirective } - .lastOrNull() as? KtImportDirective ?: return null - - val fqNameStr = importDirective.importedFqName?.asString() ?: return null - - val bindEntry = packageBindings.entries.find { (affectedImportPrefix, _) -> - fqNameStr.startsWith(affectedImportPrefix) - } ?: return null - - return Binding( - FqName(bindEntry.value), - fqNameStr in importsToRemove, - importDirective - ) - } - - private inner class ObsoleteCoroutineImportFix : ObsoleteCodeFix { - 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 = findBinding(simpleNameExpression) ?: return - - if (binding.shouldRemove) { - binding.importDirective.delete() - } else { - simpleNameExpression.mainReference.bindToFqName( - binding.bindTo, shorteningMode = KtSimpleNameReference.ShorteningMode.NO_SHORTENING - ) - } - } - } - - private class Binding( - val bindTo: FqName, - val shouldRemove: Boolean, - val importDirective: KtImportDirective - ) -} - private object ObsoleteCoroutinesImportsUsage : ObsoleteImportsUsage() { override val textMarker: String = "experimental" @@ -363,7 +184,7 @@ private object ObsoleteCoroutinesImportsUsage : ObsoleteImportsUsage() { "kotlin.coroutines.experimental.buildIterator" ) - override val wholeProjectFix: LocalQuickFix = ObsoleteCoroutineUsageInWholeProjectFix + override val wholeProjectFix: LocalQuickFix = ObsoleteCoroutinesUsageInWholeProjectFix override fun problemMessage(): String = KotlinBundle.message("experimental.coroutines.usages.are.obsolete.since.1.3") override fun wrapFix(fix: ObsoleteCodeFix): LocalQuickFix = ObsoleteCoroutinesDelegateQuickFix(fix) }