FIR IDE: move diagnostic highlighting to separate
pass
This commit is contained in:
+39
-7
@@ -52,6 +52,7 @@ abstract class AbstractDiagnosticCollector(
|
|||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
private var context = PersistentCheckerContext(this, returnTypeCalculator)
|
private var context = PersistentCheckerContext(this, returnTypeCalculator)
|
||||||
|
private var currentAction = DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
|
||||||
fun initializeComponents(vararg components: AbstractDiagnosticCollectorComponent) {
|
fun initializeComponents(vararg components: AbstractDiagnosticCollectorComponent) {
|
||||||
if (componentsInitialized) {
|
if (componentsInitialized) {
|
||||||
@@ -75,7 +76,6 @@ abstract class AbstractDiagnosticCollector(
|
|||||||
|
|
||||||
private fun visitJump(loopJump: FirLoopJump) {
|
private fun visitJump(loopJump: FirLoopJump) {
|
||||||
loopJump.runComponents()
|
loopJump.runComponents()
|
||||||
loopJump.acceptChildren(this, null)
|
|
||||||
loopJump.target.labeledElement.takeIf { it is FirErrorLoop }?.accept(this, null)
|
loopJump.target.labeledElement.takeIf { it is FirErrorLoop }?.accept(this, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,16 +157,26 @@ abstract class AbstractDiagnosticCollector(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visitWithDeclaration(declaration: FirDeclaration) {
|
private inline fun visitWithDeclaration(
|
||||||
declaration.runComponents()
|
declaration: FirDeclaration,
|
||||||
withDeclaration(declaration) {
|
block: () -> Unit = { declaration.acceptChildren(this, null) }
|
||||||
declaration.acceptChildren(this, null)
|
) {
|
||||||
|
if (!currentAction.checkNested) return
|
||||||
|
|
||||||
|
val action = onDeclarationEnter(declaration)
|
||||||
|
if (action.checkCurrentDeclaration) {
|
||||||
|
declaration.runComponents()
|
||||||
}
|
}
|
||||||
|
withDiagnosticsAction(action) {
|
||||||
|
withDeclaration(declaration) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDeclarationExit(declaration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visitWithDeclarationAndReceiver(declaration: FirDeclaration, labelName: Name?, receiverTypeRef: FirTypeRef?) {
|
private fun visitWithDeclarationAndReceiver(declaration: FirDeclaration, labelName: Name?, receiverTypeRef: FirTypeRef?) {
|
||||||
declaration.runComponents()
|
visitWithDeclaration(declaration) {
|
||||||
withDeclaration(declaration) {
|
|
||||||
withLabelAndReceiverType(
|
withLabelAndReceiverType(
|
||||||
labelName,
|
labelName,
|
||||||
declaration,
|
declaration,
|
||||||
@@ -178,6 +188,11 @@ abstract class AbstractDiagnosticCollector(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun onDeclarationEnter(declaration: FirDeclaration): DiagnosticCollectorDeclarationAction =
|
||||||
|
DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
|
||||||
|
protected open fun onDeclarationExit(declaration: FirDeclaration) {}
|
||||||
|
|
||||||
private inline fun <R> withDeclaration(declaration: FirDeclaration, block: () -> R): R {
|
private inline fun <R> withDeclaration(declaration: FirDeclaration, block: () -> R): R {
|
||||||
val existingContext = context
|
val existingContext = context
|
||||||
context = context.addDeclaration(declaration)
|
context = context.addDeclaration(declaration)
|
||||||
@@ -208,6 +223,23 @@ abstract class AbstractDiagnosticCollector(
|
|||||||
context = existingContext
|
context = existingContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <R> withDiagnosticsAction(action: DiagnosticCollectorDeclarationAction, block: () -> R): R {
|
||||||
|
val oldAction = currentAction
|
||||||
|
currentAction = action
|
||||||
|
return try {
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
currentAction = oldAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DiagnosticCollectorDeclarationAction(val checkCurrentDeclaration: Boolean, val checkNested: Boolean) {
|
||||||
|
CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED(checkCurrentDeclaration = true, checkNested = true),
|
||||||
|
CHECK_CURRENT_DECLARATION_AND_SKIP_NESTED(checkCurrentDeclaration = true, checkNested = false),
|
||||||
|
SKIP_CURRENT_DECLARATION_AND_CHECK_NESTED(checkCurrentDeclaration = false, checkNested = true),
|
||||||
|
SKIP(checkCurrentDeclaration = false, checkNested = false),
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AbstractDiagnosticCollector.registerAllComponents() {
|
fun AbstractDiagnosticCollector.registerAllComponents() {
|
||||||
|
|||||||
@@ -30,52 +30,9 @@ class KotlinFirPsiChecker : AbstractKotlinPsiChecker() {
|
|||||||
throw ProcessCanceledException()
|
throw ProcessCanceledException()
|
||||||
}
|
}
|
||||||
analyze(element) {
|
analyze(element) {
|
||||||
this.highlightDiagnostics(element, holder)
|
|
||||||
|
|
||||||
FirAfterResolveHighlightingVisitor
|
FirAfterResolveHighlightingVisitor
|
||||||
.createListOfVisitors(this, holder)
|
.createListOfVisitors(this, holder)
|
||||||
.forEach(element::accept)
|
.forEach(element::accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtAnalysisSession.highlightDiagnostics(element: KtElement, holder: AnnotationHolder) {
|
|
||||||
val diagnostics = element.getDiagnostics()
|
|
||||||
if (diagnostics.isEmpty()) return
|
|
||||||
|
|
||||||
if (diagnostics.none(Diagnostic::isValid)) return
|
|
||||||
|
|
||||||
if (shouldHighlightErrors(element)) {
|
|
||||||
highlightDiagnostics(diagnostics, holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun highlightDiagnostics(diagnostics: Collection<Diagnostic>, holder: AnnotationHolder) {
|
|
||||||
diagnostics.groupBy { it.factory }.forEach { group -> registerDiagnosticAnnotations(group.value, holder) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun registerDiagnosticAnnotations(diagnostics: List<Diagnostic>, holder: AnnotationHolder) {
|
|
||||||
assert(diagnostics.isNotEmpty())
|
|
||||||
val diagnostic = diagnostics.first()
|
|
||||||
|
|
||||||
val ranges = diagnostic.textRanges
|
|
||||||
|
|
||||||
ranges.forEach { range ->
|
|
||||||
diagnostics.forEach { diagnostic ->
|
|
||||||
Diagnostic2Annotation.createAnnotation(
|
|
||||||
diagnostic,
|
|
||||||
range,
|
|
||||||
holder,
|
|
||||||
nonDefaultMessage = null,
|
|
||||||
textAttributes = null,
|
|
||||||
highlightType = null,
|
|
||||||
renderMessage = IdeErrorMessages::render
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldHighlightErrors(element: KtElement): Boolean {
|
|
||||||
return true // todo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.fir.highlighter
|
||||||
|
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPass
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactoryRegistrar
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||||
|
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
|
||||||
|
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.PsiElementVisitor
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.Diagnostic2Annotation
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.IdeErrorMessages
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||||
|
|
||||||
|
class KotlinHighLevelDiagnosticHighlightingPass(
|
||||||
|
private val ktFile: KtFile,
|
||||||
|
document: Document,
|
||||||
|
) : TextEditorHighlightingPass(ktFile.project, document) {
|
||||||
|
private val diagnosticInfos = mutableListOf<HighlightInfo>()
|
||||||
|
|
||||||
|
override fun doCollectInformation(progress: ProgressIndicator) = analyze(ktFile) {
|
||||||
|
ktFile.collectDiagnosticsForFile().forEach { diagnostic ->
|
||||||
|
if (!diagnostic.isValid) return@forEach
|
||||||
|
diagnostic.textRanges.forEach { range ->
|
||||||
|
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR/*TODO*/)
|
||||||
|
.descriptionAndTooltip(Diagnostic2Annotation.getMessage(diagnostic, IdeErrorMessages::render))
|
||||||
|
.range(range)
|
||||||
|
.create()
|
||||||
|
?.let(diagnosticInfos::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun doApplyInformationToEditor() {
|
||||||
|
UpdateHighlightersUtil.setHighlightersToEditor(
|
||||||
|
myProject, myDocument!!, /*startOffset=*/0, ktFile.textLength, diagnosticInfos, colorsScheme, id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class KotlinDiagnosticHighlightingPassFactory : TextEditorHighlightingPassFactory, TextEditorHighlightingPassFactoryRegistrar {
|
||||||
|
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
|
||||||
|
registrar.registerTextEditorHighlightingPass(this, null, null, false, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||||
|
if (file !is KtFile) return null
|
||||||
|
if (file.isCompiled) return null
|
||||||
|
return KotlinHighLevelDiagnosticHighlightingPass(file, editor.document)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,8 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo
|
|||||||
|
|
||||||
fun KtElement.getDiagnostics(): Collection<Diagnostic> = diagnosticProvider.getDiagnosticsForElement(this)
|
fun KtElement.getDiagnostics(): Collection<Diagnostic> = diagnosticProvider.getDiagnosticsForElement(this)
|
||||||
|
|
||||||
|
fun KtFile.collectDiagnosticsForFile(): Collection<Diagnostic> = diagnosticProvider.collectDiagnosticsForFile(this)
|
||||||
|
|
||||||
fun KtSymbolWithKind.getContainingSymbol(): KtSymbolWithKind? = containingDeclarationProvider.getContainingDeclaration(this)
|
fun KtSymbolWithKind.getContainingSymbol(): KtSymbolWithKind? = containingDeclarationProvider.getContainingDeclaration(this)
|
||||||
|
|
||||||
fun KtClassOrObjectSymbol.getMemberScope(): KtMemberScope = scopeProvider.getMemberScope(this)
|
fun KtClassOrObjectSymbol.getMemberScope(): KtMemberScope = scopeProvider.getMemberScope(this)
|
||||||
|
|||||||
+2
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.idea.frontend.api.components
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
abstract class KtDiagnosticProvider : KtAnalysisSessionComponent() {
|
abstract class KtDiagnosticProvider : KtAnalysisSessionComponent() {
|
||||||
abstract fun getDiagnosticsForElement(element: KtElement): Collection<Diagnostic>
|
abstract fun getDiagnosticsForElement(element: KtElement): Collection<Diagnostic>
|
||||||
|
abstract fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic>
|
||||||
}
|
}
|
||||||
+5
@@ -39,6 +39,8 @@ abstract class FirModuleResolveState {
|
|||||||
|
|
||||||
abstract fun getDiagnostics(element: KtElement): List<Diagnostic>
|
abstract fun getDiagnostics(element: KtElement): List<Diagnostic>
|
||||||
|
|
||||||
|
abstract fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic>
|
||||||
|
|
||||||
// todo temporary, used only in completion
|
// todo temporary, used only in completion
|
||||||
abstract fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile)
|
abstract fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile)
|
||||||
|
|
||||||
@@ -80,6 +82,9 @@ internal class FirModuleResolveStateImpl(
|
|||||||
override fun getDiagnostics(element: KtElement): List<Diagnostic> =
|
override fun getDiagnostics(element: KtElement): List<Diagnostic> =
|
||||||
diagnosticsCollector.getDiagnosticsFor(element)
|
diagnosticsCollector.getDiagnosticsFor(element)
|
||||||
|
|
||||||
|
override fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic> =
|
||||||
|
diagnosticsCollector.collectDiagnosticsForFile(ktFile)
|
||||||
|
|
||||||
override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
|
override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
|
||||||
error("Should be called only from FirModuleResolveStateForCompletion")
|
error("Should be called only from FirModuleResolveStateForCompletion")
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -64,4 +64,8 @@ internal class FirModuleResolveStateForCompletion(
|
|||||||
override fun getDiagnostics(element: KtElement): List<Diagnostic> {
|
override fun getDiagnostics(element: KtElement): List<Diagnostic> {
|
||||||
error("Diagnostics should not be retrieved in completion")
|
error("Diagnostics should not be retrieved in completion")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic> {
|
||||||
|
error("Diagnostics should not be retrieved in completion")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+10
-25
@@ -9,44 +9,39 @@ import com.intellij.openapi.diagnostic.Logger
|
|||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
|
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
|
||||||
import org.jetbrains.kotlin.fir.analysis.collectors.registerAllComponents
|
import org.jetbrains.kotlin.fir.analysis.collectors.components.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
||||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.createReturnTypeCalculatorForIDE
|
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.createReturnTypeCalculatorForIDE
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.util.addValueFor
|
import org.jetbrains.kotlin.idea.fir.low.level.api.util.addValueFor
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled
|
import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
|
||||||
internal class FirIdeDiagnosticsCollector private constructor(
|
internal abstract class AbstractFirIdeDiagnosticsCollector(
|
||||||
session: FirSession,
|
session: FirSession,
|
||||||
) : AbstractDiagnosticCollector(
|
) : AbstractDiagnosticCollector(
|
||||||
session,
|
session,
|
||||||
returnTypeCalculator = createReturnTypeCalculatorForIDE(session, ScopeSession())
|
returnTypeCalculator = createReturnTypeCalculatorForIDE(session, ScopeSession())
|
||||||
) {
|
) {
|
||||||
private val result = mutableMapOf<KtElement, MutableList<Diagnostic>>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
registerAllComponents()
|
registerAllComponents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract fun onDiagnostic(diagnostic: Diagnostic)
|
||||||
|
|
||||||
|
|
||||||
private inner class Reporter : DiagnosticReporter() {
|
private inner class Reporter : DiagnosticReporter() {
|
||||||
override fun report(diagnostic: FirDiagnostic<*>?) {
|
override fun report(diagnostic: FirDiagnostic<*>?) {
|
||||||
checkCanceled()
|
if (diagnostic !is FirPsiDiagnostic<*>) return
|
||||||
try {
|
if (diagnostic.element.psi !is KtElement) return
|
||||||
if (diagnostic !is FirPsiDiagnostic<*>) return
|
onDiagnostic(diagnostic.asPsiBasedDiagnostic())
|
||||||
val psi = diagnostic.element.psi as? KtElement ?: return
|
|
||||||
result.addValueFor(psi, diagnostic.asPsiBasedDiagnostic())
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
LOG.error(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var reporter: DiagnosticReporter = Reporter()
|
override var reporter: DiagnosticReporter = Reporter()
|
||||||
private set
|
|
||||||
|
|
||||||
override fun initializeCollector() {
|
override fun initializeCollector() {
|
||||||
reporter = Reporter()
|
reporter = Reporter()
|
||||||
@@ -58,16 +53,6 @@ internal class FirIdeDiagnosticsCollector private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG = Logger.getInstance(FirIdeDiagnosticsCollector::class.java)
|
private val LOG = Logger.getInstance(AbstractFirIdeDiagnosticsCollector::class.java)
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects diagnostics for given [firFile]
|
|
||||||
* Should be called under [firFile]-based lock
|
|
||||||
*/
|
|
||||||
fun collect(firFile: FirFile): Map<KtElement, List<Diagnostic>> =
|
|
||||||
FirIdeDiagnosticsCollector(firFile.session).let { collector ->
|
|
||||||
collector.collectDiagnostics(firFile)
|
|
||||||
collector.result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-36
@@ -6,48 +6,23 @@
|
|||||||
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
|
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
|
||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
|
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureCache
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
internal class DiagnosticsCollector(
|
internal class DiagnosticsCollector(
|
||||||
private val firFileBuilder: FirFileBuilder,
|
private val fileStructureCache: FileStructureCache,
|
||||||
private val cache: ModuleFileCache,
|
private val cache: ModuleFileCache,
|
||||||
) {
|
) {
|
||||||
private val diagnosticsForFile = ConcurrentHashMap<KtFile, DiagnosticsForFile>()
|
fun getDiagnosticsFor(element: KtElement): List<Diagnostic> =
|
||||||
|
fileStructureCache
|
||||||
|
.getFileStructure(element.containingKtFile, cache)
|
||||||
|
.getStructureElementFor(element)
|
||||||
|
.diagnostics.diagnosticsFor(element)
|
||||||
|
|
||||||
fun getDiagnosticsFor(element: KtElement): List<Diagnostic> {
|
fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic> =
|
||||||
val ktFile = element.containingKtFile
|
fileStructureCache
|
||||||
val diagnostics = diagnosticsForFile.computeIfAbsent(ktFile) {
|
.getFileStructure(ktFile, cache)
|
||||||
val firFile = firFileBuilder.getFirFileResolvedToPhaseWithCaching(
|
.getAllDiagnosticsForFile()
|
||||||
ktFile,
|
|
||||||
cache,
|
|
||||||
toPhase = FirResolvePhase.BODY_RESOLVE,
|
|
||||||
checkPCE = true
|
|
||||||
)
|
|
||||||
DiagnosticsForFile.collectDiagnosticsForFile(firFile)
|
|
||||||
}
|
|
||||||
return diagnostics.getDiagnosticsFor(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DiagnosticsForFile private constructor(private val diagnostics: Map<KtElement, List<Diagnostic>>) {
|
|
||||||
fun getDiagnosticsFor(element: KtElement): List<Diagnostic> = diagnostics[element].orEmpty()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Collects diagnostics for given [firFile]
|
|
||||||
* Should be called under [firFile]-based lock
|
|
||||||
*/
|
|
||||||
fun collectDiagnosticsForFile(firFile: FirFile): DiagnosticsForFile {
|
|
||||||
require(firFile.resolvePhase >= FirResolvePhase.BODY_RESOLVE) {
|
|
||||||
"To collect diagnostics at least FirResolvePhase.BODY_RESOLVE is needed, but file ${firFile.name} was resolved to ${firFile.resolvePhase}"
|
|
||||||
}
|
|
||||||
return DiagnosticsForFile(FirIdeDiagnosticsCollector.collect(firFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.fir.low.level.api.diagnostics
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||||
|
|
||||||
|
internal class FirIdeFileDiagnosticsCollector private constructor(
|
||||||
|
session: FirSession,
|
||||||
|
) : AbstractFirIdeDiagnosticsCollector(
|
||||||
|
session,
|
||||||
|
) {
|
||||||
|
private val result = mutableListOf<Diagnostic>()
|
||||||
|
|
||||||
|
override fun onDiagnostic(diagnostic: Diagnostic) {
|
||||||
|
result += diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun collectForFile(firFile: FirFile): List<Diagnostic> =
|
||||||
|
FirIdeFileDiagnosticsCollector(firFile.session).let { collector ->
|
||||||
|
collector.collectDiagnostics(firFile)
|
||||||
|
collector.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.fir.low.level.api.diagnostics
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarationAction
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||||
|
import org.jetbrains.kotlin.fir.psi
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureElementDiagnostics
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.util.addValueFor
|
||||||
|
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
|
||||||
|
internal class FirIdeStructureElementDiagnosticsCollector private constructor(
|
||||||
|
session: FirSession,
|
||||||
|
private val onDeclarationEnter: (FirDeclaration) -> DiagnosticCollectorDeclarationAction,
|
||||||
|
private val onDeclarationExit: (FirDeclaration) -> Unit
|
||||||
|
) : AbstractFirIdeDiagnosticsCollector(
|
||||||
|
session,
|
||||||
|
) {
|
||||||
|
private val result = mutableMapOf<KtElement, MutableList<Diagnostic>>()
|
||||||
|
|
||||||
|
override fun onDiagnostic(diagnostic: Diagnostic) {
|
||||||
|
(diagnostic.psiElement as? KtElement)?.let { ktElement ->
|
||||||
|
result.addValueFor(ktElement, diagnostic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeclarationEnter(
|
||||||
|
declaration: FirDeclaration,
|
||||||
|
): DiagnosticCollectorDeclarationAction =
|
||||||
|
onDeclarationEnter.invoke(declaration)
|
||||||
|
|
||||||
|
override fun onDeclarationExit(declaration: FirDeclaration) {
|
||||||
|
onDeclarationExit.invoke(declaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun collectForStructureElement(
|
||||||
|
firFile: FirFile,
|
||||||
|
onDeclarationExit: (FirDeclaration) -> Unit = {},
|
||||||
|
onDeclarationEnter: (FirDeclaration) -> DiagnosticCollectorDeclarationAction,
|
||||||
|
): FileStructureElementDiagnostics =
|
||||||
|
FirIdeStructureElementDiagnosticsCollector(firFile.session, onDeclarationEnter, onDeclarationExit).let { collector ->
|
||||||
|
collector.collectDiagnostics(firFile)
|
||||||
|
FileStructureElementDiagnostics(collector.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
-17
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.fir.FirElement
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarationAction
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
||||||
@@ -43,19 +44,28 @@ internal class WithInBlockModificationFileStructureElement(
|
|||||||
override val mappings: Map<KtElement, FirElement> =
|
override val mappings: Map<KtElement, FirElement> =
|
||||||
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)
|
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)
|
||||||
|
|
||||||
|
|
||||||
fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp
|
fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp
|
||||||
|
|
||||||
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
||||||
val innerDeclarations = psi
|
var inCurrentDeclaration = false
|
||||||
.collectDescendantsOfTypeTo<KtDeclaration, HashSet<KtDeclaration>>(
|
|
||||||
hashSetOf(psi),
|
|
||||||
canGoInside = { true }
|
|
||||||
)
|
|
||||||
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(
|
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(
|
||||||
innerDeclarations,
|
|
||||||
firFile,
|
firFile,
|
||||||
substitution = null
|
onDeclarationEnter = { firDeclaration ->
|
||||||
|
when {
|
||||||
|
firDeclaration == firSymbol.fir -> {
|
||||||
|
inCurrentDeclaration = true
|
||||||
|
DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
}
|
||||||
|
inCurrentDeclaration -> DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
else -> DiagnosticCollectorDeclarationAction.SKIP_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDeclarationExit = { declaration ->
|
||||||
|
if (declaration == firSymbol.fir) {
|
||||||
|
inCurrentDeclaration = false
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,14 +83,30 @@ internal class NonLocalDeclarationFileStructureElement(
|
|||||||
FirElementsRecorder.recordElementsFrom(fir, recorder)
|
FirElementsRecorder.recordElementsFrom(fir, recorder)
|
||||||
|
|
||||||
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
||||||
@Suppress("RemoveExplicitTypeArguments")
|
var inCurrentDeclaration = false
|
||||||
val innerDeclarations = psi
|
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(
|
||||||
.collectDescendantsOfTypeTo<KtDeclaration, HashSet<KtDeclaration>>(
|
firFile,
|
||||||
hashSetOf(psi),
|
onDeclarationEnter = { firDeclaration ->
|
||||||
canGoInside = { it.safeAs<KtNamedFunction>()?.hasExplicitTypeOrUnit != true }
|
when {
|
||||||
)
|
firDeclaration == fir -> {
|
||||||
|
inCurrentDeclaration = true
|
||||||
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(innerDeclarations, firFile)
|
DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
}
|
||||||
|
(firDeclaration.psi as? KtNamedFunction)?.hasExplicitTypeOrUnit == true -> {
|
||||||
|
DiagnosticCollectorDeclarationAction.SKIP
|
||||||
|
}
|
||||||
|
inCurrentDeclaration -> {
|
||||||
|
DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
}
|
||||||
|
else -> DiagnosticCollectorDeclarationAction.SKIP_CURRENT_DECLARATION_AND_CHECK_NESTED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDeclarationExit = { firDeclaration ->
|
||||||
|
if (firDeclaration == fir) {
|
||||||
|
inCurrentDeclaration = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -104,7 +130,10 @@ internal data class FileWithoutDeclarationsFileStructureElement(
|
|||||||
FirElementsRecorder.recordElementsFrom(firFile, recorder)
|
FirElementsRecorder.recordElementsFrom(firFile, recorder)
|
||||||
|
|
||||||
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
override val diagnostics: FileStructureElementDiagnostics by lazy {
|
||||||
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(hashSetOf(psi), firFile)
|
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(firFile) { firDeclaration ->
|
||||||
|
if (firDeclaration is FirFile) DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_SKIP_NESTED
|
||||||
|
else DiagnosticCollectorDeclarationAction.SKIP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+4
-8
@@ -6,20 +6,13 @@
|
|||||||
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
|
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
|
||||||
import org.jetbrains.kotlin.idea.fir.getOrBuildFirSafe
|
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
|
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartCast
|
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartcastKind
|
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.ValidityToken
|
import org.jetbrains.kotlin.idea.frontend.api.ValidityToken
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtDiagnosticProvider
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtDiagnosticProvider
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtSmartCastProvider
|
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
|
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
internal class KtFirDiagnosticProvider(
|
internal class KtFirDiagnosticProvider(
|
||||||
override val analysisSession: KtFirAnalysisSession,
|
override val analysisSession: KtFirAnalysisSession,
|
||||||
@@ -28,4 +21,7 @@ internal class KtFirDiagnosticProvider(
|
|||||||
override fun getDiagnosticsForElement(element: KtElement): Collection<Diagnostic> = withValidityAssertion {
|
override fun getDiagnosticsForElement(element: KtElement): Collection<Diagnostic> = withValidityAssertion {
|
||||||
LowLevelFirApiFacade.getDiagnosticsFor(element, firResolveState)
|
LowLevelFirApiFacade.getDiagnosticsFor(element, firResolveState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun collectDiagnosticsForFile(ktFile: KtFile): Collection<Diagnostic> =
|
||||||
|
firResolveState.collectDiagnosticsForFile(ktFile)
|
||||||
}
|
}
|
||||||
+10
@@ -33,6 +33,14 @@ object Diagnostic2Annotation {
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMessage(diagnostic: Diagnostic, renderMessage: (Diagnostic) -> String): String {
|
||||||
|
val message = renderMessage(diagnostic)
|
||||||
|
if (ApplicationManager.getApplication().isInternal || ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
return "[${diagnostic.factory.name}] $message"
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
fun getDefaultMessage(diagnostic: Diagnostic): String {
|
fun getDefaultMessage(diagnostic: Diagnostic): String {
|
||||||
val message = DefaultErrorMessages.render(diagnostic)
|
val message = DefaultErrorMessages.render(diagnostic)
|
||||||
if (ApplicationManager.getApplication().isInternal || ApplicationManager.getApplication().isUnitTestMode) {
|
if (ApplicationManager.getApplication().isInternal || ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
@@ -41,6 +49,8 @@ object Diagnostic2Annotation {
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun createAnnotation(
|
fun createAnnotation(
|
||||||
diagnostic: Diagnostic,
|
diagnostic: Diagnostic,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
|
|||||||
implementationClass="org.jetbrains.kotlin.idea.completion.KotlinFirCompletionContributor"/>
|
implementationClass="org.jetbrains.kotlin.idea.completion.KotlinFirCompletionContributor"/>
|
||||||
<codeInsight.typeInfo language="kotlin"
|
<codeInsight.typeInfo language="kotlin"
|
||||||
implementationClass="org.jetbrains.kotlin.idea.codeInsight.KotlinHighLevelExpressionTypeProvider"/>
|
implementationClass="org.jetbrains.kotlin.idea.codeInsight.KotlinHighLevelExpressionTypeProvider"/>
|
||||||
|
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.fir.highlighter.KotlinDiagnosticHighlightingPassFactory"/>
|
||||||
|
|
||||||
|
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user