Allow to create single quickfix for several same-type diagnostics

This going to be used for multiple auto-import for components functions and get/set pair for delegated properties
This commit is contained in:
Nikolay Krasko
2015-10-09 20:06:26 +03:00
committed by Nikolay Krasko
parent 43ae8bb1f3
commit 4bae99e8bd
2 changed files with 81 additions and 27 deletions
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.quickfix.JetIntentionActionsFactory
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
@@ -79,23 +80,26 @@ public open class JetPsiChecker : Annotator, HighlightRangeExtension {
fun annotateElement(element: PsiElement, holder: AnnotationHolder, diagnostics: Diagnostics) {
if (ProjectRootsUtil.isInProjectSource(element) || element.getContainingFile() is KtCodeFragment) {
val elementAnnotator = ElementAnnotator(element, holder)
for (diagnostic in diagnostics.forElement(element)) {
elementAnnotator.registerDiagnosticAnnotations(diagnostic)
}
ElementAnnotator(element, holder).registerDiagnosticsAnnotations(diagnostics.forElement(element))
}
}
private inner class ElementAnnotator(private val element: PsiElement, private val holder: AnnotationHolder) {
private var isMarkedWithRedeclaration = false
fun registerDiagnosticsAnnotations(diagnostics: Collection<Diagnostic>) {
diagnostics.groupBy { diagnostic -> diagnostic.factory }.forEach { group -> registerDiagnosticAnnotations(group.getValue()) }
}
fun registerDiagnosticAnnotations(diagnostic: Diagnostic) {
if (!diagnostic.isValid()) return
private fun registerDiagnosticAnnotations(diagnostics: List<Diagnostic>) {
assert(diagnostics.isNotEmpty())
assert(diagnostic.getPsiElement() == element)
val validDiagnostics = diagnostics.filter { it.isValid }
if (validDiagnostics.isEmpty()) return
val diagnostic = diagnostics.first()
val factory = diagnostic.getFactory()
assert(diagnostics.all { it.getPsiElement() == element && it.factory == factory })
val presentationInfo: AnnotationPresentationInfo = when (factory.severity) {
Severity.ERROR -> {
when (factory) {
@@ -142,32 +146,59 @@ public open class JetPsiChecker : Annotator, HighlightRangeExtension {
Severity.INFO -> return // Do nothing
}
setUpAnnotation(diagnostic, presentationInfo)
setUpAnnotations(diagnostics, presentationInfo)
}
private fun setUpAnnotation(diagnostic: Diagnostic, data: AnnotationPresentationInfo) {
private fun setUpAnnotations(diagnostics: List<Diagnostic>, data: AnnotationPresentationInfo) {
for (range in data.ranges) {
registerQuickFix(diagnostic, range, data)
registerQuickFixes(diagnostics, range, data)
}
}
private fun registerQuickFix(diagnostic: Diagnostic, range: TextRange, data: AnnotationPresentationInfo) {
val annotation = data.create(range, holder)
fun registerQuickFixes(diagnostics: List<Diagnostic>, range: TextRange, data: AnnotationPresentationInfo) {
val (multiFixes, processedFactories) = createQuickFixesForSameTypeDiagnostics(diagnostics)
createQuickfixes(diagnostic).forEach {
annotation.registerFix(it)
val annotations = diagnostics.map { diagnostic ->
val annotation = data.create(range, holder)
createQuickfixes(diagnostic, processedFactories).forEach { annotation.registerFix(it) }
// Making warnings suppressable
if (diagnostic.getSeverity() == Severity.WARNING) {
annotation.setProblemGroup(KotlinSuppressableWarningProblemGroup(diagnostic.getFactory()))
val fixes = annotation.getQuickFixes()
if (fixes == null || (fixes.isEmpty() && multiFixes.isEmpty())) {
// if there are no quick fixes we need to register an EmptyIntentionAction to enable 'suppress' actions
annotation.registerFix(EmptyIntentionAction(diagnostic.getFactory().getName()))
}
}
annotation
}
// Making warnings suppressable
if (diagnostic.getSeverity() == Severity.WARNING) {
annotation.setProblemGroup(KotlinSuppressableWarningProblemGroup(diagnostic.getFactory()))
// Always register all group fixes on the same annotation
val firstAnnotation = annotations.minBy { it.message }!!
multiFixes.forEach { fix -> firstAnnotation.registerFix(fix) }
}
val fixes = annotation.getQuickFixes()
if (fixes == null || fixes.isEmpty()) {
// if there are no quick fixes we need to register an EmptyIntentionAction to enable 'suppress' actions
annotation.registerFix(EmptyIntentionAction(diagnostic.getFactory().getName()))
private fun createQuickFixesForSameTypeDiagnostics(diagnostics: List<Diagnostic>):
Pair<Collection<IntentionAction>, Collection<JetIntentionActionsFactory>> {
val factory = diagnostics.first().factory
val sameProblemsFixesFactories = QuickFixes.getInstance().getActionFactories(factory).filter { it.canFixSeveralSameProblems() }
val processedFactories = hashSetOf<JetIntentionActionsFactory>()
val fixActions = arrayListOf<IntentionAction>()
for (actionFactory in sameProblemsFixesFactories) {
val actions = actionFactory.createActions(diagnostics)
if (actions.isNotEmpty()) {
processedFactories.add(actionFactory)
fixActions.addAll(actions)
}
}
return Pair(fixActions, processedFactories)
}
}
@@ -234,9 +265,11 @@ public open class JetPsiChecker : Annotator, HighlightRangeExtension {
TypeKindHighlightingVisitor(holder, bindingContext)
)
public fun createQuickfixes(diagnostic: Diagnostic): Collection<IntentionAction> {
public fun createQuickfixes(
diagnostic: Diagnostic,
excludedFactories : Collection<JetIntentionActionsFactory> = emptySet<JetIntentionActionsFactory>()): Collection<IntentionAction> {
val result = arrayListOf<IntentionAction>()
val intentionActionsFactories = QuickFixes.getInstance().getActionFactories(diagnostic.getFactory())
val intentionActionsFactories = QuickFixes.getInstance().getActionFactories(diagnostic.getFactory()) - excludedFactories
for (intentionActionsFactory in intentionActionsFactories.filterNotNull()) {
result.addAll(intentionActionsFactory.createActions(diagnostic))
}
@@ -24,12 +24,33 @@ import java.util.Collections
// TODO: Replace with trait when all subclasses are translated to Kotlin
public abstract class JetIntentionActionsFactory {
protected open fun isApplicableForCodeFragment(): Boolean = false
protected abstract fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction>
public fun createActions(diagnostic: Diagnostic): List<IntentionAction> {
if (diagnostic.getPsiElement().getContainingFile() is KtCodeFragment && !isApplicableForCodeFragment()) {
public open fun canFixSeveralSameProblems(): Boolean = false
protected open fun doCreateActions(@Suppress("UNUSED_PARAMETER") sameTypeDiagnostics: List<Diagnostic>): List<IntentionAction> =
throw NotImplementedError()
public fun createActions(diagnostic: Diagnostic) = createActions(listOf(diagnostic))
public fun createActions(sameTypeDiagnostics: List<Diagnostic>): List<IntentionAction> {
if (sameTypeDiagnostics.isEmpty()) return emptyList()
val first = sameTypeDiagnostics.first()
if (first.psiElement.getContainingFile() is KtCodeFragment && !isApplicableForCodeFragment()) {
return emptyList()
}
return doCreateActions(diagnostic)
if (sameTypeDiagnostics.size > 1) {
assert(sameTypeDiagnostics.all { it.psiElement == first.psiElement && it.factory == first.factory }) {
"It's expected to be the list of diagnostics of same type and for same element"
}
if (canFixSeveralSameProblems()) {
return doCreateActions(sameTypeDiagnostics)
}
}
return sameTypeDiagnostics.flatMapTo(arrayListOf()) { doCreateActions(it) }
}
}