Extract parts of plugin that depend on Java-only plugins to idea-jvn
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="idea-full" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="junit-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="testng-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="coverage-plugin" level="project" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="idea" />
|
||||
<orderEntry type="module" module-name="light-classes" />
|
||||
<orderEntry type="library" name="java-decompiler-plugin" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,5 @@
|
||||
<idea-plugin>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<javaCoverageEngineExtension implementation="org.jetbrains.kotlin.idea.coverage.KotlinCoverageExtension"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,7 @@
|
||||
<idea-plugin>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationService serviceInterface="org.jetbrains.kotlin.idea.internal.KotlinDecompilerService"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.internal.KotlinDecompilerServiceImpl"/>
|
||||
<writingAccessProvider implementation="org.jetbrains.kotlin.idea.internal.DecompiledFileWritingAccessProvider"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,6 @@
|
||||
<idea-plugin>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinJUnitRunConfigurationProducer"/>
|
||||
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinPatternConfigurationProducer"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,5 @@
|
||||
<idea-plugin>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinTestNgConfigurationProducer"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.coverage
|
||||
|
||||
import com.intellij.coverage.CoverageSuitesBundle
|
||||
import com.intellij.coverage.JavaCoverageAnnotator
|
||||
import com.intellij.coverage.JavaCoverageEngineExtension
|
||||
import com.intellij.coverage.PackageAnnotator
|
||||
import com.intellij.execution.configurations.RunConfigurationBase
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.roots.CompilerModuleExtension
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.run.JetRunConfiguration
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
|
||||
class KotlinCoverageExtension : JavaCoverageEngineExtension() {
|
||||
private val LOG = Logger.getInstance(KotlinCoverageExtension::class.java)
|
||||
|
||||
override fun isApplicableTo(conf: RunConfigurationBase?): Boolean = conf is JetRunConfiguration
|
||||
|
||||
override fun suggestQualifiedName(sourceFile: PsiFile, classes: Array<out PsiClass>, names: MutableSet<String>): Boolean {
|
||||
if (sourceFile is KtFile) {
|
||||
val qNames = collectGeneratedClassQualifiedNames(findOutputRoot(sourceFile), sourceFile)
|
||||
if (qNames != null) {
|
||||
names.addAll(qNames)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Implements API added in IDEA 14.1
|
||||
override fun getSummaryCoverageInfo(coverageAnnotator: JavaCoverageAnnotator,
|
||||
element: PsiNamedElement): PackageAnnotator.ClassCoverageInfo? {
|
||||
if (element !is KtFile) {
|
||||
return null
|
||||
}
|
||||
LOG.info("Retrieving coverage for " + element.name)
|
||||
|
||||
val qualifiedNames = collectGeneratedClassQualifiedNames(findOutputRoot(element), element)
|
||||
return if (qualifiedNames == null) null else totalCoverageForQualifiedNames(coverageAnnotator, qualifiedNames)
|
||||
}
|
||||
|
||||
// Implements API added in IDEA 14.1
|
||||
override fun keepCoverageInfoForClassWithoutSource(bundle: CoverageSuitesBundle, classFile: File): Boolean {
|
||||
// TODO check scope and source roots
|
||||
return true // keep everything, sort it out later
|
||||
}
|
||||
|
||||
override fun collectOutputFiles(srcFile: PsiFile,
|
||||
output: VirtualFile?,
|
||||
testoutput: VirtualFile?,
|
||||
suite: CoverageSuitesBundle,
|
||||
classFiles: MutableSet<File>): Boolean {
|
||||
if (srcFile is KtFile) {
|
||||
val fileIndex = ProjectRootManager.getInstance(srcFile.getProject()).fileIndex
|
||||
if (fileIndex.isInLibraryClasses(srcFile.getVirtualFile()) ||
|
||||
fileIndex.isInLibrarySource(srcFile.getVirtualFile())) {
|
||||
return false
|
||||
}
|
||||
|
||||
runReadAction {
|
||||
val outputRoot = findOutputRoot(srcFile)
|
||||
val existingClassFiles = getClassesGeneratedFromFile(outputRoot, srcFile)
|
||||
existingClassFiles.mapTo(classFiles) { File(it.path) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinCoverageExtension::class.java)
|
||||
|
||||
fun collectGeneratedClassQualifiedNames(outputRoot: VirtualFile?, file: KtFile): List<String>? {
|
||||
val existingClassFiles = getClassesGeneratedFromFile(outputRoot, file)
|
||||
if (existingClassFiles.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
LOG.debug("Classfiles: [${existingClassFiles.joinToString { it.name }}]")
|
||||
return existingClassFiles.map {
|
||||
val relativePath = VfsUtilCore.getRelativePath(it, outputRoot!!)!!
|
||||
StringUtil.trimEnd(relativePath, ".class").replace("/", ".")
|
||||
}
|
||||
}
|
||||
|
||||
private fun totalCoverageForQualifiedNames(coverageAnnotator: JavaCoverageAnnotator,
|
||||
qualifiedNames: List<String>): PackageAnnotator.ClassCoverageInfo {
|
||||
val result = PackageAnnotator.ClassCoverageInfo()
|
||||
result.totalClassCount = 0
|
||||
qualifiedNames.forEach {
|
||||
val classInfo = coverageAnnotator.getClassCoverageInfo(it)
|
||||
if (classInfo != null) {
|
||||
result.totalClassCount += classInfo.totalClassCount
|
||||
result.coveredClassCount += classInfo.coveredClassCount
|
||||
result.totalMethodCount += classInfo.totalMethodCount
|
||||
result.coveredMethodCount += classInfo.coveredMethodCount
|
||||
result.totalLineCount += classInfo.totalLineCount
|
||||
result.fullyCoveredLineCount += classInfo.fullyCoveredLineCount
|
||||
result.partiallyCoveredLineCount += classInfo.partiallyCoveredLineCount
|
||||
}
|
||||
else {
|
||||
LOG.debug("Found no coverage for $it")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getClassesGeneratedFromFile(outputRoot: VirtualFile?, file: KtFile): List<VirtualFile> {
|
||||
val relativePath = file.packageFqName.asString().replace('.', '/')
|
||||
val packageOutputDir = outputRoot?.findFileByRelativePath(relativePath)
|
||||
if (packageOutputDir == null) return listOf()
|
||||
|
||||
val prefixes = collectClassFilePrefixes(file)
|
||||
LOG.debug("Classfile prefixes: [${prefixes.joinToString(", ")}]")
|
||||
return packageOutputDir.children.filter {
|
||||
file -> prefixes.any {
|
||||
(file.name.startsWith(it + "$") && FileUtilRt.getExtension(file.name) == "class") ||
|
||||
file.name == it + ".class"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findOutputRoot(file: KtFile): VirtualFile? {
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(file)
|
||||
if (module == null) return null
|
||||
val fileIndex = ProjectRootManager.getInstance(file.project).fileIndex
|
||||
val inTests = fileIndex.isInTestSourceContent(file.virtualFile)
|
||||
val compilerOutputExtension = CompilerModuleExtension.getInstance(module)
|
||||
return if (inTests)
|
||||
compilerOutputExtension!!.compilerOutputPathForTests
|
||||
else
|
||||
compilerOutputExtension!!.compilerOutputPath
|
||||
}
|
||||
|
||||
private fun collectClassFilePrefixes(file: KtFile): Collection<String> {
|
||||
val result = file.children.filter { it is KtClassOrObject }.map { (it as KtClassOrObject).name!! }
|
||||
val packagePartFqName = JvmFileClassUtil.getFileClassInfoNoResolve(file).fileClassFqName
|
||||
return result.union(arrayListOf(packagePartFqName.shortName().asString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.internal
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.WritingAccessProvider
|
||||
import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem
|
||||
|
||||
class DecompiledFileWritingAccessProvider : WritingAccessProvider() {
|
||||
override fun isPotentiallyWritable(file: VirtualFile): Boolean {
|
||||
if (file.fileSystem is DummyFileSystem && file.parent?.name == KOTLIN_DECOMPILED_FOLDER) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun requestWriting(vararg files: VirtualFile): Collection<VirtualFile> = emptyList()
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.internal
|
||||
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.java.decompiler.IdeaLogger
|
||||
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
||||
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.actions.canBeDecompiledToJava
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
import java.util.jar.Manifest
|
||||
|
||||
class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
||||
override fun decompile(file: KtFile): String? {
|
||||
|
||||
val bytecodeMap = when {
|
||||
file.canBeDecompiledToJava() -> bytecodeMapForExistingClassfile(file.virtualFile)
|
||||
!file.isCompiled -> bytecodeMapForSourceFile(file)
|
||||
else -> return null
|
||||
}
|
||||
val resultSaver = KotlinResultSaver()
|
||||
val options = hashMapOf<String, Any>(
|
||||
IFernflowerPreferences.REMOVE_BRIDGE to "0"
|
||||
)
|
||||
|
||||
val bytecodeProvider = IBytecodeProvider {
|
||||
externalPath, _ ->
|
||||
val path = File(FileUtil.toSystemIndependentName(externalPath))
|
||||
bytecodeMap[path]?.invoke()
|
||||
}
|
||||
|
||||
val decompiler = BaseDecompiler(bytecodeProvider, resultSaver, options, IdeaLogger())
|
||||
for (path in bytecodeMap.keys) {
|
||||
decompiler.addSpace(path, true)
|
||||
}
|
||||
decompiler.decompileContext()
|
||||
return resultSaver.resultText
|
||||
}
|
||||
|
||||
private fun bytecodeMapForExistingClassfile(file: VirtualFile): Map<File, () -> ByteArray> {
|
||||
val mask = "${file.nameWithoutExtension}$"
|
||||
val files =
|
||||
mapOf(file.path to file) +
|
||||
file.parent.children.filter {
|
||||
it.nameWithoutExtension.startsWith(mask) && it.fileType === StdFileTypes.CLASS
|
||||
}.map { it.path to it }
|
||||
|
||||
return files.entries.associate {
|
||||
Pair(File(it.key), { it.value.contentsToByteArray(false) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun bytecodeMapForSourceFile(file: KtFile): Map<File, () -> ByteArray> {
|
||||
val configuration = CompilerConfiguration().apply {
|
||||
languageVersionSettings = file.languageVersionSettings
|
||||
}
|
||||
val generationState = KotlinBytecodeToolWindow.compileSingleFile(file, configuration)
|
||||
|
||||
val bytecodeMap = hashMapOf<File, () -> ByteArray>()
|
||||
generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
|
||||
bytecodeMap[File("/${it.relativePath}").absoluteFile] = { it.asByteArray() }
|
||||
}
|
||||
return bytecodeMap
|
||||
}
|
||||
|
||||
class KotlinResultSaver : IResultSaver {
|
||||
private val decompiledText = mutableMapOf<String, String>()
|
||||
|
||||
val resultText: String
|
||||
get() {
|
||||
decompiledText.values.singleOrNull()?.let { return it }
|
||||
return buildString {
|
||||
for ((filename, content) in decompiledText) {
|
||||
appendln("// $filename")
|
||||
append(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveFolder(path: String?) { }
|
||||
|
||||
override fun closeArchive(path: String?, archiveName: String?) { }
|
||||
|
||||
override fun copyEntry(source: String?, path: String?, archiveName: String?, entry: String?) { }
|
||||
|
||||
override fun createArchive(path: String?, archiveName: String?, manifest: Manifest?) { }
|
||||
|
||||
override fun saveClassFile(path: String?, qualifiedName: String?, entryName: String?, content: String?, mapping: IntArray?) {
|
||||
if (entryName != null && content != null) {
|
||||
decompiledText[entryName] = content
|
||||
}
|
||||
}
|
||||
|
||||
override fun copyFile(source: String?, path: String?, entryName: String?) { }
|
||||
|
||||
override fun saveClassEntry(path: String?, archiveName: String?, qualifiedName: String?, entryName: String?, content: String?) { }
|
||||
|
||||
override fun saveDirEntry(path: String?, archiveName: String?, entryName: String?) { }
|
||||
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.run
|
||||
|
||||
import com.intellij.execution.*
|
||||
import com.intellij.execution.actions.ConfigurationContext
|
||||
import com.intellij.execution.actions.ConfigurationFromContext
|
||||
import com.intellij.execution.actions.RunConfigurationProducer
|
||||
import com.intellij.execution.configurations.ModuleBasedConfiguration
|
||||
import com.intellij.execution.junit.*
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
|
||||
class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfiguration>(JUnitConfigurationType.getInstance()) {
|
||||
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
|
||||
return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(PatternConfigurationProducer::class.java)
|
||||
}
|
||||
|
||||
override fun isConfigurationFromContext(configuration: JUnitConfiguration,
|
||||
context: ConfigurationContext): Boolean {
|
||||
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val leaf = context.location?.psiElement ?: return false
|
||||
val methodLocation = getTestMethodLocation(leaf)
|
||||
val testClass = getTestClass(leaf)
|
||||
val testObject = configuration.testObject
|
||||
|
||||
if (!testObject.isConfiguredByElement(configuration, testClass, methodLocation?.psiElement, null, null)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return settingsMatchTemplate(configuration, context)
|
||||
}
|
||||
|
||||
// copied from JUnitConfigurationProducer in IDEA
|
||||
private fun settingsMatchTemplate(configuration: JUnitConfiguration, context: ConfigurationContext): Boolean {
|
||||
val predefinedConfiguration = context.getOriginalConfiguration(JUnitConfigurationType.getInstance())
|
||||
|
||||
val vmParameters = (predefinedConfiguration as? CommonJavaRunConfigurationParameters)?.vmParameters
|
||||
if (vmParameters != null && configuration.vmParameters != vmParameters) return false
|
||||
|
||||
val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory)
|
||||
val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module
|
||||
val configurationModule = configuration.configurationModule.module
|
||||
return configurationModule == context.location?.module || configurationModule == predefinedModule
|
||||
}
|
||||
|
||||
override fun setupConfigurationFromContext(configuration: JUnitConfiguration,
|
||||
context: ConfigurationContext,
|
||||
sourceElement: Ref<PsiElement>): Boolean {
|
||||
if (DumbService.getInstance(context.project).isDumb) return false
|
||||
|
||||
val location = context.location ?: return false
|
||||
val leaf = location.psiElement
|
||||
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (leaf.containingFile !is KtFile) {
|
||||
return false
|
||||
}
|
||||
|
||||
val ktFile = leaf.containingFile as KtFile
|
||||
|
||||
if (TargetPlatformDetector.getPlatform(ktFile) != JvmPlatform) {
|
||||
return false
|
||||
}
|
||||
|
||||
val methodLocation = getTestMethodLocation(leaf)
|
||||
if (methodLocation != null) {
|
||||
val originalModule = configuration.configurationModule.module
|
||||
configuration.beMethodConfiguration(methodLocation)
|
||||
configuration.restoreOriginalModule(originalModule)
|
||||
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
|
||||
return true
|
||||
}
|
||||
|
||||
val testClass = getTestClass(leaf)
|
||||
if (testClass != null) {
|
||||
val originalModule = configuration.configurationModule.module
|
||||
configuration.beClassConfiguration(testClass)
|
||||
configuration.restoreOriginalModule(originalModule)
|
||||
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) {
|
||||
val leaf = fromContext.sourceElement
|
||||
getTestClass(leaf)?.let { testClass ->
|
||||
val fromContextSubstitute = object : ConfigurationFromContext() {
|
||||
override fun getConfigurationSettings() = fromContext.configurationSettings
|
||||
|
||||
override fun setConfigurationSettings(configurationSettings: RunnerAndConfigurationSettings) {
|
||||
fromContext.configurationSettings = configurationSettings
|
||||
}
|
||||
|
||||
override fun getSourceElement() = testClass
|
||||
}
|
||||
// TODO: use TestClassConfigurationProducer when constructor becomes public
|
||||
return object : AbstractTestClassConfigurationProducer(JUnitConfigurationType.getInstance()){}
|
||||
.onFirstRun(fromContextSubstitute, context, performRunnable)
|
||||
}
|
||||
|
||||
super.onFirstRun(fromContext, context, performRunnable)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getTestClass(leaf: PsiElement): PsiClass? {
|
||||
val containingFile = leaf.containingFile as? KtFile ?: return null
|
||||
var ktClass = leaf.getParentOfType<KtClass>(false)
|
||||
if (!ktClass.isJUnitTestClass()) {
|
||||
ktClass = getTestClassInFile(containingFile)
|
||||
}
|
||||
return ktClass?.toLightClass()
|
||||
}
|
||||
|
||||
fun getTestMethodLocation(leaf: PsiElement): Location<PsiMethod>? {
|
||||
val function = leaf.getParentOfType<KtNamedFunction>(false) ?: return null
|
||||
val owner = PsiTreeUtil.getParentOfType(function, KtFunction::class.java, KtClass::class.java)
|
||||
|
||||
if (owner is KtClass) {
|
||||
val delegate = owner.toLightClass() ?: return null
|
||||
val method = delegate.methods.firstOrNull() { it.navigationElement == function } ?: return null
|
||||
val methodLocation = PsiLocation.fromPsiElement(method)
|
||||
if (JUnitUtil.isTestMethod(methodLocation, false)) {
|
||||
return methodLocation
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun KtClass?.isJUnitTestClass() =
|
||||
this?.toLightClass()?.let { JUnitUtil.isTestClass(it, false, true) } ?: false
|
||||
|
||||
private fun getTestClassInFile(ktFile: KtFile) =
|
||||
ktFile.declarations.filterIsInstance<KtClass>().singleOrNull { it.isJUnitTestClass() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.run
|
||||
|
||||
import com.intellij.execution.actions.ConfigurationContext
|
||||
import com.intellij.execution.actions.ConfigurationFromContext
|
||||
import com.intellij.execution.junit.JUnitConfiguration
|
||||
import com.intellij.execution.junit.PatternConfigurationProducer
|
||||
import com.intellij.execution.junit.TestClassConfigurationProducer
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.PsiElementProcessor
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
|
||||
class KotlinPatternConfigurationProducer : PatternConfigurationProducer() {
|
||||
override fun setupConfigurationFromContext(
|
||||
configuration: JUnitConfiguration,
|
||||
context: ConfigurationContext,
|
||||
sourceElement: Ref<PsiElement>
|
||||
): Boolean {
|
||||
return super.setupConfigurationFromContext(configuration, context, sourceElement)
|
||||
}
|
||||
|
||||
override fun collectTestMembers(
|
||||
psiElements: Array<out PsiElement>,
|
||||
checkAbstract: Boolean,
|
||||
checkIsTest: Boolean,
|
||||
collectingProcessor: PsiElementProcessor.CollectElements<PsiElement>
|
||||
) {
|
||||
val adjustedElements = psiElements.mapNotNull { if (it is KtClassOrObject) it.toLightClass() else it }.toTypedArray()
|
||||
super.collectTestMembers(adjustedElements, checkAbstract, checkIsTest, collectingProcessor)
|
||||
}
|
||||
|
||||
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
|
||||
return other.isProducedBy(PatternConfigurationProducer::class.java)
|
||||
|| other.isProducedBy(TestClassConfigurationProducer::class.java)
|
||||
|| other.isProducedBy(KotlinJUnitRunConfigurationProducer::class.java)
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.run;
|
||||
|
||||
import com.intellij.execution.JavaRunConfigurationExtensionManager;
|
||||
import com.intellij.execution.Location;
|
||||
import com.intellij.execution.PsiLocation;
|
||||
import com.intellij.execution.actions.ConfigurationContext;
|
||||
import com.intellij.execution.actions.ConfigurationFromContext;
|
||||
import com.intellij.execution.junit.InheritorChooser;
|
||||
import com.intellij.execution.junit2.info.MethodLocation;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiClassUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.theoryinpractice.testng.configuration.TestNGConfiguration;
|
||||
import com.theoryinpractice.testng.configuration.TestNGConfigurationProducer;
|
||||
import com.theoryinpractice.testng.util.TestNGUtil;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector;
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.kotlin.asJava.LightClassUtilsKt.toLightClass;
|
||||
|
||||
public class KotlinTestNgConfigurationProducer extends TestNGConfigurationProducer {
|
||||
@Override
|
||||
public boolean shouldReplace(ConfigurationFromContext self, ConfigurationFromContext other) {
|
||||
return other.isProducedBy(TestNGConfigurationProducer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean setupConfigurationFromContext(
|
||||
TestNGConfiguration configuration, ConfigurationContext context, Ref<PsiElement> sourceElement
|
||||
) {
|
||||
// TODO: check TestNG Pattern running first, before method/class (see TestNGInClassConfigurationProducer for logic)
|
||||
// TODO: and PsiClassOwner not handled, which is in TestNGInClassConfigurationProducer
|
||||
|
||||
Location location = context.getLocation();
|
||||
if (location == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Project project = context.getProject();
|
||||
PsiElement leaf = location.getPsiElement();
|
||||
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(leaf.getContainingFile() instanceof KtFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KtFile ktFile = (KtFile) leaf.getContainingFile();
|
||||
|
||||
if (TargetPlatformDetector.getPlatform(ktFile) != JvmPlatform.INSTANCE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KtNamedDeclaration declarationToRun = getDeclarationToRun(leaf);
|
||||
|
||||
if (declarationToRun instanceof KtNamedFunction) {
|
||||
KtNamedFunction function = (KtNamedFunction) declarationToRun;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
KtElement owner = PsiTreeUtil.getParentOfType(function, KtFunction.class, KtClass.class);
|
||||
|
||||
if (owner instanceof KtClass) {
|
||||
PsiClass delegate = toLightClass((KtClass) owner);
|
||||
if (delegate != null) {
|
||||
for (PsiMethod method : delegate.getMethods()) {
|
||||
if (method.getNavigationElement() == function) {
|
||||
if (TestNGUtil.hasTest(method)) {
|
||||
return configure(configuration, location, context, project, delegate, method);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (declarationToRun instanceof KtClass) {
|
||||
PsiClass delegate = toLightClass((KtClassOrObject) declarationToRun);
|
||||
if (!isTestNGClass(delegate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return configure(configuration, location, context, project, delegate, null);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirstRun(ConfigurationFromContext configuration, ConfigurationContext context, Runnable startRunnable) {
|
||||
KtNamedDeclaration declarationToRun = getDeclarationToRun(configuration.getSourceElement());
|
||||
final PsiNamedElement lightElement = CollectionsKt.firstOrNull(LightClassUtilsKt.toLightElements(declarationToRun));
|
||||
|
||||
// Copied from TestNGInClassConfigurationProducer.onFirstRun()
|
||||
if (lightElement instanceof PsiMethod || lightElement instanceof PsiClass) {
|
||||
PsiMethod psiMethod;
|
||||
PsiClass containingClass;
|
||||
|
||||
if (lightElement instanceof PsiMethod) {
|
||||
psiMethod = (PsiMethod)lightElement;
|
||||
containingClass = psiMethod.getContainingClass();
|
||||
} else {
|
||||
psiMethod = null;
|
||||
containingClass = (PsiClass)lightElement;
|
||||
}
|
||||
|
||||
InheritorChooser inheritorChooser = new InheritorChooser() {
|
||||
@Override
|
||||
protected void runForClasses(List<PsiClass> classes, PsiMethod method, ConfigurationContext context, Runnable performRunnable) {
|
||||
((TestNGConfiguration)context.getConfiguration().getConfiguration()).bePatternConfiguration(classes, method);
|
||||
super.runForClasses(classes, method, context, performRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runForClass(PsiClass aClass,
|
||||
PsiMethod psiMethod,
|
||||
ConfigurationContext context,
|
||||
Runnable performRunnable) {
|
||||
if (lightElement instanceof PsiMethod) {
|
||||
Project project = psiMethod.getProject();
|
||||
MethodLocation methodLocation = new MethodLocation(project, psiMethod, PsiLocation.fromPsiElement(aClass));
|
||||
((TestNGConfiguration)context.getConfiguration().getConfiguration()).setMethodConfiguration(methodLocation);
|
||||
} else {
|
||||
((TestNGConfiguration)context.getConfiguration().getConfiguration()).setClassConfiguration(aClass);
|
||||
}
|
||||
super.runForClass(aClass, psiMethod, context, performRunnable);
|
||||
}
|
||||
};
|
||||
if (inheritorChooser.runMethodInAbstractClass(context,
|
||||
startRunnable,
|
||||
psiMethod,
|
||||
containingClass,
|
||||
new Condition<PsiClass>() {
|
||||
@Override
|
||||
public boolean value(PsiClass aClass) {
|
||||
return aClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
|
||||
TestNGUtil.hasTest(aClass);
|
||||
}
|
||||
})) return;
|
||||
}
|
||||
|
||||
super.onFirstRun(configuration, context, startRunnable);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KtNamedDeclaration getDeclarationToRun(@NotNull PsiElement leaf) {
|
||||
if (!(leaf.getContainingFile() instanceof KtFile)) return null;
|
||||
KtFile jetFile = (KtFile) leaf.getContainingFile();
|
||||
|
||||
KtNamedFunction function = PsiTreeUtil.getParentOfType(leaf, KtNamedFunction.class, false);
|
||||
if (function != null) return function;
|
||||
|
||||
KtClass ktClass = PsiTreeUtil.getParentOfType(leaf, KtClass.class, false);
|
||||
if (ktClass != null) return ktClass;
|
||||
|
||||
return getClassDeclarationInFile(jetFile);
|
||||
}
|
||||
|
||||
private boolean configure(
|
||||
TestNGConfiguration configuration, Location location, ConfigurationContext context, Project project,
|
||||
@Nullable PsiClass delegate, @Nullable PsiMethod method
|
||||
) {
|
||||
if (delegate == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setupConfigurationModule(context, configuration);
|
||||
Module originalModule = configuration.getConfigurationModule().getModule();
|
||||
configuration.setClassConfiguration(delegate);
|
||||
if (method != null) {
|
||||
configuration.setMethodConfiguration(PsiLocation.fromPsiElement(project, method));
|
||||
}
|
||||
configuration.restoreOriginalModule(originalModule);
|
||||
configuration.setName(configuration.getName());
|
||||
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isTestNGClass(PsiClass psiClass) {
|
||||
return psiClass != null && PsiClassUtil.isRunnableClass(psiClass, true, false) && TestNGUtil.hasTest(psiClass);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static KtClass getClassDeclarationInFile(KtFile jetFile) {
|
||||
KtClass tempSingleDeclaration = null;
|
||||
|
||||
for (KtDeclaration ktDeclaration : jetFile.getDeclarations()) {
|
||||
if (ktDeclaration instanceof KtClass) {
|
||||
KtClass declaration = (KtClass) ktDeclaration;
|
||||
|
||||
if (tempSingleDeclaration == null) {
|
||||
tempSingleDeclaration = declaration;
|
||||
}
|
||||
else {
|
||||
// There are several class declarations in file
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tempSingleDeclaration;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user