Add test checking file annotations resolution
#KT-37219 Fixed
This commit is contained in:
@@ -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)*/
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+50
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user