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")
|
model("resolve/partialBodyResolve")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractResolveModeComparisonTest> {
|
||||||
|
model("resolve/resolveModeComparison")
|
||||||
|
}
|
||||||
|
|
||||||
testClass<AbstractPsiCheckerTest> {
|
testClass<AbstractPsiCheckerTest> {
|
||||||
model("checker", recursive = false)
|
model("checker", recursive = false)
|
||||||
model("checker/regression")
|
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