FIR IDE: add tests for building kt symbols by PSI

This commit is contained in:
Ilya Kirillov
2020-06-26 18:08:59 +03:00
parent 8a52954bf8
commit c44756bbfd
14 changed files with 380 additions and 46 deletions
@@ -88,6 +88,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolve
import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqualityChecker
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolsByPsiBuildingTest
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest
import org.jetbrains.kotlin.idea.highlighter.*
@@ -935,14 +936,18 @@ fun main(args: Array<String>) {
}
testGroup("idea/idea-frontend-fir/tests", "idea/idea-frontend-fir/testData") {
testClass<AbstractKtDeclarationAndFirDeclarationEqualityChecker> {
model("ktDeclarationAndFirDeclarationEqualityChecker")
}
testClass<AbstractKtDeclarationAndFirDeclarationEqualityChecker> {
model("ktDeclarationAndFirDeclarationEqualityChecker")
}
testClass<AbstractResolveCallTest> {
model("analysisSession/resolveCall")
testClass<AbstractResolveCallTest> {
model("analysisSession/resolveCall")
}
testClass<AbstractSymbolsByPsiBuildingTest> {
model("symbolsByPsi")
}
}
}
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
testClass<AbstractFirMultiModuleResolveTest> {
+2
View File
@@ -4,6 +4,8 @@ plugins {
}
dependencies {
compileOnly(project(":kotlin-reflect-api"))
compileOnly(project(":compiler:psi"))
compileOnly(project(":compiler:frontend"))
compileOnly(project(":core:type-system"))
@@ -0,0 +1,54 @@
/*
* 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.frontend.api.symbols
import org.jetbrains.kotlin.idea.frontend.api.TypeInfo
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaGetter
object DebugSymbolRenderer {
fun render(symbol: KtSymbol): String = buildString {
val klass = symbol::class
appendLine("${klass.simpleName}:")
klass.members.filterIsInstance<KProperty<*>>().sortedBy { it.name }.forEach { property ->
if (property.name in ignoredPropertyNames) return@forEach
val value = property.javaGetter?.invoke(symbol) ?: return@forEach
val stringValue = renderValue(value)
appendLine(" ${property.name}: $stringValue")
}
}
private fun renderValue(value: Any?): String = when (value) {
null -> "null"
is Boolean -> value.toString()
is Name -> value.asString()
is FqName -> value.asString()
is ClassId -> value.asString()
is Enum<*> -> value.name
is List<*> -> buildString {
append("[")
value.joinTo(this) { renderValue(it) }
append("]")
}
is TypeInfo -> value.asDenotableTypeStringRepresentation()
is KtSymbol -> {
val symbolTag = when (value) {
is KtClassLikeSymbol -> renderValue(value.classId)
is KtFunctionSymbol -> renderValue(value.fqName)
is KtConstructorSymbol -> "<constructor>"
is KtNamedSymbol -> renderValue(value.name)
else -> TODO(value::class.toString())
}
"${value::class.simpleName!!}($symbolTag)"
}
else -> value::class.simpleName!!
}
private val ignoredPropertyNames = setOf("fir", "psi", "token")
}
+13
View File
@@ -0,0 +1,13 @@
class A {
}
// SYMBOLS:
/*
KtFirClassOrObjectSymbol:
classId: A
classKind: CLASS
name: A
origin: SOURCE
symbolKind: TOP_LEVEL
typeParameters: []
*/
@@ -0,0 +1,37 @@
class A {
val a: Int = 10
fun x() = 10
}
// SYMBOLS:
/*
KtFirPropertySymbol:
fqName: A.a
isExtension: false
isVal: true
name: a
origin: SOURCE
receiverType: kotlin/Int
symbolKind: MEMBER
type: kotlin/Int
KtFirFunctionSymbol:
fqName: A.x
isExtension: false
isOperator: false
isSuspend: false
name: x
origin: SOURCE
symbolKind: MEMBER
type: kotlin/Int
typeParameters: []
valueParameters: []
KtFirClassOrObjectSymbol:
classId: A
classKind: CLASS
name: A
origin: SOURCE
symbolKind: TOP_LEVEL
typeParameters: []
*/
@@ -0,0 +1,21 @@
class A<T, R> {
}
// SYMBOLS:
/*
KtFirTypeParameterSymbol:
name: T
origin: SOURCE
KtFirTypeParameterSymbol:
name: R
origin: SOURCE
KtFirClassOrObjectSymbol:
classId: A
classKind: CLASS
name: A
origin: SOURCE
symbolKind: TOP_LEVEL
typeParameters: [KtFirTypeParameterSymbol(T), KtFirTypeParameterSymbol(R)]
*/
@@ -0,0 +1,17 @@
fun String.foo(): Int = 10
// SYMBOLS:
/*
KtFirFunctionSymbol:
fqName: foo
isExtension: true
isOperator: false
isSuspend: false
name: foo
origin: SOURCE
receiverType: kotlin/String
symbolKind: TOP_LEVEL
type: kotlin/Int
typeParameters: []
valueParameters: []
*/
@@ -0,0 +1,23 @@
fun foo(x: Int) {}
// SYMBOLS:
/*
KtFirFunctionValueParameterSymbol:
isVararg: false
name: x
origin: SOURCE
symbolKind: LOCAL
type: kotlin/Int
KtFirFunctionSymbol:
fqName: foo
isExtension: false
isOperator: false
isSuspend: false
name: foo
origin: SOURCE
symbolKind: TOP_LEVEL
type: kotlin/Unit
typeParameters: []
valueParameters: [KtFirFunctionValueParameterSymbol(x)]
*/
@@ -0,0 +1,27 @@
fun <X> foo(x: X) {}
// SYMBOLS:
/*
KtFirTypeParameterSymbol:
name: X
origin: SOURCE
KtFirFunctionValueParameterSymbol:
isVararg: false
name: x
origin: SOURCE
symbolKind: LOCAL
type: X
KtFirFunctionSymbol:
fqName: foo
isExtension: false
isOperator: false
isSuspend: false
name: foo
origin: SOURCE
symbolKind: TOP_LEVEL
type: kotlin/Unit
typeParameters: [KtFirTypeParameterSymbol(X)]
valueParameters: [KtFirFunctionValueParameterSymbol(x)]
*/
@@ -0,0 +1,16 @@
fun foo(): Int = 10
// SYMBOLS:
/*
KtFirFunctionSymbol:
fqName: foo
isExtension: false
isOperator: false
isSuspend: false
name: foo
origin: SOURCE
symbolKind: TOP_LEVEL
type: kotlin/Int
typeParameters: []
valueParameters: []
*/
@@ -5,28 +5,21 @@
package org.jetbrains.kotlin.idea.frontend.api.fir
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiParameter
import com.intellij.testFramework.LightPlatformTestCase
import org.intellij.plugins.relaxNG.compact.psi.util.PsiFunction
import org.jetbrains.kotlin.idea.addExternalTestFiles
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.idea.frontend.api.CallInfo
import org.jetbrains.kotlin.idea.frontend.api.TypeInfo
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtParameterSymbol
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import kotlin.reflect.KProperty1
@@ -39,7 +32,7 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod
override fun getTestDataPath(): String = KotlinTestUtils.getHomeDirectory() + "/"
protected fun doTest(path: String) {
File(path).getExternalFiles().forEach(::addFile)
addExternalTestFiles(path)
configureByFile(path)
val elements = editor.caretModel.caretsAndSelections.map { selection ->
getSingleSelectedElement(selection)
@@ -89,29 +82,6 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod
editor.logicalPositionToOffset(selectionStart!!),
editor.logicalPositionToOffset(selectionEnd!!)
)
private fun addFile(file: File) {
addFile(FileUtil.loadFile(file, /* convertLineSeparators = */true), file.name)
}
private fun addFile(text: String, fileName: String) {
runWriteAction {
val virtualDir = LightPlatformTestCase.getSourceRoot()!!
val virtualFile = virtualDir.createChildData(null, fileName)
virtualFile.getOutputStream(null)!!.writer().use { it.write(text) }
}
}
}
private fun <R> executeOnPooledThreadInReadAction(action: () -> R): R =
ApplicationManager.getApplication().executeOnPooledThread<R> { runReadAction(action) }.get()
private fun File.getExternalFiles(): List<File> {
val directory = parentFile
val externalFileName = "${nameWithoutExtension}.external"
return directory.listFiles { _, name ->
name == "$externalFileName.kt" || name == "$externalFileName.java"
}!!.filterNotNull()
}
private fun CallInfo.stringRepresentation(): String {
@@ -0,0 +1,51 @@
/*
* 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.frontend.api.symbols
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import org.jetbrains.kotlin.idea.addExternalTestFiles
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.idea.frontend.api.fir.FirAnalysisSession
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractSymbolsByPsiBuildingTest : KotlinLightCodeInsightFixtureTestCase() {
protected fun doTest(path: String) {
addExternalTestFiles(path)
val file = File(path)
val ktFile = myFixture.configureByText(file.name, FileUtil.loadFile(file)) as KtFile
val renderedSymbols = executeOnPooledThreadInReadAction {
val analysisSession = FirAnalysisSession(ktFile)
val declarationSymbols = ktFile.collectDescendantsOfType<KtDeclaration>().map { declaration ->
analysisSession.symbolProvider.getSymbol(declaration)
}
declarationSymbols.map(DebugSymbolRenderer::render)
}
val actual = buildString {
val actualSymbolsData = renderedSymbols.joinToString(separator = "\n")
val fileTextWithoutSymbolsData = ktFile.text.substringBeforeLast(SYMBOLS_TAG).trimEnd()
appendLine(fileTextWithoutSymbolsData)
appendLine()
appendLine(SYMBOLS_TAG)
appendLine("/*")
append(actualSymbolsData)
appendLine("*/")
}
KotlinTestUtils.assertEqualsToFile(file, actual)
}
companion object {
private const val SYMBOLS_TAG = "// SYMBOLS:"
}
}
@@ -0,0 +1,65 @@
/*
* 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.frontend.api.symbols;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
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/testData/symbolsByPsi")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class SymbolsByPsiBuildingTestGenerated extends AbstractSymbolsByPsiBuildingTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInSymbolsByPsi() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/symbolsByPsi"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("class.kt")
public void testClass() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/class.kt");
}
@TestMetadata("classMembes.kt")
public void testClassMembes() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/classMembes.kt");
}
@TestMetadata("classWithTypeParams.kt")
public void testClassWithTypeParams() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/classWithTypeParams.kt");
}
@TestMetadata("extensionFunction.kt")
public void testExtensionFunction() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/extensionFunction.kt");
}
@TestMetadata("function.kt")
public void testFunction() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/function.kt");
}
@TestMetadata("functionWithTypeParams.kt")
public void testFunctionWithTypeParams() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/functionWithTypeParams.kt");
}
@TestMetadata("implicitReturn.kt")
public void testImplicitReturn() throws Exception {
runTest("idea/idea-frontend-fir/testData/symbolsByPsi/implicitReturn.kt");
}
}
@@ -5,8 +5,41 @@
package org.jetbrains.kotlin.idea
import org.jetbrains.kotlin.idea.fir.low.level.api.DuplicatedFirSourceElementsException
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.LightPlatformTestCase
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.idea.fir.low.level.api.DuplicatedFirSourceElementsException
import java.io.File
fun <R> executeOnPooledThreadInReadAction(action: () -> R): R =
ApplicationManager.getApplication().executeOnPooledThread<R> { runReadAction(action) }.get()
fun addExternalTestFiles(testDataFilePath: String) {
File(testDataFilePath).getExternalFiles().forEach(::addFile)
}
private fun addFile(file: File) {
addFile(FileUtil.loadFile(file, /* convertLineSeparators = */true), file.name)
}
private fun addFile(text: String, fileName: String) {
runWriteAction {
val virtualDir = LightPlatformTestCase.getSourceRoot()!!
val virtualFile = virtualDir.createChildData(null, fileName)
virtualFile.getOutputStream(null)!!.writer().use { it.write(text) }
}
}
private fun File.getExternalFiles(): List<File> {
val directory = parentFile
val externalFileName = "${nameWithoutExtension}.external"
return directory.listFiles { _, name ->
name == "$externalFileName.kt" || name == "$externalFileName.java"
}!!.filterNotNull()
}
/**
* Temporary