FIR IDE: introduce tests for checking diagnostic visitor context collection
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
+4
-1
@@ -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()
|
||||
|
||||
+8
-2
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+7
-3
@@ -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) {
|
||||
|
||||
+87
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+116
@@ -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");
|
||||
}
|
||||
}
|
||||
+20
@@ -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}")
|
||||
}
|
||||
Reference in New Issue
Block a user