Import all components and delegates accessors if possible
This commit is contained in:
@@ -47,6 +47,8 @@ public fun FqName.tail(prefix: FqName): FqName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun FqName.parentOrNull(): FqName? = if (this.isRoot) null else parent()
|
||||||
|
|
||||||
private enum class State {
|
private enum class State {
|
||||||
BEGINNING,
|
BEGINNING,
|
||||||
MIDDLE,
|
MIDDLE,
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
package org.jetbrains.kotlin.idea.actions
|
package org.jetbrains.kotlin.idea.actions
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||||
|
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass
|
||||||
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction
|
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction
|
||||||
|
import com.intellij.codeInsight.hint.HintManager
|
||||||
import com.intellij.codeInsight.hint.QuestionAction
|
import com.intellij.codeInsight.hint.QuestionAction
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
@@ -27,6 +29,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory
|
|||||||
import com.intellij.openapi.ui.popup.PopupStep
|
import com.intellij.openapi.ui.popup.PopupStep
|
||||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.statistics.StatisticsManager
|
import com.intellij.psi.statistics.StatisticsManager
|
||||||
import com.intellij.psi.util.ProximityLocation
|
import com.intellij.psi.util.ProximityLocation
|
||||||
import com.intellij.psi.util.proximity.PsiProximityComparator
|
import com.intellij.psi.util.proximity.PsiProximityComparator
|
||||||
@@ -44,70 +47,94 @@ import org.jetbrains.kotlin.idea.references.mainReference
|
|||||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.name.parentOrNull
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
|
||||||
|
fun createSingleImportAction(project: Project,
|
||||||
|
editor: Editor,
|
||||||
|
element: KtElement,
|
||||||
|
descriptors: Collection<DeclarationDescriptor>): KotlinAddImportAction {
|
||||||
|
val prioritizer = Prioritizer(element.getContainingJetFile())
|
||||||
|
val variants = descriptors
|
||||||
|
.groupBy { it.importableFqName!! }
|
||||||
|
.map {
|
||||||
|
val (fqName, sameFqNameDescriptors) = it
|
||||||
|
val priority = sameFqNameDescriptors.map { prioritizer.priority(it) }.min()!!
|
||||||
|
Prioritizer.VariantWithPriority(SingleImportVariant(fqName, sameFqNameDescriptors), priority)
|
||||||
|
}
|
||||||
|
.sortedBy { it.priority }
|
||||||
|
.map { it.variant }
|
||||||
|
|
||||||
|
return KotlinAddImportAction(project, editor, element, variants)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createGroupedImportsAction(project: Project,
|
||||||
|
editor: Editor,
|
||||||
|
element: KtElement,
|
||||||
|
autoImportDescription: String,
|
||||||
|
descriptors: Collection<DeclarationDescriptor>): KotlinAddImportAction {
|
||||||
|
val prioritizer = DescriptorGroupPrioritizer(element.getContainingJetFile())
|
||||||
|
|
||||||
|
val variants = descriptors
|
||||||
|
.groupBy { it.importableFqName!!.parentOrNull() ?: FqName.ROOT }
|
||||||
|
.map {
|
||||||
|
val samePackageDescriptors = it.value
|
||||||
|
val variant = if (samePackageDescriptors.size > 1) {
|
||||||
|
GroupedImportVariant(autoImportDescription, samePackageDescriptors)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SingleImportVariant(samePackageDescriptors.first().importableFqName!!, samePackageDescriptors)
|
||||||
|
}
|
||||||
|
|
||||||
|
val priority = prioritizer.priority(samePackageDescriptors)
|
||||||
|
DescriptorGroupPrioritizer.VariantWithPriority(variant, priority)
|
||||||
|
}
|
||||||
|
.sortedBy {
|
||||||
|
it.priority
|
||||||
|
}
|
||||||
|
.map { it.variant }
|
||||||
|
|
||||||
|
return KotlinAddImportAction(project, editor, element, variants)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically adds import directive to the file for resolving reference.
|
* Automatically adds import directive to the file for resolving reference.
|
||||||
* Based on {@link AddImportAction}
|
* Based on {@link AddImportAction}
|
||||||
*/
|
*/
|
||||||
public class KotlinAddImportAction(
|
internal class KotlinAddImportAction(
|
||||||
private val project: Project,
|
private val project: Project,
|
||||||
private val editor: Editor,
|
private val editor: Editor,
|
||||||
private val element: KtExpression,
|
private val element: KtElement,
|
||||||
candidates: Collection<DeclarationDescriptor>
|
private val variants: List<AutoImportVariant>) : QuestionAction {
|
||||||
) : QuestionAction {
|
fun showHint(): Boolean {
|
||||||
|
if (variants.isEmpty()) return false
|
||||||
|
|
||||||
private val file = element.getContainingJetFile()
|
val hintText = ShowAutoImportPass.getMessage(variants.size > 1, variants.first().hint)
|
||||||
private val prioritizer = Prioritizer(file)
|
HintManager.getInstance().showQuestionHint(editor, hintText, element.getTextOffset(), element.getTextRange()!!.getEndOffset(), this)
|
||||||
|
|
||||||
private inner class Variant(
|
|
||||||
val fqName: FqName,
|
|
||||||
val descriptors: Collection<DeclarationDescriptor>
|
|
||||||
) {
|
|
||||||
val priority = descriptors
|
|
||||||
.map { prioritizer.priority(fqName, it) }
|
|
||||||
.min()!!
|
|
||||||
|
|
||||||
val descriptorToImport: DeclarationDescriptor
|
|
||||||
get() {
|
|
||||||
return descriptors.singleOrNull()
|
|
||||||
?: descriptors.sortedBy { if (it is ClassDescriptor) 0 else 1 }.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
val declarationToImport = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptorToImport)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val variants = candidates
|
|
||||||
.groupBy { it.importableFqName!! }
|
|
||||||
.map { Variant(it.key, it.value) }
|
|
||||||
.sortedBy { it.priority }
|
|
||||||
|
|
||||||
public val highestPriorityFqName: FqName
|
|
||||||
get() = variants.first().fqName
|
|
||||||
|
|
||||||
override fun execute(): Boolean {
|
|
||||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
|
||||||
if (!element.isValid()) return false
|
|
||||||
|
|
||||||
// TODO: Validate resolution variants. See AddImportAction.execute()
|
|
||||||
|
|
||||||
if (variants.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
|
||||||
addImport(variants.first())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chooseCandidateAndImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getImportSelectionPopup(): BaseListPopupStep<Variant> {
|
override fun execute(): Boolean {
|
||||||
return object : BaseListPopupStep<Variant>(JetBundle.message("imports.chooser.title"), variants) {
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
if (!element.isValid) return false
|
||||||
|
|
||||||
|
if (variants.size == 1 || ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
addImport(variants.first())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
JBPopupFactory.getInstance().createListPopup(getVariantSelectionPopup()).showInBestPositionFor(editor)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVariantSelectionPopup(): BaseListPopupStep<AutoImportVariant> {
|
||||||
|
return object : BaseListPopupStep<AutoImportVariant>(JetBundle.message("imports.chooser.title"), variants) {
|
||||||
override fun isAutoSelectionEnabled() = false
|
override fun isAutoSelectionEnabled() = false
|
||||||
|
|
||||||
override fun onChosen(selectedValue: Variant?, finalChoice: Boolean): PopupStep<String>? {
|
override fun onChosen(selectedValue: AutoImportVariant?, finalChoice: Boolean): PopupStep<String>? {
|
||||||
if (selectedValue == null || project.isDisposed) return null
|
if (selectedValue == null || project.isDisposed) return null
|
||||||
|
|
||||||
if (finalChoice) {
|
if (finalChoice) {
|
||||||
@@ -115,7 +142,7 @@ public class KotlinAddImportAction(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val toExclude = AddImportAction.getAllExcludableStrings(selectedValue.fqName.asString())
|
val toExclude = AddImportAction.getAllExcludableStrings(selectedValue.excludeFqNameCheck.asString())
|
||||||
|
|
||||||
return object : BaseListPopupStep<String>(null, toExclude) {
|
return object : BaseListPopupStep<String>(null, toExclude) {
|
||||||
override fun getTextFor(value: String): String {
|
override fun getTextFor(value: String): String {
|
||||||
@@ -131,65 +158,116 @@ public class KotlinAddImportAction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasSubstep(selectedValue: Variant?) = true
|
override fun hasSubstep(selectedValue: AutoImportVariant?) = true
|
||||||
|
override fun getTextFor(value: AutoImportVariant) = value.hint
|
||||||
override fun getTextFor(value: Variant) = value.fqName.asString()
|
override fun getIconFor(value: AutoImportVariant) = value.icon(project)
|
||||||
|
|
||||||
override fun getIconFor(value: Variant) = JetDescriptorIconProvider.getIcon(value.descriptorToImport, value.declarationToImport, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chooseCandidateAndImport() {
|
private fun addImport(variant: AutoImportVariant) {
|
||||||
JBPopupFactory.getInstance().createListPopup(getImportSelectionPopup()).showInBestPositionFor(editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addImport(selectedVariant: Variant) {
|
|
||||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
|
||||||
project.executeWriteCommand(QuickFixBundle.message("add.import")) {
|
project.executeWriteCommand(QuickFixBundle.message("add.import")) {
|
||||||
if (!element.isValid()) return@executeWriteCommand
|
if (!element.isValid) return@executeWriteCommand
|
||||||
|
|
||||||
selectedVariant.declarationToImport?.let {
|
val file = element.getContainingJetFile()
|
||||||
val location = ProximityLocation(file, ModuleUtilCore.findModuleForPsiElement(file))
|
|
||||||
|
variant.declarationToImport(project)?.let {
|
||||||
|
val location = ProximityLocation(element, ModuleUtilCore.findModuleForPsiElement(element))
|
||||||
StatisticsManager.getInstance().incUseCount(PsiProximityComparator.STATISTICS_KEY, it, location)
|
StatisticsManager.getInstance().incUseCount(PsiProximityComparator.STATISTICS_KEY, it, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
val descriptor = selectedVariant.descriptorToImport
|
for (descriptor in variant.descriptorsToImport) {
|
||||||
// for class or package we use ShortenReferences because we not necessary insert an import but may want to insert partly qualified name
|
// for class or package we use ShortenReferences because we not necessary insert an import but may want to
|
||||||
if (descriptor is ClassDescriptor || descriptor is PackageViewDescriptor) {
|
// insert partly qualified name
|
||||||
if (element is KtSimpleNameExpression) {
|
if (descriptor is ClassDescriptor || descriptor is PackageViewDescriptor) {
|
||||||
element.mainReference.bindToFqName(descriptor.importableFqName!!, KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING)
|
if (element is KtSimpleNameExpression) {
|
||||||
|
element.mainReference.bindToFqName(descriptor.importableFqName!!, KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptor)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private class Prioritizer(private val file: KtFile) {
|
|
||||||
private val classifier = ImportableFqNameClassifier(file)
|
private class Prioritizer(private val file: KtFile, private val compareNames: Boolean = true) {
|
||||||
private val proximityComparator = PsiProximityComparator(file)
|
private val classifier = ImportableFqNameClassifier(file)
|
||||||
|
private val proximityComparator = PsiProximityComparator(file)
|
||||||
private inner class Priority(private val fqName: FqName, private val descriptor: DeclarationDescriptor) : Comparable<Priority> {
|
|
||||||
private val isDeprecated = KotlinBuiltIns.isDeprecated(descriptor)
|
inner class Priority(private val descriptor: DeclarationDescriptor) : Comparable<Priority> {
|
||||||
private val classification = classifier.classify(fqName, false)
|
private val isDeprecated = KotlinBuiltIns.isDeprecated(descriptor)
|
||||||
private val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
|
private val fqName = descriptor.importableFqName!!
|
||||||
|
private val classification = classifier.classify(fqName, false)
|
||||||
override fun compareTo(other: Priority): Int {
|
private val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
|
||||||
if (isDeprecated != other.isDeprecated) {
|
|
||||||
return if (isDeprecated) +1 else -1
|
override fun compareTo(other: Priority): Int {
|
||||||
}
|
if (isDeprecated != other.isDeprecated) {
|
||||||
|
return if (isDeprecated) +1 else -1
|
||||||
val c1 = classification.compareTo(other.classification)
|
}
|
||||||
if (c1 != 0) return c1
|
|
||||||
|
val c1 = classification.compareTo(other.classification)
|
||||||
val c2 = proximityComparator.compare(declaration, other.declaration)
|
if (c1 != 0) return c1
|
||||||
if (c2 != 0) return c2
|
|
||||||
|
val c2 = proximityComparator.compare(declaration, other.declaration)
|
||||||
return fqName.asString().compareTo(other.fqName.asString())
|
if (c2 != 0) return c2
|
||||||
}
|
|
||||||
}
|
if (compareNames) {
|
||||||
|
return fqName.asString().compareTo(other.fqName.asString())
|
||||||
fun priority(fqName: FqName, descriptor: DeclarationDescriptor) = Priority(fqName, descriptor)
|
}
|
||||||
}
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun priority(descriptor: DeclarationDescriptor) = Priority(descriptor)
|
||||||
|
|
||||||
|
data class VariantWithPriority(val variant: AutoImportVariant, val priority: Priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DescriptorGroupPrioritizer(private val file: KtFile) {
|
||||||
|
private val prioritizer = Prioritizer(file, false)
|
||||||
|
|
||||||
|
inner class Priority(val descriptors: List<DeclarationDescriptor>) : Comparable<Priority> {
|
||||||
|
val ownDescriptorsPriority = descriptors.map { prioritizer.priority(it) }.max()!!
|
||||||
|
|
||||||
|
override fun compareTo(other: Priority): Int {
|
||||||
|
val c1 = ownDescriptorsPriority.compareTo(other.ownDescriptorsPriority)
|
||||||
|
if (c1 != 0) return c1
|
||||||
|
|
||||||
|
return other.descriptors.size - descriptors.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun priority(descriptors: List<DeclarationDescriptor>) = Priority(descriptors)
|
||||||
|
|
||||||
|
data class VariantWithPriority(val variant: AutoImportVariant, val priority: Priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface AutoImportVariant {
|
||||||
|
val descriptorsToImport: Collection<DeclarationDescriptor>
|
||||||
|
val hint: String
|
||||||
|
val excludeFqNameCheck: FqName
|
||||||
|
|
||||||
|
fun icon(project: Project) = JetDescriptorIconProvider.getIcon(descriptorsToImport.first(), declarationToImport(project), 0)
|
||||||
|
|
||||||
|
fun declarationToImport(project: Project): PsiElement? =
|
||||||
|
DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptorsToImport.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GroupedImportVariant(val autoImportDescription: String, val descriptors: Collection<DeclarationDescriptor>) : AutoImportVariant {
|
||||||
|
override val excludeFqNameCheck: FqName = descriptors.first().importableFqName!!.parent()
|
||||||
|
override val descriptorsToImport: Collection<DeclarationDescriptor> get() = descriptors
|
||||||
|
override val hint: String get() = "$autoImportDescription from $excludeFqNameCheck"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SingleImportVariant(
|
||||||
|
override val excludeFqNameCheck: FqName,
|
||||||
|
val descriptors: Collection<DeclarationDescriptor>
|
||||||
|
) : AutoImportVariant {
|
||||||
|
override val descriptorsToImport: Collection<DeclarationDescriptor> get() =
|
||||||
|
listOf(descriptors.singleOrNull() ?: descriptors.sortedBy { if (it is ClassDescriptor) 0 else 1 }.first())
|
||||||
|
|
||||||
|
override val hint: String get() = excludeFqNameCheck.asString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.quickfix
|
package org.jetbrains.kotlin.idea.quickfix
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass
|
|
||||||
import com.intellij.codeInsight.hint.HintManager
|
import com.intellij.codeInsight.hint.HintManager
|
||||||
import com.intellij.codeInsight.intention.HighPriorityAction
|
import com.intellij.codeInsight.intention.HighPriorityAction
|
||||||
|
import com.intellij.codeInsight.intention.IntentionAction
|
||||||
import com.intellij.codeInspection.HintAction
|
import com.intellij.codeInspection.HintAction
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
@@ -33,7 +33,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
|||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.idea.JetBundle
|
import org.jetbrains.kotlin.idea.JetBundle
|
||||||
import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction
|
import org.jetbrains.kotlin.idea.actions.*
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope
|
||||||
@@ -80,12 +80,7 @@ abstract class AutoImportFixBase<T: KtExpression>(expression: T, val diagnostics
|
|||||||
|
|
||||||
if (suggestionCount == 0) return false
|
if (suggestionCount == 0) return false
|
||||||
|
|
||||||
val suggestions = computeSuggestions()
|
return createAction(element.project, editor).showHint()
|
||||||
val action = KotlinAddImportAction(element.project, editor, element, suggestions)
|
|
||||||
val hintText = ShowAutoImportPass.getMessage(suggestions.size > 1, action.highestPriorityFqName.asString())
|
|
||||||
HintManager.getInstance().showQuestionHint(editor, hintText, element.textOffset, element.textRange.endOffset, action)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getText() = JetBundle.message("import.fix")
|
override fun getText() = JetBundle.message("import.fix")
|
||||||
@@ -97,8 +92,7 @@ abstract class AutoImportFixBase<T: KtExpression>(expression: T, val diagnostics
|
|||||||
|
|
||||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
val suggestions = computeSuggestions()
|
createAction(project, editor!!).execute()
|
||||||
KotlinAddImportAction(project, editor!!, element, suggestions).execute()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +100,10 @@ abstract class AutoImportFixBase<T: KtExpression>(expression: T, val diagnostics
|
|||||||
|
|
||||||
private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount()
|
private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.getProject()).getModificationCount()
|
||||||
|
|
||||||
|
protected open fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
|
||||||
|
return createSingleImportAction(project, editor, element, computeSuggestions())
|
||||||
|
}
|
||||||
|
|
||||||
fun computeSuggestions(): Collection<DeclarationDescriptor> {
|
fun computeSuggestions(): Collection<DeclarationDescriptor> {
|
||||||
if (!element.isValid()) return listOf()
|
if (!element.isValid()) return listOf()
|
||||||
if (element.getContainingFile() !is KtFile) return emptyList()
|
if (element.getContainingFile() !is KtFile) return emptyList()
|
||||||
@@ -291,12 +289,25 @@ class MissingDelegateAccessorsAutoImportFix(element: KtExpression, diagnostics:
|
|||||||
|
|
||||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element)
|
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element)
|
||||||
|
|
||||||
|
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
|
||||||
|
if (diagnostics.size > 1) {
|
||||||
|
return createGroupedImportsAction(project, editor, element, "Delegate accessors", computeSuggestions())
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.createAction(project, editor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSupportedErrors() = ERRORS
|
override fun getSupportedErrors() = ERRORS
|
||||||
|
|
||||||
companion object : JetSingleIntentionActionFactory() {
|
companion object : JetSingleIntentionActionFactory() {
|
||||||
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
|
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
|
||||||
assert(diagnostic.factory == Errors.DELEGATE_SPECIAL_FUNCTION_MISSING)
|
assert(diagnostic.factory == Errors.DELEGATE_SPECIAL_FUNCTION_MISSING)
|
||||||
return MissingDelegateAccessorsAutoImportFix(diagnostic.psiElement as KtExpression, listOf(diagnostic))
|
return (diagnostic.psiElement as? KtExpression)?.let { MissingDelegateAccessorsAutoImportFix(it, listOf(diagnostic)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doCreateActionsForAllProblems(sameTypeDiagnostics: Collection<Diagnostic>): List<IntentionAction> {
|
||||||
|
val element = sameTypeDiagnostics.first().psiElement
|
||||||
|
return (element as? KtExpression)?.let { MissingDelegateAccessorsAutoImportFix(it, sameTypeDiagnostics) }.singletonOrEmptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
|
private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
|
||||||
@@ -309,12 +320,25 @@ class MissingComponentsAutoImportFix(element: KtExpression, diagnostics: Collect
|
|||||||
|
|
||||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
|
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
|
||||||
|
|
||||||
|
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
|
||||||
|
if (diagnostics.size > 1) {
|
||||||
|
return createGroupedImportsAction(project, editor, element, "Component functions", computeSuggestions())
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.createAction(project, editor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSupportedErrors() = ERRORS
|
override fun getSupportedErrors() = ERRORS
|
||||||
|
|
||||||
companion object : JetSingleIntentionActionFactory() {
|
companion object : JetSingleIntentionActionFactory() {
|
||||||
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
|
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtExpression>? {
|
||||||
assert(diagnostic.factory == Errors.COMPONENT_FUNCTION_MISSING)
|
assert(diagnostic.factory == Errors.COMPONENT_FUNCTION_MISSING)
|
||||||
return MissingComponentsAutoImportFix(diagnostic.psiElement as KtExpression, listOf(diagnostic))
|
return (diagnostic.psiElement as? KtExpression)?.let { MissingComponentsAutoImportFix(it, listOf(diagnostic)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doCreateActionsForAllProblems(sameTypeDiagnostics: Collection<Diagnostic>): List<IntentionAction> {
|
||||||
|
val element = sameTypeDiagnostics.first().psiElement
|
||||||
|
return (element as? KtExpression)?.let { MissingComponentsAutoImportFix(it, sameTypeDiagnostics) }.singletonOrEmptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
|
private val ERRORS by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) }
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
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.analyze
|
||||||
import org.jetbrains.kotlin.idea.core.targetDescriptors
|
import org.jetbrains.kotlin.idea.core.targetDescriptors
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
@@ -96,7 +96,7 @@ public class KotlinReferenceImporter : ReferenceImporter {
|
|||||||
|
|
||||||
var result = false
|
var result = false
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
result = KotlinAddImportAction(project, editor, this, suggestions).execute()
|
result = createSingleImportAction(project, editor, this, suggestions).execute()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// FILE: first.before.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Missing 'getValue(testing.BigTest, kotlin.reflect.KProperty<*>)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
|
||||||
|
// ERROR: Missing 'setValue(testing.BigTest, kotlin.reflect.KProperty<*>, [ERROR : Type from delegate])' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.DelegateImpl
|
||||||
|
|
||||||
|
class BigTest {
|
||||||
|
var a by <caret>DelegateImpl<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
package some
|
||||||
|
|
||||||
|
class DelegateImpl<T> {
|
||||||
|
val value: T = null!!
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T> DelegateImpl<T>.getValue(thisRef: Any?, property: PropertyMetadata): T = value
|
||||||
|
public fun <T> DelegateImpl<T>.setValue(thisRef: Any, propertyMetadata: PropertyMetadata, t: T) {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: first.after.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Missing 'getValue(testing.BigTest, kotlin.reflect.KProperty<*>)' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
|
||||||
|
// ERROR: Missing 'setValue(testing.BigTest, kotlin.reflect.KProperty<*>, [ERROR : Type from delegate])' method on delegate of type 'some.DelegateImpl<kotlin.Int>'
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.DelegateImpl
|
||||||
|
import some.getValue
|
||||||
|
import some.setValue
|
||||||
|
|
||||||
|
class BigTest {
|
||||||
|
var a by <caret>DelegateImpl<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// FILE: first.before.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
// "Import" "true"
|
||||||
|
|
||||||
|
package some
|
||||||
|
|
||||||
|
public class Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: first.after.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
import some.component1
|
||||||
|
import some.component2
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
// FILE: first.before.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package some
|
||||||
|
|
||||||
|
public class Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package other
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: first.after.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
import some.component1
|
||||||
|
import some.component2
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
// FILE: first.before.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package some
|
||||||
|
|
||||||
|
public class Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package other
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: first.after.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type some.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import some.Some
|
||||||
|
import some.component1
|
||||||
|
import some.component2
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
Vendored
+54
@@ -0,0 +1,54 @@
|
|||||||
|
// FILE: first.before.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type aaa.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type aaa.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import aaa.Some
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package aaa
|
||||||
|
|
||||||
|
public class Some
|
||||||
|
|
||||||
|
@Deprecated("Use componenents from other")
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
// FILE: second.kt
|
||||||
|
|
||||||
|
package other
|
||||||
|
|
||||||
|
import aaa.Some
|
||||||
|
|
||||||
|
operator fun Some.component1() = 1
|
||||||
|
operator fun Some.component2() = 3
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
|
// FILE: first.after.kt
|
||||||
|
// "Import" "true"
|
||||||
|
// ERROR: Multi-declaration initializer of type aaa.Some must have a 'component1()' function
|
||||||
|
// ERROR: Multi-declaration initializer of type aaa.Some must have a 'component2()' function
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import aaa.Some
|
||||||
|
import other.component1
|
||||||
|
import other.component2
|
||||||
|
|
||||||
|
fun testing() {
|
||||||
|
val (a, b) = <caret>Some()
|
||||||
|
}
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -83,6 +83,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
|||||||
doTestWithExtraFile(fileName);
|
doTestWithExtraFile(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("delegateExtensionBoth.test")
|
||||||
|
public void testDelegateExtensionBoth() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/delegateExtensionBoth.test");
|
||||||
|
doTestWithExtraFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("delegateExtensionGet.test")
|
@TestMetadata("delegateExtensionGet.test")
|
||||||
public void testDelegateExtensionGet() throws Exception {
|
public void testDelegateExtensionGet() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/delegateExtensionGet.test");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/delegateExtensionGet.test");
|
||||||
@@ -215,6 +221,30 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
|||||||
doTestWithExtraFile(fileName);
|
doTestWithExtraFile(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multiDeclarationExtensionAllComponents.test")
|
||||||
|
public void testMultiDeclarationExtensionAllComponents() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionAllComponents.test");
|
||||||
|
doTestWithExtraFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multiDeclarationExtensionAllComponentsMany.test")
|
||||||
|
public void testMultiDeclarationExtensionAllComponentsMany() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionAllComponentsMany.test");
|
||||||
|
doTestWithExtraFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multiDeclarationExtensionAllComponentsPrefereFull.test")
|
||||||
|
public void testMultiDeclarationExtensionAllComponentsPrefereFull() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionAllComponentsPrefereFull.test");
|
||||||
|
doTestWithExtraFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multiDeclarationExtensionAllComponentsPrefereNotDeprecated.test")
|
||||||
|
public void testMultiDeclarationExtensionAllComponentsPrefereNotDeprecated() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionAllComponentsPrefereNotDeprecated.test");
|
||||||
|
doTestWithExtraFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("multiDeclarationExtensionComponent1.test")
|
@TestMetadata("multiDeclarationExtensionComponent1.test")
|
||||||
public void testMultiDeclarationExtensionComponent1() throws Exception {
|
public void testMultiDeclarationExtensionComponent1() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionComponent1.test");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/multiDeclarationExtensionComponent1.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user