Refactored to use exiting impl of AutoImportBase

This commit is contained in:
Simon Ogorodnik
2016-07-23 15:03:47 +03:00
committed by Valentin Kipyatkov
parent 9d56716897
commit f40bb57d79
3 changed files with 100 additions and 235 deletions
@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -59,7 +60,7 @@ import java.util.*
/**
* Check possibility and perform fix for unresolved references.
*/
internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
internal abstract class AutoImportFixBase<T : KtExpression>(expression: T) :
KotlinQuickFixAction<T>(expression), HighPriorityAction, HintAction {
private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount
@@ -126,8 +127,6 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
val file = element.containingFile as KtFile
fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor)
val searchScope = getResolveScope(file)
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
@@ -146,25 +145,10 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
return true
}
val result = ArrayList<DeclarationDescriptor>()
val indicesHelper = KotlinIndicesHelper(resolutionFacade, searchScope, ::isVisible)
val expression = element
if (expression is KtSimpleNameExpression) {
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
if (ProjectStructureUtil.isJsKotlinModule(file)) {
indicesHelper.getKotlinClasses({ it == nameStr }, { true }).filterTo(result, ::filterByCallType)
}
else {
indicesHelper.getJvmClassesByName(nameStr).filterTo(result, ::filterByCallType)
}
indicesHelper.getTopLevelCallablesByName(nameStr).filterTo(result, ::filterByCallType)
}
}
result.addAll(indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, element, bindingContext) { it == nameStr })
val result = fillCandidates(callTypeAndReceiver, file, bindingContext,
indicesHelper, nameStr, filterByCallType = { callTypeAndReceiver.callType.descriptorKindFilter.accepts(it) })
return if (result.size > 1)
reduceCandidatesBasedOnDependencyRuleViolation(result, file)
@@ -172,6 +156,10 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
result
}
abstract fun fillCandidates(callTypeAndReceiver: CallTypeAndReceiver<*, *>, file: KtFile,
bindingContext: BindingContext, indicesHelper: KotlinIndicesHelper,
nameStr: String, filterByCallType: (descriptor: DeclarationDescriptor) -> (Boolean)): List<DeclarationDescriptor>
private fun reduceCandidatesBasedOnDependencyRuleViolation(
candidates: Collection<DeclarationDescriptor>, file: PsiFile): Collection<DeclarationDescriptor> {
val project = file.project
@@ -183,7 +171,32 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
}
}
internal class AutoImportFix(expression: KtSimpleNameExpression) : AutoImportFixBase<KtSimpleNameExpression>(expression) {
internal abstract class AutoImportFixBaseDynamicContext<T : KtExpression>(expression: T) : AutoImportFixBase<T>(expression) {
override fun fillCandidates(callTypeAndReceiver: CallTypeAndReceiver<*, *>, file: KtFile, bindingContext: BindingContext, indicesHelper: KotlinIndicesHelper, nameStr: String, filterByCallType: (DeclarationDescriptor) -> Boolean): List<DeclarationDescriptor> {
val result = ArrayList<DeclarationDescriptor>()
val expression = element
if (expression is KtSimpleNameExpression) {
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
if (ProjectStructureUtil.isJsKotlinModule(file)) {
indicesHelper.getKotlinClasses({ it == nameStr }, { true }).filterTo(result, filterByCallType)
}
else {
indicesHelper.getJvmClassesByName(nameStr).filterTo(result, filterByCallType)
}
indicesHelper.getTopLevelCallablesByName(nameStr).filterTo(result, filterByCallType)
}
}
result.addAll(indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, expression, bindingContext) { it == nameStr })
return result
}
}
internal class AutoImportFix(expression: KtSimpleNameExpression) : AutoImportFixBaseDynamicContext<KtSimpleNameExpression>(expression) {
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
override val importNames: Collection<Name> = run {
@@ -211,6 +224,7 @@ internal class AutoImportFix(expression: KtSimpleNameExpression) : AutoImportFix
emptyList<Name>()
}
override fun getSupportedErrors() = ERRORS
companion object : KotlinSingleIntentionActionFactory() {
@@ -223,7 +237,8 @@ internal class AutoImportFix(expression: KtSimpleNameExpression) : AutoImportFix
}
}
internal class MissingInvokeAutoImportFix(expression: KtExpression) : AutoImportFixBase<KtExpression>(expression) {
internal class MissingInvokeAutoImportFix(expression: KtExpression) : AutoImportFixBaseDynamicContext<KtExpression>(expression) {
override val importNames = OperatorNameConventions.INVOKE.singletonList()
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
@@ -239,7 +254,7 @@ internal class MissingInvokeAutoImportFix(expression: KtExpression) : AutoImport
}
internal open class MissingArrayAccessorAutoImportFix(element: KtArrayAccessExpression, override val importNames: Collection<Name>, private val showHint: Boolean) :
AutoImportFixBase<KtArrayAccessExpression>(element) {
AutoImportFixBaseDynamicContext<KtArrayAccessExpression>(element) {
override fun getCallTypeAndReceiver() =
CallTypeAndReceiver.OPERATOR(element.arrayExpression!!)
@@ -274,7 +289,7 @@ internal open class MissingArrayAccessorAutoImportFix(element: KtArrayAccessExpr
internal class MissingDelegateAccessorsAutoImportFix(
element: KtExpression, override val importNames: Collection<Name>, private val solveSeveralProblems: Boolean) :
AutoImportFixBase<KtExpression>(element) {
AutoImportFixBaseDynamicContext<KtExpression>(element) {
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element)
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
@@ -315,7 +330,7 @@ internal class MissingDelegateAccessorsAutoImportFix(
}
internal class MissingComponentsAutoImportFix(element: KtExpression, override val importNames: Collection<Name>, private val solveSeveralProblems: Boolean) :
AutoImportFixBase<KtExpression>(element) {
AutoImportFixBaseDynamicContext<KtExpression>(element) {
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
@@ -349,6 +364,64 @@ internal class MissingComponentsAutoImportFix(element: KtExpression, override va
}
}
internal class AutoImportStaticFix(expression: KtSimpleNameExpression) : AutoImportFixBase<KtSimpleNameExpression>(expression) {
override fun getText() = KotlinBundle.message("import.static.fix")
override fun fillCandidates(callTypeAndReceiver: CallTypeAndReceiver<*, *>, file: KtFile, bindingContext: BindingContext, indicesHelper: KotlinIndicesHelper, nameStr: String, filterByCallType: (DeclarationDescriptor) -> Boolean): List<DeclarationDescriptor> {
val result = ArrayList<DeclarationDescriptor>()
val expression = element
if (expression is KtSimpleNameExpression) {
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
indicesHelper.getKotlinStatics(nameStr).filterTo(result, filterByCallType)
if (!(ProjectStructureUtil.isJsKotlinModule(file))) {
indicesHelper.getJvmStatics(nameStr).filterTo(result, filterByCallType)
}
}
}
return result
}
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
override val importNames: Collection<Name> = run {
if (element.getIdentifier() == null) {
val conventionName = KtPsiUtil.getConventionName(element)
if (conventionName != null) {
if (element is KtOperationReferenceExpression) {
val elementType = element.firstChild.node.elementType
if (elementType in OperatorConventions.ASSIGNMENT_OPERATIONS) {
val counterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[elementType]
val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES[counterpart]
if (counterpartName != null) {
return@run listOf(conventionName, counterpartName)
}
}
}
return@run conventionName.singletonOrEmptyList()
}
}
else if (Name.isValidIdentifier(element.getReferencedName())) {
return@run Name.identifier(element.getReferencedName()).singletonOrEmptyList()
}
emptyList<Name>()
}
override fun getSupportedErrors(): Collection<DiagnosticFactory<*>> = ERRORS
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtSimpleNameExpression)?.let(::AutoImportStaticFix)
override fun isApplicableForCodeFragment() = true
private val ERRORS: Collection<DiagnosticFactory<*>> by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
}
}
object AutoImportForMissingOperatorFactory : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val element = diagnostic.psiElement as? KtExpression ?: return null
@@ -357,7 +430,7 @@ object AutoImportForMissingOperatorFactory : KotlinSingleIntentionActionFactory(
when (name) {
OperatorNameConventions.GET, OperatorNameConventions.SET -> {
if (element is KtArrayAccessExpression) {
return object: MissingArrayAccessorAutoImportFix(element, name.singletonList(), false) {
return object : MissingArrayAccessorAutoImportFix(element, name.singletonList(), false) {
override fun getSupportedErrors() = Errors.OPERATOR_MODIFIER_REQUIRED.singletonList()
}
}
@@ -1,208 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.codeInspection.HintAction
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.packageDependencies.DependencyValidationManager
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction
import org.jetbrains.kotlin.idea.actions.createSingleImportAction
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
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.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.utils.CachedValueProperty
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import java.util.*
/**
* Created by Semoro on 08.07.16.
* ©XCodersTeam, 2016
*/
class AutoStaticImportFix(expression: KtSimpleNameExpression) :
KotlinQuickFixAction<KtSimpleNameExpression>(expression), HighPriorityAction, HintAction {
private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount
private val suggestionCount: Int by CachedValueProperty(
calculator = { computeSuggestions().size },
timestampCalculator = { PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount }
)
override fun showHint(editor: Editor): Boolean {
if (!element.isValid || isOutdated()) return false
if (ApplicationManager.getApplication().isUnitTestMode && HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) return false
if (suggestionCount == 0) return false
return createAction(element.project, editor).showHint()
}
override fun getText() = KotlinBundle.message("import.static.fix")
override fun getFamilyName() = KotlinBundle.message("import.fix")
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile)
= super.isAvailable(project, editor, file) && suggestionCount > 0
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
CommandProcessor.getInstance().runUndoTransparentAction {
createAction(project, editor!!).execute()
}
}
override fun startInWriteAction() = true
private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount
private fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
return createSingleImportAction(project, editor, element, computeSuggestions())
}
fun computeSuggestions(): Collection<DeclarationDescriptor> {
if (!element.isValid) return listOf()
if (element.containingFile !is KtFile) return emptyList()
val callTypeAndReceiver = getCallTypeAndReceiver()
if (callTypeAndReceiver is CallTypeAndReceiver.UNKNOWN) return emptyList()
if (importNames.isEmpty()) return emptyList()
return importNames.flatMapTo(LinkedHashSet()) {
computeSuggestionsForName(it, callTypeAndReceiver)
}
}
fun computeSuggestionsForName(name: Name, callTypeAndReceiver: CallTypeAndReceiver<*, *>):
Collection<DeclarationDescriptor> {
val nameStr = name.asString()
if (nameStr.isEmpty()) return emptyList()
val file = element.containingFile as KtFile
fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor)
val searchScope = getResolveScope(file)
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
val diagnostics = bindingContext.diagnostics.forElement(element)
if (!diagnostics.any { it.factory in getSupportedErrors() }) return emptyList()
val resolutionFacade = element.getResolutionFacade()
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility) {
return descriptor.isVisible(element, callTypeAndReceiver.receiver as? KtExpression, bindingContext, resolutionFacade)
}
return true
}
val result = ArrayList<DeclarationDescriptor>()
val indicesHelper = KotlinIndicesHelper(resolutionFacade, searchScope, ::isVisible)
val expression = element
if (expression is KtSimpleNameExpression) {
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
indicesHelper.getKotlinStatics(nameStr).filterTo(result, ::filterByCallType)
if (!(ProjectStructureUtil.isJsKotlinModule(file))) {
indicesHelper.getJvmStatics(nameStr).filterTo(result, ::filterByCallType)
}
}
}
return if (result.size > 1)
reduceCandidatesBasedOnDependencyRuleViolation(result, file)
else
result
}
private fun reduceCandidatesBasedOnDependencyRuleViolation(
candidates: Collection<DeclarationDescriptor>, file: PsiFile): Collection<DeclarationDescriptor> {
val project = file.project
val validationManager = DependencyValidationManager.getInstance(project)
return candidates.filter {
val targetFile = DescriptorToSourceUtilsIde.getAnyDeclaration(project, it)?.containingFile ?: return@filter true
validationManager.getViolatorDependencyRules(file, targetFile).isEmpty()
}
}
fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
val importNames: Collection<Name> = run {
if (element.getIdentifier() == null) {
val conventionName = KtPsiUtil.getConventionName(element)
if (conventionName != null) {
if (element is KtOperationReferenceExpression) {
val elementType = element.firstChild.node.elementType
if (elementType in OperatorConventions.ASSIGNMENT_OPERATIONS) {
val counterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[elementType]
val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES[counterpart]
if (counterpartName != null) {
return@run listOf(conventionName, counterpartName)
}
}
}
return@run conventionName.singletonOrEmptyList()
}
}
else if (Name.isValidIdentifier(element.getReferencedName())) {
return@run Name.identifier(element.getReferencedName()).singletonOrEmptyList()
}
emptyList<Name>()
}
fun getSupportedErrors(): Collection<DiagnosticFactory<*>> = ERRORS
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtSimpleNameExpression)?.let { AutoStaticImportFix(it) }
override fun isApplicableForCodeFragment() = true
private val ERRORS: Collection<DiagnosticFactory<*>> by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
}
}
@@ -128,7 +128,7 @@ class QuickFixRegistrar : QuickFixContributor {
NON_PRIVATE_CONSTRUCTOR_IN_ENUM.registerFactory(removeModifierFactory)
NON_PRIVATE_CONSTRUCTOR_IN_SEALED.registerFactory(removeModifierFactory)
UNRESOLVED_REFERENCE.registerFactory(AutoStaticImportFix)
UNRESOLVED_REFERENCE.registerFactory(AutoImportStaticFix)
UNRESOLVED_REFERENCE.registerFactory(AutoImportFix)
UNRESOLVED_REFERENCE.registerFactory(AddTestLibQuickFix)