FIR IDE: introduce tests for checking diagnostic visitor context collection

This commit is contained in:
Ilya Kirillov
2021-04-08 11:08:46 +02:00
parent ac993754e1
commit 32c1101959
8 changed files with 247 additions and 7 deletions
@@ -85,6 +85,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostic.AbstractDiagnosticTraversalCounterTest
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostic.AbstractFirContextCollectionTest
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest
import org.jetbrains.kotlin.idea.fir.low.level.api.resolve.AbstractInnerDeclarationsResolvePhaseTest
@@ -1084,6 +1085,9 @@ fun main(args: Array<String>) {
testClass<AbstractFileStructureTest> {
model("fileStructure")
}
testClass<AbstractFirContextCollectionTest> {
model("fileStructure")
}
testClass<AbstractDiagnosticTraversalCounterTest> {
model("diagnosticTraversalCounter")
}
@@ -8,9 +8,12 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSessionComponent
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
abstract class BeforeElementDiagnosticCollectionHandler: FirSessionComponent {
abstract fun beforeCollectingForElement(element: FirElement)
open fun beforeCollectingForElement(element: FirElement) {}
open fun beforeGoingNestedDeclaration(declaration: FirDeclaration, context: PersistentCheckerContext) {}
}
val FirSession.beforeElementDiagnosticCollectionHandler: BeforeElementDiagnosticCollectionHandler? by FirSession.nullableSessionComponentAccessor()
@@ -9,8 +9,7 @@ import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor
import org.jetbrains.kotlin.fir.analysis.collectors.components.AbstractDiagnosticCollectorComponent
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistentCheckerContextFactory
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled
internal open class FirIdeDiagnosticVisitor(
@@ -21,6 +20,13 @@ internal open class FirIdeDiagnosticVisitor(
checkCanceled()
}
override fun goToNestedElements(element: FirElement) {
if (element is FirDeclaration) {
session.beforeElementDiagnosticCollectionHandler?.beforeGoingNestedDeclaration(element, context)
}
super.goToNestedElements(element)
}
override fun beforeRunningAllComponentsOnElement(element: FirElement) {
session.beforeElementDiagnosticCollectionHandler?.beforeCollectingForElement(element)
}
@@ -80,7 +80,7 @@ internal class FileStructure(
}
}
private fun getAllStructureElements(): Collection<FileStructureElement> {
fun getAllStructureElements(): Collection<FileStructureElement> {
val structureElements = mutableSetOf(getStructureElementFor(ktFile))
ktFile.accept(object : KtVisitorVoid() {
override fun visitElement(element: PsiElement) {
@@ -30,7 +30,7 @@ internal sealed class FileStructureElement(val firFile: FirFile, protected val l
internal sealed class ReanalyzableStructureElement<KT : KtDeclaration, S : AbstractFirBasedSymbol<*>>(
firFile: FirFile,
protected val firSymbol: S,
val firSymbol: S,
lockProvider: LockProvider<FirFile>,
) : FileStructureElement(firFile, lockProvider) {
abstract override val psi: KtDeclaration
@@ -50,7 +50,11 @@ internal sealed class ReanalyzableStructureElement<KT : KtDeclaration, S : Abstr
fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp
override val diagnostics = FileStructureElementDiagnostics(firFile, lockProvider, SingleNonLocalDeclarationDiagnosticRetriever(firSymbol.fir as FirDeclaration))
override val diagnostics = FileStructureElementDiagnostics(
firFile,
lockProvider,
SingleNonLocalDeclarationDiagnosticRetriever(firSymbol.fir as FirDeclaration)
)
companion object {
val recorder = FirElementsRecorder()
@@ -143,7 +147,7 @@ internal class ReanalyzablePropertyStructureElement(
internal class NonReanalyzableDeclarationStructureElement(
firFile: FirFile,
fir: FirDeclaration,
val fir: FirDeclaration,
override val psi: KtDeclaration,
lockProvider: LockProvider<FirFile>,
) : FileStructureElement(firFile, lockProvider) {
@@ -0,0 +1,87 @@
/*
* 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.fir.low.level.api.diagnostic
import com.intellij.openapi.util.io.FileUtil
import kotlinx.collections.immutable.PersistentList
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.SessionConfiguration
import org.jetbrains.kotlin.fir.analysis.checkers.context.PersistentCheckerContext
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.resolve.PersistentImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl
import org.jetbrains.kotlin.idea.fir.low.level.api.api.DiagnosticCheckerFilter
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getDiagnostics
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getFirFile
import org.jetbrains.kotlin.idea.fir.low.level.api.createResolveStateForNoCaching
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.BeforeElementDiagnosticCollectionHandler
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.fir.PersistenceContextCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.*
import org.jetbrains.kotlin.idea.fir.low.level.api.name
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
abstract class AbstractFirContextCollectionTest : KotlinLightCodeInsightFixtureTestCase() {
fun doTest(path: String) {
val testDataFile = File(path)
val ktFile = myFixture.configureByText(testDataFile.name, FileUtil.loadFile(testDataFile)) as KtFile
val handler = BeforeElementTestDiagnosticCollectionHandler()
@OptIn(SessionConfiguration::class)
val resolveState = createResolveStateForNoCaching(ktFile.getModuleInfo()) {
register(BeforeElementDiagnosticCollectionHandler::class, handler)
} as FirModuleResolveStateImpl
val fileStructure = resolveState.fileStructureCache.getFileStructure(ktFile, resolveState.rootModuleSession.cache)
val allStructureElements = fileStructure.getAllStructureElements()
handler.elementsToCheckContext = allStructureElements.map { it.getFirDeclaration() }
handler.firFile = ktFile.getFirFile(resolveState)
ktFile.getDiagnostics(resolveState, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS)
}
private fun FileStructureElement.getFirDeclaration(): FirDeclaration = when (this) {
is NonReanalyzableDeclarationStructureElement -> fir
is ReanalyzableStructureElement<*, *> -> firSymbol.fir as FirDeclaration
is RootStructureElement -> firFile
}
private class BeforeElementTestDiagnosticCollectionHandler : BeforeElementDiagnosticCollectionHandler() {
lateinit var elementsToCheckContext: List<FirDeclaration>
lateinit var firFile: FirFile
override fun beforeGoingNestedDeclaration(declaration: FirDeclaration, context: PersistentCheckerContext) {
if (declaration is FirFile) {
return
}
if (declaration in elementsToCheckContext) {
val collectedContext = PersistenceContextCollector.collectContext(
SessionHolderImpl.createWithEmptyScopeSession(declaration.session),
firFile,
declaration
)
compareStructurally(context, collectedContext)
}
}
private fun compareStructurally(expected: PersistentCheckerContext, actual: PersistentCheckerContext) {
assertEquals(expected.implicitReceiverStack.asString(), actual.implicitReceiverStack.asString())
assertEquals(expected.containingDeclarations.asString(), actual.containingDeclarations.asString())
}
private fun PersistentImplicitReceiverStack.asString() =
joinToString { it.boundSymbol.name() }
private fun PersistentList<FirDeclaration>.asString() =
joinToString(transform = FirDeclaration::name)
}
}
@@ -0,0 +1,116 @@
/*
* 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.fir.low.level.api.diagnostic;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class FirContextCollectionTestGenerated extends AbstractFirContextCollectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInFileStructure() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("class.kt")
public void testClass() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt");
}
@TestMetadata("classMemberProperty.kt")
public void testClassMemberProperty() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt");
}
@TestMetadata("declarationsInPropertyInit.kt")
public void testDeclarationsInPropertyInit() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/declarationsInPropertyInit.kt");
}
@TestMetadata("enumClass.kt")
public void testEnumClass() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/enumClass.kt");
}
@TestMetadata("enumClassWithBody.kt")
public void testEnumClassWithBody() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/enumClassWithBody.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/initBlock.kt");
}
@TestMetadata("localClass.kt")
public void testLocalClass() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt");
}
@TestMetadata("localFun.kt")
public void testLocalFun() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt");
}
@TestMetadata("localProperty.kt")
public void testLocalProperty() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt");
}
@TestMetadata("nestedClasses.kt")
public void testNestedClasses() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt");
}
@TestMetadata("propertyAccessors.kt")
public void testPropertyAccessors() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/propertyAccessors.kt");
}
@TestMetadata("topLevelExpressionBodyFunWithType.kt")
public void testTopLevelExpressionBodyFunWithType() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt");
}
@TestMetadata("topLevelExpressionBodyFunWithoutType.kt")
public void testTopLevelExpressionBodyFunWithoutType() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt");
}
@TestMetadata("topLevelFunWithType.kt")
public void testTopLevelFunWithType() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt");
}
@TestMetadata("topLevelUnitFun.kt")
public void testTopLevelUnitFun() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt");
}
@TestMetadata("typeAlias.kt")
public void testTypeAlias() throws Exception {
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/typeAlias.kt");
}
}
@@ -11,7 +11,14 @@ import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousInitializerSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirModificationTrackerService
@@ -29,4 +36,17 @@ internal fun FirElement.renderWithClassName(renderMode: FirRenderer.RenderMode =
internal fun Module.incModificationTracker() {
project.service<KotlinFirModificationTrackerService>().increaseModificationCountForModule(this)
}
internal fun AbstractFirBasedSymbol<*>.name(): String = when (this) {
is FirCallableSymbol<*> -> callableId.callableName.asString()
is FirClassLikeSymbol<*> -> classId.shortClassName.asString()
is FirAnonymousInitializerSymbol -> "<init>"
else -> error("unknown symbol ${this::class.simpleName}")
}
internal fun FirDeclaration.name(): String = when (this) {
is FirSymbolOwner<*> -> symbol.name()
is FirFile -> "<FILE>"
else -> error("unknown declaration ${this::class.simpleName}")
}