From 0f85d770dd031c9c3a18746394ba8691c22e96d9 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Tue, 13 Oct 2015 16:31:15 +0300 Subject: [PATCH] Refactoring: continue extracting base class for auto-import fix --- .../kotlin/idea/quickfix/AutoImportFix.kt | 200 +++++++++++------- 1 file changed, 118 insertions(+), 82 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt index bfa3e268ce2..2a18047a7a2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportFix.kt @@ -42,9 +42,7 @@ import org.jetbrains.kotlin.idea.core.getResolutionScope import org.jetbrains.kotlin.idea.core.isVisible import org.jetbrains.kotlin.idea.project.ProjectStructureUtil import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtPsiUtil -import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.utils.CachedValueProperty @@ -53,7 +51,14 @@ import java.util.* /** * Check possibility and perform fix for unresolved references. */ -public class AutoImportFix(element: KtSimpleNameExpression) : KotlinQuickFixAction(element), HighPriorityAction, HintAction { +public abstract class AutoImportFixBase( + expression: KtExpression, + val diagnostics: Collection = emptyList()) : KotlinQuickFixAction(expression), HighPriorityAction, HintAction { + + protected constructor( + expression: KtExpression, + diagnostic: Diagnostic? = null) : this(expression, if (diagnostic == null) emptyList() else listOf(diagnostic)) + private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount() @Volatile private var anySuggestionFound: Boolean? = null @@ -66,20 +71,16 @@ public class AutoImportFix(element: KtSimpleNameExpression) : KotlinQuickFixActi }, { PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount() }) - private fun getSupportedErrors(): Collection> = ERRORS - override fun showHint(editor: Editor): Boolean { if (!element.isValid() || isOutdated()) return false - if (HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) return false + if (ApplicationManager.getApplication().isUnitTestMode() && HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) return false if (suggestions.isEmpty()) return false - if (!ApplicationManager.getApplication()!!.isUnitTestMode()) { - val addImportAction = createAction(element.project, editor) - val hintText = ShowAutoImportPass.getMessage(suggestions.size > 1, addImportAction.highestPriorityFqName.asString()) - HintManager.getInstance().showQuestionHint(editor, hintText, element.getTextOffset(), element.getTextRange()!!.getEndOffset(), addImportAction) - } + val addImportAction = createAction(element.project, editor) + val hintText = ShowAutoImportPass.getMessage(suggestions.size > 1, addImportAction.highestPriorityFqName.asString()) + HintManager.getInstance().showQuestionHint(editor, hintText, element.getTextOffset(), element.getTextRange()!!.getEndOffset(), addImportAction) return true } @@ -101,88 +102,82 @@ public class AutoImportFix(element: KtSimpleNameExpression) : KotlinQuickFixActi private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount() - private fun createAction(project: Project, editor: Editor) = KotlinAddImportAction(project, editor, element, suggestions) + protected open fun createAction(project: Project, editor: Editor) = KotlinAddImportAction(project, editor, element as KtSimpleNameExpression, suggestions) - public fun computeSuggestions(): Collection { - if (!element.isValid()) return emptyList() + protected fun computeSuggestions(): Collection { + if (!element.isValid()) return listOf() val file = element.getContainingFile() as? KtFile ?: return emptyList() - val callTypeAndReceiver = CallTypeAndReceiver.detect(element) + val callTypeAndReceiver = getTypeAndReceiver() + if (callTypeAndReceiver is CallTypeAndReceiver.UNKNOWN) return emptyList() - fun filterByCallType(descriptor: DeclarationDescriptor) - = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) + var referenceNames = getImportNames(diagnostics, element).filter { it.isNotEmpty() } + if (referenceNames.isEmpty()) return emptyList() - var referenceName = element.getReferencedName() - if (element.getIdentifier() == null) { - val conventionName = KtPsiUtil.getConventionName(element) - if (conventionName != null) { - referenceName = conventionName.asString() - } + return referenceNames.flatMapTo(LinkedHashSet()) { + Helper.computeSuggestionsForName(callTypeAndReceiver, element, file, it, getSupportedErrors()) } - if (referenceName.isEmpty()) return emptyList() - - val searchScope = getResolveScope(file) - - val bindingContext = element.analyze(BodyResolveMode.PARTIAL) - - val diagnostics = bindingContext.getDiagnostics().forElement(element) - if (!diagnostics.any { it.getFactory() in getSupportedErrors() }) return emptyList() - - val resolutionScope = element.getResolutionScope(bindingContext, file.getResolutionFacade()) - val containingDescriptor = resolutionScope.ownerDescriptor - - fun isVisible(descriptor: DeclarationDescriptor): Boolean { - if (descriptor is DeclarationDescriptorWithVisibility) { - return descriptor.isVisible(containingDescriptor, bindingContext, element) - } - - return true - } - - val result = ArrayList() - - val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible, true) - - if (!element.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(element)) { - if (ProjectStructureUtil.isJsKotlinModule(file)) { - indicesHelper.getKotlinClasses({ it == referenceName }, { true }).filterTo(result, ::filterByCallType) - - } - else { - indicesHelper.getJvmClassesByName(referenceName).filterTo(result, ::filterByCallType) - } - - indicesHelper.getTopLevelCallablesByName(referenceName).filterTo(result, ::filterByCallType) - } - - result.addAll(indicesHelper.getCallableTopLevelExtensions({ it == referenceName }, callTypeAndReceiver, element, bindingContext)) - - return if (result.size > 1) - Helper.reduceCandidatesBasedOnDependencyRuleViolation(result, file) - else - result } - companion object : JetSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { - // There could be different psi elements (i.e. JetArrayAccessExpression), but we can fix only JetSimpleNameExpression case - val psiElement = diagnostic.getPsiElement() - if (psiElement is KtSimpleNameExpression) { - return AutoImportFix(psiElement) - } - - return null - } - - override fun isApplicableForCodeFragment() = true - - private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION ) { QuickFixes.getInstance().getDiagnostics(this) } - } + protected abstract fun getSupportedErrors(): Collection> + protected abstract fun getTypeAndReceiver(): CallTypeAndReceiver<*, *> + protected abstract fun getImportNames(diagnostics: Collection, element: KtExpression): Collection private object Helper { - public fun reduceCandidatesBasedOnDependencyRuleViolation( + public fun computeSuggestionsForName( + callTypeAndReceiver: CallTypeAndReceiver, + expression: KtExpression, file: KtFile, referenceName: String, + supportedErrors: Collection>): Collection { + fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) + + val searchScope = getResolveScope(file) + + val bindingContext = expression.analyze(BodyResolveMode.PARTIAL) + + val diagnostics = bindingContext.getDiagnostics().forElement(expression) + + if (!diagnostics.any { it.getFactory() in supportedErrors }) return emptyList() + + val resolutionScope = expression.getResolutionScope(bindingContext, file.getResolutionFacade()) + val containingDescriptor = resolutionScope.ownerDescriptor + + fun isVisible(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is DeclarationDescriptorWithVisibility) { + return descriptor.isVisible(containingDescriptor, bindingContext, expression as? KtSimpleNameExpression) + } + + return true + } + + val result = ArrayList() + + val indicesHelper = KotlinIndicesHelper(expression.getResolutionFacade(), searchScope, ::isVisible, true) + + if (expression is KtSimpleNameExpression) { + if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) { + if (ProjectStructureUtil.isJsKotlinModule(file)) { + indicesHelper.getKotlinClasses({ it == referenceName }, { true }).filterTo(result, ::filterByCallType) + + } + else { + indicesHelper.getJvmClassesByName(referenceName).filterTo(result, ::filterByCallType) + } + + indicesHelper.getTopLevelCallablesByName(referenceName).filterTo(result, ::filterByCallType) + } + } + + result.addAll(indicesHelper.getCallableTopLevelExtensions({ it == referenceName }, callTypeAndReceiver, expression, bindingContext)) + + return if (result.size > 1) + reduceCandidatesBasedOnDependencyRuleViolation(result, file) + else + result + } + + private fun reduceCandidatesBasedOnDependencyRuleViolation( candidates: Collection, file: PsiFile): Collection { val project = file.project val validationManager = DependencyValidationManager.getInstance(project) @@ -193,3 +188,44 @@ public class AutoImportFix(element: KtSimpleNameExpression) : KotlinQuickFixActi } } } + +public class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic? = null) : AutoImportFixBase(expression, diagnostic) { + override fun getTypeAndReceiver(): CallTypeAndReceiver<*, *> = CallTypeAndReceiver.detect(element as KtSimpleNameExpression) + override fun getImportNames(diagnostics: Collection, element: KtExpression): Collection { + element as KtSimpleNameExpression + + if (element.getIdentifier() == null) { + val conventionName = KtPsiUtil.getConventionName(element) + if (conventionName != null) { + if (element is KtOperationReferenceExpression) { + return listOf(conventionName.asString()) + } + + return listOf(conventionName.asString()) + } + } + else { + return listOf(element.getReferencedName()) + } + + return emptyList() + } + + override fun getSupportedErrors() = ERRORS + + companion object : JetSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { + // There could be different psi elements (i.e. JetArrayAccessExpression), but we can fix only JetSimpleNameExpression case + val psiElement = diagnostic.getPsiElement() + if (psiElement is KtSimpleNameExpression) { + return AutoImportFix(psiElement, diagnostic) + } + + return null + } + + override fun isApplicableForCodeFragment() = true + + private val ERRORS: Collection> by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) } + } +} \ No newline at end of file