FIR IDE: add tests for call resolve
This commit is contained in:
@@ -87,6 +87,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest
|
||||
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.hierarchy.AbstractHierarchyTest
|
||||
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest
|
||||
import org.jetbrains.kotlin.idea.highlighter.*
|
||||
@@ -923,20 +924,26 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractKtDeclarationAndFirDeclarationEqualityChecker> {
|
||||
model("ktDeclarationAndFirDeclarationEqualityChecker")
|
||||
}
|
||||
|
||||
testClass<AbstractResolveCallTest> {
|
||||
model("analysisSession/resolveCall")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/idea-fir/tests", "idea/testData") {
|
||||
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
testClass<AbstractFirHighlightingTest> {
|
||||
model("highlighter")
|
||||
}
|
||||
|
||||
testClass<AbstractFirLazyResolveTest> {
|
||||
model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/idea-fir/tests", "idea/testData") {
|
||||
testClass<AbstractFirHighlightingTest> {
|
||||
model("highlighter")
|
||||
}
|
||||
|
||||
testClass<AbstractFirReferenceResolveTest> {
|
||||
model("resolve/references", pattern = KT_WITHOUT_DOTS_IN_NAME)
|
||||
|
||||
@@ -21,6 +21,8 @@ dependencies {
|
||||
compile(project(":idea:idea-core"))
|
||||
// </temp>
|
||||
|
||||
testCompile(intellijDep())
|
||||
testCompile(intellijCoreDep())
|
||||
testCompile(toolsJar())
|
||||
testCompile(projectTests(":idea"))
|
||||
testCompile(projectTests(":compiler:tests-common"))
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ 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/testData/fir/lazyResolve")
|
||||
@TestDataPath("/")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class FirLazyResolveTestGenerated extends AbstractFirLazyResolveTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ 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/testData/fir/multiModule")
|
||||
@TestDataPath("/")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class FirMultiModuleResolveTestGenerated extends AbstractFirMultiModuleResolveTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun function(a: Int) {}
|
||||
|
||||
fun call() {
|
||||
<selection>function(1)</selection>
|
||||
}
|
||||
|
||||
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(a: Int): IMPLICIT_TYPE
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun String.function(a: Int) {}
|
||||
|
||||
fun call() {
|
||||
"str".<selection>function(1)</selection>
|
||||
}
|
||||
|
||||
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(<receiver> : String, a: Int): IMPLICIT_TYPE
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
fun String.function(a: Int) {}
|
||||
|
||||
fun call() {
|
||||
"str"?.<selection>function(1)</selection>
|
||||
}
|
||||
|
||||
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(<receiver> : String, a: Int): IMPLICIT_TYPE
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class A
|
||||
|
||||
fun call() {
|
||||
val a = <selection>A()</selection>
|
||||
}
|
||||
|
||||
// CALL: KtImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A
|
||||
idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.external.java
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
|
||||
fun call() {
|
||||
val a = <selection>A()</selection>
|
||||
}
|
||||
|
||||
// CALL: JavaImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class JavaClass {
|
||||
void javaMethod() {}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun call() {
|
||||
val javaClass = JavaClass()
|
||||
javaClass.<selection>javaMethod()</selection>
|
||||
}
|
||||
|
||||
// CALL: SimpleJavaFunctionCallInfo: targetFunction = JavaClass.javaMethod(): void
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun call(x: (Int) -> String) {
|
||||
<selection>x(1)</selection>
|
||||
}
|
||||
|
||||
// CALL: VariableAsFunctionCallInfo: target = x, isSuspendCall = false
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
operator fun Int.invoke(): String {}
|
||||
|
||||
fun call(x: Int) {
|
||||
<selection>x()</selection>
|
||||
}
|
||||
|
||||
// CALL: SimpleKtFunctionCallInfo: targetFunction = invoke(<receiver> : Int): String
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.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.frontend.api.CallInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
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.psiUtil.elementsInRange
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.reflect.jvm.javaGetter
|
||||
|
||||
|
||||
abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCodeInsightTestCase() {
|
||||
override fun getTestDataPath(): String = KotlinTestUtils.getHomeDirectory() + "/"
|
||||
|
||||
protected fun doTest(path: String) {
|
||||
File(path).getExternalFiles().forEach(::addFile)
|
||||
configureByFile(path)
|
||||
val elements = editor.caretModel.caretsAndSelections.map { selection ->
|
||||
getSingleSelectedElement(selection)
|
||||
}
|
||||
val callInfos = executeOnPooledThreadInReadAction {
|
||||
val analysisSession = FirAnalysisSession(file as KtFile)
|
||||
elements.map { element ->
|
||||
when (element) {
|
||||
is KtCallExpression -> analysisSession.resolveCall(element)
|
||||
is KtBinaryExpression -> analysisSession.resolveCall(element)
|
||||
else -> error("Selected should be either KtCallExpression or KtBinaryExpression but was $element")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (callInfos.isEmpty()) {
|
||||
error("There are should be at least one call selected")
|
||||
}
|
||||
|
||||
val textWithoutLatestComments = run {
|
||||
val rawText = File(path).readText()
|
||||
"""(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd()
|
||||
}
|
||||
val actualText = buildString {
|
||||
append(textWithoutLatestComments)
|
||||
append("\n\n")
|
||||
callInfos.joinTo(this, separator = "\n") { info ->
|
||||
"// CALL: ${info?.stringRepresentation()}"
|
||||
}
|
||||
}
|
||||
KotlinTestUtils.assertEqualsToFile(File(path), actualText)
|
||||
}
|
||||
|
||||
|
||||
private fun getSingleSelectedElement(selection: CaretState): PsiElement {
|
||||
val selectionRange = selection.getTextRange()
|
||||
val elements = file.elementsInRange(selectionRange)
|
||||
if (elements.size != 1) {
|
||||
val selectionText = file.text.substring(selectionRange.startOffset, selectionRange.endOffset)
|
||||
error("Single element should be found for selection `$selectionText`, but $elements were found")
|
||||
}
|
||||
return elements.first()
|
||||
}
|
||||
|
||||
private fun CaretState.getTextRange() = TextRange.create(
|
||||
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 {
|
||||
fun Any.stringValue(): String? = when (this) {
|
||||
is PsiMethod -> buildString {
|
||||
append(getKotlinFqName()!!)
|
||||
@Suppress("UnstableApiUsage")
|
||||
parameters.joinTo(this, prefix = "(", postfix = ")") { parameter ->
|
||||
"${parameter.name}: ${(parameter as PsiParameter).typeElement!!.text}"
|
||||
}
|
||||
append(": ${returnTypeElement!!.text}")
|
||||
}
|
||||
is KtFunction -> buildString {
|
||||
append(getKotlinFqName()!!)
|
||||
append("(")
|
||||
receiverTypeReference?.let { receiver ->
|
||||
append("<receiver> : ${receiver.text}")
|
||||
if (valueParameters.isNotEmpty()) append(", ")
|
||||
}
|
||||
valueParameters.joinTo(this,) { parameter ->
|
||||
"${parameter.name}: ${parameter.typeReference!!.text}"
|
||||
}
|
||||
append(")")
|
||||
append(": ${typeReference?.text ?: "IMPLICIT_TYPE"}")
|
||||
}
|
||||
is KtClass -> "Implicit constructor of ${getKotlinFqName()!!}"
|
||||
is PsiClass -> "Implicit constructor of ${getKotlinFqName()!!}"
|
||||
is KtParameter -> name!!
|
||||
is Boolean -> toString()
|
||||
else -> error("unexpected parameter type ${this::class}")
|
||||
}
|
||||
|
||||
val callInfoClass = this::class
|
||||
return buildString {
|
||||
append(callInfoClass.simpleName!!)
|
||||
append(": ")
|
||||
val propertyByName = callInfoClass.memberProperties.associateBy(KProperty1<*, *>::name)
|
||||
callInfoClass.primaryConstructor!!.parameters.joinTo(this) { parameter ->
|
||||
val value = propertyByName[parameter.name]!!.javaGetter!!(this@stringRepresentation)?.stringValue()
|
||||
"${parameter.name!!} = $value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.fir;
|
||||
|
||||
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/analysisSession/resolveCall")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class ResolveCallTestGenerated extends AbstractResolveCallTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInResolveCall() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/analysisSession/resolveCall"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("functionCallInTheSameFile.kt")
|
||||
public void testFunctionCallInTheSameFile() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionWithReceiverCall.kt")
|
||||
public void testFunctionWithReceiverCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("functionWithReceiverSafeCall.kt")
|
||||
public void testFunctionWithReceiverSafeCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("implicitConstuctorCall.kt")
|
||||
public void testImplicitConstuctorCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("implicitJavaConstuctorCall.kt")
|
||||
public void testImplicitJavaConstuctorCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("javaFunctionCall.kt")
|
||||
public void testJavaFunctionCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("variableAsFunction.kt")
|
||||
public void testVariableAsFunction() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("variableAsFunctionLikeCall.kt")
|
||||
public void testVariableAsFunctionLikeCall() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user