HL API: implement tests for reference resolve
This commit is contained in:
committed by
teamcityserver
parent
55489b6249
commit
970d7b5a78
@@ -462,10 +462,15 @@ class KotlinCoreEnvironment private constructor(
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinCoreEnvironment::class.java)
|
||||
|
||||
private val APPLICATION_LOCK = Object()
|
||||
@PublishedApi
|
||||
internal val APPLICATION_LOCK = Object()
|
||||
|
||||
private var ourApplicationEnvironment: KotlinCoreApplicationEnvironment? = null
|
||||
private var ourProjectCount = 0
|
||||
|
||||
inline fun <R> underApplicationLock(action: () -> R): R =
|
||||
synchronized(APPLICATION_LOCK) { action() }
|
||||
|
||||
@JvmStatic
|
||||
fun createForProduction(
|
||||
parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
|
||||
|
||||
+2
@@ -108,6 +108,8 @@ public enum class RendererModifier(public val includeByDefault: Boolean) {
|
||||
|
||||
public companion object {
|
||||
public val ALL: Set<RendererModifier> = values().toSet()
|
||||
public val DEFAULT: Set<RendererModifier> = values().filterTo(mutableSetOf()) { it.includeByDefault }
|
||||
public val NONE: Set<RendererModifier> = emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-8
@@ -5,17 +5,19 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir.low.level.api.test.base
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import com.intellij.core.CoreApplicationEnvironment
|
||||
import com.intellij.mock.MockApplication
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElementFinder
|
||||
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
|
||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||
import org.jetbrains.kotlin.fir.session.FirModuleInfoBasedModuleData
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.compiler.based.FirModuleResolveStateConfiguratorForSingleModuleTestImpl
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.compiler.based.registerTestServices
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.psi.impl.source.tree.TreeCopyHandler
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.compiler.based.*
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.TestInfrastructureInternals
|
||||
import org.jetbrains.kotlin.test.bind
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
import org.jetbrains.kotlin.test.builders.testConfiguration
|
||||
@@ -36,6 +38,7 @@ import kotlin.io.path.nameWithoutExtension
|
||||
abstract class AbstractLowLevelApiTest : TestWithDisposable() {
|
||||
private lateinit var testInfo: KotlinTestInfo
|
||||
|
||||
@OptIn(TestInfrastructureInternals::class)
|
||||
private val configure: TestConfigurationBuilder.() -> Unit = {
|
||||
globalDefaults {
|
||||
frontend = FrontendKinds.FIR
|
||||
@@ -95,11 +98,26 @@ abstract class AbstractLowLevelApiTest : TestWithDisposable() {
|
||||
registerServicesForProject(this)
|
||||
}
|
||||
|
||||
doTestByFileStructure(moduleInfo.ktFiles.toList(), moduleStructure, testServices)
|
||||
registerApplicationServices()
|
||||
|
||||
doTestByFileStructure(
|
||||
moduleInfo.testFilesToKtFiles.filter { (testFile, _) -> !testFile.isAdditional }.values.toList(),
|
||||
moduleStructure,
|
||||
testServices
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerApplicationServices() {
|
||||
val application = ApplicationManager.getApplication() as MockApplication
|
||||
KotlinCoreEnvironment.underApplicationLock {
|
||||
registerApplicationServices(application)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun registerServicesForProject(project: MockProject) {}
|
||||
|
||||
protected open fun registerApplicationServices(application: MockApplication) {}
|
||||
|
||||
protected abstract fun doTestByFileStructure(ktFiles: List<KtFile>, moduleStructure: TestModuleStructure, testServices: TestServices)
|
||||
|
||||
@BeforeEach
|
||||
|
||||
+6
-2
@@ -71,9 +71,13 @@ class ExpressionMarkerProvider(testServices: TestServices) : TestService {
|
||||
}
|
||||
|
||||
@OptIn(PrivateForInline::class)
|
||||
inline fun <reified P : KtElement> getElementOfTypAtCaret(file: KtFile): P {
|
||||
val offset = atCaret[file.name]
|
||||
fun getCaretPosition(file: KtFile): Int {
|
||||
return atCaret[file.name]
|
||||
?: error("No caret found in file")
|
||||
}
|
||||
|
||||
inline fun <reified P : KtElement> getElementOfTypAtCaret(file: KtFile): P {
|
||||
val offset = getCaretPosition(file)
|
||||
return file.findElementAt(offset)
|
||||
?.parentOfType<P>()
|
||||
?: error("No expression found at caret")
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.references
|
||||
|
||||
import com.intellij.psi.ContributedReferenceHost
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.PsiReferenceService
|
||||
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.idea.references.*
|
||||
import org.jetbrains.kotlin.psi.KotlinReferenceProvidersService
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
|
||||
class HLApiReferenceProviderService : KotlinReferenceProvidersService() {
|
||||
private val originalProvidersBinding: MultiMap<Class<out PsiElement>, KotlinPsiReferenceProvider>
|
||||
private val providersBindingCache: Map<Class<out PsiElement>, List<KotlinPsiReferenceProvider>>
|
||||
|
||||
init {
|
||||
val registrar = KotlinPsiReferenceRegistrar()
|
||||
KotlinReferenceProviderContributor.getInstance().registerReferenceProviders(registrar)
|
||||
originalProvidersBinding = registrar.providers
|
||||
|
||||
providersBindingCache = ConcurrentFactoryMap.createMap<Class<out PsiElement>, List<KotlinPsiReferenceProvider>> { klass ->
|
||||
val result = SmartList<KotlinPsiReferenceProvider>()
|
||||
for (bindingClass in originalProvidersBinding.keySet()) {
|
||||
if (bindingClass.isAssignableFrom(klass)) {
|
||||
result.addAll(originalProvidersBinding.get(bindingClass))
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun doGetKotlinReferencesFromProviders(context: PsiElement): Array<PsiReference> {
|
||||
val providers: List<KotlinPsiReferenceProvider>? = providersBindingCache[context.javaClass]
|
||||
if (providers.isNullOrEmpty()) return PsiReference.EMPTY_ARRAY
|
||||
|
||||
val result = SmartList<PsiReference>()
|
||||
for (provider in providers) {
|
||||
result.addAll(provider.getReferencesByElement(context))
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return PsiReference.EMPTY_ARRAY
|
||||
}
|
||||
|
||||
return result.toTypedArray()
|
||||
}
|
||||
|
||||
override fun getReferences(psiElement: PsiElement): Array<PsiReference> {
|
||||
if (psiElement is ContributedReferenceHost) {
|
||||
return ReferenceProvidersRegistry.getReferencesFromProviders(psiElement, PsiReferenceService.Hints.NO_HINTS)
|
||||
}
|
||||
|
||||
return CachedValuesManager.getCachedValue(psiElement) {
|
||||
CachedValueProvider.Result.create(
|
||||
doGetKotlinReferencesFromProviders(psiElement),
|
||||
PsiModificationTracker.MODIFICATION_COUNT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.frontend.api
|
||||
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
|
||||
import org.jetbrains.kotlin.idea.fir.frontend.api.test.framework.AbstractHLApiSingleModuleTest
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.test.base.expressionMarkerProvider
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtDeclarationRendererOptions
|
||||
import org.jetbrains.kotlin.idea.frontend.api.components.RendererModifier
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassLikeSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtConstructorSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
|
||||
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.assertions
|
||||
|
||||
abstract class AbstractReferenceResolveTest : AbstractHLApiSingleModuleTest() {
|
||||
override fun configureTest(builder: TestConfigurationBuilder) {
|
||||
super.configureTest(builder)
|
||||
with(builder) {
|
||||
defaultDirectives {
|
||||
+JvmEnvironmentConfigurationDirectives.WITH_STDLIB
|
||||
}
|
||||
useDirectives(Directives)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
|
||||
val mainKtFile = ktFiles.singleOrNull() ?: ktFiles.first { it.name == "main.kt" }
|
||||
val caretPosition = testServices.expressionMarkerProvider.getCaretPosition(mainKtFile)
|
||||
val ktReferences = findReferencesAtCaret(mainKtFile, caretPosition)
|
||||
if (ktReferences.isEmpty()) {
|
||||
testServices.assertions.fail { "No references at caret found" }
|
||||
}
|
||||
|
||||
val resolvedTo = analyse(mainKtFile) {
|
||||
val symbols = ktReferences.flatMap { it.resolveToSymbols() }
|
||||
checkReferenceResultForValidity(module, testServices, symbols)
|
||||
renderResolvedTo(symbols)
|
||||
}
|
||||
|
||||
if (Directives.UNRESOLVED_REFERENCE in module.directives) {
|
||||
return
|
||||
}
|
||||
|
||||
val actual = "Resolved to:\n$resolvedTo"
|
||||
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
|
||||
}
|
||||
|
||||
private fun findReferencesAtCaret(mainKtFile: KtFile, caretPosition: Int): List<KtReference> =
|
||||
mainKtFile.findReferenceAt(caretPosition)?.unwrapMultiReferences().orEmpty().filterIsInstance<KtReference>()
|
||||
|
||||
private fun KtAnalysisSession.checkReferenceResultForValidity(
|
||||
module: TestModule,
|
||||
testServices: TestServices,
|
||||
resolvedTo: List<KtSymbol>
|
||||
) {
|
||||
if (Directives.UNRESOLVED_REFERENCE in module.directives) {
|
||||
testServices.assertions.assertTrue(resolvedTo.isEmpty()) {
|
||||
"Reference should be unresolved, but was resolved to ${renderResolvedTo(resolvedTo)}"
|
||||
}
|
||||
} else {
|
||||
testServices.assertions.assertTrue(resolvedTo.isNotEmpty()) { "Unresolved reference" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.renderResolvedTo(symbols: List<KtSymbol>) =
|
||||
symbols.map { renderResolveResult(it) }
|
||||
.sorted()
|
||||
.withIndex()
|
||||
.joinToString(separator = "\n") { "${it.index}: ${it.value}" }
|
||||
|
||||
|
||||
private fun KtAnalysisSession.renderResolveResult(symbol: KtSymbol): String {
|
||||
return buildString {
|
||||
symbolFqName(symbol)?.let { fqName ->
|
||||
append("(in $fqName) ")
|
||||
}
|
||||
append(symbol.render(renderingOptions))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")// KtAnalysisSession receiver
|
||||
private fun KtAnalysisSession.symbolFqName(symbol: KtSymbol): String? = when (symbol) {
|
||||
is KtCallableSymbol -> symbol.callableIdIfNonLocal?.asSingleFqName()?.parent()
|
||||
is KtClassLikeSymbol -> symbol.classIdIfNonLocal?.asSingleFqName()?.parent()
|
||||
is KtConstructorSymbol -> symbol.containingClassIdIfNonLocal?.asSingleFqName()
|
||||
else -> null
|
||||
}?.let { if (it == FqName.ROOT) "ROOT" else it.asString()}
|
||||
|
||||
private object Directives : SimpleDirectivesContainer() {
|
||||
val UNRESOLVED_REFERENCE by directive(
|
||||
"Reference should be unresolved",
|
||||
)
|
||||
}
|
||||
|
||||
private val renderingOptions = KtDeclarationRendererOptions.DEFAULT.copy(
|
||||
modifiers = RendererModifier.DEFAULT - RendererModifier.ANNOTATIONS
|
||||
)
|
||||
|
||||
private fun PsiReference.unwrapMultiReferences(): List<PsiReference> = when (this) {
|
||||
is KtReference -> listOf(this)
|
||||
is PsiMultiReference -> references.flatMap { it.unwrapMultiReferences() }
|
||||
else -> error("Unexpected reference $this")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.test.services.TestServices
|
||||
|
||||
abstract class AbstractHLApiSingleModuleTest : AbstractHLApiTest() {
|
||||
final override fun doTestByFileStructure(ktFiles: List<KtFile>, moduleStructure: TestModuleStructure, testServices: TestServices) {
|
||||
val singleModule = moduleStructure.modules.single()
|
||||
val singleModule = moduleStructure.modules.single()
|
||||
doTestByFileStructure(ktFiles, singleModule, testServices)
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -5,11 +5,16 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir.frontend.api.test.framework
|
||||
|
||||
import com.intellij.mock.MockApplication
|
||||
import com.intellij.mock.MockProject
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.test.base.AbstractLowLevelApiTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.idea.references.HLApiReferenceProviderService
|
||||
import org.jetbrains.kotlin.idea.references.KotlinFirReferenceContributor
|
||||
import org.jetbrains.kotlin.idea.references.KotlinReferenceProviderContributor
|
||||
import org.jetbrains.kotlin.psi.KotlinReferenceProvidersService
|
||||
|
||||
abstract class AbstractHLApiTest : AbstractLowLevelApiTest() {
|
||||
@OptIn(InvalidWayOfUsingAnalysisSession::class)
|
||||
@@ -17,4 +22,11 @@ abstract class AbstractHLApiTest : AbstractLowLevelApiTest() {
|
||||
super.registerServicesForProject(project)
|
||||
project.registerService(KtAnalysisSessionProvider::class.java, KtFirAnalysisSessionProvider::class.java)
|
||||
}
|
||||
|
||||
override fun registerApplicationServices(application: MockApplication) {
|
||||
super.registerApplicationServices(application)
|
||||
if (application.getServiceIfCreated(KotlinReferenceProvidersService::class.java) != null) return
|
||||
application.registerService(KotlinReferenceProvidersService::class.java, HLApiReferenceProviderService::class.java)
|
||||
application.registerService(KotlinReferenceProviderContributor::class.java, KotlinFirReferenceContributor::class.java)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user