Fixes after review in imports for additional expressions

This commit is contained in:
Nikolay Krasko
2015-10-19 15:20:38 +03:00
committed by Nikolay Krasko
parent 2f26480e6b
commit eb2feeeb16
3 changed files with 114 additions and 119 deletions
@@ -31,7 +31,6 @@ 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.diagnostics.DiagnosticWithParameters2
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction
@@ -44,25 +43,24 @@ 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.lexer.KtTokens
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.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.CachedValueProperty
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import java.util.*
/**
* Check possibility and perform fix for unresolved references.
*/
abstract class AutoImportFixBase public constructor(
expression: KtExpression,
val diagnostics: Collection<Diagnostic> = emptyList()) : KotlinQuickFixAction<KtExpression>(expression), HighPriorityAction, HintAction {
abstract class AutoImportFixBase<T: KtExpression>(expression: T, val diagnostics: Collection<Diagnostic>) :
KotlinQuickFixAction<T>(expression), HighPriorityAction, HintAction {
protected constructor(
expression: KtExpression,
diagnostic: Diagnostic? = null) : this(expression, if (diagnostic == null) emptyList() else listOf(diagnostic))
protected constructor(expression: T, diagnostic: Diagnostic? = null) : this(expression, diagnostic.singletonOrEmptyList())
private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount()
@@ -76,6 +74,10 @@ abstract class AutoImportFixBase public constructor(
},
{ PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount() })
protected abstract fun getSupportedErrors(): Collection<DiagnosticFactory<*>>
protected abstract fun getCallTypeAndReceiver(): CallTypeAndReceiver<*, *>
protected abstract fun getImportNames(): Collection<Name>
override fun showHint(editor: Editor): Boolean {
if (!element.isValid() || isOutdated()) return false
@@ -107,98 +109,94 @@ abstract class AutoImportFixBase public constructor(
private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount()
protected open fun createAction(project: Project, editor: Editor) = KotlinAddImportAction(project, editor, element as KtSimpleNameExpression, suggestions)
protected open fun createAction(project: Project, editor: Editor) = KotlinAddImportAction(project, editor, element, suggestions)
protected fun computeSuggestions(): Collection<DeclarationDescriptor> {
if (!element.isValid()) return listOf()
if (element.getContainingFile() !is KtFile) return emptyList()
val file = element.getContainingFile() as? KtFile ?: return emptyList()
val callTypeAndReceiver = getTypeAndReceiver()
val callTypeAndReceiver = getCallTypeAndReceiver()
if (callTypeAndReceiver is CallTypeAndReceiver.UNKNOWN) return emptyList()
var referenceNames = getImportNames(diagnostics, element).filter { it.isNotEmpty() }
var referenceNames = getImportNames()
if (referenceNames.isEmpty()) return emptyList()
return referenceNames.flatMapTo(LinkedHashSet()) {
Helper.computeSuggestionsForName(callTypeAndReceiver, element, file, it, getSupportedErrors())
computeSuggestionsForName(it, callTypeAndReceiver)
}
}
protected abstract fun getSupportedErrors(): Collection<DiagnosticFactory<*>>
protected abstract fun getTypeAndReceiver(): CallTypeAndReceiver<*, *>
protected abstract fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression): Collection<String>
public fun computeSuggestionsForName(name: Name, callTypeAndReceiver: CallTypeAndReceiver<*, *>):
Collection<DeclarationDescriptor> {
val nameStr = name.asString()
if (nameStr.isEmpty()) return emptyList()
private object Helper {
public fun computeSuggestionsForName(
callTypeAndReceiver: CallTypeAndReceiver<out KtElement?, *>,
expression: KtExpression, file: KtFile, referenceName: String,
supportedErrors: Collection<DiagnosticFactory<*>>): Collection<DeclarationDescriptor> {
fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor)
val file = element.getContainingFile() as KtFile
val searchScope = getResolveScope(file)
fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor)
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
val searchScope = getResolveScope(file)
val diagnostics = bindingContext.getDiagnostics().forElement(expression)
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
if (!diagnostics.any { it.getFactory() in supportedErrors }) return emptyList()
val diagnostics = bindingContext.getDiagnostics().forElement(element)
val resolutionScope = expression.getResolutionScope(bindingContext, file.getResolutionFacade())
val containingDescriptor = resolutionScope.ownerDescriptor
if (!diagnostics.any { it.getFactory() in getSupportedErrors() }) return emptyList()
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility) {
return descriptor.isVisible(containingDescriptor, bindingContext, expression as? KtSimpleNameExpression)
}
val resolutionScope = element.getResolutionScope(bindingContext, file.getResolutionFacade())
val containingDescriptor = resolutionScope.ownerDescriptor
return true
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
if (descriptor is DeclarationDescriptorWithVisibility) {
return descriptor.isVisible(containingDescriptor, bindingContext, element as? KtSimpleNameExpression)
}
val result = ArrayList<DeclarationDescriptor>()
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
return true
}
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()
val result = ArrayList<DeclarationDescriptor>()
val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible, true)
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({ it == nameStr }, callTypeAndReceiver, element, bindingContext))
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()
}
}
}
class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic? = null) : AutoImportFixBase(expression, diagnostic) {
override fun getTypeAndReceiver(): CallTypeAndReceiver<*, *> = CallTypeAndReceiver.detect(element as JetSimpleNameExpression)
override fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression): Collection<String> {
element as KtSimpleNameExpression
class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic? = null) :
AutoImportFixBase<KtSimpleNameExpression>(expression, diagnostic) {
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
override fun getImportNames(): Collection<Name> {
if (element.getIdentifier() == null) {
val conventionName = KtPsiUtil.getConventionName(element)
if (conventionName != null) {
@@ -208,16 +206,16 @@ class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic?
val conterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS.get(elementType)
val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES.get(conterpart)
if (counterpartName != null) {
return listOf(conventionName.asString(), counterpartName.asString())
return listOf(conventionName, counterpartName)
}
}
}
return listOf(conventionName.asString())
return conventionName.singletonOrEmptyList()
}
}
else {
return listOf(element.getReferencedName())
else if (Name.isValidIdentifier(element.getReferencedName())) {
return Name.identifier(element.getReferencedName()).singletonOrEmptyList()
}
return emptyList()
@@ -226,15 +224,8 @@ class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic?
override fun getSupportedErrors() = ERRORS
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
// 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 createAction(diagnostic: Diagnostic) =
(diagnostic.getPsiElement() as? KtSimpleNameExpression)?.let { AutoImportFix(it, diagnostic) }
override fun isApplicableForCodeFragment() = true
@@ -242,40 +233,43 @@ class AutoImportFix(expression: KtSimpleNameExpression, diagnostic: Diagnostic?
}
}
class MissingInvokeAutoImportFix(expression: KtExpression, diagnostic: Diagnostic) : AutoImportFixBase(expression, diagnostic) {
override fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression) = setOf("invoke")
class MissingInvokeAutoImportFix(expression: KtExpression, diagnostic: Diagnostic) :
AutoImportFixBase<KtExpression>(expression, diagnostic) {
override fun getImportNames() = OperatorNameConventions.INVOKE.singletonList()
override fun getTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element as KtExpression)
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
override fun getSupportedErrors() = ERRORS
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
val element = diagnostic.psiElement
if (element is KtExpression) {
return MissingInvokeAutoImportFix(element, diagnostic)
}
return null
}
override fun createAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtExpression)?.let { MissingInvokeAutoImportFix(it, diagnostic) }
private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
}
}
class MissingArrayAccessorAutoImportFix(element: KtArrayAccessExpression, diagnostic: Diagnostic) : AutoImportFixBase(element, diagnostic) {
override fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression): Set<String> {
val s = if ((element.parent as? KtBinaryExpression)?.operationToken == KtTokens.EQ) "set" else "get"
return setOf(s)
class MissingArrayAccessorAutoImportFix(element: KtArrayAccessExpression, diagnostic: Diagnostic) :
AutoImportFixBase<KtArrayAccessExpression>(element, diagnostic) {
override fun getImportNames(): Collection<Name> {
val name = if (diagnostics.first().factory == Errors.NO_GET_METHOD) {
OperatorNameConventions.GET
}
else {
OperatorNameConventions.SET
}
return name.singletonList()
}
override fun getTypeAndReceiver() =
CallTypeAndReceiver.OPERATOR((element as KtArrayAccessExpression).arrayExpression!!)
override fun getCallTypeAndReceiver() =
CallTypeAndReceiver.OPERATOR(element.arrayExpression!!)
override fun getSupportedErrors() = ERRORS
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtArrayAccessExpression>? {
assert(diagnostic.factory == Errors.NO_GET_METHOD || diagnostic.factory == Errors.NO_SET_METHOD)
val element = diagnostic.psiElement
if (element is KtArrayAccessExpression && element.arrayExpression != null) {
return MissingArrayAccessorAutoImportFix(element, diagnostic)
@@ -288,16 +282,23 @@ class MissingArrayAccessorAutoImportFix(element: KtArrayAccessExpression, diagno
}
}
class MissingDelegateAccessorsAutoImportFix(element: KtExpression, diagnostics: Collection<Diagnostic>) : AutoImportFixBase(element, diagnostics) {
class MissingDelegateAccessorsAutoImportFix(element: KtExpression, diagnostics: Collection<Diagnostic>) :
AutoImportFixBase<KtExpression>(element, diagnostics) {
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
return KotlinAddImportAction(project, editor, element, suggestions)
}
override fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression): Set<String> {
return diagnostics.mapTo(LinkedHashSet()) { if (it.toString().contains("setValue")) "setValue" else "getValue" }
override fun getImportNames(): Collection<Name> {
return diagnostics.map {
val missingMethodSignature = Errors.DELEGATE_SPECIAL_FUNCTION_MISSING.cast(it).a
if (missingMethodSignature.startsWith(OperatorNameConventions.GET_VALUE.identifier))
OperatorNameConventions.GET_VALUE
else
OperatorNameConventions.SET_VALUE
}.distinct()
}
override fun getTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element as KtExpression)
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element)
override fun getSupportedErrors() = ERRORS
@@ -311,19 +312,13 @@ class MissingDelegateAccessorsAutoImportFix(element: KtExpression, diagnostics:
}
}
class MissingComponentsAutoImportFix(element: KtExpression, diagnostics: Collection<Diagnostic>) : AutoImportFixBase(element, diagnostics) {
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
return KotlinAddImportAction(project, editor, element, suggestions)
}
class MissingComponentsAutoImportFix(element: KtExpression, diagnostics: Collection<Diagnostic>) :
AutoImportFixBase<KtExpression>(element, diagnostics) {
override fun createAction(project: Project, editor: Editor) = KotlinAddImportAction(project, editor, element, suggestions)
override fun getImportNames(diagnostics: Collection<Diagnostic>, element: KtExpression): List<String> {
return diagnostics.map {
@Suppress("UNCHECKED_CAST")
(it as DiagnosticWithParameters2<*, Name, *>).a.identifier
}
}
override fun getImportNames() = diagnostics.map { Name.identifier(Errors.COMPONENT_FUNCTION_MISSING.cast(it).a.identifier) }
override fun getTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element as KtExpression)
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
override fun getSupportedErrors() = ERRORS
@@ -1,6 +1,6 @@
// FILE: first.before.kt
// "Import" "true"
// ERROR: Missing 'getValue(testing.BigTest, kotlin.PropertyMetadata)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
// ERROR: Missing 'getValue(testing.BigTest, kotlin.reflect.KProperty<*>)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
package testing
@@ -25,7 +25,7 @@ public fun <T> DelegateImpl<T>.getValue(thisRef: Any?, property: PropertyMetadat
// FILE: first.after.kt
// "Import" "true"
// ERROR: Missing 'getValue(testing.BigTest, kotlin.PropertyMetadata)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
// ERROR: Missing 'getValue(testing.BigTest, kotlin.reflect.KProperty<*>)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
package testing
@@ -1,6 +1,6 @@
// FILE: first.before.kt
// "Import" "true"
// ERROR: Missing 'setValue(testing.BigTest, kotlin.PropertyMetadata, kotlin.Int)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
// ERROR: Missing 'setValue(testing.BigTest, kotlin.reflect.KProperty<*>, kotlin.Int)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
package testing
@@ -26,7 +26,7 @@ public fun <T> DelegateImpl<T>.setValue(thisRef: Any, propertyMetadata: Property
// FILE: first.after.kt
// "Import" "true"
// ERROR: Missing 'setValue(testing.BigTest, kotlin.PropertyMetadata, kotlin.Int)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
// ERROR: Missing 'setValue(testing.BigTest, kotlin.reflect.KProperty<*>, kotlin.Int)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
package testing