Does UAST support Kotlin?
Answered
I thought UAST supported Kotlin but I'm trying to do something fairly simple which suggests it doesn't. I'm trying to write code that extracts the methods of source code files using UAST. I am able to successfully do so with Java and Groovy but Kotlin doesn't seem to correctly detect the classes/methods even though it successfully turns the KtFile into a KotlinUFile.
Here is the code I'm using which demonstrates the issue:
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiJavaFile
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixture4TestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.plugins.groovy.GroovyFileType
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
import org.jetbrains.uast.UFile
import org.jetbrains.uast.toUElement
import org.junit.Test
class UGetMethodTest : LightPlatformCodeInsightFixture4TestCase() {
@Test
fun `get java method`() {
@Language("Java") val code = """
public class GetterMethod {
private String str;
public String getStr() {
return str;
}
}
""".trimIndent()
val sourceFile = PsiFileFactory.getInstance(project).createFileFromText(
"GetterMethod.java", JavaFileType.INSTANCE, code
) as PsiJavaFile
var foundFunction = false
val uFile = sourceFile.toUElement() as UFile
assertEquals(1, uFile.classes.count())
uFile.classes.forEach {
assertEquals(1, it.methods.count())
foundFunction = true
}
assertTrue(foundFunction)
}
@Test
fun `get groovy method`() {
@Language("Groovy") val code = """
class GetterMethod {
private String str
String getStr() {
return str
}
}
""".trimIndent()
val sourceFile = PsiFileFactory.getInstance(project).createFileFromText(
"GetterMethod.groovy", GroovyFileType.GROOVY_FILE_TYPE, code
) as GroovyFile
var foundFunction = false
val uFile = sourceFile.toUElement() as UFile
assertEquals(1, uFile.classes.count())
uFile.classes.forEach {
assertEquals(1, it.methods.count())
foundFunction = true
}
assertTrue(foundFunction)
}
@Test
fun `get kotlin method`() {
@Language("Kt") val code = """
class GetterMethod(private val str: String) {
fun getStr(): String {
return str
}
}
""".trimIndent()
val sourceFile = PsiFileFactory.getInstance(project).createFileFromText(
"GetterMethod.kt", KotlinFileType.INSTANCE, code
) as KtFile
var foundFunction = false
val uFile = sourceFile.toUElement() as UFile
assertEquals(1, uFile.classes.count())
uFile.classes.forEach {
assertEquals(1, it.methods.count())
foundFunction = true
}
assertTrue(foundFunction)
}
}
I've also tried using UastVisitor to no avail.
Please sign in to leave a comment.
Hello! Kotlin resolving doesn't work properly for files created "in the air" (I mean created by the PsiFileFactory and not connected to the project and module), which is necessary to build a proper UAST tree.
The correct way to create real physical files in tests is to use the `myFixture` field:
And If you still want to have a file "in the air" there is a specific method in Kotlin for it (but you still need to have a physical file to make the resolve work):
I see this post is marked as planned. Is there something I can do to keep track of whatever is being planned?
Also, I wanted to add that the following:
Produces this error:
That solved my issue. Thank you, Nicolay.
I can create a new topic if necessary but decided to follow up on this topic as the issue I'm facing now seems related. Given this code:
Are you aware of why annotations are not found? I've tried it in Java, Groovy, and Scala to no issue.
Where is "@Test" declared/coming from in your test case?
I haven't been able to figure out how to properly declare annotations in a test case. I've been getting around that by using fully qualified annotation names like @org.junit.Test. That allows me to correctly resolve the annotations. If you know how to fix that I would be interested in knowing, but my Kotlin test doesn't work with the simple or qualified name whereas all my other tests do. Here are those tests:
P.s. Any clue why @Language("Scala") doesn't work?
Please try visitAnnotationEntry instead for Kotlin.
What does "@Language(Scala)" does not work" mean? You mean the language ID is not resolved? It will resolve only to languages from plugins enabled in current IDE instance.
Thanks, Yann. That does indeed work. If you don't mind I would like to ask one more question. When I started this post my goal was to get the annotations from JVM source code without writing language-specific code. You guys have successfully helped me get to parsing methods via UAST and parsing Kotlin annotations via PSI. The final step I'm trying to accomplish is to get to parsing method annotations via UAST. Adding the following code to the code I posted above works in all languages except Kotlin:
I've searched around for a UAST implementation of finding Kotlin annotations and the closest I could find is this:
It's promising that I can get UAnnotations from Kotlin, but that method requires me to know what I'm looking for upfront. Is there a way to get all the UAnnotations for a Kotlin method without Kotlin specific code or code which is looking for specific text?
P.s. By @Language("Scala") not working I just meant that it's not doing the syntax highlighting on the string like all the other strings benefit from.
Brandon, if you want to collect all annotations from the file, you could use following options, please note also that there could be nested classes and classes defined in methods and so on. So one option is to use the UastVisitor:
Other is to use a `PsiVisitor` it is better approach it is less resource consuming and could find annotation is some compliated cases like nested annotations:
Also instead of visitors the `SyntaxTraverser` could be used:
If you need only method annotation you could check for uastParent of annotation. or seach for UMethods insdeaf of UAnnotations and then check for annotations