[kotlin] Port Create Test intention to K2

#KTIJ-30467
#KTIJ-9787

GitOrigin-RevId: efb5d9d40646aeb861548eb4d4def625192f5900
This commit is contained in:
Vladimir Dolzhenko
2024-10-10 09:42:11 +02:00
committed by intellij-monorepo-bot
parent 60527dd48d
commit 64e6daa2b6
28 changed files with 671 additions and 170 deletions

View File

@@ -2,8 +2,10 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.util;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.vfs.VirtualFile;
@@ -30,6 +32,10 @@ public final class RefactoringMessageUtil {
* an error message, if cannot create a class
*/
public static @Nls(capitalization = Sentence) @Nullable String checkCanCreateClass(PsiDirectory destinationDirectory, String className) {
return checkCanCreateClass(destinationDirectory, className, JavaFileType.INSTANCE);
}
public static @Nls(capitalization = Sentence) @Nullable String checkCanCreateClass(PsiDirectory destinationDirectory, String className, FileType fileType) {
PsiClass[] classes = JavaDirectoryService.getInstance().getClasses(destinationDirectory);
VirtualFile file = destinationDirectory.getVirtualFile();
for (PsiClass aClass : classes) {
@@ -38,7 +44,7 @@ public final class RefactoringMessageUtil {
file.getPresentableUrl(), UsageViewUtil.getType(aClass), className);
}
}
@NonNls String fileName = className + ".java";
@NonNls String fileName = className + "." + fileType.getDefaultExtension();
return checkCanCreateFile(destinationDirectory, fileName);
}

View File

@@ -176,8 +176,30 @@ public final class TestIntegrationUtils {
final PsiClass targetClass,
final PsiMethod method,
boolean automatic, final Template template) {
runTestMethodTemplate(editor, targetClass, method, method.getModifierList(), automatic, template);
}
final int startOffset = method.getModifierList().getTextRange().getStartOffset();
public static void runTestMethodTemplate(@NotNull MethodKind methodKind,
TestFramework framework,
final Editor editor,
final PsiElement targetClass,
@Nullable PsiClass sourceClass,
final PsiElement method,
final PsiElement methodModifierList,
@Nullable String name,
boolean automatic,
Set<? super String> existingNames) {
runTestMethodTemplate(editor, targetClass, method, methodModifierList, automatic,
createTestMethodTemplate(methodKind, framework, targetClass, sourceClass, name, automatic, existingNames));
}
public static void runTestMethodTemplate(final Editor editor,
final PsiElement targetClass,
final PsiElement method,
final PsiElement methodModifierList,
boolean automatic,
final Template template) {
final int startOffset = methodModifierList.getTextRange().getStartOffset();
final TextRange range = new TextRange(startOffset, method.getTextRange().getEndOffset());
editor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), "");
editor.getCaretModel().moveToOffset(range.getStartOffset());
@@ -228,14 +250,25 @@ public final class TestIntegrationUtils {
@Nullable String name,
boolean automatic,
Set<? super String> existingNames) {
return createTestMethodTemplate(methodKind, descriptor, (PsiElement) targetClass, sourceClass, name, automatic, existingNames);
}
public static Template createTestMethodTemplate(@NotNull MethodKind methodKind,
TestFramework descriptor,
@NotNull PsiElement targetClass,
@Nullable PsiClass sourceClass,
@Nullable String name,
boolean automatic,
Set<? super String> existingNames) {
FileTemplateDescriptor templateDesc = methodKind.getFileTemplateDescriptor(descriptor);
String templateName = templateDesc.getFileName();
FileTemplate fileTemplate = FileTemplateManager.getInstance(targetClass.getProject()).getCodeTemplate(templateName);
Template template = TemplateManager.getInstance(targetClass.getProject()).createTemplate("", "");
Project project = targetClass.getProject();
FileTemplate fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(templateName);
Template template = TemplateManager.getInstance(project).createTemplate("", "");
String templateText;
try {
Properties properties = FileTemplateManager.getInstance(targetClass.getProject()).getDefaultProperties();
Properties properties = FileTemplateManager.getInstance(project).getDefaultProperties();
if (sourceClass != null && sourceClass.isValid()) {
properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, sourceClass.getQualifiedName());
}

View File

@@ -307,6 +307,10 @@ intention.wrap.in.with.context=Wrap call in 'withContext'
intention.flow.on.dispatchers.io=Flow on 'Dispatchers.IO'
intention.switch.context.to.dispatchers.io=Switch to 'Dispatchers.IO' context
intention.error.cannot.create.class.message=Cannot Create Class ''{0}''
intention.error.cannot.create.class.title=Failed to Create Class
intention.create.test.dialog.kotlin=Kotlin
# Code insight
copy.paste.select.imports.to.remove.dialog=Select Imports to Remove
copy.paste.select.imports.to.remove.text=<html>The code fragment which you have pasted uses elements that are not accessible in the new context. Necessary imports have been added.<br/>Select which of the added imports you want to remove from the file.</html>
@@ -2704,4 +2708,4 @@ progress.title.converting.to.if.then.else.expression=Converting to if-then-else
progress.title.introducing.value.for.condition=Introducing value for condition\u2026
inspection.kotlin.options.to.compiler.options.display.name=Use of deprecated 'kotlinOptions' DSL
replace.kotlin.options.with.compiler.options=Replace 'kotlinOptions' with 'compilerOptions'
replace.kotlin.options.with.compiler.options=Replace 'kotlinOptions' with 'compilerOptions'

View File

@@ -6,7 +6,6 @@ import com.intellij.CommonBundle
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.FileModificationService
import com.intellij.ide.util.PropertiesComponent
import com.intellij.lang.java.JavaLanguage
import com.intellij.model.SideEffectGuard
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
@@ -15,7 +14,6 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.GlobalSearchScopesCore
@@ -47,11 +45,11 @@ abstract class AbstractKotlinCreateTestIntention : SelfTargetingRangeIntention<K
protected abstract fun isApplicableForModule(module: Module): Boolean
protected abstract fun convertJavaClass(
protected abstract fun convertClass(
project: Project,
generatedClass: PsiClass,
existingClass: KtClassOrObject?,
generatedFile: PsiJavaFile,
generatedFile: PsiFile,
srcModule: Module
)
@@ -92,7 +90,31 @@ abstract class AbstractKotlinCreateTestIntention : SelfTargetingRangeIntention<K
}
}
override fun startInWriteAction() = false
override fun startInWriteAction(): Boolean = false
protected open fun getTempClassName(project: Project, existingClass: KtClassOrObject): String {
val kotlinFile = existingClass.containingKtFile.virtualFile
val baseName = kotlinFile.nameWithoutExtension
val psiDir = kotlinFile.parent!!.toPsiDirectory(project)!!
return generateSequence(0) { it + 1 }
.map { "$baseName$it" }
.first {
psiDir.findFile("$it.java") == null &&
findTestClass(project, psiDir, it) == null
}
}
// Based on the com.intellij.testIntegration.createTest.JavaTestGenerator.createTestClass()
private fun findTestClass(project: Project, targetDirectory: PsiDirectory, className: String): PsiElement? {
val psiPackage = JavaDirectoryService.getInstance()?.getPackage(targetDirectory) ?: return null
val scope = GlobalSearchScopesCore.directoryScope(targetDirectory, false)
findKtClassOrObject(className, project, scope)?.let { return it }
val klass = psiPackage.findClassByShortName(className, scope).firstOrNull() ?: return null
if (!FileModificationService.getInstance().preparePsiElementForWrite(klass)) return null
return klass
}
override fun applyTo(element: KtNamedDeclaration, editor: Editor?) {
val lightClass = when (element) {
@@ -101,26 +123,6 @@ abstract class AbstractKotlinCreateTestIntention : SelfTargetingRangeIntention<K
} ?: return
object : CreateTestAction() {
// Based on the com.intellij.testIntegration.createTest.JavaTestGenerator.createTestClass()
private fun findTestClass(project: Project, targetDirectory: PsiDirectory, className: String): PsiElement? {
val psiPackage = JavaDirectoryService.getInstance()?.getPackage(targetDirectory) ?: return null
val scope = GlobalSearchScopesCore.directoryScope(targetDirectory, false)
findKtClassOrObject(className, project, scope)?.let { return it }
val klass = psiPackage.findClassByShortName(className, scope).firstOrNull() ?: return null
if (!FileModificationService.getInstance().preparePsiElementForWrite(klass)) return null
return klass
}
private fun getTempJavaClassName(project: Project, kotlinFile: VirtualFile): String {
val baseName = kotlinFile.nameWithoutExtension
val psiDir = kotlinFile.parent!!.toPsiDirectory(project)!!
return generateSequence(0) { it + 1 }
.map { "$baseName$it" }
.first { psiDir.findFile("$it.java") == null && findTestClass(project, psiDir, it) == null }
}
// Based on the com.intellij.testIntegration.createTest.CreateTestAction.CreateTestAction.invoke()
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
val srcModule = ModuleUtilCore.findModuleForPsiElement(element) ?: return
@@ -170,18 +172,17 @@ abstract class AbstractKotlinCreateTestIntention : SelfTargetingRangeIntention<K
val generator = TestGenerators.INSTANCE.forLanguage(dialog.selectedTestFrameworkDescriptor.language)
project.runWithAlternativeResolveEnabled {
if (existingClass != null) {
dialog.explicitClassName = getTempJavaClassName(project, existingClass.containingFile.virtualFile)
dialog.explicitClassName = getTempClassName(project, existingClass)
}
generator.generateTest(project, dialog)
}
} as? PsiClass ?: return
project.runWhenSmart {
val generatedFile = generatedClass.containingFile as? PsiJavaFile ?: return@runWhenSmart
val containingFile = generatedClass.containingFile
val generatedFile = containingFile as? PsiJavaFile ?: return@runWhenSmart
if (generatedClass.language == JavaLanguage.INSTANCE) {
convertJavaClass(project, generatedClass, existingClass, generatedFile, srcModule)
}
convertClass(project, generatedClass, existingClass, generatedFile, srcModule)
}
}
}.invoke(element.project, editor, lightClass)

View File

@@ -6,8 +6,10 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiPackage
import com.intellij.refactoring.util.RefactoringMessageUtil
import com.intellij.testIntegration.createTest.CreateTestDialog
import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.idea.KotlinFileType
class KotlinCreateTestDialog(
project: Project,
@@ -18,5 +20,8 @@ class KotlinCreateTestDialog(
) : CreateTestDialog(project, title, targetClass, targetPackage, targetModule) {
var explicitClassName: String? = null
override fun getClassName(): String = explicitClassName ?: super.getClassName()
override fun getClassName(): String = explicitClassName ?: super.className
override fun checkCanCreateClass(): String? =
RefactoringMessageUtil.checkCanCreateClass(myTargetDirectory, getClassName(), KotlinFileType.INSTANCE)
}

View File

@@ -1,14 +1,21 @@
// Copyright 2000-2023 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.codeinsight.intentions.testIntegration
import com.intellij.codeInsight.navigation.activateFileWithPsiElement
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.idea.base.facet.platform.platform
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsights.impl.base.testIntegration.AbstractKotlinCreateTestIntention
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.psi.KtClassOrObject
// do not change intention class to be aligned with docs
@@ -22,23 +29,40 @@ class KotlinCreateTestIntention: AbstractKotlinCreateTestIntention() {
}
}
override fun isApplicableForModule(module: Module): Boolean =
override fun isApplicableForModule(module: Module): Boolean {
// TODO: KMP JS case is not applicable
//
// TODO: in short, disabled for K2 as far as it has no J2K for it
//
// Details: CreateTestIntention relies on JavaTestCreation, all test framework templates are java-based,
// and entire logic is java-focused. It's way way more easier and reasonable to reuse it and run J2K
// rather do a copy-cat from java-test-frameworks
false
return !module.platform.isJs()
}
override fun convertJavaClass(
override fun getTempClassName(project: Project, existingClass: KtClassOrObject): String {
// no reason for a new temp class name, reuse existed
return existingClass.name!!
}
override fun convertClass(
project: Project,
generatedClass: PsiClass,
existingClass: KtClassOrObject?,
generatedFile: PsiJavaFile,
generatedFile: PsiFile,
srcModule: Module
) {
TODO("Not yet implemented: J2K is required")
project.executeCommand<Unit>(
KotlinBundle.message("convert.class.0.to.kotlin", generatedClass.name.toString()),
this
) {
runWriteAction {
generatedClass.methods.forEach {
it.throwsList.referenceElements.forEach { referenceElement -> referenceElement.delete() }
}
}
if (existingClass != null) {
activateFileWithPsiElement(existingClass)
} else {
with(PsiDocumentManager.getInstance(project)) {
getDocument(generatedFile)?.let { doPostponedOperationsAndUnblockDocument(it) }
}
}
}
}
}

View File

@@ -245,6 +245,8 @@
implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.overrides.KotlinOverrideHierarchyProvider"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinEqualsHashCodeTemplatesManager"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinToStringTemplatesManager"/>
<testGenerator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.generate.KotlinTestGenerator"/>
</extensions>
<actions>
<group id="KotlinGenerateGroup">

View File

@@ -47,6 +47,105 @@ abstract class KotlinGenerateTestSupportActionBase(
private val methodKind: MethodKind,
) : KotlinGenerateActionBase(), GenerateActionPopupTemplateInjector {
companion object {
private val DUMMY_NAME = "__KOTLIN_RULEZZZ__"
internal fun doGenerate(
editor: Editor,
file: PsiFile, klass: KtClassOrObject,
framework: TestFramework,
methodKind: MethodKind
): KtNamedFunction? {
val project = file.project
val commandName = KotlinBundle.message("command.generate.test.support.generate.test.function")
val fileTemplateDescriptor = methodKind.getFileTemplateDescriptor(framework)
val fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(fileTemplateDescriptor.fileName)
var templateText = fileTemplate.text.replace(BODY_VAR, "")
var name: String? = null
if (templateText.contains(NAME_VAR)) {
name = if (templateText.contains("test$NAME_VAR")) "Name" else "name"
if (!isUnitTestMode()) {
val message = KotlinBundle.message("action.generate.test.support.choose.test.name")
name = Messages.showInputDialog(message, commandName, null, name, NAME_VALIDATOR) ?: return null
}
templateText = templateText.replace(NAME_VAR, DUMMY_NAME)
}
return try {
val factory = KtPsiFactory(project)
var function = factory.createFunction(templateText)
name?.let {
function = substituteNewName(function, it)
}
val functionInPlace = runWriteAction { insertMembersAfterAndReformat(editor, klass, function) }
val (bodyText, needToOverride) = analyzeInModalWindow(functionInPlace, commandName) {
val functionSymbol = functionInPlace.symbol as KaNamedFunctionSymbol
val overriddenSymbols = functionSymbol.directlyOverriddenSymbols.filterIsInstance<KaNamedFunctionSymbol>().toList()
fun isDefaultTemplate(): Boolean =
(functionInPlace.bodyBlockExpression?.text?.trimStart('{')?.trimEnd('}') ?: functionInPlace.bodyExpression?.text).isNullOrBlank()
when (overriddenSymbols.size) {
0 -> if (isDefaultTemplate()) generateUnsupportedOrSuperCall(project, functionSymbol, BodyType.FromTemplate) else null
1 -> generateUnsupportedOrSuperCall(project, overriddenSymbols.single(), BodyType.Super)
else -> generateUnsupportedOrSuperCall(project, overriddenSymbols.first(), BodyType.QualifiedSuper)
} to overriddenSymbols.isNotEmpty()
}
runWriteAction {
if (bodyText != null) {
functionInPlace.bodyExpression?.delete()
functionInPlace.add(KtPsiFactory(project).createBlock(bodyText))
}
if (needToOverride) {
functionInPlace.addModifier(KtTokens.OVERRIDE_KEYWORD)
}
}
setupEditorSelection(editor, functionInPlace)
functionInPlace
} catch (e: IncorrectOperationException) {
val message = KotlinBundle.message("action.generate.test.support.error.cant.generate.method", e.message.toString())
HintManager.getInstance().showErrorHint(editor, message)
null
}
}
private fun substituteNewName(function: KtNamedFunction, name: String): KtNamedFunction {
val psiFactory = KtPsiFactory(function.project)
// First replace all DUMMY_NAME occurrences in names as they need special treatment due to quotation
var function1 = function
function1.accept(
object : KtTreeVisitorVoid() {
private fun getNewId(currentId: String): String? {
if (!currentId.contains(DUMMY_NAME)) return null
return currentId.replace(DUMMY_NAME, name).quoteIfNeeded()
}
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
val nameIdentifier = declaration.nameIdentifier ?: return
val newId = getNewId(nameIdentifier.text) ?: return
declaration.setName(newId)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val newId = getNewId(expression.text) ?: return
expression.replace(psiFactory.createSimpleName(newId))
}
}
)
// Then text-replace remaining occurrences (if any)
val functionText = function1.text
if (functionText.contains(DUMMY_NAME)) {
function1 = psiFactory.createFunction(function1.text.replace(DUMMY_NAME, name))
}
return function1
}
private fun findTargetClass(editor: Editor, file: PsiFile): KtClassOrObject? {
val elementAtCaret = file.findElementAt(editor.caretModel.offset) ?: return null
return elementAtCaret.parentsWithSelf.filterIsInstance<KtClassOrObject>().firstOrNull { !it.isLocal }
@@ -85,7 +184,7 @@ abstract class KotlinGenerateTestSupportActionBase(
}
class Test : KotlinGenerateTestSupportActionBase(MethodKind.TEST) {
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject) = true
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean = true
}
class Data : KotlinGenerateTestSupportActionBase(MethodKind.DATA) {
@@ -117,7 +216,7 @@ abstract class KotlinGenerateTestSupportActionBase(
if (testFrameworkToUse != null) {
val frameworkToUse = findSuitableFrameworks(klass).first { it.name == testFrameworkToUse }
if (isApplicableTo(frameworkToUse, klass)) {
doGenerate(editor, file, klass, frameworkToUse)
doGenerate(editor, file, klass, frameworkToUse, methodKind)
}
} else {
val frameworks = findSuitableFrameworks(klass).filter {
@@ -126,7 +225,7 @@ abstract class KotlinGenerateTestSupportActionBase(
chooseAndPerform(editor, frameworks) {
project.executeCommand(KotlinBundle.message("command.generate.test.support.generate.test.function"), null) {
doGenerate(editor, file, klass, it)
doGenerate(editor, file, klass, it, methodKind)
}
}
}
@@ -134,99 +233,6 @@ abstract class KotlinGenerateTestSupportActionBase(
var testFrameworkToUse: String? = null
private val DUMMY_NAME = "__KOTLIN_RULEZZZ__"
private fun doGenerate(editor: Editor, file: PsiFile, klass: KtClassOrObject, framework: TestFramework) {
val project = file.project
val commandName = KotlinBundle.message("command.generate.test.support.generate.test.function")
val fileTemplateDescriptor = methodKind.getFileTemplateDescriptor(framework)
val fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(fileTemplateDescriptor.fileName)
var templateText = fileTemplate.text.replace(BODY_VAR, "")
var name: String? = null
if (templateText.contains(NAME_VAR)) {
name = if (templateText.contains("test$NAME_VAR")) "Name" else "name"
if (!isUnitTestMode()) {
val message = KotlinBundle.message("action.generate.test.support.choose.test.name")
name = Messages.showInputDialog(message, commandName, null, name, NAME_VALIDATOR) ?: return
}
templateText = templateText.replace(NAME_VAR, DUMMY_NAME)
}
try {
val factory = KtPsiFactory(project)
var function = factory.createFunction(templateText)
name?.let {
function = substituteNewName(function, it)
}
val functionInPlace = runWriteAction { insertMembersAfterAndReformat(editor, klass, function) }
val (bodyText, needToOverride) = analyzeInModalWindow(functionInPlace, commandName) {
val functionSymbol = functionInPlace.symbol as KaNamedFunctionSymbol
val overriddenSymbols = functionSymbol.directlyOverriddenSymbols.filterIsInstance<KaNamedFunctionSymbol>().toList()
fun isDefaultTemplate(): Boolean =
(functionInPlace.bodyBlockExpression?.text?.trimStart('{')?.trimEnd('}') ?: functionInPlace.bodyExpression?.text).isNullOrBlank()
when (overriddenSymbols.size) {
0 -> if (isDefaultTemplate()) generateUnsupportedOrSuperCall(project, functionSymbol, BodyType.FromTemplate) else null
1 -> generateUnsupportedOrSuperCall(project, overriddenSymbols.single(), BodyType.Super)
else -> generateUnsupportedOrSuperCall(project, overriddenSymbols.first(), BodyType.QualifiedSuper)
} to overriddenSymbols.isNotEmpty()
}
runWriteAction {
if (bodyText != null) {
functionInPlace.bodyExpression?.delete()
functionInPlace.add(KtPsiFactory(project).createBlock(bodyText))
}
if (needToOverride) {
functionInPlace.addModifier(KtTokens.OVERRIDE_KEYWORD)
}
}
setupEditorSelection(editor, functionInPlace)
} catch (e: IncorrectOperationException) {
val message = KotlinBundle.message("action.generate.test.support.error.cant.generate.method", e.message.toString())
HintManager.getInstance().showErrorHint(editor, message)
}
}
private fun substituteNewName(function: KtNamedFunction, name: String): KtNamedFunction {
val psiFactory = KtPsiFactory(function.project)
// First replace all DUMMY_NAME occurrences in names as they need special treatment due to quotation
var function1 = function
function1.accept(
object : KtTreeVisitorVoid() {
private fun getNewId(currentId: String): String? {
if (!currentId.contains(DUMMY_NAME)) return null
return currentId.replace(DUMMY_NAME, name).quoteIfNeeded()
}
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
val nameIdentifier = declaration.nameIdentifier ?: return
val newId = getNewId(nameIdentifier.text) ?: return
declaration.setName(newId)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val newId = getNewId(expression.text) ?: return
expression.replace(psiFactory.createSimpleName(newId))
}
}
)
// Then text-replace remaining occurrences (if any)
val functionText = function1.text
if (functionText.contains(DUMMY_NAME)) {
function1 = psiFactory.createFunction(function1.text.replace(DUMMY_NAME, name))
}
return function1
}
override fun createEditTemplateAction(dataContext: DataContext): AnAction? {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return null
val editor = CommonDataKeys.EDITOR.getData(dataContext) ?: return null

View File

@@ -0,0 +1,249 @@
// 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.codeinsight.generate
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.daemon.impl.analysis.ImportsHighlightUtil
import com.intellij.ide.fileTemplates.FileTemplate
import com.intellij.ide.fileTemplates.FileTemplateDescriptor
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.fileTemplates.FileTemplateUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.impl.source.PostprocessReformattingAspect
import com.intellij.psi.search.GlobalSearchScopesCore
import com.intellij.psi.util.PsiUtilCore
import com.intellij.refactoring.util.classMembers.MemberInfo
import com.intellij.testIntegration.TestFramework
import com.intellij.testIntegration.TestIntegrationUtils
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
import com.intellij.testIntegration.createTest.CreateTestDialog
import com.intellij.testIntegration.createTest.TestGenerator
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.core.insertMembersAfter
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.*
import java.util.*
class KotlinTestGenerator: TestGenerator {
override fun generateTest(project: Project, d: CreateTestDialog): PsiElement? {
return PostprocessReformattingAspect.getInstance(project).postponeFormattingInside<PsiElement?> {
runWriteAction {
try {
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace()
val targetClass = createTestClass(d) as? KtClass ?: return@runWriteAction null
val frameworkDescriptor = d.selectedTestFrameworkDescriptor
val defaultSuperClass = frameworkDescriptor.getDefaultSuperClass()
d.getSuperClassName().takeIf { !Comparing.strEqual(it, defaultSuperClass) }?.let { superClassName ->
addSuperClass(targetClass, project, superClassName)
}
val file = targetClass.containingFile
val editor = CodeInsightUtil.positionCursor(
project,
file,
targetClass.body?.lBrace ?: targetClass
) ?: return@runWriteAction null
addTestMethods(
editor,
targetClass,
d.targetClass,
frameworkDescriptor,
d.selectedMethods,
d.shouldGeneratedBefore(),
d.shouldGeneratedAfter()
)
if (file is KtFile) {
val list = file.importList
if (list != null) {
val importStatements = file.importDirectives
if (importStatements.isNotEmpty()) {
val virtualFile = PsiUtilCore.getVirtualFile(list)
if (virtualFile != null) {
val imports: MutableSet<String?> = HashSet<String?>()
for (base in importStatements) {
imports.add(base.text)
}
virtualFile.putCopyableUserData<MutableSet<String?>?>(
ImportsHighlightUtil.IMPORTS_FROM_TEMPLATE,
imports
)
}
}
}
}
return@runWriteAction targetClass
} catch (_: IncorrectOperationException) {
showErrorLater(project, d.className)
return@runWriteAction null
}
}
}
}
override fun toString(): String {
return KotlinBundle.message("intention.create.test.dialog.kotlin")
}
companion object {
private fun createTestClass(d: CreateTestDialog): PsiElement? {
val testFrameworkDescriptor = d.selectedTestFrameworkDescriptor
val fileTemplateDescriptor = TestIntegrationUtils.MethodKind.TEST_CLASS.getFileTemplateDescriptor(testFrameworkDescriptor)
val targetDirectory = d.targetDirectory
val aPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory)
val className = d.className
var targetFile: KtFile? = null
if (aPackage != null) {
val scope = GlobalSearchScopesCore.directoryScope(targetDirectory, false)
targetFile = aPackage.getFiles(scope).firstOrNull { file ->
file is KtFile && file.name == "$className.${KotlinFileType.EXTENSION}"
} as? KtFile
// TODO: it could be that file is exists, but there is no such class declaration in it
}
if (targetFile == null && fileTemplateDescriptor != null) {
val element = createTestClassFromCodeTemplate(d, fileTemplateDescriptor, targetDirectory)
if (element is KtClass) return element
targetFile = element as? KtFile
}
return targetFile?.declarations?.firstOrNull { it is KtClass && it.name == className }?.let { return it }
}
private fun createTestClassFromCodeTemplate(
d: CreateTestDialog,
fileTemplateDescriptor: FileTemplateDescriptor,
targetDirectory: PsiDirectory
): KtElement? {
val templateName = fileTemplateDescriptor.fileName
val project = targetDirectory.getProject()
val fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(templateName)
val defaultProperties = FileTemplateManager.getInstance(project).getDefaultProperties()
val properties = Properties(defaultProperties)
properties.setProperty(FileTemplate.ATTRIBUTE_NAME, d.className)
val targetClass = d.targetClass
if (targetClass != null && targetClass.isValid()) {
properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, targetClass.getQualifiedName())
}
val fileName = d.className + KotlinFileType.DOT_DEFAULT_EXTENSION
return try {
val createdFromTemplate = FileTemplateUtil.createFromTemplate(fileTemplate, fileName, properties, targetDirectory)
createdFromTemplate as? KtElement
} catch (_: Exception) {
null
}
}
private fun addSuperClass(targetClass: KtClass, project: Project, superClassName: String) {
val extendsList = targetClass.superTypeListEntries
val referenceElements = extendsList.flatMap { it.references.toList() }
val psiFactory = KtPsiFactory(project)
val superTypeEntry = psiFactory.createSuperTypeEntry(superClassName)
if (referenceElements.isEmpty()) {
val superTypeListEntry =
targetClass.addSuperTypeListEntry(superTypeEntry)
ShortenReferencesFacility.getInstance().shorten(superTypeListEntry)
} else {
referenceElements[0]!!.element.replace(superTypeEntry)
}
}
@OptIn(KaAllowAnalysisOnEdt::class, KaAllowAnalysisFromWriteAction::class)
fun addTestMethods(
editor: Editor,
targetClass: KtClass,
sourceClass: PsiClass?,
descriptor: TestFramework,
methods: Collection<out MemberInfo>,
generateBefore: Boolean,
generateAfter: Boolean
) {
val existingNames: MutableSet<String?> = HashSet<String?>()
var anchor: KtNamedFunction? = null
if (generateBefore && descriptor.findSetUpMethod(targetClass) == null) {
anchor = allowAnalysisOnEdt {
allowAnalysisFromWriteAction {
KotlinGenerateTestSupportActionBase.doGenerate(editor, targetClass.containingFile, targetClass, descriptor, MethodKind.SET_UP)
}
}
}
if (generateAfter && descriptor.findTearDownMethod(targetClass) == null) {
anchor = allowAnalysisOnEdt {
allowAnalysisFromWriteAction {
KotlinGenerateTestSupportActionBase.doGenerate(editor, targetClass.containingFile, targetClass, descriptor, MethodKind.TEAR_DOWN)
}
}
}
val template = TestIntegrationUtils.createTestMethodTemplate(
MethodKind.TEST, descriptor,
targetClass, sourceClass, null, true, existingNames
)
val elementFactory = JVMElementFactories.getFactory(targetClass.language, targetClass.getProject())
val prefix = try {
elementFactory?.createMethodFromText(template.getTemplateText(), targetClass)?.getName() ?: ""
} catch (_: IncorrectOperationException) {
""
}
for (existingMethod in targetClass.declarations.filterIsInstance<KtNamedFunction>()) {
val name = existingMethod.getName() ?: continue
existingNames.add(StringUtil.decapitalize(name.removePrefix(prefix)))
}
for (m in methods) {
anchor = generateMethod(descriptor, targetClass, sourceClass, editor, m.getMember().getName(),
existingNames, anchor)
}
}
private fun showErrorLater(project: Project?, targetClassName: String) {
ApplicationManager.getApplication().invokeLater(Runnable {
Messages.showErrorDialog(project,
KotlinBundle.message("intention.error.cannot.create.class.message", targetClassName),
KotlinBundle.message("intention.error.cannot.create.class.title"))
})
}
private fun generateMethod(
descriptor: TestFramework?,
targetClass: KtClass,
sourceClass: PsiClass?,
editor: Editor,
name: String?,
existingNames: MutableSet<in String?>?,
anchor: KtNamedFunction?
): KtNamedFunction? {
val project = targetClass.getProject()
val psiFactory = KtPsiFactory(project)
val dummyFunction = psiFactory.createFunction("fun dummy() = Unit")
val function = insertMembersAfter(editor, targetClass, listOf(dummyFunction), anchor).firstOrNull()?.element ?: return null
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument())
TestIntegrationUtils.runTestMethodTemplate(MethodKind.TEST,
descriptor, editor,
targetClass, sourceClass, function,
function.modifierList ?: function
, name,
true,
existingNames)
return function
}
}
}

View File

@@ -2,12 +2,15 @@
package org.jetbrains.kotlin.idea.testIntegration
import com.intellij.codeInsight.navigation.activateFileWithPsiElement
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.module.Module
import com.intellij.openapi.options.advanced.AdvancedSettings
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction
import org.jetbrains.kotlin.idea.base.facet.platform.platform
@@ -28,13 +31,15 @@ class KotlinCreateTestIntention: AbstractKotlinCreateTestIntention() {
override fun isApplicableForModule(module: Module): Boolean =
!(module.platform.isJs() && !AdvancedSettings.getBoolean("kotlin.mpp.experimental"))
override fun convertJavaClass(
override fun convertClass(
project: Project,
generatedClass: PsiClass,
existingClass: KtClassOrObject?,
generatedFile: PsiJavaFile,
generatedFile: PsiFile,
srcModule: Module
) {
if (generatedFile !is PsiJavaFile || generatedClass.language != JavaFileType.INSTANCE) return
project.executeCommand<Unit>(
KotlinBundle.message("convert.class.0.to.kotlin", generatedClass.name.toString()),
this

View File

@@ -1,7 +1,6 @@
// "Add 'actual' modifier" "false"
// ACTION: Create test
// ACTION: Rename file to Foo.kt
// IGNORE_K2
// KTIJ-19789 No diagnostic "actual is missing" on top-level class/function/property in IDE
class <caret>Foo

View File

@@ -1,6 +1,5 @@
// "Add 'actual' modifier" "false"
// ACTION: Create test
// IGNORE_K2
// KTIJ-19789 No diagnostic "actual is missing" on top-level class/function/property in IDE
fun <caret>foo() {}

View File

@@ -4,11 +4,14 @@ package org.jetbrains.kotlin.idea.actions
import com.intellij.ide.fileTemplates.DefaultCreateFromTemplateHandler
import com.intellij.ide.fileTemplates.FileTemplate
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
class KotlinCreateFromTemplateHandler : DefaultCreateFromTemplateHandler() {
override fun handlesTemplate(template: FileTemplate) = template.isTemplateOfType(KotlinFileType.INSTANCE)
override fun handlesTemplate(template: FileTemplate): Boolean = template.isTemplateOfType(KotlinFileType.INSTANCE)
override fun prepareProperties(props: MutableMap<String, Any>) {
val packageName = props[FileTemplate.ATTRIBUTE_PACKAGE_NAME] as? String
@@ -21,4 +24,15 @@ class KotlinCreateFromTemplateHandler : DefaultCreateFromTemplateHandler() {
props[FileTemplate.ATTRIBUTE_NAME] = name.quoteIfNeeded()
}
}
override fun createFromTemplate(
project: Project,
directory: PsiDirectory,
fileName: String?,
template: FileTemplate,
templateText: String,
props: Map<String?, Any?>
): PsiElement {
return super.createFromTemplate(project, directory, fileName, template, templateText, props)
}
}

View File

@@ -46,7 +46,7 @@ abstract class KotlinGenerateActionBase : CodeInsightAction(), CodeInsightAction
protected abstract fun isValidForClass(targetClass: KtClassOrObject): Boolean
override fun startInWriteAction() = false
override fun startInWriteAction(): Boolean = false
override fun getHandler() = this
override fun getHandler(): KotlinGenerateActionBase = this
}

View File

@@ -22,5 +22,7 @@
<orderEntry type="module" module-name="intellij.java.analysis.impl" />
<orderEntry type="module" module-name="intellij.junit" />
<orderEntry type="module" module-name="intellij.java.impl" />
<orderEntry type="module" module-name="kotlin.code-insight.impl-base" />
<orderEntry type="module" module-name="kotlin.base.plugin" />
</component>
</module>

View File

@@ -0,0 +1,5 @@
import junit.framework.TestCase
#parse("File Header.java")
class ${NAME}: TestCase() {
${BODY}
}

View File

@@ -0,0 +1,31 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a Kotlin JUnit 3 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${CLASS_NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the tested class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,5 @@
import org.junit.Assert.*
#parse("File Header.java")
class ${NAME} {
${BODY}
}

View File

@@ -0,0 +1,31 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a Kotlin JUnit 4 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${CLASS_NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the tested class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,5 @@
import org.junit.jupiter.api.Assertions.*
#parse("File Header.java")
class ${NAME} {
${BODY}
}

View File

@@ -0,0 +1,31 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a Kotlin JUnit 5 test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${CLASS_NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the tested class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
</tr>
</table>
</body>
</html>

View File

@@ -233,17 +233,17 @@ class KotlinJUnit3Framework: JUnit3Framework(), KotlinPsiBasedTestFramework {
override fun isIgnoredMethod(declaration: KtNamedFunction): Boolean =
psiBasedDelegate.isIgnoredMethod(declaration)
override fun getSetUpMethodFileTemplateDescriptor(): FileTemplateDescriptor? {
return FileTemplateDescriptor("Kotlin JUnit3 SetUp Function.kt")
}
override fun getSetUpMethodFileTemplateDescriptor(): FileTemplateDescriptor? =
FileTemplateDescriptor("Kotlin JUnit3 SetUp Function.kt")
override fun getTearDownMethodFileTemplateDescriptor(): FileTemplateDescriptor? {
return FileTemplateDescriptor("Kotlin JUnit3 TearDown Function.kt")
}
override fun getTearDownMethodFileTemplateDescriptor(): FileTemplateDescriptor? =
FileTemplateDescriptor("Kotlin JUnit3 TearDown Function.kt")
override fun getTestMethodFileTemplateDescriptor(): FileTemplateDescriptor {
return FileTemplateDescriptor("Kotlin JUnit3 Test Function.kt")
}
override fun getTestMethodFileTemplateDescriptor(): FileTemplateDescriptor =
FileTemplateDescriptor("Kotlin JUnit3 Test Function.kt")
override fun getTestClassFileTemplateDescriptor(): FileTemplateDescriptor? =
FileTemplateDescriptor("Kotlin JUnit3 Test Class.kt")
}
private val TEST_CLASS_FQN = setOf(JUnitUtil.TEST_CASE_CLASS)

View File

@@ -186,6 +186,9 @@ class KotlinJUnit4Framework: JUnit4Framework(), KotlinPsiBasedTestFramework {
return FileTemplateDescriptor("Kotlin JUnit4 Parameters Function.kt")
}
override fun getTestClassFileTemplateDescriptor(): FileTemplateDescriptor? =
FileTemplateDescriptor("Kotlin JUnit4 Test Class.kt")
override fun isFrameworkAvailable(clazz: PsiElement): Boolean {
return super.isFrameworkAvailable(clazz) || clazz is KtClass && psiBasedDelegate.isFrameworkAvailable(clazz)
}

View File

@@ -163,6 +163,9 @@ class KotlinJUnit5Framework: JUnit5Framework(), KotlinPsiBasedTestFramework {
override fun getTestMethodFileTemplateDescriptor(): FileTemplateDescriptor {
return FileTemplateDescriptor("Kotlin JUnit5 Test Function.kt")
}
override fun getTestClassFileTemplateDescriptor(): FileTemplateDescriptor? =
FileTemplateDescriptor("Kotlin JUnit5 Test Class.kt")
}
private val METHOD_ANNOTATION_FQN = setOf(

View File

@@ -23,5 +23,6 @@
<orderEntry type="module" module-name="intellij.java.execution.impl" />
<orderEntry type="module" module-name="intellij.testng" />
<orderEntry type="module" module-name="intellij.java.impl" />
<orderEntry type="module" module-name="kotlin.base.plugin" />
</component>
</module>

View File

@@ -0,0 +1,5 @@
import org.testng.Assert.*
#parse("File Header.java")
class ${NAME} {
${BODY}
}

View File

@@ -0,0 +1,31 @@
<html>
<body>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">
Creates a Kotlin TestNG test class.</font>
</td>
</tr>
</table>
<table width="100%" border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the created class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${CLASS_NAME}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Name of the tested class</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2"><b>${BODY}</b></font></nobr></td>
<td width="10">&nbsp;</td>
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
</tr>
</table>
</body>
</html>

View File

@@ -9,27 +9,21 @@ import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.parentOfType
import com.intellij.util.ThreeState
import com.intellij.util.ThreeState.NO
import com.intellij.util.ThreeState.UNSURE
import com.intellij.util.ThreeState.YES
import com.intellij.util.ThreeState.*
import com.theoryinpractice.testng.TestNGFramework
import com.theoryinpractice.testng.util.TestNGUtil
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider
import org.jetbrains.kotlin.idea.testIntegration.framework.AbstractKotlinPsiBasedTestFramework
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework.Companion.asKtClassOrObject
import org.jetbrains.kotlin.idea.testIntegration.framework.KotlinPsiBasedTestFramework.Companion.asKtNamedFunction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isPrivate
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.collections.plus
class KotlinTestNGFramework: TestNGFramework(), KotlinPsiBasedTestFramework {
private val psiBasedDelegate = object : AbstractKotlinPsiBasedTestFramework() {
@@ -191,6 +185,13 @@ class KotlinTestNGFramework: TestNGFramework(), KotlinPsiBasedTestFramework {
return FileTemplateDescriptor("Kotlin TestNG Test Function.kt")
}
override fun getTestClassFileTemplateDescriptor(): FileTemplateDescriptor? =
if (KotlinPluginModeProvider.isK1Mode()) {
super.getTestClassFileTemplateDescriptor()
} else {
FileTemplateDescriptor("Kotlin TestNG Test Class.kt")
}
override fun getParametersMethodFileTemplateDescriptor(): FileTemplateDescriptor? {
return FileTemplateDescriptor("Kotlin TestNG Parameters Function.kt")
}