mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[kotlin] Port Create Test intention to K2
#KTIJ-30467 #KTIJ-9787 GitOrigin-RevId: efb5d9d40646aeb861548eb4d4def625192f5900
This commit is contained in:
committed by
intellij-monorepo-bot
parent
60527dd48d
commit
64e6daa2b6
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
import junit.framework.TestCase
|
||||
#parse("File Header.java")
|
||||
class ${NAME}: TestCase() {
|
||||
${BODY}
|
||||
}
|
||||
@@ -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"> </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"> </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"> </td>
|
||||
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.junit.Assert.*
|
||||
#parse("File Header.java")
|
||||
class ${NAME} {
|
||||
${BODY}
|
||||
}
|
||||
@@ -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"> </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"> </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"> </td>
|
||||
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
#parse("File Header.java")
|
||||
class ${NAME} {
|
||||
${BODY}
|
||||
}
|
||||
@@ -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"> </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"> </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"> </td>
|
||||
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.testng.Assert.*
|
||||
#parse("File Header.java")
|
||||
class ${NAME} {
|
||||
${BODY}
|
||||
}
|
||||
@@ -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"> </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"> </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"> </td>
|
||||
<td width="100%" valign="top"><font face="verdana" size="-1">Generated class body</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user