diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 index 22c2d82dfc0..a78101c95bc 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 @@ -23,10 +23,7 @@ import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.KtCallElement -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -40,7 +37,7 @@ class KotlinUFunctionCallExpression( override val psi: KtCallElement, givenParent: UElement?, private val _resolvedCall: ResolvedCall<*>? -) : KotlinAbstractUExpression(givenParent), UCallExpression, KotlinUElementWithType { +) : KotlinAbstractUExpression(givenParent), UCallExpressionEx, KotlinUElementWithType { companion object { fun resolveSource(descriptor: DeclarationDescriptor, source: PsiElement?): PsiMethod? { if (descriptor is ConstructorDescriptor && descriptor.isPrimary @@ -85,6 +82,29 @@ class KotlinUFunctionCallExpression( override val valueArguments by lz { psi.valueArguments.map { KotlinConverter.convertOrEmpty(it.getArgumentExpression(), this) } } + override fun getArgumentForParameter(i: Int): UExpression? { + val resolvedCall = resolvedCall ?: return null + val actualParamIndex = if (resolvedCall.extensionReceiver == null) i else i - 1 + if (actualParamIndex == -1) return receiver + val (parameter, resolvedArgument) = resolvedCall.valueArguments.entries.find { it.key.index == actualParamIndex } ?: return null + val arguments = resolvedArgument.arguments + if (arguments.isEmpty()) return null + if (arguments.size == 1) { + val argument = arguments.single() + val expression = argument.getArgumentExpression() + if (parameter.varargElementType != null && argument.getSpreadElement() == null) { + return createVarargsHolder(arguments, this) + } + return KotlinConverter.convertOrEmpty(expression, this) + } + return createVarargsHolder(arguments, this) + } + + private fun createVarargsHolder(arguments: List, parent: UElement?): KotlinUExpressionList = + KotlinUExpressionList(null, VARARGS, parent).apply { + expressions = arguments.map { KotlinConverter.convertOrEmpty(it.getArgumentExpression(), parent) } + } + override val typeArgumentCount: Int get() = psi.typeArguments.size @@ -139,4 +159,7 @@ class KotlinUFunctionCallExpression( } } -} \ No newline at end of file +} + +@Deprecated("will be replaced by one from uast api when it comes") +val VARARGS = UastSpecialExpressionKind("varargs") \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUNamedExpression.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUNamedExpression.kt.181 new file mode 100644 index 00000000000..273bdf3b6cb --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUNamedExpression.kt.181 @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 org.jetbrains.uast.kotlin + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiType +import org.jetbrains.kotlin.psi.ValueArgument +import org.jetbrains.uast.* + +class KotlinUNamedExpression private constructor( + override val name: String?, + override val sourcePsi: PsiElement?, + givenParent: UElement?, + expressionProducer: (UElement) -> UExpression +) : KotlinAbstractUElement(givenParent), UNamedExpression { + + override val expression: UExpression by lz { expressionProducer(this) } + + override val annotations: List = emptyList() + + override val psi: PsiElement? = null + + override val javaPsi: PsiElement? = null + + companion object { + internal fun create(name: String?, valueArgument: ValueArgument, uastParent: UElement?): UNamedExpression { + val expression = valueArgument.getArgumentExpression() + return KotlinUNamedExpression(name, valueArgument.asElement(), uastParent) { expressionParent -> + expression?.let { expressionParent.getLanguagePlugin().convert(it, expressionParent) } ?: UastEmptyExpression + } + } + + internal fun create( + name: String?, + valueArguments: List, + uastParent: UElement?): UNamedExpression { + return KotlinUNamedExpression(name, null, uastParent) { expressionParent -> + KotlinUVarargExpression(valueArguments, expressionParent) + } + } + } +} +class KotlinUVarargExpression(private val valueArgs: List, + uastParent: UElement? +) : KotlinAbstractUExpression(uastParent), UCallExpressionEx { + override val kind: UastCallKind = UastCallKind.NESTED_ARRAY_INITIALIZER + + override val valueArguments: List by lz { + valueArgs.map { + it.getArgumentExpression()?.let { argumentExpression -> + getLanguagePlugin().convert(argumentExpression, this) + } ?: UastEmptyExpression + } + } + + override fun getArgumentForParameter(i: Int): UExpression? = valueArguments.getOrNull(i) + + override val valueArgumentCount: Int + get() = valueArgs.size + + override val psi: PsiElement? + get() = null + + override val methodIdentifier: UIdentifier? + get() = null + + override val classReference: UReferenceExpression? + get() = null + + override val methodName: String? + get() = null + + override val typeArgumentCount: Int + get() = 0 + + override val typeArguments: List + get() = emptyList() + + override val returnType: PsiType? + get() = null + + override fun resolve() = null + + override val receiver: UExpression? + get() = null + + override val receiverType: PsiType? + get() = null +} diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUSimpleReferenceExpression.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUSimpleReferenceExpression.kt.181 index f767c09c2a4..662858a2c38 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUSimpleReferenceExpression.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUSimpleReferenceExpression.kt.181 @@ -102,7 +102,7 @@ open class KotlinUSimpleReferenceExpression( private val resolvedCall: ResolvedCall<*>, private val accessorDescriptor: DeclarationDescriptor, val setterValue: KtExpression? - ) : UCallExpression, JvmDeclarationUElement { + ) : UCallExpressionEx, JvmDeclarationUElement { override val methodName: String? get() = accessorDescriptor.name.asString() @@ -142,6 +142,8 @@ open class KotlinUSimpleReferenceExpression( emptyList() } + override fun getArgumentForParameter(i: Int): UExpression? = valueArguments.getOrNull(i) + override val typeArgumentCount: Int get() = resolvedCall.typeArguments.size diff --git a/plugins/uast-kotlin/testData/ParametersDisorder.kt.181 b/plugins/uast-kotlin/testData/ParametersDisorder.kt.181 new file mode 100644 index 00000000000..70989d01e67 --- /dev/null +++ b/plugins/uast-kotlin/testData/ParametersDisorder.kt.181 @@ -0,0 +1,28 @@ +fun global(a: Int, b: Float) {} + +fun withDefault(c: Int = 1, d: String = "aaa") {} + +fun String.withReceiver(a: Int, b: Float) {} + +fun call() { + global(b = 2.2F, a = 2) + withDefault(d = "bbb") + "abc".withReceiver(1, 1.2F) + Math.atan2(1.3, 3.4) + unresolvedMethod("param1", "param2") + java.lang.String.format("%i %i %i", 1, 2, 3) + java.lang.String.format("%i %i %i", arrayOf(1, 2, 3)) + java.lang.String.format("%i %i %i", arrayOf(1, 2, 3), arrayOf(4, 5, 6)) + java.lang.String.format("%i %i %i", *"".chunked(2).toTypedArray()) + + with(A()) { + "def".with2Receivers(8, 7.0F) + } + +} + +class A { + + fun String.with2Receivers(a: Int, b: Float) {} + +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/ParametersDisorder.log.txt.181 b/plugins/uast-kotlin/testData/ParametersDisorder.log.txt.181 new file mode 100644 index 00000000000..07fc9a51a7f --- /dev/null +++ b/plugins/uast-kotlin/testData/ParametersDisorder.log.txt.181 @@ -0,0 +1,150 @@ +UFile (package = ) + UClass (name = ParametersDisorderKt) + UAnnotationMethod (name = global) + UParameter (name = a) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UParameter (name = b) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UBlockExpression + UAnnotationMethod (name = withDefault) + UParameter (name = c) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + ULiteralExpression (value = 1) + UParameter (name = d) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + ULiteralExpression (value = "aaa") + UBlockExpression + UAnnotationMethod (name = withReceiver) + UParameter (name = $receiver) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UParameter (name = a) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UParameter (name = b) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UBlockExpression + UAnnotationMethod (name = call) + UBlockExpression + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (global)) + USimpleNameReferenceExpression (identifier = global) + ULiteralExpression (value = 2.2) + ULiteralExpression (value = 2) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1)) + UIdentifier (Identifier (withDefault)) + USimpleNameReferenceExpression (identifier = withDefault) + ULiteralExpression (value = "bbb") + UQualifiedReferenceExpression + ULiteralExpression (value = "abc") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (withReceiver)) + USimpleNameReferenceExpression (identifier = withReceiver) + ULiteralExpression (value = 1) + ULiteralExpression (value = 1.2) + UQualifiedReferenceExpression + USimpleNameReferenceExpression (identifier = Math) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (atan2)) + USimpleNameReferenceExpression (identifier = atan2) + ULiteralExpression (value = 1.3) + ULiteralExpression (value = 3.4) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (unresolvedMethod)) + USimpleNameReferenceExpression (identifier = ) + ULiteralExpression (value = "param1") + ULiteralExpression (value = "param2") + UQualifiedReferenceExpression + UQualifiedReferenceExpression + UQualifiedReferenceExpression + USimpleNameReferenceExpression (identifier = java) + USimpleNameReferenceExpression (identifier = lang) + USimpleNameReferenceExpression (identifier = String) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 4)) + UIdentifier (Identifier (format)) + USimpleNameReferenceExpression (identifier = format) + ULiteralExpression (value = "%i %i %i") + ULiteralExpression (value = 1) + ULiteralExpression (value = 2) + ULiteralExpression (value = 3) + UQualifiedReferenceExpression + UQualifiedReferenceExpression + UQualifiedReferenceExpression + USimpleNameReferenceExpression (identifier = java) + USimpleNameReferenceExpression (identifier = lang) + USimpleNameReferenceExpression (identifier = String) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (format)) + USimpleNameReferenceExpression (identifier = format) + ULiteralExpression (value = "%i %i %i") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 3)) + UIdentifier (Identifier (arrayOf)) + USimpleNameReferenceExpression (identifier = arrayOf) + ULiteralExpression (value = 1) + ULiteralExpression (value = 2) + ULiteralExpression (value = 3) + UQualifiedReferenceExpression + UQualifiedReferenceExpression + UQualifiedReferenceExpression + USimpleNameReferenceExpression (identifier = java) + USimpleNameReferenceExpression (identifier = lang) + USimpleNameReferenceExpression (identifier = String) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 3)) + UIdentifier (Identifier (format)) + USimpleNameReferenceExpression (identifier = format) + ULiteralExpression (value = "%i %i %i") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 3)) + UIdentifier (Identifier (arrayOf)) + USimpleNameReferenceExpression (identifier = arrayOf) + ULiteralExpression (value = 1) + ULiteralExpression (value = 2) + ULiteralExpression (value = 3) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 3)) + UIdentifier (Identifier (arrayOf)) + USimpleNameReferenceExpression (identifier = arrayOf) + ULiteralExpression (value = 4) + ULiteralExpression (value = 5) + ULiteralExpression (value = 6) + UQualifiedReferenceExpression + UQualifiedReferenceExpression + UQualifiedReferenceExpression + USimpleNameReferenceExpression (identifier = java) + USimpleNameReferenceExpression (identifier = lang) + USimpleNameReferenceExpression (identifier = String) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (format)) + USimpleNameReferenceExpression (identifier = format) + ULiteralExpression (value = "%i %i %i") + UQualifiedReferenceExpression + UQualifiedReferenceExpression + ULiteralExpression (value = "") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1)) + UIdentifier (Identifier (chunked)) + USimpleNameReferenceExpression (identifier = chunked) + ULiteralExpression (value = 2) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0)) + UIdentifier (Identifier (toTypedArray)) + USimpleNameReferenceExpression (identifier = toTypedArray) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (with)) + USimpleNameReferenceExpression (identifier = with) + UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0)) + UIdentifier (Identifier (A)) + USimpleNameReferenceExpression (identifier = ) + ULambdaExpression + UBlockExpression + UQualifiedReferenceExpression + ULiteralExpression (value = "def") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2)) + UIdentifier (Identifier (with2Receivers)) + USimpleNameReferenceExpression (identifier = with2Receivers) + ULiteralExpression (value = 8) + ULiteralExpression (value = 7.0) + UClass (name = A) + UAnnotationMethod (name = with2Receivers) + UParameter (name = $receiver) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UParameter (name = a) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UParameter (name = b) + UAnnotation (fqName = org.jetbrains.annotations.NotNull) + UBlockExpression + UAnnotationMethod (name = A) diff --git a/plugins/uast-kotlin/testData/ParametersDisorder.render.txt.181 b/plugins/uast-kotlin/testData/ParametersDisorder.render.txt.181 new file mode 100644 index 00000000000..46112aa34ce --- /dev/null +++ b/plugins/uast-kotlin/testData/ParametersDisorder.render.txt.181 @@ -0,0 +1,28 @@ +public final class ParametersDisorderKt { + public static final fun global(@org.jetbrains.annotations.NotNull a: int, @org.jetbrains.annotations.NotNull b: float) : void { + } + public static final fun withDefault(@org.jetbrains.annotations.NotNull c: int, @org.jetbrains.annotations.NotNull d: java.lang.String) : void { + } + public static final fun withReceiver(@org.jetbrains.annotations.NotNull $receiver: java.lang.String, @org.jetbrains.annotations.NotNull a: int, @org.jetbrains.annotations.NotNull b: float) : void { + } + public static final fun call() : void { + global(2.2, 2) + withDefault("bbb") + "abc".withReceiver(1, 1.2) + Math.atan2(1.3, 3.4) + ("param1", "param2") + java.lang.String.format("%i %i %i", 1, 2, 3) + java.lang.String.format("%i %i %i", arrayOf(1, 2, 3)) + java.lang.String.format("%i %i %i", arrayOf(1, 2, 3), arrayOf(4, 5, 6)) + java.lang.String.format("%i %i %i", "".chunked(2).toTypedArray()) + with((), { + "def".with2Receivers(8, 7.0) + }) + } +} + +public final class A { + public final fun with2Receivers(@org.jetbrains.annotations.NotNull $receiver: java.lang.String, @org.jetbrains.annotations.NotNull a: int, @org.jetbrains.annotations.NotNull b: float) : void { + } + public fun A() = UastEmptyExpression +} diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 new file mode 100644 index 00000000000..01425546af2 --- /dev/null +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 @@ -0,0 +1,344 @@ +package org.jetbrains.uast.test.kotlin + +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiModifier +import com.intellij.testFramework.UsefulTestCase +import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList +import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.uast.* +import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin +import org.jetbrains.uast.test.env.findElementByText +import org.jetbrains.uast.test.env.findElementByTextFromPsi +import org.jetbrains.uast.visitor.AbstractUastVisitor +import org.junit.Assert +import org.junit.Test + + +class KotlinUastApiTest : AbstractKotlinUastTest() { + override fun check(testName: String, file: UFile) { + } + + @Test fun testAnnotationParameters() { + doTest("AnnotationParameters") { _, file -> + val annotation = file.findElementByText("@IntRange(from = 10, to = 0)") + assertEquals(annotation.findAttributeValue("from")?.evaluate(), 10) + val toAttribute = annotation.findAttributeValue("to")!! + assertEquals(toAttribute.evaluate(), 0) + KtUsefulTestCase.assertInstanceOf(annotation.psi.toUElement(), UAnnotation::class.java) + KtUsefulTestCase.assertInstanceOf( + annotation.psi.cast().toLightAnnotation().toUElement(), + UAnnotation::class.java + ) + KtUsefulTestCase.assertInstanceOf(toAttribute.uastParent, UNamedExpression::class.java) + KtUsefulTestCase.assertInstanceOf(toAttribute.psi.toUElement()?.uastParent, UNamedExpression::class.java) + } + } + + @Test fun testConvertStringTemplate() { + doTest("StringTemplateInClass") { _, file -> + val literalExpression = file.findElementByText("lorem") + val psi = literalExpression.psi!! + Assert.assertTrue(psi is KtLiteralStringTemplateEntry) + val literalExpressionAgain = psi.toUElement() + Assert.assertTrue(literalExpressionAgain is ULiteralExpression) + + } + } + + @Test fun testConvertStringTemplateWithExpectedType() { + doTest("StringTemplateWithVar") { _, file -> + val index = file.psi.text.indexOf("foo") + val stringTemplate = file.psi.findElementAt(index)!!.getParentOfType(false) + val uLiteral = stringTemplate.toUElementOfType() + assertNull(uLiteral) + } + } + + @Test fun testNameContainingFile() { + doTest("NameContainingFile") { _, file -> + val foo = file.findElementByText("class Foo") + assertEquals(file.psi, foo.nameIdentifier!!.containingFile) + + val bar = file.findElementByText("fun bar() {}") + assertEquals(file.psi, bar.nameIdentifier!!.containingFile) + + val xyzzy = file.findElementByText("val xyzzy: Int = 0") + assertEquals(file.psi, xyzzy.nameIdentifier!!.containingFile) + } + } + + @Test fun testInterfaceMethodWithBody() { + doTest("DefaultImpls") { _, file -> + val bar = file.findElementByText("fun bar() = \"Hello!\"") + assertFalse(bar.containingFile.text!!, bar.psi.modifierList.hasExplicitModifier(PsiModifier.DEFAULT)) + assertTrue(bar.containingFile.text!!, bar.psi.modifierList.hasModifierProperty(PsiModifier.DEFAULT)) + } + } + + @Test fun testSAM() { + doTest("SAM") { _, file -> + assertNull(file.findElementByText("{ /* Not SAM */ }").functionalInterfaceType) + + assertEquals("java.lang.Runnable", + file.findElementByText("{/* Variable */}").functionalInterfaceType?.canonicalText) + + assertEquals("java.lang.Runnable", + file.findElementByText("{/* Assignment */}").functionalInterfaceType?.canonicalText) + + assertEquals("java.lang.Runnable", + file.findElementByText("{/* Type Cast */}").functionalInterfaceType?.canonicalText) + + assertEquals("java.lang.Runnable", + file.findElementByText("{/* Argument */}").functionalInterfaceType?.canonicalText) + + assertEquals("java.lang.Runnable", + file.findElementByText("{/* Return */}").functionalInterfaceType?.canonicalText) + } + } + + @Test fun testParameterPropertyWithAnnotation() { + doTest("ParameterPropertyWithAnnotation") { _, file -> + val test1 = file.classes.find { it.name == "Test1" }!! + + val constructor1 = test1.methods.find { it.name == "Test1" }!! + assertTrue(constructor1.uastParameters.first().annotations.any { it.qualifiedName == "MyAnnotation" }) + + val getter1 = test1.methods.find { it.name == "getBar" }!! + assertFalse(getter1.annotations.any { it.qualifiedName == "MyAnnotation" }) + + val setter1 = test1.methods.find { it.name == "setBar" }!! + assertFalse(setter1.annotations.any { it.qualifiedName == "MyAnnotation" }) + assertFalse(setter1.uastParameters.first().annotations.any { it.qualifiedName == "MyAnnotation" }) + + + val test2 = file.classes.find { it.name == "Test2" }!! + val constructor2 = test2.methods.find { it.name == "Test2" }!! + assertFalse(constructor2.uastParameters.first().annotations.any { it.qualifiedName?.startsWith("MyAnnotation") ?: false }) + + val getter2 = test2.methods.find { it.name == "getBar" }!! + getter2.annotations.single { it.qualifiedName == "MyAnnotation" } + + val setter2 = test2.methods.find { it.name == "setBar" }!! + setter2.annotations.single { it.qualifiedName == "MyAnnotation2" } + setter2.uastParameters.first().annotations.single { it.qualifiedName == "MyAnnotation3" } + + test2.fields.find { it.name == "bar" }!!.annotations.single { it.qualifiedName == "MyAnnotation5" } + } + } + + @Test fun testConvertTypeInAnnotation() { + doTest("TypeInAnnotation") { _, file -> + val index = file.psi.text.indexOf("Test") + val element = file.psi.findElementAt(index)!!.getParentOfType(false)!! + assertNotNull(element.getUastParentOfType(UAnnotation::class.java)) + } + } + + @Test fun testElvisType() { + doTest("ElvisType") { _, file -> + val elvisExpression = file.findElementByText("text ?: return") + assertEquals("String", elvisExpression.getExpressionType()!!.presentableText) + } + } + + @Test fun testFindAttributeDefaultValue() { + doTest("AnnotationParameters") { _, file -> + val witDefaultValue = file.findElementByText("@WithDefaultValue") + assertEquals(42, witDefaultValue.findAttributeValue("value")!!.evaluate()) + assertEquals(42, witDefaultValue.findAttributeValue(null)!!.evaluate()) + } + } + + @Test fun testIfCondition() { + doTest("IfStatement") { _, file -> + val psiFile = file.psi + val element = psiFile.findElementAt(psiFile.text.indexOf("\"abc\""))!! + val binaryExpression = element.getParentOfType(false)!! + val uBinaryExpression = KotlinUastLanguagePlugin().convertElementWithParent(binaryExpression, null)!! + UsefulTestCase.assertInstanceOf(uBinaryExpression.uastParent, UIfExpression::class.java) + } + } + + @Test + fun testWhenStringLiteral() { + doTest("WhenStringLiteral") { _, file -> + + file.findElementByTextFromPsi("abc").let { literalExpression -> + val psi = literalExpression.psi!! + Assert.assertTrue(psi is KtLiteralStringTemplateEntry) + UsefulTestCase.assertInstanceOf(literalExpression.uastParent, USwitchClauseExpressionWithBody::class.java) + } + + file.findElementByTextFromPsi("def").let { literalExpression -> + val psi = literalExpression.psi!! + Assert.assertTrue(psi is KtLiteralStringTemplateEntry) + UsefulTestCase.assertInstanceOf(literalExpression.uastParent, USwitchClauseExpressionWithBody::class.java) + } + + file.findElementByTextFromPsi("def1").let { literalExpression -> + val psi = literalExpression.psi!! + Assert.assertTrue(psi is KtLiteralStringTemplateEntry) + UsefulTestCase.assertInstanceOf(literalExpression.uastParent, UBlockExpression::class.java) + } + + + } + } + + @Test + fun testWhenAndDestructing() { + doTest("WhenAndDestructing") { _, file -> + + file.findElementByTextFromPsi("val (bindingContext, statementFilter) = arr").let { e -> + val uBlockExpression = e.getParentOfType() + Assert.assertNotNull(uBlockExpression) + val uMethod = uBlockExpression!!.getParentOfType() + Assert.assertNotNull(uMethod) + } + + } + } + + @Test + fun testBrokenMethodTypeResolve() { + doTest("BrokenMethod") { _, file -> + + file.accept(object : AbstractUastVisitor() { + override fun visitCallExpression(node: UCallExpression): Boolean { + node.returnType + return false + } + }) + } + } + + @Test + fun testSimpleAnnotated() { + doTest("SimpleAnnotated") { _, file -> + file.findElementByTextFromPsi("@SinceKotlin(\"1.0\")\n val property: String = \"Mary\"").let { field -> + val annotation = field.annotations.assertedFind("kotlin.SinceKotlin") { it.qualifiedName } + Assert.assertEquals(annotation.findDeclaredAttributeValue("version")?.evaluateString(), "1.0") + } + } + } + + + fun UFile.checkUastSuperTypes(refText: String, superTypes: List) { + findElementByTextFromPsi(refText, false).let { + assertEquals("base classes", superTypes, it.uastSuperTypes.map { it.getQualifiedName() }) + } + } + + + @Test + fun testSuperTypes() { + doTest("SuperCalls") { _, file -> + file.checkUastSuperTypes("B", listOf("A")) + file.checkUastSuperTypes("O", listOf("A")) + file.checkUastSuperTypes("innerObject ", listOf("A")) + file.checkUastSuperTypes("InnerClass", listOf("A")) + file.checkUastSuperTypes("object : A(\"textForAnon\")", listOf("A")) + } + } + + @Test + fun testAnonymousSuperTypes() { + doTest("Anonymous") { _, file -> + file.checkUastSuperTypes("object : Runnable { override fun run() {} }", listOf("java.lang.Runnable")) + file.checkUastSuperTypes( + "object : Runnable, Closeable { override fun close() {} override fun run() {} }", + listOf("java.lang.Runnable", "java.io.Closeable") + ) + file.checkUastSuperTypes( + "object : InputStream(), Runnable { override fun read(): Int = 0; override fun run() {} }", + listOf("java.io.InputStream", "java.lang.Runnable") + ) + } + } + + @Test + fun testLiteralArraysTypes() { + doTest("AnnotationParameters") { _, file -> + file.findElementByTextFromPsi("intArrayOf(1, 2, 3)").let { field -> + Assert.assertEquals("PsiType:int[]", field.returnType.toString()) + } + file.findElementByTextFromPsi("[1, 2, 3]").let { field -> + Assert.assertEquals("PsiType:int[]", field.returnType.toString()) + Assert.assertEquals("PsiType:int", field.typeArguments.single().toString()) + } + file.findElementByTextFromPsi("[\"a\", \"b\", \"c\"]").let { field -> + Assert.assertEquals("PsiType:String[]", field.returnType.toString()) + Assert.assertEquals("PsiType:String", field.typeArguments.single().toString()) + } + + } + } + + @Test + fun testTypeAliases() { + doTest("TypeAliases") { _, file -> + val g = (file.psi as KtFile).declarations.single { it.name == "G" } as KtTypeAlias + val originalType = g.getTypeReference()!!.typeElement as KtFunctionType + val originalTypeParameters = originalType.parameterList.toUElement() as UDeclarationsExpression + Assert.assertTrue((originalTypeParameters.declarations.single() as UParameter).type.isValid) + } + } + + @Test + fun testNestedAnnotation() = doTest("AnnotationComplex") { _, file -> + file.findElementByTextFromPsi("@AnnotationArray(value = Annotation())") + .findElementByTextFromPsi("Annotation()") + .sourcePsiElement + .let { referenceExpression -> + val convertedUAnnotation = referenceExpression + .cast() + .toUElementOfType() + ?: throw AssertionError("haven't got annotation from $referenceExpression(${referenceExpression?.javaClass})") + + assertEquals("Annotation", convertedUAnnotation.qualifiedName) + val lightAnnotation = convertedUAnnotation.getAsJavaPsiElement(PsiAnnotation::class.java) + ?: throw AssertionError("can't get lightAnnotation from $convertedUAnnotation") + assertEquals("Annotation", lightAnnotation.qualifiedName) + } + } + + + @Test + fun testParametersDisorder() = doTest("ParametersDisorder") { _, file -> + + fun assertArguments(argumentsInPositionalOrder: List?, refText: String) = + file.findElementByTextFromPsi(refText).let { call -> + if (call !is UCallExpressionEx) throw AssertionError("${call.javaClass} is not a UCallExpressionEx") + Assert.assertEquals( + argumentsInPositionalOrder, + call.resolve()?.let { psiMethod -> + (0 until psiMethod.parameterList.parametersCount).map { + call.getArgumentForParameter(it)?.asRenderString() + } + } + ) + } + + + assertArguments(listOf("2", "2.2"), "global(b = 2.2F, a = 2)") + assertArguments(listOf(null, "\"bbb\""), "withDefault(d = \"bbb\")") + assertArguments(listOf("1.3", "3.4"), "atan2(1.3, 3.4)") + assertArguments(null, "unresolvedMethod(\"param1\", \"param2\")") + assertArguments(listOf("\"%i %i %i\"", "varargs 1 : 2 : 3"), "format(\"%i %i %i\", 1, 2, 3)") + assertArguments(listOf("\"%i %i %i\"", "varargs arrayOf(1, 2, 3)"), "format(\"%i %i %i\", arrayOf(1, 2, 3))") + assertArguments( + listOf("\"%i %i %i\"", "varargs arrayOf(1, 2, 3) : arrayOf(4, 5, 6)"), + "format(\"%i %i %i\", arrayOf(1, 2, 3), arrayOf(4, 5, 6))" + ) + assertArguments(listOf("\"%i %i %i\"", "\"\".chunked(2).toTypedArray()"), "format(\"%i %i %i\", *\"\".chunked(2).toTypedArray())") + assertArguments(listOf("\"def\"", "8", "7.0"), "with2Receivers(8, 7.0F)") + } + +} + +fun Iterable.assertedFind(value: R, transform: (T) -> R): T = + find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}") diff --git a/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt.181 b/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt.181 new file mode 100644 index 00000000000..e78fc232d01 --- /dev/null +++ b/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt.181 @@ -0,0 +1,84 @@ +package org.jetbrains.uast.test.kotlin + +import org.junit.Test + +class SimpleKotlinRenderLogTest : AbstractKotlinRenderLogTest() { + @Test fun testLocalDeclarations() = doTest("LocalDeclarations") + + @Test fun testSimple() = doTest("Simple") + + @Test fun testWhenIs() = doTest("WhenIs") + + @Test fun testDefaultImpls() = doTest("DefaultImpls") + + @Test fun testElvis() = doTest("Elvis") + + @Test fun testPropertyAccessors() = doTest("PropertyAccessors") + + @Test fun testPropertyInitializer() = doTest("PropertyInitializer") + + @Test fun testPropertyInitializerWithoutSetter() = doTest("PropertyInitializerWithoutSetter") + + @Test fun testAnnotationParameters() = doTest("AnnotationParameters") + + @Test fun testEnumValueMembers() = doTest("EnumValueMembers") + + @Test fun testStringTemplate() = doTest("StringTemplate") + + @Test fun testStringTemplateComplex() = doTest("StringTemplateComplex") + + @Test fun testQualifiedConstructorCall() = doTest("QualifiedConstructorCall") + + @Test fun testPropertyDelegate() = doTest("PropertyDelegate") { testName, file -> check(testName, file, false) } + + @Test fun testLocalVariableWithAnnotation() = doTest("LocalVariableWithAnnotation") + + @Test fun testPropertyWithAnnotation() = doTest("PropertyWithAnnotation") + + @Test fun testIfStatement() = doTest("IfStatement") + + @Test fun testInnerClasses() = doTest("InnerClasses") + + @Test fun testSimpleScript() = doTest("SimpleScript") { testName, file -> check(testName, file, false) } + + @Test fun testDestructuringDeclaration() = doTest("DestructuringDeclaration") + + @Test fun testDefaultParameterValues() = doTest("DefaultParameterValues") + + @Test fun testParameterPropertyWithAnnotation() = doTest("ParameterPropertyWithAnnotation") + + @Test fun testParametersWithDefaultValues() = doTest("ParametersWithDefaultValues") + + @Test + fun testUnexpectedContainer() = doTest("UnexpectedContainerException") + + @Test + fun testWhenStringLiteral() = doTest("WhenStringLiteral") + + @Test + fun testWhenAndDestructing() = doTest("WhenAndDestructing") { testName, file -> check(testName, file, false) } + + @Test + fun testSuperCalls() = doTest("SuperCalls") + + @Test + fun testConstructors() = doTest("Constructors") + + @Test + fun testClassAnnotation() = doTest("ClassAnnotation") + + @Test + fun testReceiverFun() = doTest("ReceiverFun") + + @Test + fun testAnonymous() = doTest("Anonymous") + + @Test + fun testAnnotationComplex() = doTest("AnnotationComplex") + + @Test + fun testParametersDisorder() = doTest("ParametersDisorder") { testName, file -> + // disabled due to inconsistent parents for 2-receivers call (KT-22344) + check(testName, file, false) + } +} diff --git a/plugins/uast-kotlin/tests/org/jetbrains/uast/test/env/AbstractUastTest.kt.181 b/plugins/uast-kotlin/tests/org/jetbrains/uast/test/env/AbstractUastTest.kt.181 new file mode 100644 index 00000000000..eec9d09d24c --- /dev/null +++ b/plugins/uast-kotlin/tests/org/jetbrains/uast/test/env/AbstractUastTest.kt.181 @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 org.jetbrains.uast.test.env + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UFile +import org.jetbrains.uast.toUElementOfType +import org.jetbrains.uast.visitor.UastVisitor +import java.io.File +import kotlin.test.fail + +abstract class AbstractUastTest : AbstractTestWithCoreEnvironment() { + protected companion object { + val TEST_DATA_DIR = File("testData") + } + + abstract fun getVirtualFile(testName: String): VirtualFile + abstract fun check(testName: String, file: UFile) + + fun doTest(testName: String, checkCallback: (String, UFile) -> Unit = { testName, file -> check(testName, file) }) { + val virtualFile = getVirtualFile(testName) + + val psiFile = psiManager.findFile(virtualFile) ?: error("Can't get psi file for $testName") + val uFile = uastContext.convertElementWithParent(psiFile, null) ?: error("Can't get UFile for $testName") + checkCallback(testName, uFile as UFile) + } +} + +fun UElement.findElementByText(refText: String, cls: Class): T { + val matchingElements = mutableListOf() + accept(object : UastVisitor { + override fun visitElement(node: UElement): Boolean { + if (cls.isInstance(node) && node.psi?.text == refText) { + matchingElements.add(node as T) + } + return false + } + }) + + if (matchingElements.isEmpty()) { + throw IllegalArgumentException("Reference '$refText' not found") + } + if (matchingElements.size != 1) { + throw IllegalArgumentException("Reference '$refText' is ambiguous") + } + return matchingElements.single() +} + +inline fun UElement.findElementByText(refText: String): T = findElementByText(refText, T::class.java) + +inline fun UElement.findElementByTextFromPsi(refText: String, strict: Boolean = true): T = + (this.psi ?: fail("no psi for $this")).findUElementByTextFromPsi(refText, strict) + +inline fun PsiElement.findUElementByTextFromPsi(refText: String, strict: Boolean = true): T { + val elementAtStart = this.findElementAt(this.text.indexOf(refText)) + ?: throw AssertionError("requested text '$refText' was not found in $this") + val uElementContainingText = elementAtStart.parentsWithSelf.let { + if (strict) it.dropWhile { !it.text.contains(refText) } else it + }.mapNotNull { it.toUElementOfType() }.firstOrNull() + ?: throw AssertionError("requested text '$refText' not found as '${T::class.java.canonicalName}' in $this") + if (strict && uElementContainingText.psi != null && uElementContainingText.psi?.text != refText) { + throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText") + } + return uElementContainingText; +}