Merge pull request #541 from JetBrains/rr/yole/coverageView2

Implement APIs added in IDEA 14.1 for Packages view and Coverage view to work
This commit is contained in:
Dmitry Jemerov
2014-12-29 14:49:14 +01:00
2 changed files with 151 additions and 7 deletions
@@ -21,10 +21,8 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElementFinder;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiPackage;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
@@ -36,6 +34,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.JetClassOrObject;
import org.jetbrains.jet.lang.psi.JetEnumEntry;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.java.JavaPsiFacadeKotlinHacks;
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
import org.jetbrains.jet.lang.resolve.name.FqName;
@@ -213,6 +212,29 @@ public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacade
return answer.toArray(new PsiClass[answer.size()]);
}
// implements a method added in 14.1
@NotNull
public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
FqName packageFQN = new FqName(psiPackage.getQualifiedName());
Collection<JetFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope);
return result.toArray(new PsiFile[result.size()]);
}
// implements a method added in IDEA 14.1
@SuppressWarnings({"UnusedDeclaration", "MethodMayBeStatic"})
@Nullable
public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
return new Condition<PsiFile>() {
@Override
public boolean value(@Nullable PsiFile input) {
if (!(input instanceof JetFile)) {
return true;
}
return psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString());
}
};
}
private static class FindClassesRequest {
private final String fqName;
private final GlobalSearchScope scope;
@@ -26,20 +26,142 @@ import java.io.File
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.plugin.debugger.JetPositionManager
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
import java.util.HashSet
import org.jetbrains.jet.lang.psi.JetTreeVisitor
import org.jetbrains.jet.lang.psi.JetDeclaration
import com.intellij.openapi.roots.ProjectRootManager
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.annotations.TestOnly
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression
import com.intellij.psi.PsiElement
import java.util.LinkedHashSet
import com.intellij.psi.PsiClass
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiNamedElement
import com.intellij.coverage.PackageAnnotator
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.roots.CompilerModuleExtension
import com.intellij.coverage.JavaCoverageAnnotator
import org.jetbrains.jet.lang.psi.JetClassOrObject
import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils
import org.jetbrains.jet.lang.resolve.kotlin.KotlinBinaryClassCache
import com.intellij.openapi.vfs.LocalFileSystem
import org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader
import com.intellij.openapi.util.io.FileUtilRt
public class KotlinCoverageExtension(): JavaCoverageEngineExtension() {
private val LOG = Logger.getInstance(javaClass<KotlinCoverageExtension>())
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 JetFile) {
names.addAll(collectOutputClassNames(sourceFile).map { StringUtil.replaceChar(it, '/', '.' )})
return true
}
return false
}
// Implements API added in IDEA 14.1
fun getSummaryCoverageInfo(coverageAnnotator: JavaCoverageAnnotator,
element: PsiNamedElement): PackageAnnotator.ClassCoverageInfo? {
if (element !is JetFile) {
return null
}
LOG.info("Retrieving coverage for " + element.getName())
val outputRoot = findOutputRoot(element)
val existingClassFiles = getClassesGeneratedFromFile(outputRoot, element)
if (existingClassFiles.isEmpty()) {
return null
}
LOG.debug("Classfiles: [${existingClassFiles.map { it.getName() }.join()}]")
val qualifiedNames = existingClassFiles.map {
val relativePath = VfsUtilCore.getRelativePath(it, outputRoot)
StringUtil.trimEnd(relativePath, ".class").replace("/", ".")
}
return totalCoverageForQualifiedNames(coverageAnnotator, qualifiedNames)
}
private fun getClassesGeneratedFromFile(outputRoot: VirtualFile?, file: JetFile): List<VirtualFile> {
val relativePath = file.getPackageFqName().asString().replace('.', '/')
val packageOutputDir = outputRoot?.findFileByRelativePath(relativePath)
if (packageOutputDir == null) return listOf()
val prefixes = collectClassFilePrefixes(file)
LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]")
return packageOutputDir.getChildren().filter {
file -> prefixes.any {
(file.getName().startsWith(it + "$") && FileUtilRt.getExtension(file.getName()) == "class") ||
file.getName() == it + ".class"
}
}
}
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 findOutputRoot(file: JetFile): VirtualFile? {
val module = ModuleUtilCore.findModuleForPsiElement(file)
if (module == null) return null
val fileIndex = ProjectRootManager.getInstance(file.getProject()).getFileIndex()
val inTests = fileIndex.isInTestSourceContent(file.getVirtualFile())
val compilerOutputExtension = CompilerModuleExtension.getInstance(module)
return if (inTests)
compilerOutputExtension.getCompilerOutputPathForTests()
else
compilerOutputExtension.getCompilerOutputPath()
}
private fun collectClassFilePrefixes(file: JetFile): Collection<String> {
val result = file.getChildren().filter { it is JetClassOrObject }.map { (it as JetClassOrObject).getName() }
val packagePartFqName = PackagePartClassUtils.getPackagePartFqName(file)
return result.union(arrayListOf(packagePartFqName.shortName().asString()))
}
// Implements API added in IDEA 14.1
fun keepCoverageInfoForClassWithoutSource(bundle: CoverageSuitesBundle, classFile: File): Boolean {
// TODO check scope and source roots
return true // keep everything, sort it out later
}
// Implements API added in IDEA 14.1
fun ignoreCoverageForClass(bundle: CoverageSuitesBundle, classFile: File): Boolean {
// Ignore classes that only contain bridge methods delegating to package parts.
if (looksLikePackageFacade(classFile)) {
val classVFile = LocalFileSystem.getInstance().findFileByIoFile(classFile)
if (classVFile == null) return false
val header = KotlinBinaryClassCache.getKotlinBinaryClass(classVFile)?.getClassHeader()
return header != null && header.kind == KotlinClassHeader.Kind.PACKAGE_FACADE;
}
return false;
}
fun looksLikePackageFacade(classFile: File): Boolean {
val packageName = classFile.getParentFile().getName()
return classFile.getName() == StringUtil.capitalize(packageName) + PackageClassUtils.PACKAGE_CLASS_NAME_SUFFIX + ".class"
}
override fun collectOutputFiles(srcFile: PsiFile,
output: VirtualFile?,
testoutput: VirtualFile?,