From 4bae99e8bd5ceeca24136ba2d8196bce4ee153a7 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 9 Oct 2015 20:06:26 +0300 Subject: [PATCH] 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 --- .../kotlin/idea/highlighter/JetPsiChecker.kt | 81 +++++++++++++------ .../quickfix/JetIntentionActionsFactory.kt | 27 ++++++- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt index fe9ff69a860..15b98e0356c 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt @@ -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) { + diagnostics.groupBy { diagnostic -> diagnostic.factory }.forEach { group -> registerDiagnosticAnnotations(group.getValue()) } + } - fun registerDiagnosticAnnotations(diagnostic: Diagnostic) { - if (!diagnostic.isValid()) return + private fun registerDiagnosticAnnotations(diagnostics: List) { + 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, 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, 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): + Pair, Collection> { + val factory = diagnostics.first().factory + val sameProblemsFixesFactories = QuickFixes.getInstance().getActionFactories(factory).filter { it.canFixSeveralSameProblems() } + + val processedFactories = hashSetOf() + val fixActions = arrayListOf() + + 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 { + public fun createQuickfixes( + diagnostic: Diagnostic, + excludedFactories : Collection = emptySet()): Collection { val result = arrayListOf() - 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)) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/JetIntentionActionsFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/JetIntentionActionsFactory.kt index 65ad9587a3a..aca6e0000b4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/JetIntentionActionsFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/JetIntentionActionsFactory.kt @@ -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 - public fun createActions(diagnostic: Diagnostic): List { - if (diagnostic.getPsiElement().getContainingFile() is KtCodeFragment && !isApplicableForCodeFragment()) { + public open fun canFixSeveralSameProblems(): Boolean = false + protected open fun doCreateActions(@Suppress("UNUSED_PARAMETER") sameTypeDiagnostics: List): List = + throw NotImplementedError() + + public fun createActions(diagnostic: Diagnostic) = createActions(listOf(diagnostic)) + + public fun createActions(sameTypeDiagnostics: List): List { + 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) } } }