adfa8c788c
Using "JVM_1_8" always resulted in incorrect mapping of Kotlin annotation targets to Java element types. The change in AbstractKotlinRenderLogTest is needed because while CliTraceHolder.module is technically a descriptor leak, it was never detected by this test accidentally, because of the depth cutoff equal to 10, which started to not be enough after the minor refactoring of replacing `Delegates.notNull` with `lateinit`.
172 lines
6.7 KiB
Kotlin
172 lines
6.7 KiB
Kotlin
package org.jetbrains.uast.test.kotlin
|
|
|
|
import com.intellij.psi.PsiElement
|
|
import com.intellij.psi.PsiFile
|
|
import com.intellij.psi.PsiRecursiveElementVisitor
|
|
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
|
import com.intellij.util.PairProcessor
|
|
import com.intellij.util.ref.DebugReflectionUtil
|
|
import junit.framework.TestCase
|
|
import junit.framework.TestCase.*
|
|
import org.jetbrains.kotlin.cli.jvm.compiler.CliTraceHolder
|
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
|
import org.jetbrains.kotlin.psi.KtElement
|
|
import org.jetbrains.kotlin.psi.KtFile
|
|
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
|
|
import org.jetbrains.uast.UAnchorOwner
|
|
import org.jetbrains.uast.UElement
|
|
import org.jetbrains.uast.UFile
|
|
import org.jetbrains.uast.kotlin.KOTLIN_CACHED_UELEMENT_KEY
|
|
import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin
|
|
import org.jetbrains.uast.sourcePsiElement
|
|
import org.jetbrains.uast.test.common.kotlin.RenderLogTestBase
|
|
import org.jetbrains.uast.visitor.UastVisitor
|
|
import org.junit.Assert
|
|
import java.io.File
|
|
import java.util.*
|
|
|
|
interface AbstractKotlinRenderLogTest : RenderLogTestBase {
|
|
override fun getTestFile(testName: String, ext: String) =
|
|
File(File(TEST_KOTLIN_MODEL_DIR, testName).canonicalPath + '.' + ext)
|
|
|
|
override fun check(testName: String, file: UFile) {
|
|
check(testName, file, true)
|
|
}
|
|
|
|
fun check(testName: String, file: UFile, checkParentConsistency: Boolean) {
|
|
super.check(testName, file)
|
|
|
|
if (checkParentConsistency) {
|
|
checkParentConsistency(file)
|
|
}
|
|
|
|
file.checkContainingFileForAllElements()
|
|
file.checkJvmDeclarationsImplementations()
|
|
file.checkDescriptorsLeak()
|
|
}
|
|
|
|
private fun checkParentConsistency(file: UFile) {
|
|
val parentMap = mutableMapOf<PsiElement, MutableMap<String, String>>()
|
|
|
|
operator fun MutableMap<PsiElement, MutableMap<String, String>>.get(psi: PsiElement, cls: String?) =
|
|
parentMap.getOrPut(psi) { mutableMapOf() }[cls]
|
|
|
|
operator fun MutableMap<PsiElement, MutableMap<String, String>>.set(psi: PsiElement, cls: String, v: String) {
|
|
parentMap.getOrPut(psi) { mutableMapOf() }[cls] = v
|
|
}
|
|
|
|
file.accept(object : UastVisitor {
|
|
private val parentStack = Stack<UElement>()
|
|
|
|
override fun visitElement(node: UElement): Boolean {
|
|
val parent = node.uastParent
|
|
if (parent == null) {
|
|
Assert.assertTrue("Wrong parent of $node", parentStack.empty())
|
|
}
|
|
else {
|
|
Assert.assertEquals("Wrong parent of $node", parentStack.peek(), parent)
|
|
}
|
|
node.sourcePsiElement?.let {
|
|
parentMap[it, node.asLogString()] = parentStack.reversed().joinToString { it.asLogString() }
|
|
}
|
|
parentStack.push(node)
|
|
return false
|
|
}
|
|
|
|
override fun afterVisitElement(node: UElement) {
|
|
super.afterVisitElement(node)
|
|
parentStack.pop()
|
|
}
|
|
})
|
|
|
|
file.psi.clearUastCaches()
|
|
|
|
file.psi.accept(object : PsiRecursiveElementVisitor() {
|
|
override fun visitElement(element: PsiElement) {
|
|
val uElement = KotlinUastLanguagePlugin().convertElementWithParent(element, null)
|
|
val expectedParents = parentMap[element, uElement?.asLogString()]
|
|
if (expectedParents != null) {
|
|
assertNotNull("Expected to be able to convert PSI element $element", uElement)
|
|
val parents = generateSequence(uElement!!.uastParent) { it.uastParent }.joinToString { it.asLogString() }
|
|
assertEquals("Inconsistent parents for ${uElement.asRenderString()}(${uElement.asLogString()})(${uElement.javaClass}) (converted from $element[${element.text}])", expectedParents, parents)
|
|
}
|
|
super.visitElement(element)
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun UFile.checkContainingFileForAllElements() {
|
|
accept(object : UastVisitor {
|
|
override fun visitElement(node: UElement): Boolean {
|
|
if (node is PsiElement) {
|
|
node.containingFile.assertedCast<KtFile> { "containingFile should be KtFile for ${node.asLogString()}" }
|
|
}
|
|
|
|
val anchorPsi = (node as? UAnchorOwner)?.uastAnchor?.sourcePsi
|
|
if (anchorPsi != null) {
|
|
anchorPsi.containingFile.assertedCast<KtFile> { "uastAnchor.containingFile should be KtFile for ${node.asLogString()}" }
|
|
}
|
|
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun UFile.checkDescriptorsLeak() {
|
|
accept(object : UastVisitor {
|
|
override fun visitElement(node: UElement): Boolean {
|
|
checkDescriptorsLeak(node)
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun UFile.checkJvmDeclarationsImplementations() {
|
|
accept(object : UastVisitor {
|
|
override fun visitElement(node: UElement): Boolean {
|
|
|
|
if (node is UAnchorOwner) {
|
|
node.uastAnchor?.let { visitElement(it) }
|
|
}
|
|
|
|
node.sourcePsi?.let {
|
|
assertTrue("sourcePsi should be physical but ${it.javaClass} found for [${it.text}] " +
|
|
"for ${node.javaClass}->${node.uastParent?.javaClass}", it is LeafPsiElement || it is KtElement|| it is LeafPsiElement)
|
|
}
|
|
node.javaPsi?.let {
|
|
assertTrue("javaPsi should be light but ${it.javaClass} found for [${it.text}] " +
|
|
"for ${node.javaClass}->${node.uastParent?.javaClass}", it !is KtElement)
|
|
}
|
|
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private val descriptorsClasses = listOf(AnnotationDescriptor::class, DeclarationDescriptor::class)
|
|
|
|
fun checkDescriptorsLeak(node: UElement) {
|
|
DebugReflectionUtil.walkObjects(
|
|
10,
|
|
mapOf(node to node.javaClass.name),
|
|
Any::class.java,
|
|
{ it !is CliTraceHolder },
|
|
PairProcessor { value, backLink ->
|
|
descriptorsClasses.find { it.isInstance(value) }?.let {
|
|
TestCase.fail("""Leaked descriptor ${it.qualifiedName} in ${node.javaClass.name}\n$backLink""")
|
|
false
|
|
} ?: true
|
|
})
|
|
}
|
|
|
|
private fun PsiFile.clearUastCaches() {
|
|
accept(object : PsiRecursiveElementVisitor() {
|
|
override fun visitElement(element: PsiElement) {
|
|
super.visitElement(element)
|
|
element.putUserData(KOTLIN_CACHED_UELEMENT_KEY, null)
|
|
}
|
|
})
|
|
}
|