blob: 1c5cf0109237e2995e5e0604909181d214a7966c [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.detector.api
import com.intellij.openapi.util.Disposer
import junit.framework.TestCase
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UFile
import org.jetbrains.uast.asRecursiveLogString
import org.jetbrains.uast.visitor.UastVisitor
import java.io.File
// Misc tests to verify type handling in the Kotlin UAST initialization.
class TypesTest : TestCase() {
fun testPrimitiveKotlinTypes() {
val pair = LintUtilsTest.parseKotlin(
"" +
"class Kotlin(val property1: String = \"Default Value\", arg2: Int) : Parent() {\n" +
" override fun method() = \"Hello World\"\n" +
" fun otherMethod(ok: Boolean, times: Int) {\n" +
" }\n" +
"\n" +
" var property2: String? = null\n" +
"\n" +
" private var someField = 42\n" +
" @JvmField\n" +
" var someField2 = 42\n" +
"}\n" +
"\n" +
"open class Parent {\n" +
" open fun method(): String? = null\n" +
" open fun method2(value: Boolean, value: Boolean?): String? = null\n" +
" open fun method3(value: Int?, value2: Int): Int = null\n" +
"}\n", File("src/test/pkg/Tor.kt")
)
val file = pair.first.uastFile
assertEquals(
"" +
"UFile (package = ) [public final class Kotlin : Parent {...]\n" +
" UClass (name = Kotlin) [public final class Kotlin : Parent {...}]\n" +
" UField (name = property2) [@org.jetbrains.annotations.Nullable private var property2: java.lang.String = null]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" ULiteralExpression (value = null) [null] : PsiType:Void\n" +
" UField (name = someField) [@org.jetbrains.annotations.NotNull private var someField: int = 42]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" ULiteralExpression (value = 42) [42] : PsiType:int\n" +
" UField (name = someField2) [@org.jetbrains.annotations.NotNull @kotlin.jvm.JvmField public var someField2: int = 42]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UAnnotation (fqName = kotlin.jvm.JvmField) [@kotlin.jvm.JvmField]\n" +
" ULiteralExpression (value = 42) [42] : PsiType:int\n" +
" UField (name = property1) [@org.jetbrains.annotations.NotNull private final var property1: java.lang.String = \"Default Value\"]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" ULiteralExpression (value = \"Default Value\") [\"Default Value\"] : PsiType:String\n" +
" UAnnotationMethod (name = method) [public fun method() : java.lang.String {...}]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return \"Hello World\"]\n" +
" ULiteralExpression (value = \"Hello World\") [\"Hello World\"] : PsiType:String\n" +
" UAnnotationMethod (name = otherMethod) [public final fun otherMethod(@org.jetbrains.annotations.NotNull ok: boolean, @org.jetbrains.annotations.NotNull times: int) : void {...}]\n" +
" UParameter (name = ok) [@org.jetbrains.annotations.NotNull var ok: boolean]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UParameter (name = times) [@org.jetbrains.annotations.NotNull var times: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UBlockExpression [{...}] : PsiType:void\n" +
" UAnnotationMethod (name = getProperty2) [public final fun getProperty2() : java.lang.String = UastEmptyExpression]\n" +
" UAnnotationMethod (name = setProperty2) [public final fun setProperty2(@org.jetbrains.annotations.Nullable p: java.lang.String) : void = UastEmptyExpression]\n" +
" UParameter (name = p) [@org.jetbrains.annotations.Nullable var p: java.lang.String]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" UAnnotationMethod (name = getProperty1) [public final fun getProperty1() : java.lang.String = UastEmptyExpression]\n" +
" ULiteralExpression (value = \"Default Value\") [\"Default Value\"] : PsiType:String\n" +
" UAnnotationMethod (name = Kotlin) [public fun Kotlin(@org.jetbrains.annotations.NotNull property1: java.lang.String, @org.jetbrains.annotations.NotNull arg2: int) {...}]\n" +
" UParameter (name = property1) [@org.jetbrains.annotations.NotNull var property1: java.lang.String = \"Default Value\"]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" ULiteralExpression (value = \"Default Value\") [\"Default Value\"] : PsiType:String\n" +
" UParameter (name = arg2) [@org.jetbrains.annotations.NotNull var arg2: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UBlockExpression [{...}]\n" +
" UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0)) [<init>()]\n" +
" UIdentifier (Identifier (Parent)) [UIdentifier (Identifier (Parent))]\n" +
" USimpleNameReferenceExpression (identifier = <init>) [<init>]\n" +
" UClass (name = Parent) [public class Parent {...}]\n" +
" UAnnotationMethod (name = method) [public fun method() : java.lang.String {...}]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return null]\n" +
" ULiteralExpression (value = null) [null] : PsiType:Void\n" +
" UAnnotationMethod (name = method2) [public fun method2(@org.jetbrains.annotations.NotNull value: boolean, @org.jetbrains.annotations.Nullable value: java.lang.Boolean) : java.lang.String {...}]\n" +
" UParameter (name = value) [@org.jetbrains.annotations.NotNull var value: boolean]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UParameter (name = value) [@org.jetbrains.annotations.Nullable var value: java.lang.Boolean]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return null]\n" +
" ULiteralExpression (value = null) [null] : PsiType:Void\n" +
" UAnnotationMethod (name = method3) [public fun method3(@org.jetbrains.annotations.Nullable value: java.lang.Integer, @org.jetbrains.annotations.NotNull value2: int) : int {...}]\n" +
" UParameter (name = value) [@org.jetbrains.annotations.Nullable var value: java.lang.Integer]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" UParameter (name = value2) [@org.jetbrains.annotations.NotNull var value2: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return null]\n" +
" ULiteralExpression (value = null) [null] : PsiType:Void\n" +
" UAnnotationMethod (name = Parent) [public fun Parent() = UastEmptyExpression]\n",
file?.asLogTypes()
)
assertEquals(
"" +
"UFile (package = )\n" +
" UClass (name = Kotlin)\n" +
" UField (name = property2)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable)\n" +
" ULiteralExpression (value = null)\n" +
" UField (name = someField)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" ULiteralExpression (value = 42)\n" +
" UField (name = someField2)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UAnnotation (fqName = kotlin.jvm.JvmField)\n" +
" ULiteralExpression (value = 42)\n" +
" UField (name = property1)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" ULiteralExpression (value = \"Default Value\")\n" +
" UAnnotationMethod (name = method)\n" +
" UBlockExpression\n" +
" UReturnExpression\n" +
" ULiteralExpression (value = \"Hello World\")\n" +
" UAnnotationMethod (name = otherMethod)\n" +
" UParameter (name = ok)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UParameter (name = times)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UBlockExpression\n" +
" UAnnotationMethod (name = getProperty2)\n" +
" UAnnotationMethod (name = setProperty2)\n" +
" UParameter (name = p)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable)\n" +
" UAnnotationMethod (name = getProperty1)\n" +
" ULiteralExpression (value = \"Default Value\")\n" +
" UAnnotationMethod (name = Kotlin)\n" +
" UParameter (name = property1)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" ULiteralExpression (value = \"Default Value\")\n" +
" UParameter (name = arg2)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UBlockExpression\n" +
" UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))\n" +
" UIdentifier (Identifier (Parent))\n" +
" USimpleNameReferenceExpression (identifier = <init>)\n" +
" UClass (name = Parent)\n" +
" UAnnotationMethod (name = method)\n" +
" UBlockExpression\n" +
" UReturnExpression\n" +
" ULiteralExpression (value = null)\n" +
" UAnnotationMethod (name = method2)\n" +
" UParameter (name = value)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UParameter (name = value)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable)\n" +
" UBlockExpression\n" +
" UReturnExpression\n" +
" ULiteralExpression (value = null)\n" +
" UAnnotationMethod (name = method3)\n" +
" UParameter (name = value)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable)\n" +
" UParameter (name = value2)\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull)\n" +
" UBlockExpression\n" +
" UReturnExpression\n" +
" ULiteralExpression (value = null)\n" +
" UAnnotationMethod (name = Parent)\n",
file?.asRecursiveLogString()?.replace("\r", "")
)
Disposer.dispose(pair.second)
}
fun testPrimitiveKotlinTypes2() {
val pair = LintUtilsTest.parseKotlin(
"" +
"package test.pkg\n" +
"\n" +
"fun calc(@java.lang.Override x: Int, y: Int?, z: String?): Int = x * 2",
File("src/test/pkg/test.kt")
)
val file = pair.first.uastFile
assertEquals(
"" +
"UFile (package = test.pkg) [package test.pkg...]\n" +
" UClass (name = TestKt) [public final class TestKt {...}]\n" +
" UAnnotationMethod (name = calc) [public static final fun calc(@org.jetbrains.annotations.NotNull @java.lang.Override x: int, @org.jetbrains.annotations.Nullable y: java.lang.Integer, @org.jetbrains.annotations.Nullable z: java.lang.String) : int {...}]\n" +
" UParameter (name = x) [@org.jetbrains.annotations.NotNull @java.lang.Override var x: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UAnnotation (fqName = java.lang.Override) [@java.lang.Override]\n" +
" UParameter (name = y) [@org.jetbrains.annotations.Nullable var y: java.lang.Integer]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" UParameter (name = z) [@org.jetbrains.annotations.Nullable var z: java.lang.String]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.Nullable) [@org.jetbrains.annotations.Nullable]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return x * 2]\n" +
" UBinaryExpression (operator = *) [x * 2] : PsiType:int\n" +
" USimpleNameReferenceExpression (identifier = x) [x] : PsiType:int\n" +
" ULiteralExpression (value = 2) [2] : PsiType:int\n",
file?.asLogTypes()
)
Disposer.dispose(pair.second)
}
fun testSecondaryConstructorBodies() {
// Regression test for https://youtrack.jetbrains.com/issue/KT-21575
val pair = LintUtilsTest.parseKotlin(
"" +
"class Foo() {\n" +
" constructor(number: Int) : this() {\n" +
" sideeffect(number)\n" +
" }\n" +
"}\n" +
"\n" +
"fun sideeffect(number: Int) {\n" +
" println(number)\n" +
"}\n" +
"\n" +
"\n" +
"fun main(args: Array<String>) {\n" +
" Foo()\n" +
" Foo(5)\n" +
"}", File("src/test/pkg/test.kt")
)
val file = pair.first.uastFile
assertEquals(
"" +
"public final class TestKt {\n" +
" public static final fun sideeffect(@org.jetbrains.annotations.NotNull number: int) : void {\n" +
" println(number)\n" +
" }\n" +
" public static final fun main(@org.jetbrains.annotations.NotNull args: java.lang.String[]) : void {\n" +
" <init>()\n" +
" <init>(5)\n" +
" }\n" +
"}\n" +
"\n" +
"public final class Foo {\n" +
" public fun Foo() = UastEmptyExpression\n" +
" public fun Foo(@org.jetbrains.annotations.NotNull number: int) {\n" +
" <init>()\n" +
" sideeffect(number)\n" +
" }\n" +
"}\n", file?.asRenderString()?.replace("\r", "")
)
Disposer.dispose(pair.second)
}
fun testPrimitiveKotlinTypes3() {
val pair = LintUtilsTest.parseKotlin(
"" +
"open class Parent(val number: Int) {\n" +
" fun test(): Int = 6" +
"}\n" +
"\n" +
"class Five : Parent(5)", File("src/test/pkg/test.kt")
)
val file = pair.first.uastFile
assertEquals(
"" +
"UFile (package = ) [public class Parent {...]\n" +
" UClass (name = Parent) [public class Parent {...}]\n" +
" UField (name = number) [@org.jetbrains.annotations.NotNull private final var number: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UAnnotationMethod (name = test) [public final fun test() : int {...}]\n" +
" UBlockExpression [{...}]\n" +
" UReturnExpression [return 6]\n" +
" ULiteralExpression (value = 6) [6] : PsiType:int\n" +
" UAnnotationMethod (name = getNumber) [public final fun getNumber() : int = UastEmptyExpression]\n" +
" UAnnotationMethod (name = Parent) [public fun Parent(@org.jetbrains.annotations.NotNull number: int) = UastEmptyExpression]\n" +
" UParameter (name = number) [@org.jetbrains.annotations.NotNull var number: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UClass (name = Five) [public final class Five : Parent {...}]\n" +
" UAnnotationMethod (name = Five) [public fun Five() {...}]\n" +
" UBlockExpression [{...}]\n" +
" UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 1)) [<init>(5)]\n" +
" UIdentifier (Identifier (Parent)) [UIdentifier (Identifier (Parent))]\n" +
" USimpleNameReferenceExpression (identifier = <init>) [<init>]\n" +
" ULiteralExpression (value = 5) [5] : PsiType:int\n",
file?.asLogTypes()
)
Disposer.dispose(pair.second)
}
fun testJavaEnums() {
// Regression test for https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-23456
val pair = LintUtilsTest.parse(
"" +
"package test.pkg;\n" +
"public enum JavaEnum {\n" +
" FOO(1), BAR(2), BAZ(3);\n" +
"\n" +
" JavaEnum(int arg) {\n" +
" }\n" +
"}", File("src/test/pkg/JavaEnum.java")
)
val file = pair.first.uastFile
assertEquals(
"" +
"UFile (package = test.pkg) [package test.pkg...]\n" +
" UClass (name = JavaEnum) [public final enum JavaEnum {...}]\n" +
" UEnumConstant (name = FOO) [FOO(1)]\n" +
" USimpleNameReferenceExpression (identifier = JavaEnum) [JavaEnum]\n" +
" ULiteralExpression (value = 1) [1] : PsiType:int\n" +
" UEnumConstant (name = BAR) [BAR(2)]\n" +
" USimpleNameReferenceExpression (identifier = JavaEnum) [JavaEnum]\n" +
" ULiteralExpression (value = 2) [2] : PsiType:int\n" +
" UEnumConstant (name = BAZ) [BAZ(3)]\n" +
" USimpleNameReferenceExpression (identifier = JavaEnum) [JavaEnum]\n" +
" ULiteralExpression (value = 3) [3] : PsiType:int\n" +
" UMethod (name = JavaEnum) [private fun JavaEnum(arg: int) {...}]\n" +
" UParameter (name = arg) [var arg: int]\n" +
" UBlockExpression [{...}]\n",
file?.asLogTypes()
)
Disposer.dispose(pair.second)
}
fun testKotlinEnums() {
// Regression test for https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-23456
val pair = LintUtilsTest.parseKotlin(
"" +
"package test.pkg\n" +
"enum class KotlinEnum(val resId: Int) {\n" +
" FOO(1), BAR(2), BAZ(3) \n" +
"}", File("src/test/pkg/KotlinEnum.kt")
)
val file = pair.first.uastFile
assertEquals(
"" +
"UFile (package = test.pkg) [package test.pkg...]\n" +
" UClass (name = KotlinEnum) [public enum KotlinEnum {...}]\n" +
" UEnumConstant (name = FOO) [@null FOO(1)]\n" +
" UAnnotation (fqName = null) [@null]\n" +
" USimpleNameReferenceExpression (identifier = KotlinEnum) [KotlinEnum]\n" +
" ULiteralExpression (value = 1) [1] : PsiType:int\n" +
" UEnumConstant (name = BAR) [@null BAR(2)]\n" +
" UAnnotation (fqName = null) [@null]\n" +
" USimpleNameReferenceExpression (identifier = KotlinEnum) [KotlinEnum]\n" +
" ULiteralExpression (value = 2) [2] : PsiType:int\n" +
" UEnumConstant (name = BAZ) [@null BAZ(3)]\n" +
" UAnnotation (fqName = null) [@null]\n" +
" USimpleNameReferenceExpression (identifier = KotlinEnum) [KotlinEnum]\n" +
" ULiteralExpression (value = 3) [3] : PsiType:int\n" +
" UField (name = resId) [@org.jetbrains.annotations.NotNull private final var resId: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n" +
" UAnnotationMethod (name = getResId) [public final fun getResId() : int = UastEmptyExpression]\n" +
" UAnnotationMethod (name = KotlinEnum) [protected fun KotlinEnum(@org.jetbrains.annotations.NotNull resId: int) = UastEmptyExpression]\n" +
" UParameter (name = resId) [@org.jetbrains.annotations.NotNull var resId: int]\n" +
" UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull]\n",
file?.asLogTypes()
)
Disposer.dispose(pair.second)
}
}
// From Kotlin's UAST unit test support, TypesTestBase
private fun UFile.asLogTypes() = TypesLogger().apply {
this@asLogTypes.accept(this)
}.toString()
class TypesLogger : UastVisitor {
val builder = StringBuilder()
var level = 0
override fun visitElement(node: UElement): Boolean {
val initialLine = node.asLogString() + " [" + run {
val renderString = node.asRenderString().lines()
if (renderString.size == 1) {
renderString.single()
} else {
renderString.first() + "..." + renderString.last()
}
} + "]"
(1..level).forEach { builder.append(" ") }
builder.append(initialLine)
if (node is UExpression) {
val value = node.getExpressionType()
value?.let { builder.append(" : ").append(it) }
}
builder.appendln()
level++
return false
}
override fun afterVisitElement(node: UElement) {
level--
}
override fun toString() = builder.toString().replace("\r", "")
}