From 732c3fac2fe0891fb3ae47e69bb73d8cc4dd07ae Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 24 Dec 2014 15:32:26 +0100 Subject: [PATCH 1/4] Implement APIs added in IDEA 14.1 for Packages view and Coverage view to work. --- .../jet/asJava/JavaElementFinder.java | 35 +++++-- .../coverage/KotlinCoverageExtension.kt | 93 ++++++++++++++++++- 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java index 735ff5584f6..9681c97b960 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java @@ -21,29 +21,26 @@ 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.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.SmartList; +import com.intellij.util.containers.Predicate; import com.intellij.util.containers.SLRUCache; 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; import org.jetbrains.jet.lang.resolve.name.NamePackage; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacadeKotlinHacks.KotlinFinderMarker { @@ -213,6 +210,30 @@ 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) { + List result = new ArrayList(); + FqName packageFQN = new FqName(psiPackage.getQualifiedName()); + result.addAll(lightClassGenerationSupport.findFilesForPackage(packageFQN, scope)); + return result.toArray(new PsiFile[result.size()]); + } + + // implements a method added in IDEA 14.1 + @SuppressWarnings({"UnusedDeclaration", "MethodMayBeStatic"}) + @Nullable + public Predicate getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { + return new Predicate() { + @Override + public boolean apply(@Nullable PsiFile input) { + if (input instanceof JetFile && !(psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString()))) { + return false; + } + return true; + } + }; + } + private static class FindClassesRequest { private final String fqName; private final GlobalSearchScope scope; diff --git a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt index 1bdb6aea475..2c3f25f6a87 100644 --- a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt +++ b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt @@ -31,15 +31,106 @@ 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 public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { + private val LOG = Logger.getInstance(javaClass()) + override fun isApplicableTo(conf: RunConfigurationBase?): Boolean = conf is JetRunConfiguration + override fun suggestQualifiedName(sourceFile: PsiFile, classes: Array, names: MutableSet): 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) { + LOG.info("Retrieving coverage for " + element.getName()) + val module = ModuleUtilCore.findModuleForPsiElement(element) + if (module == null) return null + val fileIndex = ProjectRootManager.getInstance(element.getProject()).getFileIndex() + val inTests = fileIndex.isInTestSourceContent(element.getVirtualFile()) + val compilerOutputExtension = CompilerModuleExtension.getInstance(module) + val outputRoot = if (inTests) + compilerOutputExtension.getCompilerOutputPathForTests() + else + compilerOutputExtension.getCompilerOutputPath() + if (outputRoot == null) return null + val fqName = element.getPackageFqName().asString().replace('.', '/') + val packageOutputDir = outputRoot.findFileByRelativePath(fqName) + if (packageOutputDir == null) return null + val prefixes = collectClassFilePrefixes(element) + LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]") + val existingClassFiles = packageOutputDir.getChildren().filter { + file -> prefixes.any { file.getName().startsWith(it + "$") || file.getName().equals(it + ".class") } + } + if (existingClassFiles.size() > 0) { + LOG.debug("Classfiles: [${existingClassFiles.map { it.getName() }.join()}]") + val result = PackageAnnotator.ClassCoverageInfo() + result.totalClassCount = 0 + existingClassFiles.forEach { + val relativePath = VfsUtilCore.getRelativePath(it, outputRoot) + val qName = StringUtil.trimEnd(relativePath, ".class").replace("/", ".") + val classInfo = coverageAnnotator.getClassCoverageInfo(qName) + 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 ${qName}") + } + } + return result + } + } + return null + } + + private fun collectClassFilePrefixes(file: JetFile): Collection { + 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 { + val packageName = classFile.getParentFile().getName() + // Ignore classes that only contain bridge methods delegating to package parts. + if (classFile.getName().equals(StringUtil.capitalize(packageName) + "Package.class")) { + return true + } + return false; + } + override fun collectOutputFiles(srcFile: PsiFile, output: VirtualFile?, testoutput: VirtualFile?, From 553fd4dd125e0ba66a13d3c6261282ccb97376b6 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 29 Dec 2014 12:29:01 +0100 Subject: [PATCH 2/4] code review; adapted to IDEA API change (Predicate -> Condition) --- .../jet/asJava/JavaElementFinder.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java index 9681c97b960..098156897e6 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java @@ -21,6 +21,7 @@ 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.openapi.util.Condition; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.CachedValue; @@ -28,7 +29,6 @@ import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.SmartList; -import com.intellij.util.containers.Predicate; import com.intellij.util.containers.SLRUCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,7 +40,9 @@ import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.NamePackage; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Set; public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacadeKotlinHacks.KotlinFinderMarker { @@ -213,23 +215,22 @@ public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacade // implements a method added in 14.1 @NotNull public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { - List result = new ArrayList(); FqName packageFQN = new FqName(psiPackage.getQualifiedName()); - result.addAll(lightClassGenerationSupport.findFilesForPackage(packageFQN, scope)); + Collection 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 Predicate getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { - return new Predicate() { + public Condition getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { + return new Condition() { @Override - public boolean apply(@Nullable PsiFile input) { - if (input instanceof JetFile && !(psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString()))) { - return false; + public boolean value(@Nullable PsiFile input) { + if (!(input instanceof JetFile)) { + return true; } - return true; + return psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString()); } }; } From d1123e116fad8fa29555eb2ff92eafa24e7bdbba Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 29 Dec 2014 13:57:56 +0100 Subject: [PATCH 3/4] refactor for clarity; don't ignore XxxPackage files not related to Kotlin --- .../coverage/KotlinCoverageExtension.kt | 119 +++++++++++------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt index 2c3f25f6a87..7b5ab2c0995 100644 --- a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt +++ b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt @@ -26,8 +26,6 @@ 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 @@ -46,6 +44,10 @@ 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 public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { private val LOG = Logger.getInstance(javaClass()) @@ -63,50 +65,68 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { // Implements API added in IDEA 14.1 fun getSummaryCoverageInfo(coverageAnnotator: JavaCoverageAnnotator, element: PsiNamedElement): PackageAnnotator.ClassCoverageInfo? { - if (element is JetFile) { - LOG.info("Retrieving coverage for " + element.getName()) - val module = ModuleUtilCore.findModuleForPsiElement(element) - if (module == null) return null - val fileIndex = ProjectRootManager.getInstance(element.getProject()).getFileIndex() - val inTests = fileIndex.isInTestSourceContent(element.getVirtualFile()) - val compilerOutputExtension = CompilerModuleExtension.getInstance(module) - val outputRoot = if (inTests) - compilerOutputExtension.getCompilerOutputPathForTests() - else - compilerOutputExtension.getCompilerOutputPath() - if (outputRoot == null) return null - val fqName = element.getPackageFqName().asString().replace('.', '/') - val packageOutputDir = outputRoot.findFileByRelativePath(fqName) - if (packageOutputDir == null) return null - val prefixes = collectClassFilePrefixes(element) - LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]") - val existingClassFiles = packageOutputDir.getChildren().filter { - file -> prefixes.any { file.getName().startsWith(it + "$") || file.getName().equals(it + ".class") } + 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 { + 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 + "$") || file.getName().equals(it + ".class") } + } + } + + private fun totalCoverageForQualifiedNames(coverageAnnotator: JavaCoverageAnnotator, + qualifiedNames: List): 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 } - if (existingClassFiles.size() > 0) { - LOG.debug("Classfiles: [${existingClassFiles.map { it.getName() }.join()}]") - val result = PackageAnnotator.ClassCoverageInfo() - result.totalClassCount = 0 - existingClassFiles.forEach { - val relativePath = VfsUtilCore.getRelativePath(it, outputRoot) - val qName = StringUtil.trimEnd(relativePath, ".class").replace("/", ".") - val classInfo = coverageAnnotator.getClassCoverageInfo(qName) - 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 ${qName}") - } - } - return result + else { + LOG.debug("Found no coverage for ${it}") } } - return null + 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 { @@ -123,14 +143,21 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { // Implements API added in IDEA 14.1 fun ignoreCoverageForClass(bundle: CoverageSuitesBundle, classFile: File): Boolean { - val packageName = classFile.getParentFile().getName() // Ignore classes that only contain bridge methods delegating to package parts. - if (classFile.getName().equals(StringUtil.capitalize(packageName) + "Package.class")) { - return true + 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?, From e0677dffd4df5a384690e57730d34a6d5b4f857a Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 29 Dec 2014 14:48:26 +0100 Subject: [PATCH 4/4] a bit more cleanup --- .../jet/plugin/coverage/KotlinCoverageExtension.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt index 7b5ab2c0995..d6d381f072a 100644 --- a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt +++ b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt @@ -48,6 +48,7 @@ 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()) @@ -91,7 +92,10 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { val prefixes = collectClassFilePrefixes(file) LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]") return packageOutputDir.getChildren().filter { - file -> prefixes.any { file.getName().startsWith(it + "$") || file.getName().equals(it + ".class") } + file -> prefixes.any { + (file.getName().startsWith(it + "$") && FileUtilRt.getExtension(file.getName()) == "class") || + file.getName() == it + ".class" + } } }