blob: bfe90246da681280b3c4d394359cc39f4eb556d5 [file] [log] [blame] [edit]
package org.jetbrains.uast.test.kotlin
import com.intellij.openapi.util.Conditions
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.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,
Conditions.alwaysTrue(),
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)
}
})
}