diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt index 6b1194f34c3..b79b05e5acc 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt @@ -18,17 +18,17 @@ package org.jetbrains.uast.kotlin import com.intellij.psi.* import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClassForScript import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.toLightMethods import org.jetbrains.kotlin.load.java.JvmAbi -import org.jetbrains.kotlin.psi.KtEnumEntry -import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.* import org.jetbrains.uast.* import org.jetbrains.uast.java.AbstractJavaUClass import org.jetbrains.uast.kotlin.declarations.KotlinUMethod import org.jetbrains.uast.kotlin.declarations.UastLightIdentifier -class KotlinUClass private constructor( +open class KotlinUClass private constructor( psi: KtLightClass, override val uastParent: UElement? ) : AbstractJavaUClass(), PsiClass by psi { @@ -37,9 +37,7 @@ class KotlinUClass private constructor( override val psi = unwrap(psi) - override fun getOriginalElement(): PsiElement? { - return super.getOriginalElement() - } + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() override fun getNameIdentifier() = UastLightIdentifier(psi, ktClass) @@ -67,34 +65,12 @@ class KotlinUClass private constructor( override fun getMethods(): Array { val primaryConstructor = ktClass?.primaryConstructor?.toLightMethods()?.firstOrNull() - val initBlocks = ktClass?.getAnonymousInitializers() ?: emptyList() fun createUMethod(psiMethod: PsiMethod): UMethod { - return if (psiMethod is KtLightMethod && psiMethod.isConstructor && initBlocks.isNotEmpty() - && (primaryConstructor == null || psiMethod == primaryConstructor)) { - object : KotlinUMethod(psiMethod, this@KotlinUClass) { - override val uastBody by lz { - val initializers = ktClass?.getAnonymousInitializers() ?: return@lz UastEmptyExpression - val containingMethod = this - - object : UBlockExpression { - override val psi: PsiElement? - get() = null - - override val uastParent: UElement? - get() = containingMethod - - override val annotations: List - get() = emptyList() - - override val expressions by lz { - initializers.map { - getLanguagePlugin().convertOpt(it.body, this) ?: UastEmptyExpression - } - } - } - } - } + return if (psiMethod is KtLightMethod && + psiMethod.isConstructor && + (primaryConstructor == null || psiMethod == primaryConstructor)) { + KotlinPrimaryConstructorUMethod(ktClass, psiMethod, this) } else { getLanguagePlugin().convert(psiMethod, this) } @@ -112,11 +88,22 @@ class KotlinUClass private constructor( private fun PsiClass.isEnumEntryLightClass() = (this as? KtLightClass)?.kotlinOrigin is KtEnumEntry companion object { - fun create(psi: KtLightClass, containingElement: UElement?): UClass { - return if (psi is PsiAnonymousClass) - KotlinUAnonymousClass(psi, containingElement) - else - KotlinUClass(psi, containingElement) + fun create(psi: KtLightClass, containingElement: UElement?): UClass = when (psi) { + is PsiAnonymousClass -> KotlinUAnonymousClass(psi, containingElement) + is KtLightClassForScript -> KotlinScriptUClass(psi, containingElement) + else -> KotlinUClass(psi, containingElement) + } + } + + class KotlinPrimaryConstructorUMethod( + private val ktClass: KtClassOrObject?, + override val psi: KtLightMethod, + override val uastParent: UElement? + ): KotlinUMethod(psi, uastParent) { + override val uastBody: UExpression? by lz { + ktClass?.getAnonymousInitializers() + ?.takeIf { it.isNotEmpty() } + ?.let { KotlinUBlockExpression.create(it, this) } } } } @@ -128,9 +115,7 @@ class KotlinUAnonymousClass( override val psi: PsiAnonymousClass = unwrap(psi) - override fun getOriginalElement(): PsiElement? { - return super.getOriginalElement() - } + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() override fun getSuperClass(): UClass? = super.getSuperClass() override fun getFields(): Array = super.getFields() @@ -143,4 +128,44 @@ class KotlinUAnonymousClass( val ktClassOrObject = (psi.originalElement as? KtLightClass)?.kotlinOrigin as? KtObjectDeclaration ?: return null return UIdentifier(ktClassOrObject.getObjectKeyword(), this) } +} + +class KotlinScriptUClass( + psi: KtLightClassForScript, + override val uastParent: UElement? +) : AbstractJavaUClass(), PsiClass by psi { + + override val psi = unwrap(psi) + + override fun getSuperClass(): UClass? = super.getSuperClass() + + override fun getFields(): Array = super.getFields() + + override fun getInitializers(): Array = super.getInitializers() + + override fun getInnerClasses(): Array = super.getInnerClasses() + + override fun getMethods(): Array = psi.methods.map(this::createUMethod).toTypedArray() + + private fun createUMethod(method: PsiMethod): UMethod { + return if (method.isConstructor) { + KotlinScriptConstructorUMethod(psi.script, method as KtLightMethod, this) + } + else { + getLanguagePlugin().convert(method, this) + } + } + + override fun getOriginalElement(): PsiElement? = psi.originalElement + + class KotlinScriptConstructorUMethod( + script: KtScript, + override val psi: KtLightMethod, + override val uastParent: UElement? + ) : KotlinUMethod(psi, uastParent) { + override val uastBody: UExpression? by lz { + val initializers = script.declarations.filterIsInstance() + KotlinUBlockExpression.create(initializers, this) + } + } } \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUFile.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUFile.kt index 527fbb7f382..751ae25f973 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUFile.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUFile.kt @@ -46,14 +46,11 @@ class KotlinUFile(override val psi: KtFile, override val languagePlugin: UastLan override val imports by lz { psi.importDirectives.map { KotlinUImportStatement(it, this) } } override val classes by lz { - fun PsiClass.toUClass() = languagePlugin.convert(this, this@KotlinUFile) + val facadeOrScriptClass = if (psi.isScript()) psi.script?.toLightClass() else psi.findFacadeClass() + val classes = psi.declarations.mapNotNull { (it as? KtClassOrObject)?.toLightClass()?.toUClass() } - val classes = psi.findFacadeClass()?.let { mutableListOf(it.toUClass()) } ?: mutableListOf() - - for (declaration in psi.declarations) { - (declaration as? KtClassOrObject)?.toLightClass()?.let { classes += it.toUClass() } - } - - return@lz classes + (facadeOrScriptClass?.let { listOf(it.toUClass()) } ?: emptyList()) + classes } + + private fun PsiClass.toUClass() = languagePlugin.convert(this, this@KotlinUFile) } \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUBlockExpression.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUBlockExpression.kt index e79df1dc193..2c39e399174 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUBlockExpression.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUBlockExpression.kt @@ -16,13 +16,33 @@ package org.jetbrains.uast.kotlin +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.KtAnonymousInitializer import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.uast.UBlockExpression -import org.jetbrains.uast.UElement +import org.jetbrains.uast.* class KotlinUBlockExpression( override val psi: KtBlockExpression, override val uastParent: UElement? ) : KotlinAbstractUExpression(), UBlockExpression, KotlinUElementWithType { + override val expressions by lz { psi.statements.map { KotlinConverter.convertOrEmpty(it, this) } } + + private class KotlinLazyUBlockExpression( + override val uastParent: UElement?, + expressionProducer: (expressionParent: UElement?) -> List + ) : UBlockExpression { + override val psi: PsiElement? = null + override val annotations: List = emptyList() + override val expressions by lz { expressionProducer(this) } + } + + companion object { + fun create(initializers: List, uastParent: UElement): UBlockExpression { + val languagePlugin = uastParent.getLanguagePlugin() + return KotlinLazyUBlockExpression(uastParent) { expressionParent -> + initializers.map { languagePlugin.convertOpt(it.body, expressionParent) ?: UastEmptyExpression } + } + } + } } \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/InnerClasses.kt b/plugins/uast-kotlin/testData/InnerClasses.kt new file mode 100644 index 00000000000..1c4bfe525fa --- /dev/null +++ b/plugins/uast-kotlin/testData/InnerClasses.kt @@ -0,0 +1,8 @@ +class Foo { + class Bar(val a: Int, val b: Int) { + fun getAPlusB() = a + b + class Baz { + fun doNothing() = Unit + } + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/InnerClasses.log.txt b/plugins/uast-kotlin/testData/InnerClasses.log.txt new file mode 100644 index 00000000000..1c97aa71f89 --- /dev/null +++ b/plugins/uast-kotlin/testData/InnerClasses.log.txt @@ -0,0 +1,23 @@ +UFile (package = ) + UClass (name = Foo) + UAnnotationMethod (name = Foo) + UClass (name = Bar) + UField (name = a) + UAnnotation (fqName = null) + UField (name = b) + UAnnotation (fqName = null) + UAnnotationMethod (name = getAPlusB) + UBinaryExpression (operator = +) + USimpleNameReferenceExpression (identifier = a) + USimpleNameReferenceExpression (identifier = b) + UAnnotationMethod (name = getA) + UAnnotationMethod (name = getB) + UAnnotationMethod (name = Bar) + UParameter (name = a) + UAnnotation (fqName = null) + UParameter (name = b) + UAnnotation (fqName = null) + UClass (name = Baz) + UAnnotationMethod (name = doNothing) + USimpleNameReferenceExpression (identifier = Unit) + UAnnotationMethod (name = Baz) diff --git a/plugins/uast-kotlin/testData/InnerClasses.render.txt b/plugins/uast-kotlin/testData/InnerClasses.render.txt new file mode 100644 index 00000000000..b8f5db1b0e2 --- /dev/null +++ b/plugins/uast-kotlin/testData/InnerClasses.render.txt @@ -0,0 +1,15 @@ +public final class Foo { + public fun Foo() = UastEmptyExpression + public static final class Bar { + private final var a: int + private final var b: int + public final fun getAPlusB() : int = a + b + public final fun getA() : int = UastEmptyExpression + public final fun getB() : int = UastEmptyExpression + public fun Bar(a: int, b: int) = UastEmptyExpression + public static final class Baz { + public final fun doNothing() : void = Unit + public fun Baz() = UastEmptyExpression + } + } +} diff --git a/plugins/uast-kotlin/testData/SimpleScript.kts b/plugins/uast-kotlin/testData/SimpleScript.kts new file mode 100644 index 00000000000..820bd87cd13 --- /dev/null +++ b/plugins/uast-kotlin/testData/SimpleScript.kts @@ -0,0 +1,20 @@ +println("Hello World!") + +fun getBarOrNull(flag: Boolean): Bar? { + return if (flag) Bar(42) else null +} + +class Bar(val a: Int) { + val b: Int = 0 + + fun getAPlusB() = a + b + + class Baz { + fun doSomething() { + + } + } +} + +getBarOrNull(true) +println("Goodbye World!") diff --git a/plugins/uast-kotlin/testData/SimpleScript.log.txt b/plugins/uast-kotlin/testData/SimpleScript.log.txt new file mode 100644 index 00000000000..c58b3aab5d2 --- /dev/null +++ b/plugins/uast-kotlin/testData/SimpleScript.log.txt @@ -0,0 +1,49 @@ +UFile (package = ) + UClass (name = SimpleScript) + UAnnotationMethod (name = getBarOrNull) + UParameter (name = flag) + UAnnotation (fqName = null) + UBlockExpression + UReturnExpression + UIfExpression + USimpleNameReferenceExpression (identifier = flag) + UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 1)) + UIdentifier (Identifier (Bar)) + USimpleNameReferenceExpression (identifier = ) + ULiteralExpression (value = 42) + ULiteralExpression (value = null) + UAnnotationMethod (name = SimpleScript) + UParameter (name = p) + UAnnotation (fqName = null) + UBlockExpression + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1)) + UIdentifier (Identifier (println)) + USimpleNameReferenceExpression (identifier = println) + ULiteralExpression (value = "Hello World!") + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1)) + UIdentifier (Identifier (getBarOrNull)) + USimpleNameReferenceExpression (identifier = getBarOrNull) + ULiteralExpression (value = true) + UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1)) + UIdentifier (Identifier (println)) + USimpleNameReferenceExpression (identifier = println) + ULiteralExpression (value = "Goodbye World!") + UClass (name = Bar) + UField (name = b) + UAnnotation (fqName = null) + ULiteralExpression (value = 0) + UField (name = a) + UAnnotation (fqName = null) + UAnnotationMethod (name = getB) + UAnnotationMethod (name = getAPlusB) + UBinaryExpression (operator = +) + USimpleNameReferenceExpression (identifier = a) + USimpleNameReferenceExpression (identifier = b) + UAnnotationMethod (name = getA) + UAnnotationMethod (name = Bar) + UParameter (name = a) + UAnnotation (fqName = null) + UClass (name = Baz) + UAnnotationMethod (name = doSomething) + UBlockExpression + UAnnotationMethod (name = Baz) diff --git a/plugins/uast-kotlin/testData/SimpleScript.render.txt b/plugins/uast-kotlin/testData/SimpleScript.render.txt new file mode 100644 index 00000000000..768b4198419 --- /dev/null +++ b/plugins/uast-kotlin/testData/SimpleScript.render.txt @@ -0,0 +1,23 @@ +public class SimpleScript : kotlin.script.templates.standard.ScriptTemplateWithArgs { + public final fun getBarOrNull(flag: boolean) : SimpleScript.Bar { + return if (flag) (42) else null + } + public fun SimpleScript(p: java.lang.String[]) { + println("Hello World!") + getBarOrNull(true) + println("Goodbye World!") + } + public static final class Bar { + private final var b: int = 0 + private final var a: int + public final fun getB() : int = UastEmptyExpression + public final fun getAPlusB() : int = a + b + public final fun getA() : int = UastEmptyExpression + public fun Bar(a: int) = UastEmptyExpression + public static final class Baz { + public final fun doSomething() : void { + } + public fun Baz() = UastEmptyExpression + } + } +} diff --git a/plugins/uast-kotlin/tests/AbstractKotlinUastTest.kt b/plugins/uast-kotlin/tests/AbstractKotlinUastTest.kt index 227aebb5367..a67de181ec0 100644 --- a/plugins/uast-kotlin/tests/AbstractKotlinUastTest.kt +++ b/plugins/uast-kotlin/tests/AbstractKotlinUastTest.kt @@ -19,6 +19,8 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider +import org.jetbrains.kotlin.script.StandardScriptDefinition import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TestJdkKind @@ -43,13 +45,14 @@ abstract class AbstractKotlinUastTest : AbstractUastTest() { private var kotlinCoreEnvironment: KotlinCoreEnvironment? = null override fun getVirtualFile(testName: String): VirtualFile { - val projectDir = TEST_KOTLIN_MODEL_DIR - val testFile = File(TEST_KOTLIN_MODEL_DIR, testName.substringBefore('/') + ".kt") + val testFile = TEST_KOTLIN_MODEL_DIR.listFiles { pathname -> pathname.nameWithoutExtension == testName }.first() super.initializeEnvironment(testFile) initializeKotlinEnvironment() + KotlinScriptDefinitionProvider.getInstance(project)?.addScriptDefinition(StandardScriptDefinition) + val trace = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace() val environment = kotlinCoreEnvironment!! @@ -60,7 +63,7 @@ abstract class AbstractKotlinUastTest : AbstractUastTest() { val vfs = VirtualFileManager.getInstance().getFileSystem(URLUtil.FILE_PROTOCOL) val ideaProject = project - ideaProject.baseDir = vfs.findFileByPath(projectDir.canonicalPath) + ideaProject.baseDir = vfs.findFileByPath(TEST_KOTLIN_MODEL_DIR.canonicalPath) return vfs.findFileByPath(testFile.canonicalPath)!! } diff --git a/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt b/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt index a7512fddab7..a2aee061352 100644 --- a/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt +++ b/plugins/uast-kotlin/tests/SimpleKotlinRenderLogTest.kt @@ -30,4 +30,8 @@ class SimpleKotlinRenderLogTest : AbstractKotlinRenderLogTest() { @Test fun testPropertyDelegate() = doTest("PropertyDelegate") @Test fun testPropertyWithAnnotation() = doTest("PropertyWithAnnotation") + + @Test fun testInnerClasses() = doTest("InnerClasses") + + @Test fun testSimpleScript() = doTest("SimpleScript") } \ No newline at end of file