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..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,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 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 getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { + return new Condition() { + @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; diff --git a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt index 1bdb6aea475..d6d381f072a 100644 --- a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt +++ b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt @@ -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()) + 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) { + 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 + "$") && FileUtilRt.getExtension(file.getName()) == "class") || + file.getName() == 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 + } + 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 { + 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?,