From e625e4560b13e06f3475cf7987134c02210b164e Mon Sep 17 00:00:00 2001 From: Ivan Volkov <65076429+volivan239@users.noreply.github.com> Date: Tue, 1 Nov 2022 17:37:22 +0300 Subject: [PATCH] Refactor Class rendering in codegen (merge abstractions for regular and test classes) #1255 #1256 (#1275) * Refactor Class rendering in codegen (merge abstractions for regular and test classes) --- .../org/utbot/framework/plugin/api/Api.kt | 5 + .../framework/codegen/model/CodeGenerator.kt | 39 +++--- .../model/constructor/TestClassContext.kt | 4 +- .../constructor/builtin/UtilMethodBuiltins.kt | 1 + .../model/constructor/context/CgContext.kt | 6 +- .../tree/CgTestClassConstructor.kt | 21 +-- .../tree/CgUtilClassConstructor.kt | 29 +++-- .../framework/codegen/model/tree/Builders.kt | 52 +++----- .../framework/codegen/model/tree/CgElement.kt | 123 +++++++----------- .../model/visitor/CgAbstractRenderer.kt | 59 +++------ .../codegen/model/visitor/CgJavaRenderer.kt | 19 +-- .../codegen/model/visitor/CgKotlinRenderer.kt | 36 ++--- .../codegen/model/visitor/CgVisitor.kt | 31 ++--- .../generator/CodeGenerationController.kt | 6 +- 14 files changed, 178 insertions(+), 253 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index bdeffaae3e..0f2b8e2a11 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -27,6 +27,7 @@ import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.kClass import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull @@ -793,6 +794,9 @@ open class ClassId @JvmOverloads constructor( open val isSynthetic: Boolean get() = jClass.isSynthetic + open val isKotlinObject: Boolean + get() = kClass.objectInstance != null + /** * Collects all declared methods (including private and protected) from class and all its superclasses to sequence */ @@ -887,6 +891,7 @@ class BuiltinClassId( override val isInner: Boolean = false, override val isNested: Boolean = false, override val isSynthetic: Boolean = false, + override val isKotlinObject: Boolean = false, override val typeParameters: TypeParameters = TypeParameters(), override val allMethods: Sequence = emptySequence(), override val allConstructors: Sequence = emptySequence(), diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 865c43dff0..c79dc7b260 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -14,8 +14,7 @@ import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor import org.utbot.framework.codegen.model.constructor.tree.CgUtilClassConstructor import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport -import org.utbot.framework.codegen.model.tree.AbstractCgClassFile -import org.utbot.framework.codegen.model.tree.CgRegularClassFile +import org.utbot.framework.codegen.model.tree.CgClassFile import org.utbot.framework.codegen.model.visitor.CgAbstractRenderer import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage @@ -23,8 +22,8 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.codegen.model.constructor.TestClassModel -import org.utbot.framework.codegen.model.tree.CgComment -import org.utbot.framework.codegen.model.tree.CgSingleLineComment +import org.utbot.framework.codegen.model.tree.CgDocRegularStmt +import org.utbot.framework.codegen.model.tree.CgDocumentationComment class CodeGenerator( private val classUnderTest: ClassId, @@ -106,7 +105,7 @@ class CodeGenerator( } } - private fun renderClassFile(file: AbstractCgClassFile<*>): String { + private fun renderClassFile(file: CgClassFile): String { val renderer = CgAbstractRenderer.makeRenderer(context) file.accept(renderer) return renderer.toString() @@ -145,6 +144,17 @@ sealed class UtilClassKind( private val priority: Int ) : Comparable { + /** + * Contains comments specifying the version and the kind of util class being generated and + */ + val utilClassDocumentation: CgDocumentationComment + get() = CgDocumentationComment( + listOf( + CgDocRegularStmt(utilClassKindCommentText), + CgDocRegularStmt("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}"), + ) + ) + /** * The version of util class being generated. * For more details see [UtilClassFileMethodProvider.UTIL_CLASS_VERSION]. @@ -152,23 +162,6 @@ sealed class UtilClassKind( val utilClassVersion: String get() = UtilClassFileMethodProvider.UTIL_CLASS_VERSION - /** - * The comment specifying the version of util class being generated. - * - * @see UtilClassFileMethodProvider.UTIL_CLASS_VERSION - */ - val utilClassVersionComment: CgComment - get() = CgSingleLineComment("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}") - - - /** - * The comment specifying the kind of util class being generated. - * - * @see utilClassKindCommentText - */ - val utilClassKindComment: CgComment - get() = CgSingleLineComment(utilClassKindCommentText) - /** * The text of comment specifying the kind of util class. * At the moment, there are two kinds: [RegularUtUtils] (without Mockito) and [UtUtilsWithMockito]. @@ -200,7 +193,7 @@ sealed class UtilClassKind( } /** - * Construct an util class file as a [CgRegularClassFile] and render it. + * Construct an util class file as a [CgClassFile] and render it. * @return the text of the generated util class file. */ fun getUtilClassText(codegenLanguage: CodegenLanguage): String { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt index 4d6f764483..5cfaf2925c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt @@ -4,10 +4,10 @@ import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.tree.CgAnnotation import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.codegen.model.tree.CgTestClass +import org.utbot.framework.codegen.model.tree.CgClass /** - * This class stores context information needed to build [CgTestClass]. + * This class stores context information needed to build [CgClass]. * Should only be used in [CgContextOwner]. */ internal data class TestClassContext( diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt index ab5d94172b..68f970c336 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt @@ -280,6 +280,7 @@ internal val utUtilsClassId: ClassId canonicalName = "org.utbot.runtime.utils.UtUtils", simpleName = "UtUtils", isFinal = true, + isKotlinObject = true ) /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 4a2f433f36..4530f05df9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -495,7 +495,8 @@ internal data class CgContext( val name = "$packagePrefix$simpleName" BuiltinClassId( canonicalName = name, - simpleName = simpleName + simpleName = simpleName, + isFinal = true, ) } @@ -551,7 +552,8 @@ internal data class CgContext( val simpleName = "${testClassModel.classUnderTest.simpleName}Test" return BuiltinClassId( canonicalName = currentTestClass.canonicalName + "." + simpleName, - simpleName = simpleName + simpleName = simpleName, + isFinal = true, ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 5a341c3834..7f37cced6d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -19,20 +19,21 @@ import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructorImpl import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster +import org.utbot.framework.codegen.model.tree.CgMethodsCluster import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgRegion import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgStaticsRegion -import org.utbot.framework.codegen.model.tree.CgTestClass +import org.utbot.framework.codegen.model.tree.CgClass +import org.utbot.framework.codegen.model.tree.CgRealNestedClassesRegion import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster import org.utbot.framework.codegen.model.tree.CgTripleSlashMultilineComment import org.utbot.framework.codegen.model.tree.CgUtilEntity import org.utbot.framework.codegen.model.tree.CgUtilMethod -import org.utbot.framework.codegen.model.tree.buildTestClass -import org.utbot.framework.codegen.model.tree.buildTestClassBody +import org.utbot.framework.codegen.model.tree.buildClass +import org.utbot.framework.codegen.model.tree.buildClassBody import org.utbot.framework.codegen.model.tree.buildTestClassFile import org.utbot.framework.codegen.model.visitor.importUtilMethodDependencies import org.utbot.framework.plugin.api.ClassId @@ -69,8 +70,8 @@ internal class CgTestClassConstructor(val context: CgContext) : } } - private fun constructTestClass(testClassModel: TestClassModel): CgTestClass { - return buildTestClass { + private fun constructTestClass(testClassModel: TestClassModel): CgClass { + return buildClass { id = currentTestClass if (currentTestClass != outerMostTestClass) { @@ -86,9 +87,9 @@ internal class CgTestClassConstructor(val context: CgContext) : } } - body = buildTestClassBody { + body = buildClassBody(currentTestClass) { for (nestedClass in testClassModel.nestedClasses) { - nestedClassRegions += CgSimpleRegion( + nestedClassRegions += CgRealNestedClassesRegion( "Tests for ${nestedClass.classUnderTest.simpleName}", listOf( withNestedClassScope(nestedClass) { constructTestClass(nestedClass) } @@ -99,11 +100,11 @@ internal class CgTestClassConstructor(val context: CgContext) : for (testSet in testClassModel.methodTestSets) { updateCurrentExecutable(testSet.executableId) val currentMethodUnderTestRegions = constructTestSet(testSet) ?: continue - val executableUnderTestCluster = CgExecutableUnderTestCluster( + val executableUnderTestCluster = CgMethodsCluster( "Test suites for executable $currentExecutable", currentMethodUnderTestRegions ) - testMethodRegions += executableUnderTestCluster + methodRegions += executableUnderTestCluster } val currentTestClassDataProviderMethods = currentTestClassContext.cgDataProviderMethods diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt index fa0d27fed6..e17509ef62 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt @@ -4,29 +4,34 @@ import org.utbot.framework.codegen.model.CodeGenerator import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.builtin.utUtilsClassId import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass -import org.utbot.framework.codegen.model.tree.CgRegularClassFile +import org.utbot.framework.codegen.model.tree.CgAuxiliaryNestedClassesRegion +import org.utbot.framework.codegen.model.tree.CgClassFile +import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgUtilMethod -import org.utbot.framework.codegen.model.tree.buildRegularClass -import org.utbot.framework.codegen.model.tree.buildRegularClassBody -import org.utbot.framework.codegen.model.tree.buildRegularClassFile +import org.utbot.framework.codegen.model.tree.buildClass +import org.utbot.framework.codegen.model.tree.buildClassBody +import org.utbot.framework.codegen.model.tree.buildClassFile /** * This class is used to construct a file containing an util class UtUtils. * The util class is constructed when the argument `generateUtilClassFile` in the [CodeGenerator] is true. */ internal object CgUtilClassConstructor { - fun constructUtilsClassFile(utilClassKind: UtilClassKind): CgRegularClassFile { + fun constructUtilsClassFile(utilClassKind: UtilClassKind): CgClassFile { val utilMethodProvider = utilClassKind.utilMethodProvider - return buildRegularClassFile { + return buildClassFile { // imports are empty, because we use fully qualified classes and static methods, // so they will be imported once IDEA reformatting action has worked - declaredClass = buildRegularClass { + declaredClass = buildClass { id = utUtilsClassId - body = buildRegularClassBody { - content += utilClassKind.utilClassVersionComment - content += utilClassKind.utilClassKindComment - content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) } - content += CgAuxiliaryClass(utilMethodProvider.capturedArgumentClassId) + body = buildClassBody(utUtilsClassId) { + documentation = utilClassKind.utilClassDocumentation + staticDeclarationRegions += CgStaticsRegion("Util methods", utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) }) + nestedClassRegions += CgAuxiliaryNestedClassesRegion( + nestedClasses = listOf( + CgAuxiliaryClass(utilMethodProvider.capturedArgumentClassId) + ) + ) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt index 55be22a41d..f95ca886ad 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt @@ -12,18 +12,18 @@ interface CgBuilder { // Code entities -class CgRegularClassFileBuilder : CgBuilder { +class CgClassFileBuilder : CgBuilder { val imports: MutableList = mutableListOf() - lateinit var declaredClass: CgRegularClass + lateinit var declaredClass: CgClass - override fun build() = CgRegularClassFile(imports, declaredClass) + override fun build() = CgClassFile(imports, declaredClass) } -fun buildRegularClassFile(init: CgRegularClassFileBuilder.() -> Unit) = CgRegularClassFileBuilder().apply(init).build() +fun buildClassFile(init: CgClassFileBuilder.() -> Unit) = CgClassFileBuilder().apply(init).build() class CgTestClassFileBuilder : CgBuilder { val imports: MutableList = mutableListOf() - lateinit var declaredClass: CgTestClass + lateinit var declaredClass: CgClass lateinit var testsGenerationReport: TestsGenerationReport override fun build() = CgTestClassFile(imports, declaredClass, testsGenerationReport) @@ -31,52 +31,30 @@ class CgTestClassFileBuilder : CgBuilder { fun buildTestClassFile(init: CgTestClassFileBuilder.() -> Unit) = CgTestClassFileBuilder().apply(init).build() -class CgRegularClassBuilder : CgBuilder { +class CgClassBuilder : CgBuilder { lateinit var id: ClassId val annotations: MutableList = mutableListOf() var superclass: ClassId? = null val interfaces: MutableList = mutableListOf() - lateinit var body: CgRegularClassBody var isStatic: Boolean = false var isNested: Boolean = false + lateinit var body: CgClassBody - override fun build() = CgRegularClass(id, annotations, superclass, interfaces, body, isStatic, isNested) + override fun build() = CgClass(id, annotations, superclass, interfaces, body, isStatic, isNested) } -fun buildRegularClass(init: CgRegularClassBuilder.() -> Unit) = CgRegularClassBuilder().apply(init).build() +fun buildClass(init: CgClassBuilder.() -> Unit) = CgClassBuilder().apply(init).build() -class CgTestClassBuilder : CgBuilder { - lateinit var id: ClassId - val annotations: MutableList = mutableListOf() - var superclass: ClassId? = null - val interfaces: MutableList = mutableListOf() - var isStatic: Boolean = false - var isNested: Boolean = false - lateinit var body: CgTestClassBody - - override fun build() = CgTestClass(id, annotations, superclass, interfaces, body, isStatic, isNested) -} - -fun buildTestClass(init: CgTestClassBuilder.() -> Unit) = CgTestClassBuilder().apply(init).build() - -class CgTestClassBodyBuilder : CgBuilder { - val testMethodRegions: MutableList = mutableListOf() +class CgClassBodyBuilder(val classId: ClassId) : CgBuilder { + var documentation: CgDocumentationComment? = null + val methodRegions: MutableList = mutableListOf() val staticDeclarationRegions: MutableList = mutableListOf() - val nestedClassRegions: MutableList> = mutableListOf() + val nestedClassRegions: MutableList> = mutableListOf() - override fun build() = CgTestClassBody(testMethodRegions, staticDeclarationRegions, nestedClassRegions) + override fun build() = CgClassBody(classId, documentation, methodRegions, staticDeclarationRegions, nestedClassRegions) } -fun buildTestClassBody(init: CgTestClassBodyBuilder.() -> Unit) = CgTestClassBodyBuilder().apply(init).build() - -class CgRegularClassBodyBuilder : CgBuilder { - val content: MutableList = mutableListOf() - - override fun build() = CgRegularClassBody(content) -} - -fun buildRegularClassBody(init: CgRegularClassBodyBuilder.() -> Unit) = CgRegularClassBodyBuilder().apply(init).build() - +fun buildClassBody(classId: ClassId, init: CgClassBodyBuilder.() -> Unit) = CgClassBodyBuilder(classId).apply(init).build() // Methods interface CgMethodBuilder : CgBuilder { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index 41fbfcde6c..20e58a5f8c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -36,16 +36,15 @@ interface CgElement { // TODO: order of cases is important here due to inheritance between some of the element types fun accept(visitor: CgVisitor): R = visitor.run { when (val element = this@CgElement) { - is CgRegularClassFile -> visit(element) is CgTestClassFile -> visit(element) - is CgRegularClass -> visit(element) - is CgTestClass -> visit(element) - is CgRegularClassBody -> visit(element) - is CgTestClassBody -> visit(element) + is CgClassFile -> visit(element) + is CgClass -> visit(element) + is CgClassBody -> visit(element) is CgStaticsRegion -> visit(element) + is CgNestedClassesRegion<*> -> visit(element) is CgSimpleRegion<*> -> visit(element) is CgTestMethodCluster -> visit(element) - is CgExecutableUnderTestCluster -> visit(element) + is CgMethodsCluster -> visit(element) is CgAuxiliaryClass -> visit(element) is CgUtilMethod -> visit(element) is CgTestMethod -> visit(element) @@ -120,31 +119,26 @@ interface CgElement { // Code entities -sealed class AbstractCgClassFile> : CgElement { - abstract val imports: List - abstract val declaredClass: T -} - -data class CgRegularClassFile( - override val imports: List, - override val declaredClass: CgRegularClass -) : AbstractCgClassFile() +open class CgClassFile( + open val imports: List, + open val declaredClass: CgClass, +): CgElement data class CgTestClassFile( override val imports: List, - override val declaredClass: CgTestClass, + override val declaredClass: CgClass, val testsGenerationReport: TestsGenerationReport -) : AbstractCgClassFile() - -sealed class AbstractCgClass : CgElement { - abstract val id: ClassId - abstract val annotations: List - abstract val superclass: ClassId? - abstract val interfaces: List - abstract val body: T - abstract val isStatic: Boolean - abstract val isNested: Boolean - +) : CgClassFile(imports, declaredClass) + +class CgClass( + val id: ClassId, + val annotations: List, + val superclass: ClassId?, + val interfaces: List, + val body: CgClassBody, + val isStatic: Boolean, + val isNested: Boolean, +): CgElement { val packageName get() = id.packageName @@ -153,56 +147,21 @@ sealed class AbstractCgClass : CgElement { } /** - * This class represents any class that we may want to generate other than the test class. - * At the moment the only such case is the generation of util class UtUtils. - * - * The difference with [CgTestClass] is in the body. - * The structure of a test class body is fixed (we know what it should contain), - * whereas an arbitrary class could contain anything. - * For example, the body of UtUtils class contains a comment with information - * about the version of UTBot it was generated with, and all the util methods. - * - * @see CgUtilClassConstructor - */ -class CgRegularClass( - override val id: ClassId, - override val annotations: List, - override val superclass: ClassId?, - override val interfaces: List, - override val body: CgRegularClassBody, - override val isStatic: Boolean, - override val isNested: Boolean -) : AbstractCgClass() - -data class CgTestClass( - override val id: ClassId, - override val annotations: List, - override val superclass: ClassId?, - override val interfaces: List, - override val body: CgTestClassBody, - override val isStatic: Boolean, - override val isNested: Boolean -) : AbstractCgClass() - - -sealed class AbstractCgClassBody : CgElement - -data class CgRegularClassBody(val content: List) : AbstractCgClassBody() - -/** - * Body of the test class. - * @property testMethodRegions regions containing the test methods + * Body of a class. + * @property methodRegions regions containing methods * @property staticDeclarationRegions regions containing static declarations. * This is usually util methods and data providers. * In Kotlin all static declarations must be grouped together in a companion object. * In Java there is no such restriction, but for uniformity we are grouping * Java static declarations together as well. It can also improve code readability. */ -data class CgTestClassBody( - val testMethodRegions: List, +class CgClassBody( + val classId: ClassId, + val documentation: CgDocumentationComment?, + val methodRegions: List, val staticDeclarationRegions: List, - val nestedClassRegions: List> -) : AbstractCgClassBody() + val nestedClassRegions: List> +) : CgElement /** * A class representing the IntelliJ IDEA's regions. @@ -223,7 +182,7 @@ open class CgSimpleRegion( /** * A region that stores some static declarations, e.g. data providers or util methods. * There may be more than one static region in a class and they all are stored - * in a [CgTestClassBody.staticDeclarationRegions]. + * in a [CgClassBody.staticDeclarationRegions]. * In case of Kotlin, they all will be rendered inside of a companion object. */ class CgStaticsRegion( @@ -231,6 +190,23 @@ class CgStaticsRegion( override val content: List ) : CgSimpleRegion(header, content) +/** + * A region that stores all nested classes + */ +abstract class CgNestedClassesRegion( + override val header: String?, + override val content: List, +): CgRegion() + +class CgRealNestedClassesRegion(header: String? = null, nestedClasses: List): + CgNestedClassesRegion(header, nestedClasses) + +/** + * Regions with nested classes that are represented with [CgAuxiliaryClass], not [CgClass]. + */ +class CgAuxiliaryNestedClassesRegion(header: String? = null, nestedClasses: List): + CgNestedClassesRegion(header, nestedClasses) + data class CgTestMethodCluster( override val header: String?, val description: CgTripleSlashMultilineComment?, @@ -238,9 +214,10 @@ data class CgTestMethodCluster( ) : CgRegion() /** - * Stores all clusters (ERROR, successful, timeouts, etc.) for executable under test. + * Stores all methods as one cluster + * (for e.g. in test class we can store all tests for one executable in such cluster) */ -data class CgExecutableUnderTestCluster( +data class CgMethodsCluster( override val header: String?, override val content: List> ) : CgRegion>() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index e2f3aef97c..74f2dbee16 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -8,15 +8,14 @@ import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.UtilClassKind import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.tree.AbstractCgClass -import org.utbot.framework.codegen.model.tree.AbstractCgClassBody -import org.utbot.framework.codegen.model.tree.AbstractCgClassFile +import org.utbot.framework.codegen.model.tree.CgClassFile import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgAssignment import org.utbot.framework.codegen.model.tree.CgAuxiliaryClass import org.utbot.framework.codegen.model.tree.CgBreakStatement +import org.utbot.framework.codegen.model.tree.CgClass import org.utbot.framework.codegen.model.tree.CgComment import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation import org.utbot.framework.codegen.model.tree.CgComparison @@ -37,7 +36,7 @@ import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess import org.utbot.framework.codegen.model.tree.CgErrorTestMethod import org.utbot.framework.codegen.model.tree.CgErrorWrapper import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster +import org.utbot.framework.codegen.model.tree.CgMethodsCluster import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgForLoop @@ -56,13 +55,11 @@ import org.utbot.framework.codegen.model.tree.CgMethodCall import org.utbot.framework.codegen.model.tree.CgMultilineComment import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument +import org.utbot.framework.codegen.model.tree.CgNestedClassesRegion import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegion -import org.utbot.framework.codegen.model.tree.CgRegularClass -import org.utbot.framework.codegen.model.tree.CgRegularClassBody -import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation @@ -73,8 +70,6 @@ import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgStaticRunnable import org.utbot.framework.codegen.model.tree.CgStaticsRegion -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster import org.utbot.framework.codegen.model.tree.CgThisInstance @@ -145,47 +140,23 @@ internal abstract class CgAbstractRenderer( throw IllegalArgumentException(error) } - override fun visit(element: AbstractCgClassFile<*>) { + override fun visit(element: CgClassFile) { renderClassPackage(element.declaredClass) renderClassFileImports(element) element.declaredClass.accept(this) } - override fun visit(element: CgRegularClassFile) { - visit(element as AbstractCgClassFile<*>) - } - - override fun visit(element: CgTestClassFile) { - visit(element as AbstractCgClassFile<*>) - } - - override fun visit(element: CgRegularClass) { - visit(element as AbstractCgClass<*>) - } - - override fun visit(element: CgTestClass) { - visit(element as AbstractCgClass<*>) - } - - override fun visit(element: AbstractCgClassBody) { - visit(element as CgElement) - } - - override fun visit(element: CgRegularClassBody) { - val content = element.content - for ((index, item) in content.withIndex()) { - item.accept(this) - println() - if (index < content.lastIndex) { - println() - } - } + /** + * Render the region only if it is not empty. + */ + override fun visit(element: CgStaticsRegion) { + element.render() } /** * Render the region only if it is not empty. */ - override fun visit(element: CgStaticsRegion) { + override fun visit(element: CgNestedClassesRegion<*>) { element.render() } @@ -206,7 +177,7 @@ internal abstract class CgAbstractRenderer( /** * Render the cluster only if it is not empty. */ - override fun visit(element: CgExecutableUnderTestCluster) { + override fun visit(element: CgMethodsCluster) { // We print the next line after all contained regions to prevent gluing of region ends element.render(printLineAfterContentEnd = true) } @@ -858,14 +829,14 @@ internal abstract class CgAbstractRenderer( return if (this.isAccessibleBySimpleName()) simpleNameWithEnclosings else canonicalName } - private fun renderClassPackage(element: AbstractCgClass<*>) { + private fun renderClassPackage(element: CgClass) { if (element.packageName.isNotEmpty()) { println("package ${element.packageName}${statementEnding}") println() } } - private fun renderClassFileImports(element: AbstractCgClassFile<*>) { + private fun renderClassFileImports(element: CgClassFile) { val regularImports = element.imports.filterIsInstance() val staticImports = element.imports.filterIsInstance() @@ -886,7 +857,7 @@ internal abstract class CgAbstractRenderer( protected abstract fun renderClassVisibility(classId: ClassId) - protected abstract fun renderClassModality(aClass: AbstractCgClass<*>) + protected abstract fun renderClassModality(aClass: CgClass) private fun renderMethodDocumentation(element: CgMethod) { element.documentation.accept(this) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index 01eaec9290..98e2603278 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt @@ -3,7 +3,6 @@ package org.utbot.framework.codegen.model.visitor import org.apache.commons.text.StringEscapeUtils import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.model.tree.AbstractCgClass import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction @@ -26,14 +25,13 @@ import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgRegularClass import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgStatement import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgSwitchCase import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassBody +import org.utbot.framework.codegen.model.tree.CgClass +import org.utbot.framework.codegen.model.tree.CgClassBody import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgVariable @@ -67,7 +65,7 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C override val ClassId.methodsAreAccessibleAsTopLevel: Boolean get() = this == context.generatedClass - override fun visit(element: AbstractCgClass<*>) { + override fun visit(element: CgClass) { for (annotation in element.annotations) { annotation.accept(this) } @@ -93,9 +91,9 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C println("}") } - override fun visit(element: CgTestClassBody) { + override fun visit(element: CgClassBody) { // render regions for test methods and utils - val allRegions = element.testMethodRegions + element.nestedClassRegions + element.staticDeclarationRegions + val allRegions = element.methodRegions + element.nestedClassRegions + element.staticDeclarationRegions for ((i, region) in allRegions.withIndex()) { if (i != 0) println() @@ -366,11 +364,8 @@ internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = C } } - override fun renderClassModality(aClass: AbstractCgClass<*>) { - when (aClass) { - is CgTestClass -> Unit - is CgRegularClass -> if (aClass.id.isFinal) print("final ") - } + override fun renderClassModality(aClass: CgClass) { + if (aClass.id.isFinal) print("final ") } private fun renderExceptions(method: CgMethod) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index af938d17be..5bc606069d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -6,7 +6,6 @@ import org.utbot.common.workaround import org.utbot.framework.codegen.RegularImport import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.isLanguageKeyword -import org.utbot.framework.codegen.model.tree.AbstractCgClass import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction @@ -32,14 +31,13 @@ import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgRegularClass import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSpread import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgSwitchCase import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassBody +import org.utbot.framework.codegen.model.tree.CgClass +import org.utbot.framework.codegen.model.tree.CgClassBody import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgVariable @@ -81,7 +79,7 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = // NB: the order of operands is important as `isKotlinFile` uses reflection and thus can't be called on context.generatedClass get() = (this == context.generatedClass) || isKotlinFile - override fun visit(element: AbstractCgClass<*>) { + override fun visit(element: CgClass) { for (annotation in element.annotations) { annotation.accept(this) } @@ -91,7 +89,10 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = if (!element.isStatic && element.isNested) { print("inner ") } - print("class ") + if (element.id.isKotlinObject) + print("object ") + else + print("class ") print(element.simpleName) if (element.superclass != null || element.interfaces.isNotEmpty()) { @@ -118,9 +119,11 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = println("}") } - override fun visit(element: CgTestClassBody) { + override fun visit(element: CgClassBody) { + element.documentation?.accept(this) + // render regions for test methods - for ((i, region) in (element.testMethodRegions + element.nestedClassRegions).withIndex()) { + for ((i, region) in (element.methodRegions + element.nestedClassRegions).withIndex()) { if (i != 0) println() region.accept(this) @@ -155,13 +158,20 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = CgStaticsRegion(region.header, updatedContent) } - renderCompanionObject { + fun renderAllStaticRegions() { for ((i, staticsRegion) in updatedStaticRegions.withIndex()) { if (i != 0) println() staticsRegion.accept(this) } } + + // We should generate static methods in companion object iff generated class is not an object + if (element.classId.isKotlinObject) { + renderAllStaticRegions() + } else { + renderCompanionObject(::renderAllStaticRegions) + } } /** @@ -549,12 +559,8 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = } } - override fun renderClassModality(aClass: AbstractCgClass<*>) { - when (aClass) { - is CgTestClass -> Unit - // Kotlin classes are final by default - is CgRegularClass -> if (!aClass.id.isFinal) print("open ") - } + override fun renderClassModality(aClass: CgClass) { + if (!aClass.id.isFinal) print("open ") } private fun getKotlinClassString(id: ClassId): String = diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt index 1b89761575..951dad44b4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt @@ -1,8 +1,6 @@ package org.utbot.framework.codegen.model.visitor -import org.utbot.framework.codegen.model.tree.AbstractCgClass -import org.utbot.framework.codegen.model.tree.AbstractCgClassBody -import org.utbot.framework.codegen.model.tree.AbstractCgClassFile +import org.utbot.framework.codegen.model.tree.CgClassFile import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment import org.utbot.framework.codegen.model.tree.CgAllocateArray @@ -36,7 +34,7 @@ import org.utbot.framework.codegen.model.tree.CgEqualTo import org.utbot.framework.codegen.model.tree.CgErrorTestMethod import org.utbot.framework.codegen.model.tree.CgErrorWrapper import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster +import org.utbot.framework.codegen.model.tree.CgMethodsCluster import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgForLoop @@ -62,9 +60,6 @@ import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgRegularClass -import org.utbot.framework.codegen.model.tree.CgRegularClassBody -import org.utbot.framework.codegen.model.tree.CgRegularClassFile import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation @@ -77,9 +72,9 @@ import org.utbot.framework.codegen.model.tree.CgStaticRunnable import org.utbot.framework.codegen.model.tree.CgStaticsRegion import org.utbot.framework.codegen.model.tree.CgSwitchCase import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassBody -import org.utbot.framework.codegen.model.tree.CgTestClassFile +import org.utbot.framework.codegen.model.tree.CgClass +import org.utbot.framework.codegen.model.tree.CgClassBody +import org.utbot.framework.codegen.model.tree.CgNestedClassesRegion import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster import org.utbot.framework.codegen.model.tree.CgThisInstance @@ -94,22 +89,18 @@ import org.utbot.framework.codegen.model.tree.CgWhileLoop interface CgVisitor { fun visit(element: CgElement): R - fun visit(element: AbstractCgClassFile<*>): R - fun visit(element: CgRegularClassFile): R - fun visit(element: CgTestClassFile): R + fun visit(element: CgClassFile): R - fun visit(element: AbstractCgClass<*>): R - fun visit(element: CgRegularClass): R - fun visit(element: CgTestClass): R + fun visit(element: CgClass): R - fun visit(element: AbstractCgClassBody): R - fun visit(element: CgRegularClassBody): R - fun visit(element: CgTestClassBody): R + fun visit(element: CgClassBody): R fun visit(element: CgStaticsRegion): R + + fun visit(element: CgNestedClassesRegion<*>): R fun visit(element: CgSimpleRegion<*>): R fun visit(element: CgTestMethodCluster): R - fun visit(element: CgExecutableUnderTestCluster): R + fun visit(element: CgMethodsCluster): R fun visit(element: CgAuxiliaryClass): R fun visit(element: CgUtilMethod): R diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index 7e7e5c1f12..a81579bbc9 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -22,7 +22,6 @@ import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable -import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.ToolWindowManager import com.intellij.psi.* import com.intellij.psi.codeStyle.CodeStyleManager @@ -373,7 +372,7 @@ object CodeGenerationController { val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) - val utUtilsFile = runReadAction { + var utUtilsFile = runReadAction { PsiFileFactory.getInstance(model.project) .createFileFromText( utUtilsName, @@ -384,7 +383,8 @@ object CodeGenerationController { // add UtUtils class file into the utils directory runWriteCommandAction(model.project) { - utilClassDirectory.add(utUtilsFile) + // The file actually added to subdirectory may be the copy of original file -- see [PsiElement.add] docs + utUtilsFile = utilClassDirectory.add(utUtilsFile) as PsiFile } return utUtilsFile