Kotlin highlight passes are reworked
#KT-37702 Fixed
This commit is contained in:
committed by
Space
parent
558338f997
commit
913c298be8
@@ -312,7 +312,7 @@ fun main(args: Array<String>) {
|
||||
model("resolve/resolveModeComparison")
|
||||
}
|
||||
|
||||
testClass<AbstractPsiCheckerTest> {
|
||||
testClass<AbstractKotlinHighlightingPassTest> {
|
||||
model("checker", recursive = false)
|
||||
model("checker/regression")
|
||||
model("checker/recovery")
|
||||
@@ -1092,7 +1092,7 @@ fun main(args: Array<String>) {
|
||||
model("resolve/references", pattern = KT_WITHOUT_DOTS_IN_NAME)
|
||||
}
|
||||
|
||||
testClass<AbstractFirPsiCheckerTest> {
|
||||
testClass<AbstractFirKotlinHighlightingPassTest> {
|
||||
model("checker", recursive = false)
|
||||
model("checker/regression")
|
||||
model("checker/recovery")
|
||||
|
||||
@@ -239,7 +239,7 @@ fun main(args: Array<String>) {
|
||||
model("resolve/partialBodyResolve")
|
||||
}
|
||||
|
||||
testClass<AbstractPsiCheckerTest> {
|
||||
testClass<AbstractKotlinHighlightingPassTest> {
|
||||
model("checker", recursive = false)
|
||||
model("checker/regression")
|
||||
model("checker/recovery")
|
||||
|
||||
@@ -235,7 +235,7 @@ fun main(args: Array<String>) {
|
||||
model("resolve/partialBodyResolve")
|
||||
}
|
||||
|
||||
testClass<AbstractPsiCheckerTest> {
|
||||
testClass<AbstractKotlinHighlightingPassTest> {
|
||||
model("checker", recursive = false)
|
||||
model("checker/regression")
|
||||
model("checker/recovery")
|
||||
|
||||
@@ -40,6 +40,8 @@ html.type.mismatch.table.tr.td.required.td.td.0.td.tr.tr.td.found.td.td.1.td.tr.
|
||||
|
||||
intention.suppress.family=Suppress Warnings
|
||||
intention.suppress.text=Suppress ''{0}'' for {1} {2}
|
||||
intention.calculating.text=Quick fix is being calculated ...
|
||||
|
||||
special.module.for.files.not.under.source.root=<special module for files not under source root>
|
||||
sdk.0=<sdk {0}>
|
||||
sources.for.library.0=<sources for library {0}>
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
abstract class AbstractBindingContextAwareHighlightingPassBase(
|
||||
file: KtFile,
|
||||
document: Document
|
||||
) : AbstractHighlightingPassBase(file, document) {
|
||||
|
||||
private val cachedAnnotator by lazy { annotator }
|
||||
|
||||
protected abstract val annotator: Annotator
|
||||
|
||||
private var bindingContext: BindingContext? = null
|
||||
|
||||
protected fun bindingContext(): BindingContext = bindingContext ?: error("bindingContext has to be acquired")
|
||||
|
||||
protected open fun buildBindingContext(holder: AnnotationHolder): BindingContext =
|
||||
file.analyzeWithAllCompilerChecks().also { it.throwIfError() }.bindingContext
|
||||
|
||||
override fun runAnnotatorWithContext(element: PsiElement, holder: AnnotationHolder) {
|
||||
bindingContext = buildBindingContext(holder)
|
||||
try {
|
||||
element.accept(object : PsiRecursiveElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
cachedAnnotator.annotate(element, holder)
|
||||
super.visitElement(element)
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
bindingContext = null
|
||||
}
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.Divider
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.lang.annotation.Annotation
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.CommonProcessors
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.RenderingContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractKotlinHighlightingPass(file: KtFile, document: Document) :
|
||||
AbstractBindingContextAwareHighlightingPassBase(file, document) {
|
||||
override val annotator: Annotator
|
||||
get() = KotlinAfterAnalysisAnnotator()
|
||||
|
||||
private inner class KotlinAfterAnalysisAnnotator : Annotator {
|
||||
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
|
||||
val bindingContext = bindingContext()
|
||||
getAfterAnalysisVisitor(holder, bindingContext).forEach { visitor -> element.accept(visitor) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildBindingContext(holder: AnnotationHolder): BindingContext {
|
||||
val dividedElements: List<Divider.DividedElements> = ArrayList()
|
||||
Divider.divideInsideAndOutsideAllRoots(
|
||||
file, file.textRange, file.textRange, { true },
|
||||
CommonProcessors.CollectProcessor(dividedElements)
|
||||
)
|
||||
// TODO: for the sake of check that element belongs to the file
|
||||
// for some reason analyzeWithAllCompilerChecks could return psiElements those do not belong to the file
|
||||
// see [ScriptConfigurationHighlightingTestGenerated$Highlighting.testCustomExtension]
|
||||
val elements = dividedElements.flatMap(Divider.DividedElements::inside).toSet()
|
||||
|
||||
// annotate diagnostics on fly: show diagnostics as soon as front-end reports them
|
||||
// don't create quick fixes as it could require some resolve
|
||||
val annotationByDiagnostic = mutableMapOf<Diagnostic, Annotation>()
|
||||
val annotationByTextRange = mutableMapOf<TextRange, Annotation>()
|
||||
|
||||
// render of on-fly diagnostics with descriptors could lead to recursion
|
||||
fun checkIfDescriptor(candidate: Any?): Boolean =
|
||||
candidate is DeclarationDescriptor || candidate is Collection<*> && candidate.any(::checkIfDescriptor)
|
||||
|
||||
val analysisResult =
|
||||
file.analyzeWithAllCompilerChecks({
|
||||
val element = it.psiElement
|
||||
if (element in elements &&
|
||||
it !in annotationByDiagnostic &&
|
||||
!RenderingContext.parameters(it).any(::checkIfDescriptor)
|
||||
) {
|
||||
annotateDiagnostic(element, holder, it, annotationByDiagnostic, annotationByTextRange)
|
||||
}
|
||||
}).also { it.throwIfError() }
|
||||
// resolve is done!
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
|
||||
cleanUpCalculatingAnnotations(annotationByTextRange)
|
||||
// TODO: for some reasons it could be duplicated diagnostics for the same factory
|
||||
// see [PsiCheckerTestGenerated$Checker.testRedeclaration]
|
||||
val diagnostics = bindingContext.diagnostics.asSequence().filter { it.psiElement in elements }.toSet()
|
||||
|
||||
if (diagnostics.isNotEmpty()) {
|
||||
// annotate diagnostics those were not possible to render on fly
|
||||
diagnostics.asSequence().filterNot { it in annotationByDiagnostic }.forEach {
|
||||
annotateDiagnostic(it.psiElement, holder, it, annotationByDiagnostic, calculatingInProgress = false)
|
||||
}
|
||||
// apply quick fixes for all diagnostics grouping by element
|
||||
diagnostics.groupBy(Diagnostic::psiElement).forEach {
|
||||
annotateQuickFixes(it.key, it.value, annotationByDiagnostic)
|
||||
}
|
||||
}
|
||||
return bindingContext
|
||||
}
|
||||
|
||||
private fun annotateDiagnostic(
|
||||
element: PsiElement,
|
||||
holder: AnnotationHolder,
|
||||
diagnostic: Diagnostic,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>? = null,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>? = null,
|
||||
calculatingInProgress: Boolean = true
|
||||
) = annotateDiagnostics(element, holder, listOf(diagnostic), annotationByDiagnostic, annotationByTextRange, true, calculatingInProgress)
|
||||
|
||||
private fun cleanUpCalculatingAnnotations(annotationByTextRange: Map<TextRange, Annotation>) {
|
||||
annotationByTextRange.values.forEach { annotation ->
|
||||
annotation.quickFixes?.removeIf {
|
||||
it.quickFix is CalculatingIntentionAction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun annotateDiagnostics(
|
||||
element: PsiElement,
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: List<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>? = null,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>? = null,
|
||||
noFixes: Boolean = false,
|
||||
calculatingInProgress: Boolean = false
|
||||
) = annotateDiagnostics(
|
||||
file, element, holder, diagnostics, annotationByDiagnostic, annotationByTextRange,
|
||||
::shouldSuppressUnusedParameter,
|
||||
noFixes = noFixes, calculatingInProgress = calculatingInProgress
|
||||
)
|
||||
|
||||
/**
|
||||
* [diagnostics] has to belong to the same element
|
||||
*/
|
||||
private fun annotateQuickFixes(
|
||||
element: PsiElement,
|
||||
diagnostics: List<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>
|
||||
) {
|
||||
if (diagnostics.isEmpty()) return
|
||||
|
||||
assertBelongsToTheSameElement(element, diagnostics)
|
||||
|
||||
val shouldHighlightErrors =
|
||||
KotlinHighlightingUtil.shouldHighlightErrors(
|
||||
if (element.isPhysical) file else element
|
||||
)
|
||||
|
||||
if (shouldHighlightErrors) {
|
||||
ElementAnnotator(element) { param ->
|
||||
shouldSuppressUnusedParameter(param)
|
||||
}.registerDiagnosticsQuickFixes(diagnostics, annotationByDiagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun shouldSuppressUnusedParameter(parameter: KtParameter): Boolean = false
|
||||
|
||||
companion object {
|
||||
fun createQuickFixes(diagnostic: Diagnostic): Collection<IntentionAction> =
|
||||
createQuickFixes(listOfNotNull(diagnostic))[diagnostic]
|
||||
|
||||
private val UNRESOLVED_KEY = Key<Unit>("KotlinHighlightingPass.UNRESOLVED_KEY")
|
||||
|
||||
fun wasUnresolved(element: KtNameReferenceExpression) = element.getUserData(UNRESOLVED_KEY) != null
|
||||
|
||||
fun getAfterAnalysisVisitor(holder: AnnotationHolder, bindingContext: BindingContext) = arrayOf(
|
||||
PropertiesHighlightingVisitor(holder, bindingContext),
|
||||
FunctionsHighlightingVisitor(holder, bindingContext),
|
||||
VariablesHighlightingVisitor(holder, bindingContext),
|
||||
TypeKindHighlightingVisitor(holder, bindingContext)
|
||||
)
|
||||
|
||||
private fun assertBelongsToTheSameElement(element: PsiElement, diagnostics: Collection<Diagnostic>) {
|
||||
assert(diagnostics.all { it.psiElement == element })
|
||||
}
|
||||
|
||||
fun annotateDiagnostics(
|
||||
file: KtFile,
|
||||
element: PsiElement,
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>? = null,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>? = null,
|
||||
shouldSuppressUnusedParameter: (KtParameter) -> Boolean = { false },
|
||||
noFixes: Boolean = false,
|
||||
calculatingInProgress: Boolean = false
|
||||
) {
|
||||
if (diagnostics.isEmpty()) return
|
||||
|
||||
assertBelongsToTheSameElement(element, diagnostics)
|
||||
|
||||
if (element is KtNameReferenceExpression) {
|
||||
val unresolved = diagnostics.any { it.factory == Errors.UNRESOLVED_REFERENCE }
|
||||
element.putUserData(UNRESOLVED_KEY, if (unresolved) Unit else null)
|
||||
}
|
||||
|
||||
val shouldHighlightErrors =
|
||||
KotlinHighlightingUtil.shouldHighlightErrors(
|
||||
if (element.isPhysical) file else element
|
||||
)
|
||||
|
||||
if (shouldHighlightErrors) {
|
||||
val elementAnnotator = ElementAnnotator(element) { param ->
|
||||
shouldSuppressUnusedParameter(param)
|
||||
}
|
||||
elementAnnotator.registerDiagnosticsAnnotations(
|
||||
holder, diagnostics, annotationByDiagnostic,
|
||||
annotationByTextRange,
|
||||
noFixes = noFixes, calculatingInProgress = calculatingInProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun createQuickFixes(similarDiagnostics: Collection<Diagnostic>): MultiMap<Diagnostic, IntentionAction> {
|
||||
val first = similarDiagnostics.minByOrNull { it.toString() }
|
||||
val factory = similarDiagnostics.first().getRealDiagnosticFactory()
|
||||
|
||||
val actions = MultiMap<Diagnostic, IntentionAction>()
|
||||
|
||||
val intentionActionsFactories = QuickFixes.getInstance().getActionFactories(factory)
|
||||
for (intentionActionsFactory in intentionActionsFactories) {
|
||||
val allProblemsActions = intentionActionsFactory.createActionsForAllProblems(similarDiagnostics)
|
||||
if (allProblemsActions.isNotEmpty()) {
|
||||
actions.putValues(first, allProblemsActions)
|
||||
} else {
|
||||
for (diagnostic in similarDiagnostics) {
|
||||
actions.putValues(diagnostic, intentionActionsFactory.createActions(diagnostic))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (diagnostic in similarDiagnostics) {
|
||||
actions.putValues(diagnostic, QuickFixes.getInstance().getActions(diagnostic.factory))
|
||||
}
|
||||
|
||||
actions.values().forEach { NoDeclarationDescriptorsChecker.check(it::class.java) }
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
private fun Diagnostic.getRealDiagnosticFactory(): DiagnosticFactory<*> =
|
||||
when (factory) {
|
||||
Errors.PLUGIN_ERROR -> Errors.PLUGIN_ERROR.cast(this).a.factory
|
||||
Errors.PLUGIN_WARNING -> Errors.PLUGIN_WARNING.cast(this).a.factory
|
||||
Errors.PLUGIN_INFO -> Errors.PLUGIN_INFO.cast(this).a.factory
|
||||
else -> factory
|
||||
}
|
||||
|
||||
private object NoDeclarationDescriptorsChecker {
|
||||
private val LOG = Logger.getInstance(NoDeclarationDescriptorsChecker::class.java)
|
||||
|
||||
private val checkedQuickFixClasses = Collections.synchronizedSet(HashSet<Class<*>>())
|
||||
|
||||
fun check(quickFixClass: Class<*>) {
|
||||
if (!checkedQuickFixClasses.add(quickFixClass)) return
|
||||
|
||||
for (field in quickFixClass.declaredFields) {
|
||||
checkType(field.genericType, field)
|
||||
}
|
||||
|
||||
quickFixClass.superclass?.let { check(it) }
|
||||
}
|
||||
|
||||
private fun checkType(type: Type, field: Field) {
|
||||
when (type) {
|
||||
is Class<*> -> {
|
||||
if (DeclarationDescriptor::class.java.isAssignableFrom(type) || KotlinType::class.java.isAssignableFrom(type)) {
|
||||
LOG.error(
|
||||
"QuickFix class ${field.declaringClass.name} contains field ${field.name} that holds ${type.simpleName}. "
|
||||
+ "This leads to holding too much memory through this quick-fix instance. "
|
||||
+ "Possible solution can be wrapping it using KotlinIntentionActionFactoryWithDelegate."
|
||||
)
|
||||
}
|
||||
|
||||
if (IntentionAction::class.java.isAssignableFrom(type)) {
|
||||
check(type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
is GenericArrayType -> checkType(type.genericComponentType, field)
|
||||
|
||||
is ParameterizedType -> {
|
||||
if (Collection::class.java.isAssignableFrom(type.rawType as Class<*>)) {
|
||||
type.actualTypeArguments.forEach { checkType(it, field) }
|
||||
}
|
||||
}
|
||||
|
||||
is WildcardType -> type.upperBounds.forEach { checkType(it, field) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-24
@@ -8,10 +8,9 @@ package org.jetbrains.kotlin.idea.highlighter
|
||||
import com.intellij.codeInsight.intention.EmptyIntentionAction
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.lang.annotation.AnnotationBuilder
|
||||
import com.intellij.lang.annotation.Annotation
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.util.containers.MultiMap
|
||||
@@ -20,6 +19,8 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
|
||||
import org.jetbrains.kotlin.idea.util.application.isApplicationInternalMode
|
||||
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
||||
|
||||
class AnnotationPresentationInfo(
|
||||
val ranges: List<TextRange>,
|
||||
@@ -28,32 +29,59 @@ class AnnotationPresentationInfo(
|
||||
val textAttributes: TextAttributesKey? = null
|
||||
) {
|
||||
|
||||
fun processDiagnostics(holder: AnnotationHolder, diagnostics: List<Diagnostic>, fixesMap: MultiMap<Diagnostic, IntentionAction>) {
|
||||
fun processDiagnostics(
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
annotationBuilderByDiagnostic: MutableMap<Diagnostic, Annotation>? = null,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>?,
|
||||
fixesMap: MultiMap<Diagnostic, IntentionAction>?,
|
||||
calculatingInProgress: Boolean
|
||||
) {
|
||||
for (range in ranges) {
|
||||
for (diagnostic in diagnostics) {
|
||||
val fixes = fixesMap[diagnostic]
|
||||
create(diagnostic, range, holder) { annotation ->
|
||||
fixes.forEach {
|
||||
when (it) {
|
||||
is KotlinUniversalQuickFix -> annotation.newFix(it).universal().registerFix()
|
||||
is IntentionAction -> annotation.newFix(it).registerFix()
|
||||
}
|
||||
annotationBuilderByDiagnostic?.put(diagnostic, annotation)
|
||||
if (fixesMap != null) {
|
||||
applyFixes(fixesMap, diagnostic, annotation)
|
||||
}
|
||||
|
||||
if (diagnostic.severity == Severity.WARNING) {
|
||||
annotation.problemGroup(KotlinSuppressableWarningProblemGroup(diagnostic.factory))
|
||||
|
||||
if (fixes.isEmpty()) {
|
||||
// if there are no quick fixes we need to register an EmptyIntentionAction to enable 'suppress' actions
|
||||
annotation.newFix(EmptyIntentionAction(diagnostic.factory.name!!)).registerFix()
|
||||
}
|
||||
if (calculatingInProgress && annotationByTextRange?.containsKey(range) == false) {
|
||||
annotationByTextRange[range] = annotation
|
||||
annotation.registerFix(CalculatingIntentionAction(), range)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun create(diagnostic: Diagnostic, range: TextRange, holder: AnnotationHolder, consumer: (AnnotationBuilder) -> Unit) {
|
||||
internal fun applyFixes(
|
||||
fixesMap: MultiMap<Diagnostic, IntentionAction>,
|
||||
diagnostic: Diagnostic,
|
||||
annotation: Annotation
|
||||
) {
|
||||
val fixes = fixesMap[diagnostic]
|
||||
val textRange = TextRange(annotation.startOffset, annotation.endOffset)
|
||||
fixes.forEach {
|
||||
when (it) {
|
||||
is KotlinUniversalQuickFix -> {
|
||||
annotation.registerBatchFix(it, textRange, null)
|
||||
annotation.registerFix(it, textRange)
|
||||
}
|
||||
is IntentionAction -> {
|
||||
annotation.registerFix(it, textRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diagnostic.severity == Severity.WARNING) {
|
||||
if (fixes.isEmpty()) {
|
||||
// if there are no quick fixes we need to register an EmptyIntentionAction to enable 'suppress' actions
|
||||
//annotation.newFix(EmptyIntentionAction(diagnostic.factory.name)).registerFix()
|
||||
annotation.registerFix(EmptyIntentionAction(diagnostic.factory.name), textRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun create(diagnostic: Diagnostic, range: TextRange, holder: AnnotationHolder, consumer: (Annotation) -> Unit) {
|
||||
val severity = when (diagnostic.severity) {
|
||||
Severity.ERROR -> HighlightSeverity.ERROR
|
||||
Severity.WARNING -> if (highlightType == ProblemHighlightType.WEAK_WARNING) {
|
||||
@@ -62,18 +90,26 @@ class AnnotationPresentationInfo(
|
||||
Severity.INFO -> HighlightSeverity.WEAK_WARNING
|
||||
}
|
||||
|
||||
holder.newAnnotation(severity, nonDefaultMessage ?: getDefaultMessage(diagnostic))
|
||||
|
||||
val message = nonDefaultMessage ?: getDefaultMessage(diagnostic)
|
||||
holder.newAnnotation(severity, message)
|
||||
.range(range)
|
||||
.tooltip(getMessage(diagnostic))
|
||||
.also { builder -> highlightType?.let { builder.highlightType(it) } }
|
||||
.also { builder -> textAttributes?.let { builder.textAttributes(it) } }
|
||||
.also { consumer(it) }
|
||||
.also {
|
||||
if (diagnostic.severity == Severity.WARNING) {
|
||||
it.problemGroup(KotlinSuppressableWarningProblemGroup(diagnostic.factory))
|
||||
}
|
||||
}
|
||||
.create()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(holder as? List<Annotation>)?.last()?.let(consumer::invoke)
|
||||
}
|
||||
|
||||
private fun getMessage(diagnostic: Diagnostic): String {
|
||||
var message = IdeErrorMessages.render(diagnostic)
|
||||
if (ApplicationManager.getApplication().isInternal || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
if (isApplicationInternalMode() || isUnitTestMode()) {
|
||||
val factoryName = diagnostic.factory.name
|
||||
message = if (message.startsWith("<html>")) {
|
||||
"<html>[$factoryName] ${message.substring("<html>".length)}"
|
||||
@@ -89,10 +125,11 @@ class AnnotationPresentationInfo(
|
||||
|
||||
private fun getDefaultMessage(diagnostic: Diagnostic): String {
|
||||
val message = DefaultErrorMessages.render(diagnostic)
|
||||
if (ApplicationManager.getApplication().isInternal || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return "[${diagnostic.factory.name}] $message"
|
||||
return if (isApplicationInternalMode() || isUnitTestMode()) {
|
||||
"[${diagnostic.factory.name}] $message"
|
||||
} else {
|
||||
message
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.intention.AbstractEmptyIntentionAction
|
||||
import com.intellij.codeInsight.intention.LowPriorityAction
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Iconable
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle
|
||||
import javax.swing.Icon
|
||||
|
||||
class CalculatingIntentionAction : AbstractEmptyIntentionAction(), LowPriorityAction, Iconable {
|
||||
override fun getText(): String = KotlinIdeaAnalysisBundle.message("intention.calculating.text")
|
||||
|
||||
override fun getFamilyName(): String = KotlinIdeaAnalysisBundle.message("intention.calculating.text")
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean = true
|
||||
|
||||
override fun equals(other: Any?): Boolean = this === other || other is CalculatingIntentionAction
|
||||
|
||||
override fun hashCode(): Int = 42
|
||||
|
||||
override fun getIcon(@Iconable.IconFlags flags: Int): Icon = AllIcons.Actions.Preview
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.*
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.checkers.utils.DebugInfoUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinPluginUtil
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.isApplicationInternalMode
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
|
||||
class DebugInfoHighlightingPass(file: KtFile, document: Document) : AbstractBindingContextAwareHighlightingPassBase(file, document) {
|
||||
override val annotator: Annotator
|
||||
get() = DebugInfoAnnotator()
|
||||
|
||||
private inner class DebugInfoAnnotator : Annotator {
|
||||
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
|
||||
if (element is KtFile && element !is KtCodeFragment) {
|
||||
fun errorAnnotation(
|
||||
expression: PsiElement,
|
||||
message: String,
|
||||
textAttributes: TextAttributesKey? = KotlinHighlightingColors.DEBUG_INFO
|
||||
) =
|
||||
holder.newAnnotation(HighlightSeverity.ERROR, "[DEBUG] $message")
|
||||
.range(expression.textRange)
|
||||
.also {
|
||||
textAttributes?.let { ta -> it.textAttributes(ta) }
|
||||
}
|
||||
.create()
|
||||
|
||||
try {
|
||||
DebugInfoUtil.markDebugAnnotations(element, bindingContext(), object : DebugInfoUtil.DebugInfoReporter() {
|
||||
override fun reportElementWithErrorType(expression: KtReferenceExpression) =
|
||||
errorAnnotation(expression, "Resolved to error element", KotlinHighlightingColors.RESOLVED_TO_ERROR)
|
||||
|
||||
override fun reportMissingUnresolved(expression: KtReferenceExpression) =
|
||||
errorAnnotation(
|
||||
expression,
|
||||
"Reference is not resolved to anything, but is not marked unresolved"
|
||||
)
|
||||
|
||||
override fun reportUnresolvedWithTarget(expression: KtReferenceExpression, target: String) =
|
||||
errorAnnotation(
|
||||
expression,
|
||||
"Reference marked as unresolved is actually resolved to $target"
|
||||
)
|
||||
})
|
||||
} catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
// TODO
|
||||
errorAnnotation(element, e.javaClass.canonicalName + ": " + e.message, null)
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : TextEditorHighlightingPassFactory {
|
||||
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||
return if (file is KtFile &&
|
||||
(isApplicationInternalMode() && (KotlinPluginUtil.isSnapshotVersion() || KotlinPluginUtil.isDevVersion())) &&
|
||||
ProjectRootsUtil.isInProjectOrLibSource(file)
|
||||
) {
|
||||
DebugInfoHighlightingPass(file, editor.document)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Registrar : TextEditorHighlightingPassFactoryRegistrar {
|
||||
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||
registrar.registerTextEditorHighlightingPass(
|
||||
Factory(),
|
||||
/* runAfterCompletionOf = */ intArrayOf(Pass.UPDATE_ALL),
|
||||
/* runAfterStartingOf = */ null,
|
||||
/* runIntentionsPassAfter = */ false,
|
||||
/* forcedPassId = */ -1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.highlighter
|
||||
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.asJava.*
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
|
||||
class DuplicateJvmSignatureAnnotator : Annotator {
|
||||
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
|
||||
if (element !is KtFile && element !is KtDeclaration) return
|
||||
if (!ProjectRootsUtil.isInProjectSource(element)) return
|
||||
|
||||
val file = element.containingFile
|
||||
if (file !is KtFile || !TargetPlatformDetector.getPlatform(file).isJvm()) return
|
||||
|
||||
val otherDiagnostics = when (element) {
|
||||
is KtDeclaration -> element.analyzeWithContent()
|
||||
is KtFile -> element.analyzeWithContent()
|
||||
else -> throw AssertionError("DuplicateJvmSignatureAnnotator: should not get here! Element: ${element.text}")
|
||||
}.diagnostics
|
||||
|
||||
val moduleScope = element.getModuleInfo().contentScope()
|
||||
val diagnostics = getJvmSignatureDiagnostics(element, otherDiagnostics, moduleScope) ?: return
|
||||
|
||||
KotlinPsiChecker().annotateElement(element, holder, diagnostics)
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.*
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.Annotator
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.asJava.getJvmSignatureDiagnostics
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.highlighter.AbstractKotlinHighlightingPass.Companion.annotateDiagnostics
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class DuplicateJvmSignatureHighlightPass(file: KtFile, document: Document) :
|
||||
AbstractBindingContextAwareHighlightingPassBase(file, document) {
|
||||
override val annotator: Annotator
|
||||
get() = DuplicateJvmSignatureAnnotator()
|
||||
|
||||
inner class DuplicateJvmSignatureAnnotator : Annotator {
|
||||
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
|
||||
if (element !is KtFile && element !is KtDeclaration) return
|
||||
|
||||
val otherDiagnostics = when (element) {
|
||||
is KtDeclaration -> element.analyzeWithContent()
|
||||
is KtFile -> element.analyzeWithContent()
|
||||
else -> throw AssertionError("DuplicateJvmSignatureAnnotator: should not get here! Element: ${element.text}")
|
||||
}.diagnostics
|
||||
|
||||
val moduleScope = element.getModuleInfo().contentScope()
|
||||
val diagnostics = getJvmSignatureDiagnostics(element, otherDiagnostics, moduleScope) ?: return
|
||||
|
||||
val diagnosticsForElement = diagnostics.forElement(element).toSet()
|
||||
|
||||
annotateDiagnostics(file, element, holder, diagnosticsForElement)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : TextEditorHighlightingPassFactory {
|
||||
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||
return if (file is KtFile &&
|
||||
ProjectRootsUtil.isInProjectSource(file) &&
|
||||
TargetPlatformDetector.getPlatform(file).isJvm()
|
||||
) {
|
||||
DuplicateJvmSignatureHighlightPass(file, editor.document)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Registrar : TextEditorHighlightingPassFactoryRegistrar {
|
||||
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||
registrar.registerTextEditorHighlightingPass(
|
||||
Factory(),
|
||||
/* runAfterCompletionOf = */ intArrayOf(Pass.UPDATE_ALL),
|
||||
/* runAfterStartingOf = */ null,
|
||||
/* runIntentionsPassAfter = */ false,
|
||||
/* forcedPassId = */ -1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.lang.annotation.Annotation
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.MultiRangeReference
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.module
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
|
||||
internal class ElementAnnotator(
|
||||
private val element: PsiElement,
|
||||
private val shouldSuppressUnusedParameter: (KtParameter) -> Boolean
|
||||
) {
|
||||
fun registerDiagnosticsAnnotations(
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>?,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>?,
|
||||
noFixes: Boolean,
|
||||
calculatingInProgress: Boolean
|
||||
) = diagnostics.groupBy { it.factory }
|
||||
.forEach {
|
||||
registerSameFactoryDiagnosticsAnnotations(
|
||||
holder,
|
||||
it.value,
|
||||
annotationByDiagnostic,
|
||||
annotationByTextRange,
|
||||
noFixes,
|
||||
calculatingInProgress
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerSameFactoryDiagnosticsAnnotations(
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>?,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>?,
|
||||
noFixes: Boolean,
|
||||
calculatingInProgress: Boolean
|
||||
) {
|
||||
val presentationInfo = presentationInfo(diagnostics) ?: return
|
||||
setUpAnnotations(
|
||||
holder,
|
||||
diagnostics,
|
||||
presentationInfo,
|
||||
annotationByDiagnostic,
|
||||
annotationByTextRange,
|
||||
noFixes,
|
||||
calculatingInProgress
|
||||
)
|
||||
}
|
||||
|
||||
fun registerDiagnosticsQuickFixes(
|
||||
diagnostics: List<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>
|
||||
) = diagnostics.groupBy { it.factory }
|
||||
.forEach { registerDiagnosticsSameFactoryQuickFixes(it.value, annotationByDiagnostic) }
|
||||
|
||||
private fun registerDiagnosticsSameFactoryQuickFixes(
|
||||
diagnostics: List<Diagnostic>,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>
|
||||
) {
|
||||
val presentationInfo = presentationInfo(diagnostics) ?: return
|
||||
val fixesMap = createFixesMap(diagnostics) ?: return
|
||||
|
||||
diagnostics.forEach {
|
||||
val annotation = annotationByDiagnostic[it] ?: return
|
||||
|
||||
presentationInfo.applyFixes(fixesMap, it, annotation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentationInfo(diagnostics: Collection<Diagnostic>): AnnotationPresentationInfo? {
|
||||
if (diagnostics.isEmpty() || !diagnostics.any { it.isValid }) return null
|
||||
|
||||
val diagnostic = diagnostics.first()
|
||||
// hack till the root cause #KT-21246 is fixed
|
||||
if (isUnstableAbiClassDiagnosticForModulesWithEnabledUnstableAbi(diagnostic)) return null
|
||||
|
||||
val factory = diagnostic.factory
|
||||
|
||||
assert(diagnostics.all { it.psiElement == element && it.factory == factory })
|
||||
|
||||
val ranges = diagnostic.textRanges
|
||||
val presentationInfo: AnnotationPresentationInfo = when (factory.severity) {
|
||||
Severity.ERROR -> {
|
||||
when (factory) {
|
||||
in Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS -> {
|
||||
val referenceExpression = element as KtReferenceExpression
|
||||
val reference = referenceExpression.mainReference
|
||||
if (reference is MultiRangeReference) {
|
||||
AnnotationPresentationInfo(
|
||||
ranges = reference.ranges.map { it.shiftRight(referenceExpression.textOffset) },
|
||||
highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
|
||||
)
|
||||
} else {
|
||||
AnnotationPresentationInfo(ranges, highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL)
|
||||
}
|
||||
}
|
||||
|
||||
Errors.ILLEGAL_ESCAPE -> AnnotationPresentationInfo(
|
||||
ranges, textAttributes = KotlinHighlightingColors.INVALID_STRING_ESCAPE
|
||||
)
|
||||
|
||||
Errors.REDECLARATION -> AnnotationPresentationInfo(
|
||||
ranges = listOf(diagnostic.textRanges.first()), nonDefaultMessage = ""
|
||||
)
|
||||
|
||||
else -> {
|
||||
AnnotationPresentationInfo(
|
||||
ranges,
|
||||
highlightType = if (factory == Errors.INVISIBLE_REFERENCE)
|
||||
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
|
||||
else
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Severity.WARNING -> {
|
||||
if (factory == Errors.UNUSED_PARAMETER && shouldSuppressUnusedParameter(element as KtParameter)) {
|
||||
return null
|
||||
}
|
||||
|
||||
AnnotationPresentationInfo(
|
||||
ranges,
|
||||
textAttributes = when (factory) {
|
||||
Errors.DEPRECATION -> CodeInsightColors.DEPRECATED_ATTRIBUTES
|
||||
Errors.UNUSED_ANONYMOUS_PARAMETER -> CodeInsightColors.WEAK_WARNING_ATTRIBUTES
|
||||
else -> null
|
||||
},
|
||||
highlightType = when (factory) {
|
||||
in Errors.UNUSED_ELEMENT_DIAGNOSTICS -> ProblemHighlightType.LIKE_UNUSED_SYMBOL
|
||||
Errors.UNUSED_ANONYMOUS_PARAMETER -> ProblemHighlightType.WEAK_WARNING
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
Severity.INFO -> AnnotationPresentationInfo(ranges, highlightType = ProblemHighlightType.INFORMATION)
|
||||
}
|
||||
return presentationInfo
|
||||
}
|
||||
|
||||
private fun setUpAnnotations(
|
||||
holder: AnnotationHolder,
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
data: AnnotationPresentationInfo,
|
||||
annotationByDiagnostic: MutableMap<Diagnostic, Annotation>?,
|
||||
annotationByTextRange: MutableMap<TextRange, Annotation>?,
|
||||
noFixes: Boolean,
|
||||
calculatingInProgress: Boolean
|
||||
) {
|
||||
val fixesMap =
|
||||
createFixesMap(diagnostics, noFixes)
|
||||
|
||||
data.processDiagnostics(holder, diagnostics, annotationByDiagnostic, annotationByTextRange, fixesMap, calculatingInProgress)
|
||||
}
|
||||
|
||||
private fun createFixesMap(
|
||||
diagnostics: Collection<Diagnostic>,
|
||||
noFixes: Boolean = false
|
||||
): MultiMap<Diagnostic, IntentionAction>? = if (noFixes) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
createQuickFixes(diagnostics)
|
||||
} catch (e: Exception) {
|
||||
if (e is ControlFlowException) {
|
||||
throw e
|
||||
}
|
||||
LOG.error(e)
|
||||
MultiMap()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUnstableAbiClassDiagnosticForModulesWithEnabledUnstableAbi(diagnostic: Diagnostic): Boolean {
|
||||
val factory = diagnostic.factory
|
||||
if (factory != Errors.IR_WITH_UNSTABLE_ABI_COMPILED_CLASS && factory != Errors.FIR_COMPILED_CLASS) return false
|
||||
|
||||
val module = element.module ?: return false
|
||||
val moduleFacetSettings = KotlinFacetSettingsProvider.getInstance(element.project)?.getSettings(module) ?: return false
|
||||
return when (factory) {
|
||||
Errors.IR_WITH_UNSTABLE_ABI_COMPILED_CLASS ->
|
||||
moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useIR) &&
|
||||
!moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useOldBackend)
|
||||
Errors.FIR_COMPILED_CLASS ->
|
||||
moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useFir)
|
||||
else -> error(factory)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance(ElementAnnotator::class.java)
|
||||
}
|
||||
}
|
||||
+9
-2
@@ -34,7 +34,10 @@ import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
object KotlinHighlightingUtil {
|
||||
fun shouldHighlight(psiElement: PsiElement): Boolean {
|
||||
val ktFile = psiElement.containingFile as? KtFile ?: return false
|
||||
return shouldHighlightFile(ktFile)
|
||||
}
|
||||
|
||||
fun shouldHighlightFile(ktFile: KtFile): Boolean {
|
||||
if (ktFile is KtCodeFragment && ktFile.context != null) {
|
||||
return true
|
||||
}
|
||||
@@ -52,8 +55,12 @@ object KotlinHighlightingUtil {
|
||||
return ProjectRootsUtil.isInProjectOrLibraryContent(ktFile) && ktFile.getModuleInfo() !is NotUnderContentRootModuleInfo
|
||||
}
|
||||
|
||||
fun shouldHighlightErrors(psiElement: PsiElement): Boolean {
|
||||
val ktFile = psiElement.containingFile as? KtFile ?: return false
|
||||
fun shouldHighlightErrors(psiElement: PsiElement): Boolean =
|
||||
(psiElement.containingFile as? KtFile)?.let {
|
||||
shouldHighlightErrors(it)
|
||||
} ?: false
|
||||
|
||||
fun shouldHighlightErrors(ktFile: KtFile): Boolean {
|
||||
if (ktFile.isCompiled) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.MultiRangeReference
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettings
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.module
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtReferenceExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
|
||||
open class KotlinPsiChecker : AbstractKotlinPsiChecker() {
|
||||
override fun shouldHighlight(file: KtFile): Boolean = KotlinHighlightingUtil.shouldHighlight(file)
|
||||
|
||||
override fun annotateElement(
|
||||
element: PsiElement,
|
||||
containingFile: KtFile,
|
||||
holder: AnnotationHolder
|
||||
) {
|
||||
val analysisResult = containingFile.analyzeWithAllCompilerChecks()
|
||||
if (analysisResult.isError()) {
|
||||
throw ProcessCanceledException(analysisResult.error)
|
||||
}
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
|
||||
getAfterAnalysisVisitor(holder, bindingContext).forEach { visitor -> element.accept(visitor) }
|
||||
|
||||
annotateElement(element, holder, bindingContext.diagnostics)
|
||||
}
|
||||
|
||||
protected open fun shouldSuppressUnusedParameter(parameter: KtParameter): Boolean = false
|
||||
|
||||
fun annotateElement(element: PsiElement, holder: AnnotationHolder, diagnostics: Diagnostics) {
|
||||
val diagnosticsForElement = diagnostics.forElement(element).toSet()
|
||||
|
||||
if (element is KtNameReferenceExpression) {
|
||||
val unresolved = diagnostics.any { it.factory == Errors.UNRESOLVED_REFERENCE }
|
||||
element.putUserData(UNRESOLVED_KEY, if (unresolved) Unit else null)
|
||||
}
|
||||
|
||||
if (diagnosticsForElement.isEmpty()) return
|
||||
|
||||
if (KotlinHighlightingUtil.shouldHighlightErrors(element)) {
|
||||
ElementAnnotator(element, holder) { param ->
|
||||
shouldSuppressUnusedParameter(param)
|
||||
}.registerDiagnosticsAnnotations(diagnosticsForElement)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getAfterAnalysisVisitor(holder: AnnotationHolder, bindingContext: BindingContext) = arrayOf(
|
||||
PropertiesHighlightingVisitor(holder, bindingContext),
|
||||
FunctionsHighlightingVisitor(holder, bindingContext),
|
||||
VariablesHighlightingVisitor(holder, bindingContext),
|
||||
TypeKindHighlightingVisitor(holder, bindingContext)
|
||||
)
|
||||
|
||||
fun createQuickFixes(diagnostic: Diagnostic): Collection<IntentionAction> =
|
||||
createQuickFixes(listOfNotNull(diagnostic))[diagnostic]
|
||||
|
||||
private val UNRESOLVED_KEY = Key<Unit>("KotlinPsiChecker.UNRESOLVED_KEY")
|
||||
|
||||
fun wasUnresolved(element: KtNameReferenceExpression) = element.getUserData(UNRESOLVED_KEY) != null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createQuickFixes(similarDiagnostics: Collection<Diagnostic>): MultiMap<Diagnostic, IntentionAction> {
|
||||
val first = similarDiagnostics.minByOrNull { it.toString() }
|
||||
val factory = similarDiagnostics.first().getRealDiagnosticFactory()
|
||||
|
||||
val actions = MultiMap<Diagnostic, IntentionAction>()
|
||||
|
||||
val intentionActionsFactories = QuickFixes.getInstance().getActionFactories(factory)
|
||||
for (intentionActionsFactory in intentionActionsFactories) {
|
||||
val allProblemsActions = intentionActionsFactory.createActionsForAllProblems(similarDiagnostics)
|
||||
if (allProblemsActions.isNotEmpty()) {
|
||||
actions.putValues(first, allProblemsActions)
|
||||
} else {
|
||||
for (diagnostic in similarDiagnostics) {
|
||||
actions.putValues(diagnostic, intentionActionsFactory.createActions(diagnostic))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (diagnostic in similarDiagnostics) {
|
||||
actions.putValues(diagnostic, QuickFixes.getInstance().getActions(diagnostic.factory))
|
||||
}
|
||||
|
||||
actions.values().forEach { NoDeclarationDescriptorsChecker.check(it::class.java) }
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
private fun Diagnostic.getRealDiagnosticFactory(): DiagnosticFactory<*> =
|
||||
when (factory) {
|
||||
Errors.PLUGIN_ERROR -> Errors.PLUGIN_ERROR.cast(this).a.factory
|
||||
Errors.PLUGIN_WARNING -> Errors.PLUGIN_WARNING.cast(this).a.factory
|
||||
Errors.PLUGIN_INFO -> Errors.PLUGIN_INFO.cast(this).a.factory
|
||||
else -> factory
|
||||
}
|
||||
|
||||
private object NoDeclarationDescriptorsChecker {
|
||||
private val LOG = Logger.getInstance(NoDeclarationDescriptorsChecker::class.java)
|
||||
|
||||
private val checkedQuickFixClasses = Collections.synchronizedSet(HashSet<Class<*>>())
|
||||
|
||||
fun check(quickFixClass: Class<*>) {
|
||||
if (!checkedQuickFixClasses.add(quickFixClass)) return
|
||||
|
||||
for (field in quickFixClass.declaredFields) {
|
||||
checkType(field.genericType, field)
|
||||
}
|
||||
|
||||
quickFixClass.superclass?.let { check(it) }
|
||||
}
|
||||
|
||||
private fun checkType(type: Type, field: Field) {
|
||||
when (type) {
|
||||
is Class<*> -> {
|
||||
if (DeclarationDescriptor::class.java.isAssignableFrom(type) || KotlinType::class.java.isAssignableFrom(type)) {
|
||||
LOG.error(
|
||||
"QuickFix class ${field.declaringClass.name} contains field ${field.name} that holds ${type.simpleName}. "
|
||||
+ "This leads to holding too much memory through this quick-fix instance. "
|
||||
+ "Possible solution can be wrapping it using KotlinIntentionActionFactoryWithDelegate."
|
||||
)
|
||||
}
|
||||
|
||||
if (IntentionAction::class.java.isAssignableFrom(type)) {
|
||||
check(type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
is GenericArrayType -> checkType(type.genericComponentType, field)
|
||||
|
||||
is ParameterizedType -> {
|
||||
if (Collection::class.java.isAssignableFrom(type.rawType as Class<*>)) {
|
||||
type.actualTypeArguments.forEach { checkType(it, field) }
|
||||
}
|
||||
}
|
||||
|
||||
is WildcardType -> type.upperBounds.forEach { checkType(it, field) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ElementAnnotator(
|
||||
private val element: PsiElement,
|
||||
private val holder: AnnotationHolder,
|
||||
private val shouldSuppressUnusedParameter: (KtParameter) -> Boolean
|
||||
) {
|
||||
fun registerDiagnosticsAnnotations(diagnostics: Collection<Diagnostic>) {
|
||||
diagnostics.groupBy { it.factory }.forEach { group -> registerDiagnosticAnnotations(group.value) }
|
||||
}
|
||||
|
||||
private fun registerDiagnosticAnnotations(diagnostics: List<Diagnostic>) {
|
||||
assert(diagnostics.isNotEmpty())
|
||||
|
||||
val validDiagnostics = diagnostics.filter { it.isValid }
|
||||
if (validDiagnostics.isEmpty()) return
|
||||
|
||||
val diagnostic = diagnostics.first()
|
||||
val factory = diagnostic.factory
|
||||
|
||||
// hack till the root cause #KT-21246 is fixed
|
||||
if (isUnstableAbiClassDiagnosticForModulesWithEnabledUnstableAbi(diagnostic)) return
|
||||
|
||||
assert(diagnostics.all { it.psiElement == element && it.factory == factory })
|
||||
|
||||
val ranges = diagnostic.textRanges
|
||||
|
||||
val presentationInfo: AnnotationPresentationInfo = when (factory.severity) {
|
||||
Severity.ERROR -> {
|
||||
when (factory) {
|
||||
in Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS -> {
|
||||
val referenceExpression = element as KtReferenceExpression
|
||||
val reference = referenceExpression.mainReference
|
||||
if (reference is MultiRangeReference) {
|
||||
AnnotationPresentationInfo(
|
||||
ranges = reference.ranges.map { it.shiftRight(referenceExpression.textOffset) },
|
||||
highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
|
||||
)
|
||||
} else {
|
||||
AnnotationPresentationInfo(ranges, highlightType = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL)
|
||||
}
|
||||
}
|
||||
|
||||
Errors.ILLEGAL_ESCAPE -> AnnotationPresentationInfo(
|
||||
ranges, textAttributes = KotlinHighlightingColors.INVALID_STRING_ESCAPE
|
||||
)
|
||||
|
||||
Errors.REDECLARATION -> AnnotationPresentationInfo(
|
||||
ranges = listOf(diagnostic.textRanges.first()), nonDefaultMessage = ""
|
||||
)
|
||||
|
||||
else -> {
|
||||
AnnotationPresentationInfo(
|
||||
ranges,
|
||||
highlightType = if (factory == Errors.INVISIBLE_REFERENCE)
|
||||
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL
|
||||
else
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Severity.WARNING -> {
|
||||
if (factory == Errors.UNUSED_PARAMETER && shouldSuppressUnusedParameter(element as KtParameter)) {
|
||||
return
|
||||
}
|
||||
|
||||
AnnotationPresentationInfo(
|
||||
ranges,
|
||||
textAttributes = when (factory) {
|
||||
Errors.DEPRECATION -> CodeInsightColors.DEPRECATED_ATTRIBUTES
|
||||
Errors.UNUSED_ANONYMOUS_PARAMETER -> CodeInsightColors.WEAK_WARNING_ATTRIBUTES
|
||||
else -> null
|
||||
},
|
||||
highlightType = when (factory) {
|
||||
in Errors.UNUSED_ELEMENT_DIAGNOSTICS -> ProblemHighlightType.LIKE_UNUSED_SYMBOL
|
||||
Errors.UNUSED_ANONYMOUS_PARAMETER -> ProblemHighlightType.WEAK_WARNING
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
Severity.INFO -> AnnotationPresentationInfo(ranges, highlightType = ProblemHighlightType.INFORMATION)
|
||||
}
|
||||
|
||||
setUpAnnotations(diagnostics, presentationInfo)
|
||||
}
|
||||
|
||||
private fun setUpAnnotations(diagnostics: List<Diagnostic>, data: AnnotationPresentationInfo) {
|
||||
val fixesMap = try {
|
||||
createQuickFixes(diagnostics)
|
||||
} catch (e: Exception) {
|
||||
if (e is ControlFlowException) {
|
||||
throw e
|
||||
}
|
||||
LOG.error(e)
|
||||
MultiMap<Diagnostic, IntentionAction>()
|
||||
}
|
||||
|
||||
data.processDiagnostics(holder, diagnostics, fixesMap)
|
||||
}
|
||||
|
||||
private fun isUnstableAbiClassDiagnosticForModulesWithEnabledUnstableAbi(diagnostic: Diagnostic): Boolean {
|
||||
val factory = diagnostic.factory
|
||||
if (factory != Errors.IR_WITH_UNSTABLE_ABI_COMPILED_CLASS && factory != Errors.FIR_COMPILED_CLASS) return false
|
||||
|
||||
val module = element.module ?: return false
|
||||
val moduleFacetSettings = KotlinFacetSettingsProvider.getInstance(element.project)?.getSettings(module) ?: return false
|
||||
return when (factory) {
|
||||
Errors.IR_WITH_UNSTABLE_ABI_COMPILED_CLASS ->
|
||||
moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useIR) &&
|
||||
!moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useOldBackend)
|
||||
Errors.FIR_COMPILED_CLASS ->
|
||||
moduleFacetSettings.isCompilerSettingPresent(K2JVMCompilerArguments::useFir)
|
||||
else -> error(factory)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance(ElementAnnotator::class.java)
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -35,8 +35,8 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon
|
||||
if (target is ValueParameterDescriptor && bindingContext.get(AUTO_CREATED_IT, target) == true) {
|
||||
createInfoAnnotation(
|
||||
expression,
|
||||
FUNCTION_LITERAL_DEFAULT_PARAMETER,
|
||||
KotlinIdeaAnalysisBundle.message("automatically.declared.based.on.the.expected.type")
|
||||
KotlinIdeaAnalysisBundle.message("automatically.declared.based.on.the.expected.type"),
|
||||
FUNCTION_LITERAL_DEFAULT_PARAMETER
|
||||
)
|
||||
} else if (expression.parent !is KtValueArgumentName) { // highlighted separately
|
||||
highlightVariable(expression, target)
|
||||
@@ -91,14 +91,15 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon
|
||||
"0.smart.cast.to.1",
|
||||
receiverName,
|
||||
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type)
|
||||
)
|
||||
).textAttributes = SMART_CAST_RECEIVER
|
||||
),
|
||||
SMART_CAST_RECEIVER
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val nullSmartCast = bindingContext.get(SMARTCAST_NULL, expression) == true
|
||||
if (nullSmartCast) {
|
||||
createInfoAnnotation(expression, KotlinIdeaAnalysisBundle.message("always.null")).textAttributes = SMART_CONSTANT
|
||||
createInfoAnnotation(expression, KotlinIdeaAnalysisBundle.message("always.null"), SMART_CONSTANT)
|
||||
}
|
||||
|
||||
val smartCast = bindingContext.get(SMARTCAST, expression)
|
||||
@@ -107,8 +108,9 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon
|
||||
if (defaultType != null) {
|
||||
createInfoAnnotation(
|
||||
getSmartCastTarget(expression),
|
||||
KotlinIdeaAnalysisBundle.message("smart.cast.to.0", DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(defaultType))
|
||||
).textAttributes = SMART_CAST_VALUE
|
||||
KotlinIdeaAnalysisBundle.message("smart.cast.to.0", DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(defaultType)),
|
||||
SMART_CAST_VALUE
|
||||
)
|
||||
} else if (smartCast is MultipleSmartCasts) {
|
||||
for ((call, type) in smartCast.map) {
|
||||
createInfoAnnotation(
|
||||
@@ -117,8 +119,9 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon
|
||||
"smart.cast.to.0.for.1.call",
|
||||
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type),
|
||||
call.toString()
|
||||
)
|
||||
).textAttributes = SMART_CAST_VALUE
|
||||
),
|
||||
SMART_CAST_VALUE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +157,7 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon
|
||||
|
||||
val parent = elementToHighlight.parent
|
||||
if (!(parent is PsiNameIdentifierOwner && parent.nameIdentifier == elementToHighlight)) {
|
||||
createInfoAnnotation(elementToHighlight, WRAPPED_INTO_REF, msg)
|
||||
createInfoAnnotation(elementToHighlight, msg, WRAPPED_INTO_REF)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -9,7 +9,8 @@ import com.intellij.codeInsight.completion.PrefixMatcher
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiChecker
|
||||
import org.jetbrains.kotlin.idea.highlighter.AbstractKotlinHighlightingPass
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass
|
||||
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
@@ -28,7 +29,7 @@ class FromUnresolvedNamesCompletion(
|
||||
scope.forEachDescendantOfType<KtNameReferenceExpression> { refExpr ->
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (KotlinPsiChecker.wasUnresolved(refExpr)) {
|
||||
if (AbstractKotlinHighlightingPass.wasUnresolved(refExpr)) {
|
||||
val callTypeAndReceiver = CallTypeAndReceiver.detect(refExpr)
|
||||
if (callTypeAndReceiver.receiver != null) return@forEachDescendantOfType
|
||||
if (sampleDescriptor != null) {
|
||||
|
||||
+7
-4
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartcastKind
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
internal class ExpressionsSmartcastHighlightingVisitor(
|
||||
@@ -30,8 +31,9 @@ internal class ExpressionsSmartcastHighlightingVisitor(
|
||||
"0.smart.cast.to.1",
|
||||
receiverName,
|
||||
type.asStringForDebugging()
|
||||
)
|
||||
).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_RECEIVER
|
||||
),
|
||||
KotlinHighlightingColors.SMART_CAST_RECEIVER
|
||||
)
|
||||
}
|
||||
}
|
||||
expression.getSmartCasts()?.forEach { type ->
|
||||
@@ -40,8 +42,9 @@ internal class ExpressionsSmartcastHighlightingVisitor(
|
||||
KotlinIdeaAnalysisBundle.message(
|
||||
"smart.cast.to.0",
|
||||
type.asStringForDebugging()
|
||||
)
|
||||
).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_VALUE
|
||||
),
|
||||
KotlinHighlightingColors.SMART_CAST_VALUE
|
||||
)
|
||||
}
|
||||
|
||||
//todo smartcast to null
|
||||
|
||||
+8
@@ -6,6 +6,8 @@
|
||||
package org.jetbrains.kotlin.idea.fir.highlighter.visitors
|
||||
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.highlighter.HighlightingVisitor
|
||||
|
||||
@@ -14,6 +16,12 @@ abstract class FirAfterResolveHighlightingVisitor(
|
||||
protected val holder: AnnotationHolder
|
||||
) : HighlightingVisitor(holder) {
|
||||
|
||||
override fun createInfoAnnotation(textRange: TextRange, message: String?, textAttributes: TextAttributesKey?) {
|
||||
// TODO: Temporary use deprecated for FIR plugin as it is supposes to be rewritten fully
|
||||
holder.createInfoAnnotation(textRange, message)
|
||||
.also { annotation -> textAttributes?.let { annotation.textAttributes = textAttributes } }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createListOfVisitors(
|
||||
analysisSession: KtAnalysisSession,
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ internal class VariableReferenceHighlightingVisitor(
|
||||
if (expression.isAutoCreatedItParameter()) {
|
||||
createInfoAnnotation(
|
||||
expression,
|
||||
Colors.FUNCTION_LITERAL_DEFAULT_PARAMETER,
|
||||
KotlinIdeaAnalysisBundle.message("automatically.declared.based.on.the.expected.type")
|
||||
KotlinIdeaAnalysisBundle.message("automatically.declared.based.on.the.expected.type"),
|
||||
Colors.FUNCTION_LITERAL_DEFAULT_PARAMETER
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.uitls.IgnoreTests
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractFirPsiCheckerTest : AbstractPsiCheckerTest() {
|
||||
abstract class AbstractFirKotlinHighlightingPassTest : AbstractKotlinHighlightingPassTest() {
|
||||
override val captureExceptions: Boolean = false
|
||||
|
||||
override fun isFirPlugin(): Boolean = true
|
||||
+12
-7
@@ -18,11 +18,11 @@ import java.util.regex.Pattern;
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
public class FirKotlinHighlightingPassTestGenerated extends AbstractFirKotlinHighlightingPassTest {
|
||||
@TestMetadata("idea/testData/checker")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Checker extends AbstractFirPsiCheckerTest {
|
||||
public static class Checker extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
runTest("idea/testData/checker/AnnotationOnFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AnnotationSupressing.kt")
|
||||
public void testAnnotationSupressing() throws Exception {
|
||||
runTest("idea/testData/checker/AnnotationSupressing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AnonymousInitializers.kt")
|
||||
public void testAnonymousInitializers() throws Exception {
|
||||
runTest("idea/testData/checker/AnonymousInitializers.kt");
|
||||
@@ -365,7 +370,7 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/regression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Regression extends AbstractFirPsiCheckerTest {
|
||||
public static class Regression extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -608,7 +613,7 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/recovery")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Recovery extends AbstractFirPsiCheckerTest {
|
||||
public static class Recovery extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -636,7 +641,7 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/rendering")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Rendering extends AbstractFirPsiCheckerTest {
|
||||
public static class Rendering extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -654,7 +659,7 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/infos")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Infos extends AbstractFirPsiCheckerTest {
|
||||
public static class Infos extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -747,7 +752,7 @@ public class FirPsiCheckerTestGenerated extends AbstractFirPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/diagnosticsMessage")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class DiagnosticsMessage extends AbstractFirPsiCheckerTest {
|
||||
public static class DiagnosticsMessage extends AbstractFirKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.TextEditorHighlightingPass
|
||||
import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
|
||||
import com.intellij.lang.annotation.Annotation
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.AnnotationSession
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
/**
|
||||
* Single thread model only (as any other [TextEditorHighlightingPass])
|
||||
*/
|
||||
@Suppress("UnstableApiUsage")
|
||||
abstract class AbstractHighlightingPassBase(
|
||||
protected val file: KtFile,
|
||||
document: Document
|
||||
) : TextEditorHighlightingPass(file.project, document), DumbAware {
|
||||
|
||||
private val highlightInfos: MutableList<HighlightInfo> = mutableListOf()
|
||||
protected var annotationCallback: ((Annotation) -> Unit)? = null
|
||||
protected var annotationHolder: AnnotationHolderImpl? = null
|
||||
|
||||
fun annotationCallback(callback: (Annotation) -> Unit) {
|
||||
annotationCallback = callback
|
||||
}
|
||||
|
||||
fun resetAnnotationCallback() {
|
||||
annotationCallback = null
|
||||
}
|
||||
|
||||
override fun doCollectInformation(progress: ProgressIndicator) {
|
||||
highlightInfos.clear()
|
||||
|
||||
// TODO: YES, IT USES `@ApiStatus.Internal` AnnotationHolderImpl intentionally:
|
||||
// there is no other way to highlight:
|
||||
// - HighlightInfo could not be highlighted immediately as myHighlightInfoProcessor.infoIsAvailable is not accessible
|
||||
// (HighlightingSessionImpl impl is closed) and/or UpdateHighlightersUtil.addHighlighterToEditorIncrementally is closed as well.
|
||||
// therefore direct usage of AnnotationHolderImpl is the smallest evil
|
||||
|
||||
val annotationHolder = object : AnnotationHolderImpl(AnnotationSession(file)) {
|
||||
override fun add(element: Annotation?): Boolean {
|
||||
element?.let { annotationCallback?.invoke(it) }
|
||||
return super.add(element)
|
||||
}
|
||||
}
|
||||
annotationHolder.runAnnotatorWithContext(file) { element, holder ->
|
||||
runAnnotatorWithContext(element, holder)
|
||||
}
|
||||
this.annotationHolder = annotationHolder
|
||||
}
|
||||
|
||||
protected open fun runAnnotatorWithContext(
|
||||
element: PsiElement,
|
||||
holder: AnnotationHolder
|
||||
) {
|
||||
element.accept(object : PsiRecursiveElementVisitor() {})
|
||||
}
|
||||
|
||||
override fun getInfos(): MutableList<HighlightInfo> = highlightInfos
|
||||
|
||||
override fun doApplyInformationToEditor() {
|
||||
try {
|
||||
val infos = annotationHolder?.map { HighlightInfo.fromAnnotation(it) } ?: return
|
||||
highlightInfos.addAll(infos)
|
||||
// NOTE: keep !! for 201 version
|
||||
UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument!!, 0, file.textLength, infos, colorsScheme, id)
|
||||
} finally {
|
||||
annotationHolder = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
-6
@@ -38,7 +38,7 @@ internal class BeforeResolveHighlightingVisitor(holder: AnnotationHolder) : High
|
||||
else -> return
|
||||
}
|
||||
|
||||
createInfoAnnotation(element, null).textAttributes = attributes
|
||||
createInfoAnnotation(element, textAttributes = attributes)
|
||||
}
|
||||
|
||||
private fun willApplyRainbowHighlight(element: KDocLink): Boolean {
|
||||
@@ -53,27 +53,29 @@ internal class BeforeResolveHighlightingVisitor(holder: AnnotationHolder) : High
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return
|
||||
|
||||
val functionLiteral = lambdaExpression.functionLiteral
|
||||
createInfoAnnotation(functionLiteral.lBrace, null).textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW
|
||||
createInfoAnnotation(functionLiteral.lBrace, textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW)
|
||||
|
||||
val closingBrace = functionLiteral.rBrace
|
||||
if (closingBrace != null) {
|
||||
createInfoAnnotation(closingBrace, null).textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW
|
||||
createInfoAnnotation(closingBrace, textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW)
|
||||
}
|
||||
|
||||
val arrow = functionLiteral.arrow
|
||||
if (arrow != null) {
|
||||
createInfoAnnotation(arrow, null).textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW
|
||||
createInfoAnnotation(arrow, textAttributes = KotlinHighlightingColors.FUNCTION_LITERAL_BRACES_AND_ARROW)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitArgument(argument: KtValueArgument) {
|
||||
val argumentName = argument.getArgumentName() ?: return
|
||||
val eq = argument.equalsToken ?: return
|
||||
createInfoAnnotation(TextRange(argumentName.startOffset, eq.endOffset), null).textAttributes =
|
||||
if (argument.parent.parent is KtAnnotationEntry)
|
||||
createInfoAnnotation(
|
||||
TextRange(argumentName.startOffset, eq.endOffset),
|
||||
textAttributes = if (argument.parent.parent is KtAnnotationEntry)
|
||||
KotlinHighlightingColors.ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES
|
||||
else
|
||||
KotlinHighlightingColors.NAMED_ARGUMENT
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitExpressionWithLabel(expression: KtExpressionWithLabel) {
|
||||
|
||||
+17
-11
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.lang.annotation.Annotation
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -17,29 +18,34 @@ abstract class HighlightingVisitor protected constructor(
|
||||
private val holder: AnnotationHolder
|
||||
) : KtVisitorVoid() {
|
||||
|
||||
protected fun createInfoAnnotation(element: PsiElement, textAttributes: TextAttributesKey, message: String? = null) {
|
||||
createInfoAnnotation(element.textRange, textAttributes, message)
|
||||
protected fun createInfoAnnotation(element: PsiElement, message: String? = null, textAttributes: TextAttributesKey) {
|
||||
createInfoAnnotation(element.textRange, message, textAttributes)
|
||||
}
|
||||
|
||||
protected fun createInfoAnnotation(range: TextRange, textAttributes: TextAttributesKey, message: String? = null) {
|
||||
createInfoAnnotation(range, message).textAttributes = textAttributes
|
||||
}
|
||||
|
||||
protected fun createInfoAnnotation(element: PsiElement, message: String? = null): Annotation =
|
||||
protected fun createInfoAnnotation(element: PsiElement, message: String? = null) =
|
||||
createInfoAnnotation(element.textRange, message)
|
||||
|
||||
protected fun createInfoAnnotation(textRange: TextRange, message: String? = null): Annotation =
|
||||
holder.createInfoAnnotation(textRange, message)
|
||||
protected open fun createInfoAnnotation(textRange: TextRange, message: String? = null, textAttributes: TextAttributesKey? = null) {
|
||||
(message?.let { holder.newAnnotation(HighlightSeverity.INFORMATION, it) }
|
||||
?: holder.newSilentAnnotation(HighlightSeverity.INFORMATION))
|
||||
.range(textRange)
|
||||
.also { builder ->
|
||||
textAttributes?.let {
|
||||
builder.textAttributes(it)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
protected fun highlightName(element: PsiElement, attributesKey: TextAttributesKey, message: String? = null) {
|
||||
if (NameHighlighter.namesHighlightingEnabled && !element.textRange.isEmpty) {
|
||||
createInfoAnnotation(element, attributesKey, message)
|
||||
createInfoAnnotation(element, message, attributesKey)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun highlightName(textRange: TextRange, attributesKey: TextAttributesKey, message: String? = null) {
|
||||
if (NameHighlighter.namesHighlightingEnabled) {
|
||||
createInfoAnnotation(textRange, attributesKey, message)
|
||||
createInfoAnnotation(textRange, message, attributesKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-32
@@ -6,51 +6,29 @@
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.*
|
||||
import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.lang.annotation.AnnotationSession
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinBeforeResolveHighlightingPass(
|
||||
private val file: KtFile,
|
||||
document: Document
|
||||
) : TextEditorHighlightingPass(file.project, document), DumbAware {
|
||||
class KotlinBeforeResolveHighlightingPass(file: KtFile, document: Document) : AbstractHighlightingPassBase(file, document) {
|
||||
|
||||
@Volatile
|
||||
private var annotationHolder: AnnotationHolderImpl? = null
|
||||
override fun runAnnotatorWithContext(element: PsiElement, holder: AnnotationHolder) {
|
||||
val visitor = BeforeResolveHighlightingVisitor(holder)
|
||||
val extensions = EP_NAME.extensionList.map { it.createVisitor(holder) }
|
||||
|
||||
override fun doCollectInformation(progress: ProgressIndicator) {
|
||||
val annotationHolder = AnnotationHolderImpl(AnnotationSession(file))
|
||||
val visitor = BeforeResolveHighlightingVisitor(annotationHolder)
|
||||
val extensions = EP_NAME.extensionList.map { it.createVisitor(annotationHolder) }
|
||||
file.accept(object : PsiRecursiveElementVisitor() {
|
||||
element.accept(object : PsiRecursiveElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
super.visitElement(element)
|
||||
element.accept(visitor)
|
||||
extensions.forEach(element::accept)
|
||||
super.visitElement(element)
|
||||
}
|
||||
})
|
||||
this.annotationHolder = annotationHolder
|
||||
}
|
||||
|
||||
override fun doApplyInformationToEditor() {
|
||||
if (annotationHolder == null) return
|
||||
|
||||
val infos = annotationHolder!!.map { HighlightInfo.fromAnnotation(it) }
|
||||
|
||||
UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument!!, 0, file.textLength, infos, colorsScheme, id)
|
||||
annotationHolder = null
|
||||
}
|
||||
|
||||
class Factory : TextEditorHighlightingPassFactory {
|
||||
@@ -64,10 +42,10 @@ class KotlinBeforeResolveHighlightingPass(
|
||||
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||
registrar.registerTextEditorHighlightingPass(
|
||||
Factory(),
|
||||
TextEditorHighlightingPassRegistrar.Anchor.BEFORE,
|
||||
Pass.UPDATE_FOLDING,
|
||||
false,
|
||||
false
|
||||
/* anchor = */ TextEditorHighlightingPassRegistrar.Anchor.BEFORE,
|
||||
/* anchorPassId = */ Pass.UPDATE_FOLDING,
|
||||
/* needAdditionalIntentionsPass = */ false,
|
||||
/* inPostHighlightingPass = */ false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ inline fun invokeLater(expired: Condition<*>, crossinline action: () -> Unit) =
|
||||
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
inline fun isApplicationInternalMode(): Boolean = ApplicationManager.getApplication().isInternal
|
||||
|
||||
inline fun <reified T : Any> ComponentManager.getServiceSafe(): T =
|
||||
this.getService(T::class.java) ?: error("Unable to locate service ${T::class.java.name}")
|
||||
|
||||
|
||||
+63
-14
@@ -5,15 +5,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.perf
|
||||
|
||||
import com.intellij.codeHighlighting.*
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.lang.annotation.AnnotationHolder
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.testFramework.RunAll
|
||||
import com.intellij.util.ThrowableRunnable
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiChecker
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiCheckerAndHighlightingUpdater
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass
|
||||
import org.jetbrains.kotlin.idea.perf.Stats.Companion.TEST_KEY
|
||||
import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure
|
||||
import org.jetbrains.kotlin.idea.perf.util.Metric
|
||||
@@ -22,6 +24,7 @@ import org.jetbrains.kotlin.idea.testFramework.Fixture
|
||||
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.cleanupCaches
|
||||
import org.jetbrains.kotlin.idea.testFramework.Fixture.Companion.isAKotlinScriptFile
|
||||
import org.jetbrains.kotlin.idea.testFramework.ProjectOpenAction.GRADLE_PROJECT
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
@@ -38,6 +41,9 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
@JvmStatic
|
||||
val timer: AtomicLong = AtomicLong()
|
||||
|
||||
@JvmStatic
|
||||
val diagnosticTimer: AtomicLong = AtomicLong()
|
||||
|
||||
fun resetTimestamp() {
|
||||
timer.set(0)
|
||||
}
|
||||
@@ -326,6 +332,7 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
val testName = "fileAnalysis ${notePrefix(note)}${simpleFilename(fileName)}"
|
||||
val extraStats = Stats("${stats.name} $testName")
|
||||
val extraTimingsNs = mutableListOf<Map<String, Any>?>()
|
||||
val diagnosticTimingsNs = mutableListOf<Map<String, Any>?>()
|
||||
|
||||
val warmUpIterations = 30
|
||||
val iterations = 50
|
||||
@@ -337,7 +344,7 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
iterations(iterations)
|
||||
setUp(perfKtsFileAnalysisSetUp(project, fileName))
|
||||
test(perfKtsFileAnalysisTest())
|
||||
tearDown(perfKtsFileAnalysisTearDown(extraTimingsNs, project))
|
||||
tearDown(perfKtsFileAnalysisTearDown(extraTimingsNs, diagnosticTimingsNs, project))
|
||||
profilerConfig.enabled = true
|
||||
}
|
||||
|
||||
@@ -349,19 +356,31 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
metricChildren
|
||||
)
|
||||
|
||||
extraStats.printWarmUpTimings(
|
||||
"diagnostic",
|
||||
diagnosticTimingsNs.take(warmUpIterations).toTypedArray(),
|
||||
metricChildren
|
||||
)
|
||||
|
||||
extraStats.processTimings(
|
||||
"annotator",
|
||||
extraTimingsNs.drop(warmUpIterations).toTypedArray(),
|
||||
metricChildren
|
||||
)
|
||||
|
||||
extraStats.processTimings(
|
||||
"diagnostic",
|
||||
diagnosticTimingsNs.drop(warmUpIterations).toTypedArray(),
|
||||
metricChildren
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceWithCustomHighlighter() {
|
||||
org.jetbrains.kotlin.idea.testFramework.replaceWithCustomHighlighter(
|
||||
testRootDisposable,
|
||||
KotlinPsiCheckerAndHighlightingUpdater::class.java.name,
|
||||
TestKotlinPsiChecker::class.java.name
|
||||
KotlinHighlightingPass.Registrar::class.java.name,
|
||||
TestKotlinHighlightingPass.Registrar::class.java.name
|
||||
)
|
||||
}
|
||||
|
||||
@@ -385,18 +404,22 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
fun perfKtsFileAnalysisTest(): (TestData<Fixture, Pair<Long, List<HighlightInfo>>>) -> Unit {
|
||||
return {
|
||||
it.value = it.setUpValue?.let { fixture ->
|
||||
Pair(System.nanoTime(), fixture.doHighlighting())
|
||||
val nowNs = System.nanoTime()
|
||||
diagnosticTimer.set(-nowNs)
|
||||
Pair(nowNs, fixture.doHighlighting())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun perfKtsFileAnalysisTearDown(
|
||||
extraTimingsNs: MutableList<Map<String, Any>?>,
|
||||
diagnosticTimingsMs: MutableList<Map<String, Any>?>,
|
||||
project: Project
|
||||
): (TestData<Fixture, Pair<Long, List<HighlightInfo>>>) -> Unit {
|
||||
return {
|
||||
it.setUpValue?.let { fixture ->
|
||||
it.value?.let { v ->
|
||||
diagnosticTimingsMs.add(mapOf(TEST_KEY to diagnosticTimer.getAndSet(0)))
|
||||
assertTrue(v.second.isNotEmpty())
|
||||
assertNotEquals(0, timer.get())
|
||||
|
||||
@@ -411,12 +434,38 @@ class PerformanceProjectsTest : AbstractPerformanceProjectsTest() {
|
||||
}
|
||||
|
||||
|
||||
class TestKotlinPsiChecker : KotlinPsiChecker() {
|
||||
override fun annotate(
|
||||
element: PsiElement, holder: AnnotationHolder
|
||||
) {
|
||||
super.annotate(element, holder)
|
||||
markTimestamp()
|
||||
class TestKotlinHighlightingPass(file: KtFile, document: Document) : KotlinHighlightingPass(file, document) {
|
||||
override fun doCollectInformation(progress: ProgressIndicator) {
|
||||
annotationCallback {
|
||||
val nowNs = System.nanoTime()
|
||||
diagnosticTimer.addAndGet(nowNs)
|
||||
resetAnnotationCallback()
|
||||
}
|
||||
try {
|
||||
super.doCollectInformation(progress)
|
||||
} finally {
|
||||
resetAnnotationCallback()
|
||||
markTimestamp()
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : TextEditorHighlightingPassFactory {
|
||||
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||
if (file !is KtFile) return null
|
||||
return TestKotlinHighlightingPass(file, editor.document)
|
||||
}
|
||||
}
|
||||
|
||||
class Registrar : TextEditorHighlightingPassFactoryRegistrar {
|
||||
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||
registrar.registerTextEditorHighlightingPass(
|
||||
Factory(),
|
||||
null,
|
||||
intArrayOf(Pass.UPDATE_ALL),
|
||||
false,
|
||||
-1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DebugInfoHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DuplicateJvmSignatureHighlightPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DebugInfoHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DuplicateJvmSignatureHighlightPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
|
||||
@@ -122,6 +122,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DebugInfoHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DuplicateJvmSignatureHighlightPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
|
||||
@@ -103,6 +103,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DebugInfoHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.DuplicateJvmSignatureHighlightPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
|
||||
@@ -491,13 +491,8 @@
|
||||
<liveTemplateMacro implementation="org.jetbrains.kotlin.idea.liveTemplates.macro.KotlinMainArgumentsMacro"/>
|
||||
<liveTemplateOptionalProcessor implementation="org.jetbrains.kotlin.idea.liveTemplates.KotlinShortenFQNamesProcessor"/>
|
||||
|
||||
<annotator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.highlighter.KotlinPsiCheckerAndHighlightingUpdater"/>
|
||||
<highlightRangeExtension implementation="org.jetbrains.kotlin.idea.highlighter.KotlinPsiChecker"/>
|
||||
<daemon.changeLocalityDetector implementation="org.jetbrains.kotlin.idea.highlighter.KotlinChangeLocalityDetector"/>
|
||||
|
||||
<annotator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.highlighter.DebugInfoAnnotator"/>
|
||||
<annotator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.highlighter.DuplicateJvmSignatureAnnotator"/>
|
||||
|
||||
<highlightVisitor implementation="org.jetbrains.kotlin.idea.highlighter.KotlinRainbowVisitor"/>
|
||||
|
||||
<annotator language="JAVA" implementationClass="org.jetbrains.kotlin.idea.java.UnimplementedKotlinInterfaceMemberAnnotator"/>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeHighlighting.*
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection
|
||||
import org.jetbrains.kotlin.idea.isMainFunction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
|
||||
open class KotlinHighlightingPass(file: KtFile, document: Document) : AbstractKotlinHighlightingPass(file, document) {
|
||||
|
||||
override fun shouldSuppressUnusedParameter(parameter: KtParameter): Boolean {
|
||||
val grandParent = parameter.parent.parent as? KtNamedFunction ?: return false
|
||||
if (!UnusedSymbolInspection.isEntryPoint(grandParent)) return false
|
||||
return !grandParent.isMainFunction()
|
||||
}
|
||||
|
||||
class Factory : TextEditorHighlightingPassFactory {
|
||||
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||
if (file !is KtFile) return null
|
||||
return KotlinHighlightingPass(file, editor.document)
|
||||
}
|
||||
}
|
||||
|
||||
class Registrar : TextEditorHighlightingPassFactoryRegistrar {
|
||||
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||
registrar.registerTextEditorHighlightingPass(
|
||||
Factory(),
|
||||
/* runAfterCompletionOf = */ null,
|
||||
/* runAfterStartingOf = */ intArrayOf(Pass.UPDATE_ALL),
|
||||
/* runIntentionsPassAfter = */false,
|
||||
/* forcedPassId = */-1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.highlighter
|
||||
|
||||
import org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection
|
||||
import org.jetbrains.kotlin.idea.isMainFunction
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
|
||||
class KotlinPsiCheckerAndHighlightingUpdater : KotlinPsiChecker() {
|
||||
override fun shouldSuppressUnusedParameter(parameter: KtParameter): Boolean {
|
||||
val grandParent = parameter.parent.parent as? KtNamedFunction ?: return false
|
||||
if (!UnusedSymbolInspection.isEntryPoint(grandParent)) return false
|
||||
return !grandParent.isMainFunction()
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinPsiChecker
|
||||
import org.jetbrains.kotlin.idea.highlighter.AbstractKotlinHighlightingPass
|
||||
import org.jetbrains.kotlin.idea.quickfix.CleanupFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.ReplaceObsoleteLabelSyntaxFix
|
||||
@@ -99,7 +99,7 @@ class KotlinCleanupInspection : LocalInspectionTool(), CleanupLocalInspectionToo
|
||||
}
|
||||
|
||||
private fun Diagnostic.toCleanupFixes(): Collection<CleanupFix> {
|
||||
return KotlinPsiChecker.createQuickFixes(this).filterIsInstance<CleanupFix>()
|
||||
return AbstractKotlinHighlightingPass.createQuickFixes(this).filterIsInstance<CleanupFix>()
|
||||
}
|
||||
|
||||
private class Wrapper(val intention: IntentionAction, file: KtFile) : IntentionWrapper(intention, file) {
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
annotation class A(val i: Int)
|
||||
annotation class Z(val i: Int)
|
||||
|
||||
@Z("BAD") @Suppress("TYPE_MISMATCH")
|
||||
fun some0() {}
|
||||
|
||||
@Z("BAD") <error descr="[REPEATED_ANNOTATION] This annotation is not repeatable">@Z("BAD")</error> @Suppress("TYPE_MISMATCH")
|
||||
fun some01() {}
|
||||
|
||||
@Suppress("TYPE_MISMATCH") @Z("BAD")
|
||||
fun some1() {
|
||||
}
|
||||
|
||||
@Suppress("TYPE_MISMATCH") @Z("BAD") <error descr="[REPEATED_ANNOTATION] This annotation is not repeatable">@Z("BAD")</error>
|
||||
fun some11() {
|
||||
}
|
||||
|
||||
@A("BAD") @Suppress("TYPE_MISMATCH")
|
||||
fun some2() {
|
||||
}
|
||||
|
||||
@Suppress("TYPE_MISMATCH") @A("BAD")
|
||||
fun some3() {
|
||||
}
|
||||
|
||||
@A(<error descr="[TYPE_MISMATCH] Type mismatch: inferred type is String but Int was expected">"BAD"</error>) <error descr="[REPEATED_ANNOTATION] This annotation is not repeatable">@A(<error descr="[TYPE_MISMATCH] Type mismatch: inferred type is String but Int was expected">"BAD"</error>)</error>
|
||||
fun some4() {
|
||||
}
|
||||
|
||||
@Z(<error descr="[TYPE_MISMATCH] Type mismatch: inferred type is String but Int was expected">"BAD"</error>)
|
||||
fun someN() {
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ import java.io.File;
|
||||
|
||||
import static org.jetbrains.kotlin.resolve.lazy.ResolveSession.areDescriptorsCreatedForDeclaration;
|
||||
|
||||
public abstract class AbstractPsiCheckerTest extends KotlinLightCodeInsightFixtureTestCase {
|
||||
public abstract class AbstractKotlinHighlightingPassTest extends KotlinLightCodeInsightFixtureTestCase {
|
||||
public void doTest(@NotNull VirtualFile file) throws Exception {
|
||||
myFixture.configureFromExistingVirtualFile(file);
|
||||
checkHighlighting(true, false, false);
|
||||
+1
-1
@@ -13,7 +13,7 @@ import org.junit.runner.RunWith
|
||||
|
||||
@TestMetadata("idea/testData/checker/custom")
|
||||
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||
class PsiCheckerCustomTest : AbstractPsiCheckerTest() {
|
||||
class KotlinHighlightingPassCustomTest : AbstractKotlinHighlightingPassTest() {
|
||||
|
||||
@TestMetadata("noUnusedParameterWhenCustom.kt")
|
||||
fun testNoUnusedParameterWhenCustom() {
|
||||
+1
-1
@@ -12,7 +12,7 @@ import org.junit.runner.RunWith
|
||||
|
||||
@TestMetadata("idea/testData/checker/sealed")
|
||||
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||
class PsiCheckerSealedTest : AbstractPsiCheckerTest() {
|
||||
class KotlinHighlightingPassSealedTest : AbstractKotlinHighlightingPassTest() {
|
||||
|
||||
fun testOutsideOfPackageInheritors() {
|
||||
doTest(
|
||||
+17
-12
@@ -18,11 +18,11 @@ import java.util.regex.Pattern;
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
public class KotlinHighlightingPassTestGenerated extends AbstractKotlinHighlightingPassTest {
|
||||
@TestMetadata("idea/testData/checker")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Checker extends AbstractPsiCheckerTest {
|
||||
public static class Checker extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
runTest("idea/testData/checker/AnnotationOnFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AnnotationSupressing.kt")
|
||||
public void testAnnotationSupressing() throws Exception {
|
||||
runTest("idea/testData/checker/AnnotationSupressing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("AnonymousInitializers.kt")
|
||||
public void testAnonymousInitializers() throws Exception {
|
||||
runTest("idea/testData/checker/AnonymousInitializers.kt");
|
||||
@@ -365,7 +370,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/regression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Regression extends AbstractPsiCheckerTest {
|
||||
public static class Regression extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -608,7 +613,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/recovery")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Recovery extends AbstractPsiCheckerTest {
|
||||
public static class Recovery extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -636,7 +641,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/rendering")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Rendering extends AbstractPsiCheckerTest {
|
||||
public static class Rendering extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -654,7 +659,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/scripts")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Scripts extends AbstractPsiCheckerTest {
|
||||
public static class Scripts extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -687,7 +692,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/duplicateJvmSignature")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class DuplicateJvmSignature extends AbstractPsiCheckerTest {
|
||||
public static class DuplicateJvmSignature extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -699,7 +704,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/duplicateJvmSignature/fields")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Fields extends AbstractPsiCheckerTest {
|
||||
public static class Fields extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -717,7 +722,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/duplicateJvmSignature/functionAndProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class FunctionAndProperty extends AbstractPsiCheckerTest {
|
||||
public static class FunctionAndProperty extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -780,7 +785,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/duplicateJvmSignature/traitImpl")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TraitImpl extends AbstractPsiCheckerTest {
|
||||
public static class TraitImpl extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
@@ -799,7 +804,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/infos")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Infos extends AbstractPsiCheckerTest {
|
||||
public static class Infos extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithInfos, this, testDataFilePath);
|
||||
}
|
||||
@@ -892,7 +897,7 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest {
|
||||
@TestMetadata("idea/testData/checker/diagnosticsMessage")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class DiagnosticsMessage extends AbstractPsiCheckerTest {
|
||||
public static class DiagnosticsMessage extends AbstractKotlinHighlightingPassTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
+2
-2
@@ -5,12 +5,12 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractPsiCheckerTest
|
||||
import org.jetbrains.kotlin.checkers.AbstractKotlinHighlightingPassTest
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import kotlin.test.assertNull
|
||||
|
||||
abstract class AbstractCodeFragmentAutoImportTest : AbstractPsiCheckerTest() {
|
||||
abstract class AbstractCodeFragmentAutoImportTest : AbstractKotlinHighlightingPassTest() {
|
||||
override fun doTest(filePath: String) {
|
||||
myFixture.configureByCodeFragment(filePath)
|
||||
myFixture.doHighlighting()
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.checkers.AbstractPsiCheckerTest
|
||||
import org.jetbrains.kotlin.checkers.AbstractKotlinHighlightingPassTest
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
@@ -16,7 +16,7 @@ import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractCodeFragmentHighlightingTest : AbstractPsiCheckerTest() {
|
||||
abstract class AbstractCodeFragmentHighlightingTest : AbstractKotlinHighlightingPassTest() {
|
||||
override fun doTest(filePath: String) {
|
||||
myFixture.configureByCodeFragment(filePath)
|
||||
checkHighlighting(filePath)
|
||||
|
||||
@@ -34,10 +34,12 @@ abstract class AbstractDslHighlighterTest : KotlinLightCodeInsightFixtureTestCas
|
||||
val styleIdByCall = extension.highlightCall(element, call)?.externalName
|
||||
if (styleIdByCall != null && styleIdByCall == styleIdByComment) {
|
||||
val annotationHolder = AnnotationHolderImpl(AnnotationSession(psiFile))
|
||||
val checkers = KotlinPsiChecker.getAfterAnalysisVisitor(annotationHolder, bindingContext)
|
||||
checkers.forEach { call.call.callElement.accept(it) }
|
||||
annotationHolder.runAnnotatorWithContext(file) { _, _ ->
|
||||
val checkers = AbstractKotlinHighlightingPass.getAfterAnalysisVisitor(annotationHolder, bindingContext)
|
||||
checkers.forEach { call.call.callElement.accept(it) }
|
||||
}
|
||||
assertTrue(
|
||||
"KotlinPsiChecker did not contribute an Annotation containing the correct text attribute key at line ${lineNumber + 1}",
|
||||
"KotlinHighlightingPass did not contribute an Annotation containing the correct text attribute key at line ${lineNumber + 1}",
|
||||
annotationHolder.any {
|
||||
it.textAttributes.externalName == styleIdByComment
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.idea.inspections
|
||||
import com.google.common.collect.Lists
|
||||
import com.intellij.codeHighlighting.HighlightDisplayLevel
|
||||
import com.intellij.codeHighlighting.Pass
|
||||
import com.intellij.codeHighlighting.TextEditorHighlightingPass
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||
import com.intellij.codeInsight.daemon.impl.TextEditorHighlightingPassRegistrarEx
|
||||
import com.intellij.codeInsight.intention.EmptyIntentionAction
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
@@ -19,6 +21,7 @@ import junit.framework.ComparisonFailure
|
||||
import junit.framework.TestCase
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||
import org.jetbrains.kotlin.idea.highlighter.AbstractHighlightingPassBase
|
||||
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.withCustomCompilerOptions
|
||||
@@ -29,6 +32,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
|
||||
|
||||
abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
private val inspectionFileName: String
|
||||
get() = ".inspection"
|
||||
@@ -145,16 +149,23 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
||||
state.tool.tool.readSettings(inspectionSettings)
|
||||
}
|
||||
|
||||
val passIdsToIgnore = mutableListOf(
|
||||
Pass.LINE_MARKERS,
|
||||
Pass.EXTERNAL_TOOLS,
|
||||
Pass.POPUP_HINTS,
|
||||
Pass.UPDATE_ALL,
|
||||
Pass.UPDATE_FOLDING,
|
||||
Pass.WOLF
|
||||
)
|
||||
val passRegistrar = TextEditorHighlightingPassRegistrarEx.getInstanceEx(myFixture.project)
|
||||
// to exclude AbstractHighlightingPassBase instances based on their ids
|
||||
passRegistrar.instantiatePasses(
|
||||
file, editor, passIdsToIgnore.toIntArray()
|
||||
).filterIsInstance<AbstractHighlightingPassBase>().map(TextEditorHighlightingPass::getId).forEach(passIdsToIgnore::add)
|
||||
|
||||
val caretOffset = myFixture.caretOffset
|
||||
val highlightInfos = CodeInsightTestFixtureImpl.instantiateAndRun(
|
||||
file, editor, intArrayOf(
|
||||
Pass.LINE_MARKERS,
|
||||
Pass.EXTERNAL_TOOLS,
|
||||
Pass.POPUP_HINTS,
|
||||
Pass.UPDATE_ALL,
|
||||
Pass.UPDATE_FOLDING,
|
||||
Pass.WOLF
|
||||
), (file as? KtFile)?.isScript() == true
|
||||
file, editor, passIdsToIgnore.toIntArray(), (file as? KtFile)?.isScript() == true
|
||||
).filter { it.description != null && caretOffset in it.startOffset..it.endOffset }
|
||||
|
||||
Assert.assertTrue(
|
||||
|
||||
+2
-4
@@ -5,11 +5,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.pacelize.ide.test
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractPsiCheckerTest
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.checkers.AbstractKotlinHighlightingPassTest
|
||||
|
||||
abstract class AbstractParcelizeCheckerTest : AbstractPsiCheckerTest() {
|
||||
abstract class AbstractParcelizeCheckerTest : AbstractKotlinHighlightingPassTest() {
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
addParcelizeLibraries(module)
|
||||
|
||||
@@ -79,7 +79,7 @@ org.jetbrains.kotlin.idea.caches.resolve.IdeLightClassTestGenerated.Facades.test
|
||||
org.jetbrains.kotlin.idea.caches.resolve.IdeLightClassTestGenerated.CompilationErrors.testActualTypeAliasCustomJvmPackageName, Invalid behavior of old lightclasses in common tests,,
|
||||
org.jetbrains.kotlin.idea.caches.resolve.IdeLightClassTestGenerated.CompilationErrors.testJvmPackageName, Invalid behavior of old lightclasses in common tests,,
|
||||
org.jetbrains.uast.test.kotlin.SimpleKotlinRenderLogTest.testReceiverFun, Analysing of facade annotation with receiver site is broken (connected with KT-40403),,
|
||||
org.jetbrains.kotlin.checkers.FirPsiCheckerTestGenerated.Regression.testJet53,,, FLAKY
|
||||
org.jetbrains.kotlin.checkers.FirKotlinHighlightingPassTestGenerated.Regression.testJet53,,, FLAKY
|
||||
org.jetbrains.kotlin.idea.caches.resolve.MultiModuleHighlightingTest.testLanguageVersionsViaFacets,,, FLAKY
|
||||
org.jetbrains.kotlin.jps.build.IncrementalJvmJpsTestGenerated.Jvm.testCircular, Temporary muted due to problems with IC and JVM IR backend,,
|
||||
org.jetbrains.kotlin.jps.build.IncrementalJvmJpsTestGenerated.Jvm.testCircularDependencyClasses, Temporary muted due to problems with IC and JVM IR backend,,
|
||||
|
||||
|
Reference in New Issue
Block a user