Add test checking file annotations resolution

#KT-37219 Fixed
This commit is contained in:
Pavel Kirpichenkov
2020-07-14 12:52:22 +03:00
parent 36a46482c5
commit 79d7babb57
11 changed files with 316 additions and 0 deletions
@@ -273,6 +273,10 @@ fun main(args: Array<String>) {
model("resolve/partialBodyResolve")
}
testClass<AbstractResolveModeComparisonTest> {
model("resolve/resolveModeComparison")
}
testClass<AbstractPsiCheckerTest> {
model("checker", recursive = false)
model("checker/regression")
@@ -0,0 +1,21 @@
/*All (1)*/class Foo/*None (2)*/(/*None (3)*/val prop: /*None (4)*/String/*(4)*//*(3)*/)/*(2)*/ {
/*None (5)*/class Inner {
/*None (6)*/val boo = 42/*(6)*/
/*None (7)*/fun inner() {}/*(7)*/
}/*(5)*/
/*All (8)*/fun first(/*All (9)*/arg1: /*All (10)*/String/*(10)*//*(9)*/) {
/*All (11)*/arg1/*(11)*/
}/*(8)*/
/*None (12)*/fun second(/*None (13)*/arg2: /*None (14)*/Int/*(14)*//*(13)*/) {
/*None (15)*/arg2/*(15)*/
}/*(12)*/
}/*(1)*/
/*None (16)*/class Bar/*None (17)*/(/*None (18)*/val baz: /*None (19)*/Int/*(19)*//*(18)*/)/*(17)*/ {
/*None (20)*/fun bee() {
/*None (21)*/val local = "abc"/*(21)*/
}/*(20)*/
}/*(16)*/
+21
View File
@@ -0,0 +1,21 @@
class Foo(val prop: String) {
class Inner {
val boo = 42
fun inner() {}
}
fun first(arg1: String) {
<caret>arg1
}
fun second(arg2: Int) {
arg2
}
}
class Bar(val baz: Int) {
fun bee() {
val local = "abc"
}
}
@@ -0,0 +1,14 @@
// COMPILER_ARGUMENTS: -Xopt-in=kotlin.RequiresOptIn
@file:/*All (1)*/OptIn/*(1)*/(/*All (2)*/Experimental/*(2)*/::class)
/*All (3)*/@/*All (4)*/Retention/*(4)*/(/*None (5)*/value/*(5)*/ = /*None (6)*/AnnotationRetention/*(6)*/./*None (7)*/BINARY/*(7)*/)
@/*All (8)*/RequiresOptIn/*(8)*/(/*All (9)*/level/*(9)*/ = /*All (10)*/RequiresOptIn/*(10)*/./*All (11)*/Level/*(11)*/./*All (12)*/WARNING/*(12)*/)
annotation class Experimental/*(3)*/
/*All (13)*/object MyObject/*(13)*/
/*All (14)*/@/*All (15)*/Experimental/*(15)*/
operator fun /*All (16)*/MyObject/*(16)*/.invoke(/*All (17)*/closure: () -> /*All (18)*/Unit/*(18)*//*(17)*/) {}/*(14)*/
/*All (19)*/fun d() = /*All (20)*//*All (21)*/MyObject/*(21)*/ {
}/*(20)*//*(19)*/
@@ -0,0 +1,14 @@
// COMPILER_ARGUMENTS: -Xopt-in=kotlin.RequiresOptIn
@file:OptIn(Experimental::class)
@Retention(value = AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
annotation class Experimental
object MyObject
@Experimental
operator fun MyObject.invoke(closure: () -> Unit) {}
fun d() = <caret>MyObject {
}
@@ -0,0 +1,7 @@
/*All (1)*/fun foo(/*All (2)*/arg: /*All (3)*/String/*(3)*//*(2)*/) {
/*All (4)*/arg/*(4)*/
}/*(1)*/
/*None (5)*/fun bar(/*None (6)*/arg: /*None (7)*/Int/*(7)*//*(6)*/) {
/*None (8)*/arg/*(8)*/
}/*(5)*/
@@ -0,0 +1,7 @@
fun foo(arg: String) {
<caret>arg
}
fun bar(arg: Int) {
arg
}
@@ -0,0 +1,6 @@
/*All (1)*/fun test(/*All (2)*/baz: /*All (3)*/Int/*(3)*//*(2)*/) {
/*Full (4)*/fun irrelevant() {
/*Full (5)*/val foo = "bar"/*(5)*/
}/*(4)*/
/*All (6)*/baz/*(6)*/ /*None (7)*/=/*(7)*/ 42
}/*(1)*/
@@ -0,0 +1,6 @@
fun test(baz: Int) {
fun irrelevant() {
val foo = "bar"
}
<caret>baz = 42
}
@@ -0,0 +1,166 @@
/*
* 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.resolve
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.withCustomCompilerOptions
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
/**
* This test runs analysis, then for each declaration and reference writes modes in which they have been resolved, if any.
* "Targeted" resolve for selected expression is used for analysis.
*/
abstract class AbstractResolveModeComparisonTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
fun doTest(unused: String) {
val testPath = testPath()
val dumpResults = dump(testPath)
val testPathNoExt = FileUtil.getNameWithoutExtension(testPath)
KotlinTestUtils.assertEqualsToFile(File("$testPathNoExt.after"), dumpResults)
}
private fun dump(testPath: String): String {
myFixture.configureByText(KotlinFileType.INSTANCE, File(testPath).readText())
return withCustomCompilerOptions(myFixture.file.text, project, module) {
val file = myFixture.file as KtFile
val resolutionFacade = file.getResolutionFacade()
val expression = findTargetExpression()
val analysisResults: Map<BodyResolveMode, BindingContext> =
RESOLVE_MODES.associateWith { resolveMode -> resolutionFacade.analyze(expression, resolveMode) }
val markup: Map<KtElement, List<BodyResolveMode>> = markResolveModes(analysisResults)
val offsets: MultiMap<Int, Mark> = findOffsets(markup)
composeTextWithMarkup(offsets)
}
}
private fun findTargetExpression(): KtExpression {
val editor = myFixture.editor
val selectionModel = editor.selectionModel
return if (selectionModel.hasSelection()) {
PsiTreeUtil.findElementOfClassAtRange(
file,
selectionModel.selectionStart,
selectionModel.selectionEnd,
KtExpression::class.java
)
?: error("No KtExpression at selection range")
} else {
val offset = editor.caretModel.offset
val element = file.findElementAt(offset)!!
element.getNonStrictParentOfType<KtExpression>() ?: error("No KtExpression at caret")
}
}
private fun markResolveModes(analysisResults: Map<BodyResolveMode, BindingContext>): Map<KtElement, List<BodyResolveMode>> {
val modesPerformingResolveForElements = hashMapOf<KtElement, List<BodyResolveMode>>()
file.accept(object : KtTreeVisitorVoid() {
override fun visitReferenceExpression(reference: KtReferenceExpression) {
super.visitReferenceExpression(reference)
modesPerformingResolveForElements[reference] = analysisResults.keys.filter { bodyResolveMode ->
val bindingContext = analysisResults[bodyResolveMode] ?: error("No resolution results for mode: $bodyResolveMode")
bindingContext[BindingContext.REFERENCE_TARGET, reference] != null
}
}
override fun visitDeclaration(declaration: KtDeclaration) {
super.visitDeclaration(declaration)
modesPerformingResolveForElements[declaration] = analysisResults.keys.filter { bodyResolveMode ->
val bindingContext = analysisResults[bodyResolveMode] ?: error("No resolution results for mode: $bodyResolveMode")
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] != null
}
}
})
return modesPerformingResolveForElements
}
private fun findOffsets(resolveModesMarkup: Map<KtElement, List<BodyResolveMode>>): MultiMap<Int, Mark> {
val offsets = MultiMap.createSmart<Int, Mark>()
for ((ktElement, resolveModesForElement) in resolveModesMarkup) {
val start = ktElement.startOffset
val end = ktElement.endOffset
if (resolveModesForElement.isEmpty())
offsets.putValue(start, Mark.NotAnalyzed())
else
offsets.putValue(start, Mark.Analyzed(resolveModesForElement))
offsets.putValue(end, Mark.End())
}
return offsets
}
private fun composeTextWithMarkup(offsets: MultiMap<Int, Mark>): String {
val builder = StringBuilder()
var offsetBefore = 0
var counter = 0
val endIndices = ArrayDeque<Int>()
for ((offset, marks) in offsets.entrySet().sortedBy { it.key }) {
builder.append(file.text.substring(offsetBefore, offset))
for (mark in marks) {
if (mark !is Mark.End) {
counter += 1
endIndices.add(counter)
}
when (mark) {
is Mark.Analyzed -> {
val modes = mark.modes!!
val modesRepresentation =
if (modes.size == RESOLVE_MODES.size) "All"
else modes.joinToString { it.toString().toLowerCase().capitalize() }
builder.append("/*${modesRepresentation} ($counter)*/")
}
is Mark.NotAnalyzed -> {
builder.append("/*None ($counter)*/")
}
is Mark.End -> {
builder.append("/*(${endIndices.pop()})*/")
}
else -> error("Sealed")
}
}
offsetBefore = offset
}
builder.append(file.text.substring(offsetBefore, file.textLength))
return builder.toString()
}
private sealed class Mark(val modes: List<BodyResolveMode>? = null) {
class NotAnalyzed : Mark()
class Analyzed(modes: List<BodyResolveMode>) : Mark(modes) {
init {
require(modes.isNotEmpty()) { "'Analyzed' mark should not be created without modes" }
}
}
class End : Mark()
}
private companion object {
val RESOLVE_MODES: List<BodyResolveMode> = listOf(
BodyResolveMode.PARTIAL,
BodyResolveMode.FULL, // Note that due to caching it's important to do the full resolve as the last one
)
}
}
@@ -0,0 +1,50 @@
/*
* 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.resolve;
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/testData/resolve/resolveModeComparison")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class ResolveModeComparisonTestGenerated extends AbstractResolveModeComparisonTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInResolveModeComparison() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/resolve/resolveModeComparison"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("Classes.kt")
public void testClasses() throws Exception {
runTest("idea/testData/resolve/resolveModeComparison/Classes.kt");
}
@TestMetadata("FileAnnotations.kt")
public void testFileAnnotations() throws Exception {
runTest("idea/testData/resolve/resolveModeComparison/FileAnnotations.kt");
}
@TestMetadata("Functions.kt")
public void testFunctions() throws Exception {
runTest("idea/testData/resolve/resolveModeComparison/Functions.kt");
}
@TestMetadata("NestedFunctions.kt")
public void testNestedFunctions() throws Exception {
runTest("idea/testData/resolve/resolveModeComparison/NestedFunctions.kt");
}
}