diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml b/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml index 405c8e2a4db0..6f69505e3069 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/resources/kotlin.code-insight.k2.xml @@ -244,6 +244,8 @@ + () { + class Info( + val propertiesToInitialize: List, + val superConstructors: List, + val klass: KtClass + ) + + override fun isValidForClass(targetClass: KtClassOrObject): Boolean { + return targetClass is KtClass + && targetClass !is KtEnumEntry + && !targetClass.isInterface() + && !targetClass.isAnnotation() + && !targetClass.hasExplicitPrimaryConstructor() + } + + private fun shouldPreselect(element: PsiElement) = element is KtProperty && !element.isVar + + context(KaSession) + @OptIn(KaExperimentalApi::class) + private fun chooseSuperConstructors(classSymbol: KaClassSymbol): List { + val superClassSymbol = getSuperClassSymbolNoAny(classSymbol) ?: return emptyList() + val candidates = superClassSymbol.declaredMemberScope.constructors + .filter { it.isVisibleInClass(classSymbol) } + .mapNotNull { constructorSymbol -> + constructorSymbol.psi?.let { KotlinPsiElementMemberChooserObject.getMemberChooserObject(it) } as? ClassMember + }.toList() + return candidates + } + + context(KaSession) + private fun getSuperClassSymbolNoAny(classSymbol: KaClassSymbol): KaClassSymbol? = + classSymbol.superTypes.mapNotNull { it.symbol as? KaClassSymbol }.find { superClassSymbol -> + superClassSymbol.classKind == KaClassKind.CLASS && superClassSymbol.classId != StandardClassIds.Any + } + + context(KaSession) + @OptIn(KaExperimentalApi::class) + private fun KtProperty.isPropertyNotInitialized(): Boolean { + return containingKtFile.collectDiagnostics(KaDiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + .filter { it.psi == this@isPropertyNotInitialized } + .any { it is KaFirDiagnostic.MustBeInitializedOrBeAbstract } + } + + context(KaSession) + private fun choosePropertiesToInitialize(klass: KtClassOrObject): List { + return klass.declarations + .asSequence() + .filterIsInstance() + .filter { it.isVar || it.isPropertyNotInitialized() } + .map { KotlinPsiElementMemberChooserObject.getKotlinMemberChooserObject(it) } + .toList() + } + + override fun prepareMembersInfo(klass: KtClassOrObject, project: Project, editor: Editor): Info? { + if (klass !is KtClass) return null + val preInfo = analyzeInModalWindow(klass, KotlinBundle.message("fix.change.signature.prepare")) { + val classSymbol = klass.symbol as? KaClassSymbol ?: return@analyzeInModalWindow null + val superConstructors = chooseSuperConstructors(classSymbol) + val propertiesToInitialize = choosePropertiesToInitialize(klass) + return@analyzeInModalWindow Info(propertiesToInitialize, superConstructors, klass) + } ?: return null + + val superConstructors = preInfo.superConstructors + val chosenConstructors = if (isUnitTestMode() || superConstructors.size <= 1) { + superConstructors + } + else { + val chooser = MemberChooser(superConstructors.toTypedArray(), false, true, klass.project) + chooser.title = JavaBundle.message("generate.constructor.super.constructor.chooser.title") + chooser.setCopyJavadocVisible(false) + chooser.show() + + chooser.selectedElements + } ?: return null + + val properties = preInfo.propertiesToInitialize + val chosenProperties = if (isUnitTestMode() || properties.isEmpty()) { + properties + } + else { + with(MemberChooser(properties.toTypedArray(), true, true, klass.project, false, null)) { + title = KotlinBundle.message("action.generate.secondary.constructor.choose.properties") + setCopyJavadocVisible(false) + selectElements(properties.filter { shouldPreselect(it.element) }.toTypedArray()) + show() + + selectedElements + } + } ?: return null + return Info(chosenProperties, chosenConstructors, preInfo.klass) + } + + override fun generateMembers(project: Project, editor: Editor, info: Info): List { + val targetClass = info.klass + + fun Info.findAnchor(): PsiElement? { + targetClass.declarations.lastIsInstanceOrNull()?.let { return it } + val lastPropertyToInitialize = propertiesToInitialize.lastOrNull()?.psiElement + val declarationsAfter = + lastPropertyToInitialize?.siblings()?.filterIsInstance() ?: targetClass.declarations.asSequence() + val firstNonProperty = declarationsAfter.firstOrNull { it !is KtProperty } ?: return null + return firstNonProperty.siblings(forward = false).firstIsInstanceOrNull() ?: targetClass.getOrCreateBody().lBrace + } + + return with(info) { + val prototypes = + analyzeInModalWindow(klass, KotlinBundle.message("fix.change.signature.prepare")) { + if (superConstructors.isNotEmpty()) { + superConstructors.mapNotNull { generateConstructor(klass, propertiesToInitialize.mapNotNull { it.psiElement as? KtProperty }, it) } + } else { + listOfNotNull(generateConstructor(klass, propertiesToInitialize.mapNotNull { it.psiElement as? KtProperty }, null)) + } + } + + + if (prototypes.isEmpty()) { + val errorText = KotlinBundle.message("action.generate.secondary.constructor.error.already.exists") + CommonRefactoringUtil.showErrorHint(targetClass.project, editor, errorText, commandName, null) + return emptyList() + } + + var constructors: List? = null + project.executeWriteCommand(commandName) { + constructors = insertMembersAfterAndReformat(editor, targetClass, prototypes, findAnchor()) + } + return constructors ?: emptyList() + } + } + + context(KaSession) + @OptIn(KaExperimentalApi::class) + private fun generateConstructor( + klass: KtClass, + propertiesToInitialize: List, + superConstructor: ClassMember? + ): KtSecondaryConstructor? { + + val classSymbol = klass.symbol as? KaClassSymbol ?: return null + + val superConstructorPsi = (superConstructor as? PsiElementMemberChooserObject)?.psiElement + val superConstructorSymbol = when (superConstructorPsi) { + is KtConstructor<*> -> superConstructorPsi.symbol + is PsiMethod -> superConstructorPsi.containingClass?.namedClassSymbol?.declaredMemberScope?.constructors?.find { it.psi == superConstructorPsi } + else -> null + } + + val constructorParamTypes = propertiesToInitialize.map { it.returnType } + + (superConstructorSymbol?.valueParameters?.map { it.returnType } ?: emptyList()) + + fun equalTypes(types1: Collection, types2: Collection): Boolean { + return types1.size == types2.size && types1.zip(types2).all { it.first.semanticallyEquals(it.second) } + } + + if (classSymbol.declaredMemberScope.constructors.any { constructorSymbol -> + constructorSymbol.psi is KtConstructor<*> && + equalTypes(constructorSymbol.valueParameters.map { it.returnType }, constructorParamTypes) + } + ) return null + + val psiFactory = KtPsiFactory(klass.project) + + val validator = CollectingNameValidator() + + val constructor = psiFactory.createSecondaryConstructor("constructor()") + val parameterList = constructor.valueParameterList!! + + if (superConstructorSymbol != null) { + val superClassSymbol = superConstructorSymbol.containingSymbol as? KaClassSymbol ?: return null + val substitutor = createInheritanceTypeSubstitutor(classSymbol, superClassSymbol) ?: return null + + val delegationCallArguments = ArrayList() + for (parameter in superConstructorSymbol.valueParameters) { + val isVararg = parameter.isVararg + val paramName = suggestSafeNameByName(parameter.name.asString(), validator) + val typeToUse = parameter.returnType + val paramType = substitutor.substitute(typeToUse).render(position = Variance.IN_VARIANCE) + val modifiers = if (isVararg) "vararg " else "" + + parameterList.addParameter(psiFactory.createParameter("$modifiers$paramName: $paramType")) + delegationCallArguments.add(if (isVararg) "*$paramName" else paramName) + } + + val delegationCall = + psiFactory.creareDelegatedSuperTypeEntry(delegationCallArguments.joinToString(prefix = "super(", postfix = ")")) + constructor.replaceImplicitDelegationCallWithExplicit(false).replace(delegationCall) + } + + if (propertiesToInitialize.isNotEmpty()) { + val body = psiFactory.createEmptyBody() + for (property in propertiesToInitialize) { + val propertyName = property.name ?: continue + val paramName = suggestSafeNameByName(propertyName, validator) + val paramType = property.returnType.render(position = Variance.IN_VARIANCE) + + parameterList.addParameter(psiFactory.createParameter("$paramName: $paramType")) + body.addElement(psiFactory.createExpression("this.${propertyName.quoteIfNeeded()} = $paramName"), true) + } + + constructor.add(body) + } + + return constructor + } + + private fun suggestSafeNameByName(originalName: String, validator: CollectingNameValidator): String = + KotlinNameSuggester.suggestNameByName(originalName, validator).quoteIfNeeded() +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/AbstractFirGenerateSecondaryConstructorActionTest.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/AbstractFirGenerateSecondaryConstructorActionTest.kt new file mode 100644 index 000000000000..657a5f3cf55d --- /dev/null +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/AbstractFirGenerateSecondaryConstructorActionTest.kt @@ -0,0 +1,20 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.generate + +import com.intellij.codeInsight.actions.CodeInsightAction +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.Presentation +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils +import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractCodeInsightActionTest +import org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinGenerateSecondaryConstructorAction +import org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinGenerateToStringAction +import org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinToStringTemplatesManager +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +abstract class AbstractFirGenerateSecondaryConstructorActionTest : AbstractCodeInsightActionTest() { + override fun createAction(fileText: String): CodeInsightAction { + return KotlinGenerateSecondaryConstructorAction() + } +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/FirGenerateSecondaryConstructorActionTestGenerated.java b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/FirGenerateSecondaryConstructorActionTestGenerated.java new file mode 100644 index 000000000000..699272f401c7 --- /dev/null +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/generate/FirGenerateSecondaryConstructorActionTestGenerated.java @@ -0,0 +1,107 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.generate; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("code-insight/kotlin.code-insight.k2") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("../../idea/tests/testData/codeInsight/generate/secondaryConstructors") +public class FirGenerateSecondaryConstructorActionTestGenerated extends AbstractFirGenerateSecondaryConstructorActionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("empty.kt") + public void testEmpty() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/empty.kt"); + } + + @TestMetadata("emptyExists.kt") + public void testEmptyExists() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/emptyExists.kt"); + } + + @TestMetadata("javaSupers.kt") + public void testJavaSupers() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupers.kt"); + } + + @TestMetadata("javaSupersWithGenerics.kt") + public void testJavaSupersWithGenerics() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupersWithGenerics.kt"); + } + + @TestMetadata("javaSupersWithKeywords.kt") + public void testJavaSupersWithKeywords() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupersWithKeywords.kt"); + } + + @TestMetadata("primaryExists.kt") + public void testPrimaryExists() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/primaryExists.kt"); + } + + @TestMetadata("properties.kt") + public void testProperties() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/properties.kt"); + } + + @TestMetadata("propertiesWithKeywords.kt") + public void testPropertiesWithKeywords() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/propertiesWithKeywords.kt"); + } + + @TestMetadata("propertiesWithSupers.kt") + public void testPropertiesWithSupers() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/propertiesWithSupers.kt"); + } + + @TestMetadata("superInvisible.kt") + public void testSuperInvisible() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt"); + } + + @TestMetadata("supers.kt") + public void testSupers() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/supers.kt"); + } + + @TestMetadata("supersAllExist.kt") + public void testSupersAllExist() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/supersAllExist.kt"); + } + + @TestMetadata("supersSomeExist.kt") + public void testSupersSomeExist() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/supersSomeExist.kt"); + } + + @TestMetadata("supersWithGenerics.kt") + public void testSupersWithGenerics() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/supersWithGenerics.kt"); + } + + @TestMetadata("supersWithVarargs.kt") + public void testSupersWithVarargs() throws Exception { + runTest("../../idea/tests/testData/codeInsight/generate/secondaryConstructors/supersWithVarargs.kt"); + } +} diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/generate/CodeInsightActionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/generate/CodeInsightActionTestGenerated.java index 19486e8498da..4f78f48d4cbf 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/generate/CodeInsightActionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/generate/CodeInsightActionTestGenerated.java @@ -75,6 +75,11 @@ public class CodeInsightActionTestGenerated extends AbstractCodeInsightActionTes runTest("testData/codeInsight/generate/secondaryConstructors/propertiesWithSupers.kt"); } + @TestMetadata("superInvisible.kt") + public void testSuperInvisible() throws Exception { + runTest("testData/codeInsight/generate/secondaryConstructors/superInvisible.kt"); + } + @TestMetadata("supers.kt") public void testSupers() throws Exception { runTest("testData/codeInsight/generate/secondaryConstructors/supers.kt"); diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupersWithGenerics.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupersWithGenerics.kt.k2.after new file mode 100644 index 000000000000..cedb997df85a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/javaSupersWithGenerics.kt.k2.after @@ -0,0 +1,15 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +class Foo : Base { + val x = 1 + + constructor(x: U) : super(x) + constructor(x: U, y: Int) : super(x, y) + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/propertiesWithSupers.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/propertiesWithSupers.kt.k2.after new file mode 100644 index 000000000000..dc75143bf5e2 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/propertiesWithSupers.kt.k2.after @@ -0,0 +1,26 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +open class Base(n: Int) { + constructor(a: Int, b: Int): this(a + b) +} + +class Foo : Base { + val n: Int + + val x = 1 + + constructor(n: Int, n1: Int) : super(n) { + this.n = n1 + } + + constructor(a: Int, b: Int, n: Int) : super(a, b) { + this.n = n + } + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt new file mode 100644 index 000000000000..e1b7d63aceed --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt @@ -0,0 +1,16 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +open class Base(n: Int) { + private constructor(a: Int, b: Int): this(a + b) +} + +class Foo : Base { + val x = 1 + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt.after new file mode 100644 index 000000000000..0e42ec8ecea1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/superInvisible.kt.after @@ -0,0 +1,18 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +open class Base(n: Int) { + private constructor(a: Int, b: Int): this(a + b) +} + +class Foo : Base { + val x = 1 + + constructor(n: Int) : super(n) + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supers.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supers.kt.k2.after new file mode 100644 index 000000000000..06453a18aff5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supers.kt.k2.after @@ -0,0 +1,19 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +open class Base(n: Int) { + constructor(a: Int, b: Int): this(a + b) +} + +class Foo : Base { + val x = 1 + + constructor(n: Int) : super(n) + constructor(a: Int, b: Int) : super(a, b) + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supersWithGenerics.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supersWithGenerics.kt.k2.after new file mode 100644 index 000000000000..0aff1fedf83e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/secondaryConstructors/supersWithGenerics.kt.k2.after @@ -0,0 +1,19 @@ +// ACTION_CLASS: org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction +open class Base(n: X) { + constructor(x: X, y: Y): this(x) +} + +class Foo : Base { + val x = 1 + + constructor(n: U) : super(n) + constructor(x: U, y: Int) : super(x, y) + + fun foo() { + + } + + fun bar() { + + } +} \ No newline at end of file diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2CodeInsightTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2CodeInsightTests.kt index 7a97e5b9155b..b4f7d4c0e515 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2CodeInsightTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2CodeInsightTests.kt @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.idea.k2.AbstractKotlinFirJoinLinesTest import org.jetbrains.kotlin.idea.k2.AbstractKotlinFirPairMatcherTest import org.jetbrains.kotlin.idea.k2.copyPaste.AbstractK2InsertImportOnPasteTest import org.jetbrains.kotlin.idea.k2.generate.AbstractFirGenerateHashCodeAndEqualsActionTest +import org.jetbrains.kotlin.idea.k2.generate.AbstractFirGenerateSecondaryConstructorActionTest import org.jetbrains.kotlin.idea.k2.generate.AbstractFirGenerateToStringActionTest import org.jetbrains.kotlin.idea.k2.hierarchy.AbstractFirHierarchyTest import org.jetbrains.kotlin.idea.k2.hierarchy.AbstractFirHierarchyWithLibTest @@ -156,6 +157,10 @@ internal fun MutableTWorkspace.generateK2CodeInsightTests() { model("../../../idea/tests/testData/codeInsight/generate/toString") } + testClass { + model("../../../idea/tests/testData/codeInsight/generate/secondaryConstructors") + } + testClass { model("../../../idea/tests/testData/joinLines") }