Unit Test Tooling: Implement "Create Test" action
#KT-6472 In Progress
This commit is contained in:
@@ -17,8 +17,9 @@
|
|||||||
package org.jetbrains.kotlin.idea.util.application
|
package org.jetbrains.kotlin.idea.util.application
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
|
import com.intellij.openapi.project.DumbService
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
|
||||||
public fun runReadAction<T>(action: () -> T): T {
|
public fun runReadAction<T>(action: () -> T): T {
|
||||||
return ApplicationManager.getApplication().runReadAction<T>(action)
|
return ApplicationManager.getApplication().runReadAction<T>(action)
|
||||||
@@ -32,13 +33,20 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) {
|
|||||||
CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null)
|
CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Project.executeCommand(name: String, groupId: Any? = null, command: () -> Unit) {
|
public fun <T> Project.executeWriteCommand(name: String, groupId: Any? = null, command: () -> T): T {
|
||||||
CommandProcessor.getInstance().executeCommand(this, command, name, groupId)
|
return executeCommand<T>(name, groupId) { runWriteAction(command) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T> Project.executeWriteCommand(name: String, groupId: Any? = null, command: () -> T): T {
|
public fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -> T): T {
|
||||||
var result: T = null as T
|
var result: T = null as T
|
||||||
CommandProcessor.getInstance().executeCommand(this, { result = runWriteAction(command) }, name, groupId)
|
CommandProcessor.getInstance().executeCommand(this, { result = command() }, name, groupId)
|
||||||
@Suppress("USELESS_CAST")
|
@Suppress("USELESS_CAST")
|
||||||
return result as T
|
return result as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun <T> Project.runWithAlternativeResolveEnabled(action: () -> T): T {
|
||||||
|
var result: T = null as T
|
||||||
|
DumbService.getInstance(this).withAlternativeResolveEnabled { result = action() }
|
||||||
|
@Suppress("USELESS_CAST")
|
||||||
|
return result as T
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
class Foo {
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooTest : TestCase {
|
||||||
|
fun testFoo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<spot>class Foo</spot> {
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This intention generates a test case for the selected class. The generated class contains skeleton test functions for the chosen public functions.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1062,6 +1062,11 @@
|
|||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention</className>
|
||||||
|
<category>Kotlin</category>
|
||||||
|
</intentionAction>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
||||||
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
||||||
groupName="Kotlin"
|
groupName="Kotlin"
|
||||||
|
|||||||
@@ -32,67 +32,104 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.openapi.vfs.VirtualFileVisitor
|
import com.intellij.openapi.vfs.VirtualFileVisitor
|
||||||
import com.intellij.psi.PsiJavaFile
|
import com.intellij.psi.PsiJavaFile
|
||||||
import com.intellij.psi.PsiManager
|
import com.intellij.psi.PsiManager
|
||||||
|
import org.jetbrains.kotlin.idea.core.refactoring.toPsiFile
|
||||||
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
||||||
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||||
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||||
|
import org.jetbrains.kotlin.psi.JetFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
public class JavaToKotlinAction : AnAction() {
|
public class JavaToKotlinAction : AnAction() {
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
companion object {
|
||||||
ApplicationManager.getApplication().saveAll()
|
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
|
||||||
|
val ioFile = File(javaFile.getPath().replace('/', File.separatorChar))
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while (true) {
|
||||||
|
val fileName = javaFile.getNameWithoutExtension() + (if (i > 0) i else "") + ".kt"
|
||||||
|
if (!ioFile.resolveSibling(fileName).exists()) return fileName
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
|
||||||
|
val result = ArrayList<VirtualFile>()
|
||||||
|
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
|
||||||
|
val virtualFile = psiFile.getVirtualFile()
|
||||||
|
val fileName = uniqueKotlinFileName(virtualFile)
|
||||||
|
try {
|
||||||
|
virtualFile.rename(this, fileName)
|
||||||
|
virtualFile.setBinaryContent(CharsetToolkit.getUtf8Bytes(text))
|
||||||
|
result.add(virtualFile)
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, enableExternalCodeProcessing: Boolean = true): List<JetFile> {
|
||||||
|
ApplicationManager.getApplication().saveAll()
|
||||||
|
|
||||||
|
var converterResult: JavaToKotlinConverter.FilesResult? = null
|
||||||
|
fun convert() {
|
||||||
|
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
|
||||||
|
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
|
||||||
|
}
|
||||||
|
|
||||||
|
val title = "Convert Java to Kotlin"
|
||||||
|
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
|
{
|
||||||
|
runReadAction(::convert)
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
true,
|
||||||
|
project)) return emptyList()
|
||||||
|
|
||||||
|
|
||||||
|
var externalCodeUpdate: (() -> Unit)? = null
|
||||||
|
|
||||||
|
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
|
||||||
|
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
|
||||||
|
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
|
||||||
|
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
|
{
|
||||||
|
runReadAction {
|
||||||
|
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().getProgressIndicator())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
true,
|
||||||
|
project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return project.executeWriteCommand("Convert files from Java to Kotlin", null) {
|
||||||
|
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
|
||||||
|
|
||||||
|
val newFiles = saveResults(javaFiles, converterResult!!.results)
|
||||||
|
|
||||||
|
externalCodeUpdate?.invoke()
|
||||||
|
|
||||||
|
newFiles.singleOrNull()?.let {
|
||||||
|
FileEditorManager.getInstance(project).openFile(it, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
newFiles.map { it.toPsiFile(project) as JetFile }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
val javaFiles = selectedJavaFiles(e).toList()
|
val javaFiles = selectedJavaFiles(e).toList()
|
||||||
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
|
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
|
||||||
|
convertFiles(javaFiles, project)
|
||||||
var converterResult: JavaToKotlinConverter.FilesResult? = null
|
|
||||||
fun convert() {
|
|
||||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
|
|
||||||
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
|
|
||||||
}
|
|
||||||
|
|
||||||
val title = "Convert Java to Kotlin"
|
|
||||||
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
|
||||||
{
|
|
||||||
runReadAction(::convert)
|
|
||||||
},
|
|
||||||
title,
|
|
||||||
true,
|
|
||||||
project)) return
|
|
||||||
|
|
||||||
|
|
||||||
var externalCodeUpdate: (() -> Unit)? = null
|
|
||||||
|
|
||||||
if (converterResult!!.externalCodeProcessing != null) {
|
|
||||||
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
|
|
||||||
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
|
|
||||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
|
||||||
{
|
|
||||||
runReadAction {
|
|
||||||
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().getProgressIndicator())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title,
|
|
||||||
true,
|
|
||||||
project)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project.executeWriteCommand("Convert files from Java to Kotlin") {
|
|
||||||
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
|
|
||||||
|
|
||||||
val newFiles = saveResults(javaFiles, converterResult!!.results)
|
|
||||||
|
|
||||||
externalCodeUpdate?.invoke()
|
|
||||||
|
|
||||||
newFiles.singleOrNull()?.let {
|
|
||||||
FileEditorManager.getInstance(project).openFile(it, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(e: AnActionEvent) {
|
override fun update(e: AnActionEvent) {
|
||||||
@@ -126,32 +163,4 @@ public class JavaToKotlinAction : AnAction() {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
|
|
||||||
val result = ArrayList<VirtualFile>()
|
|
||||||
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
|
|
||||||
val virtualFile = psiFile.getVirtualFile()
|
|
||||||
val fileName = uniqueKotlinFileName(virtualFile)
|
|
||||||
try {
|
|
||||||
virtualFile.rename(this, fileName)
|
|
||||||
virtualFile.setBinaryContent(CharsetToolkit.getUtf8Bytes(text))
|
|
||||||
result.add(virtualFile)
|
|
||||||
}
|
|
||||||
catch (e: IOException) {
|
|
||||||
MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
|
|
||||||
val ioFile = File(javaFile.getPath().replace('/', File.separatorChar))
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (true) {
|
|
||||||
val fileName = javaFile.getNameWithoutExtension() + (if (i > 0) i else "") + ".kt"
|
|
||||||
if (!ioFile.resolveSibling(fileName).exists()) return fileName
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-10
@@ -21,14 +21,12 @@ import com.intellij.codeInsight.generation.actions.GenerateActionPopupTemplateIn
|
|||||||
import com.intellij.codeInsight.hint.HintManager
|
import com.intellij.codeInsight.hint.HintManager
|
||||||
import com.intellij.ide.fileTemplates.FileTemplateManager
|
import com.intellij.ide.fileTemplates.FileTemplateManager
|
||||||
import com.intellij.ide.fileTemplates.impl.AllFileTemplatesConfigurable
|
import com.intellij.ide.fileTemplates.impl.AllFileTemplatesConfigurable
|
||||||
import com.intellij.lang.java.JavaLanguage
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.extensions.Extensions
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.InputValidator
|
import com.intellij.openapi.ui.InputValidator
|
||||||
import com.intellij.openapi.ui.Messages
|
import com.intellij.openapi.ui.Messages
|
||||||
@@ -42,7 +40,6 @@ import com.intellij.testIntegration.TestFramework
|
|||||||
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
|
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
|
||||||
import com.intellij.ui.components.JBList
|
import com.intellij.ui.components.JBList
|
||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import com.intellij.util.SmartList
|
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
@@ -52,6 +49,7 @@ import org.jetbrains.kotlin.idea.core.overrideImplement.generateUnsupportedOrSup
|
|||||||
import org.jetbrains.kotlin.idea.core.refactoring.j2k
|
import org.jetbrains.kotlin.idea.core.refactoring.j2k
|
||||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
|
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
|
||||||
import org.jetbrains.kotlin.idea.quickfix.generateMember
|
import org.jetbrains.kotlin.idea.quickfix.generateMember
|
||||||
|
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.lexer.JetTokens
|
import org.jetbrains.kotlin.lexer.JetTokens
|
||||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||||
@@ -69,13 +67,6 @@ abstract class KotlinGenerateTestSupportActionBase(
|
|||||||
return elementAtCaret.parentsWithSelf.filterIsInstance<JetClassOrObject>().firstOrNull { !it.isLocal() }
|
return elementAtCaret.parentsWithSelf.filterIsInstance<JetClassOrObject>().firstOrNull { !it.isLocal() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findSuitableFrameworks(klass: JetClassOrObject): List<TestFramework> {
|
|
||||||
val lightClass = klass.toLightClass() ?: return emptyList()
|
|
||||||
val frameworks = Extensions.getExtensions(TestFramework.EXTENSION_NAME).filter { it.language == JavaLanguage.INSTANCE }
|
|
||||||
return frameworks.firstOrNull { it.isTestClass(lightClass) }?.let { listOf(it) }
|
|
||||||
?: frameworks.filterTo(SmartList<TestFramework>()) { it.isPotentialTestClass(lightClass) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun chooseAndPerform(editor: Editor, frameworks: List<TestFramework>, consumer: (TestFramework) -> Unit) {
|
private fun chooseAndPerform(editor: Editor, frameworks: List<TestFramework>, consumer: (TestFramework) -> Unit) {
|
||||||
frameworks.ifEmpty { return }
|
frameworks.ifEmpty { return }
|
||||||
frameworks.singleOrNull()?.let { return consumer(it) }
|
frameworks.singleOrNull()?.let { return consumer(it) }
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.testIntegration
|
||||||
|
|
||||||
|
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.testIntegration.createTest.CreateTestDialog
|
||||||
|
|
||||||
|
class KotlinCreateTestDialog(project: Project,
|
||||||
|
title: String,
|
||||||
|
targetClass: PsiClass?,
|
||||||
|
targetPackage: PsiPackage?,
|
||||||
|
targetModule: Module) : CreateTestDialog(project, title, targetClass, targetPackage, targetModule) {
|
||||||
|
public var explicitClassName: String? = null
|
||||||
|
|
||||||
|
override fun getClassName() = explicitClassName ?: super.getClassName()
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.testIntegration
|
||||||
|
|
||||||
|
import com.intellij.CommonBundle
|
||||||
|
import com.intellij.codeInsight.CodeInsightBundle
|
||||||
|
import com.intellij.codeInsight.FileModificationService
|
||||||
|
import com.intellij.codeInsight.navigation.NavigationUtil
|
||||||
|
import com.intellij.ide.util.PropertiesComponent
|
||||||
|
import com.intellij.lang.java.JavaLanguage
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
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.GlobalSearchScopesCore
|
||||||
|
import com.intellij.testIntegration.createTest.CreateTestAction
|
||||||
|
import com.intellij.testIntegration.createTest.TestGenerators
|
||||||
|
import org.jetbrains.kotlin.asJava.KotlinLightClass
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
|
import org.jetbrains.kotlin.idea.core.getPackage
|
||||||
|
import org.jetbrains.kotlin.idea.core.refactoring.j2k
|
||||||
|
import org.jetbrains.kotlin.idea.core.refactoring.toPsiDirectory
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingRangeIntention
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWithAlternativeResolveEnabled
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.psi.JetClass
|
||||||
|
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.JetEnumEntry
|
||||||
|
import org.jetbrains.kotlin.psi.JetNamedFunction
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class KotlinCreateTestIntention : JetSelfTargetingRangeIntention<JetClassOrObject>(
|
||||||
|
JetClassOrObject::class.java,
|
||||||
|
CodeInsightBundle.message("intention.create.test")
|
||||||
|
) {
|
||||||
|
override fun applicabilityRange(element: JetClassOrObject): TextRange? {
|
||||||
|
if (element.isLocal()) return null
|
||||||
|
if (element is JetEnumEntry) return null
|
||||||
|
if (element is JetClass && (element.isAnnotation() || element.isInterface())) return null
|
||||||
|
if (ModuleUtilCore.findModuleForPsiElement(element) == null) return null
|
||||||
|
if (element.resolveToDescriptorIfAny() == null) return null
|
||||||
|
|
||||||
|
return TextRange(
|
||||||
|
element.startOffset,
|
||||||
|
element.getDelegationSpecifierList()?.startOffset ?: element.getBody()?.startOffset ?: element.endOffset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyTo(element: JetClassOrObject, editor: Editor) {
|
||||||
|
object : CreateTestAction() {
|
||||||
|
// Based on the com.intellij.testIntegration.createTest.JavaTestGenerator.createTestClass()
|
||||||
|
private fun findTestClass(targetDirectory: PsiDirectory, className: String): PsiClass? {
|
||||||
|
val psiPackage = targetDirectory.getPackage() ?: return null
|
||||||
|
val scope = GlobalSearchScopesCore.directoryScope(targetDirectory, false)
|
||||||
|
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 sequence(0) { it + 1 }
|
||||||
|
.map { "$baseName$it" }
|
||||||
|
.first { psiDir.findFile("$it.java") == null && findTestClass(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
|
||||||
|
val propertiesComponent = PropertiesComponent.getInstance()
|
||||||
|
val testFolders = HashSet<VirtualFile>()
|
||||||
|
CreateTestAction.checkForTestRoots(srcModule, testFolders)
|
||||||
|
if (testFolders.isEmpty() && !propertiesComponent.getBoolean("create.test.in.the.same.root")) {
|
||||||
|
if (Messages.showOkCancelDialog(
|
||||||
|
project,
|
||||||
|
"Create test in the same source root?",
|
||||||
|
"No Test Roots Found",
|
||||||
|
Messages.getQuestionIcon()) != Messages.OK) return
|
||||||
|
|
||||||
|
propertiesComponent.setValue("create.test.in.the.same.root", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val srcClass = CreateTestAction.getContainingClass(element) ?: return
|
||||||
|
|
||||||
|
val srcDir = element.containingFile.containingDirectory
|
||||||
|
val srcPackage = JavaDirectoryService.getInstance().getPackage(srcDir)
|
||||||
|
|
||||||
|
val dialog = KotlinCreateTestDialog(project, text, srcClass, srcPackage, srcModule)
|
||||||
|
if (!dialog.showAndGet()) return
|
||||||
|
|
||||||
|
val existingClass = (findTestClass(dialog.targetDirectory, dialog.className) as? KotlinLightClass)?.getOrigin()
|
||||||
|
if (existingClass != null) {
|
||||||
|
// TODO: Override dialog method when it becomes protected
|
||||||
|
val answer = Messages.showYesNoDialog(
|
||||||
|
project,
|
||||||
|
"Kotlin class '${existingClass.name}' already exists. Do you want to update it?",
|
||||||
|
CommonBundle.getErrorTitle(),
|
||||||
|
"Rewrite",
|
||||||
|
"Cancel",
|
||||||
|
Messages.getErrorIcon()
|
||||||
|
)
|
||||||
|
if (answer == Messages.NO) return
|
||||||
|
}
|
||||||
|
|
||||||
|
val generatedClass = project.executeCommand(CodeInsightBundle.message("intention.create.test"), this) {
|
||||||
|
val generator = TestGenerators.INSTANCE.forLanguage(dialog.selectedTestFrameworkDescriptor.language)
|
||||||
|
project.runWithAlternativeResolveEnabled {
|
||||||
|
if (existingClass != null) {
|
||||||
|
dialog.explicitClassName = getTempJavaClassName(project, existingClass.containingFile.virtualFile)
|
||||||
|
}
|
||||||
|
generator.generateTest(project, dialog)
|
||||||
|
}
|
||||||
|
} as? PsiClass ?: return
|
||||||
|
|
||||||
|
val generatedFile = generatedClass.containingFile as? PsiJavaFile ?: return
|
||||||
|
|
||||||
|
if (generatedClass.language == JavaLanguage.INSTANCE) {
|
||||||
|
project.executeCommand("Convert class '${generatedClass.name}' to Kotlin", this) {
|
||||||
|
runWriteAction {
|
||||||
|
generatedClass.methods.forEach { it.throwsList.referenceElements.forEach { it.delete() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingClass != null) {
|
||||||
|
runWriteAction {
|
||||||
|
val existingMethodNames = existingClass
|
||||||
|
.declarations
|
||||||
|
.filterIsInstance<JetNamedFunction>()
|
||||||
|
.mapTo(HashSet()) { it.name }
|
||||||
|
generatedClass
|
||||||
|
.methods
|
||||||
|
.filter { it.name !in existingMethodNames }
|
||||||
|
.forEach { it.j2k()?.let { existingClass.addDeclaration(it) } }
|
||||||
|
generatedClass.delete()
|
||||||
|
}
|
||||||
|
NavigationUtil.activateFileWithPsiElement(existingClass)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
JavaToKotlinAction.convertFiles(generatedFile.singletonList(), project, false).singleOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.invoke(element.project, editor, element.toLightClass()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.testIntegration
|
||||||
|
|
||||||
|
import com.intellij.lang.java.JavaLanguage
|
||||||
|
import com.intellij.openapi.extensions.Extensions
|
||||||
|
import com.intellij.testIntegration.TestFramework
|
||||||
|
import com.intellij.util.SmartList
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||||
|
|
||||||
|
public fun findSuitableFrameworks(klass: JetClassOrObject): List<TestFramework> {
|
||||||
|
val lightClass = klass.toLightClass() ?: return emptyList()
|
||||||
|
val frameworks = Extensions.getExtensions(TestFramework.EXTENSION_NAME).filter { it.language == JavaLanguage.INSTANCE }
|
||||||
|
return frameworks.firstOrNull { it.isTestClass(lightClass) }?.let { listOf(it) }
|
||||||
|
?: frameworks.filterTo(SmartList<TestFramework>()) { it.isPotentialTestClass(lightClass) }
|
||||||
|
}
|
||||||
idea/testData/quickfix/createFromUsage/createClass/delegationSpecifier/classDelegatorToSuperclass.kt
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
// "Create class 'A'" "false"
|
// "Create class 'A'" "false"
|
||||||
// ACTION: Create interface 'A'
|
// ACTION: Create interface 'A'
|
||||||
|
// ACTION: Create Test
|
||||||
// ERROR: Unresolved reference: A
|
// ERROR: Unresolved reference: A
|
||||||
package p
|
package p
|
||||||
|
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
// "class org.jetbrains.kotlin.idea.quickfix.AddOpenModifierToClassDeclarationFix" "false"
|
// "class org.jetbrains.kotlin.idea.quickfix.AddOpenModifierToClassDeclarationFix" "false"
|
||||||
// ERROR: This type is final, so it cannot be inherited from
|
// ERROR: This type is final, so it cannot be inherited from
|
||||||
|
// ACTION: Create Test
|
||||||
import testPackage.*
|
import testPackage.*
|
||||||
|
|
||||||
class foo : <caret>JavaClass() {}
|
class foo : <caret>JavaClass() {}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// "Cast expression 'Foo<Number>()' to 'Foo<Int>'" "false"
|
// "Cast expression 'Foo<Number>()' to 'Foo<Int>'" "false"
|
||||||
|
// ACTION: Create Test
|
||||||
// ERROR: <html>Type mismatch.<table><tr><td>Required:</td><td>Foo<kotlin.Int></td></tr><tr><td>Found:</td><td>Foo<kotlin.Number></td></tr></table></html>
|
// ERROR: <html>Type mismatch.<table><tr><td>Required:</td><td>Foo<kotlin.Int></td></tr><tr><td>Found:</td><td>Foo<kotlin.Number></td></tr></table></html>
|
||||||
class Foo<T>
|
class Foo<T>
|
||||||
|
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
// "Remove 'val' from parameter" "false"
|
// "Remove 'val' from parameter" "false"
|
||||||
// ACTION: Make internal
|
// ACTION: Make internal
|
||||||
// ACTION: Make private
|
// ACTION: Make private
|
||||||
|
// ACTION: Create Test
|
||||||
class C(<caret>val x: String) {
|
class C(<caret>val x: String) {
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user