diff --git a/plugins/coverage-common/src/com/intellij/coverage/CoverageEngine.java b/plugins/coverage-common/src/com/intellij/coverage/CoverageEngine.java index df8c336ca8ba..19222fd4a63e 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/CoverageEngine.java +++ b/plugins/coverage-common/src/com/intellij/coverage/CoverageEngine.java @@ -353,6 +353,14 @@ public abstract class CoverageEngine { return false; } + /** + * + * @return true if highlighting should skip the line as it represents no actual source code + */ + public boolean isGeneratedCode(Project project, String qualifiedName, Object lineData) { + return false; + } + public static String getEditorTitle() { return "Code Coverage"; } diff --git a/plugins/coverage-common/src/com/intellij/coverage/CoverageSuitesBundle.java b/plugins/coverage-common/src/com/intellij/coverage/CoverageSuitesBundle.java index fa28c5a4a566..5320730cab69 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/CoverageSuitesBundle.java +++ b/plugins/coverage-common/src/com/intellij/coverage/CoverageSuitesBundle.java @@ -60,6 +60,9 @@ public class CoverageSuitesBundle { return true; } + public Project getProject() { + return mySuites[0].getProject(); + } public long getLastCoverageTimeStamp() { long max = 0; diff --git a/plugins/coverage-common/src/com/intellij/coverage/SrcFileAnnotator.java b/plugins/coverage-common/src/com/intellij/coverage/SrcFileAnnotator.java index 035786282d0f..cb586c3c6889 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/SrcFileAnnotator.java +++ b/plugins/coverage-common/src/com/intellij/coverage/SrcFileAnnotator.java @@ -355,7 +355,7 @@ public class SrcFileAnnotator implements Disposable { if (fileData != null) { final Object[] lines = fileData.getLines(); if (lines != null) { - final Object[] postProcessedLines = suite.getCoverageEngine().postProcessExecutableLines(lines, editor); + final Object[] postProcessedLines = engine.postProcessExecutableLines(lines, editor); for (Object lineData : postProcessedLines) { if (lineData instanceof LineData) { final int line = ((LineData)lineData).getLineNumber() - 1; @@ -371,6 +371,7 @@ public class SrcFileAnnotator implements Disposable { // use id mapping lineNumberInCurrent = line; } + if (engine.isGeneratedCode(myProject, qualifiedName, lineData)) continue; executableLines.put(line, (LineData)lineData); classLines.put(line, postProcessedLines); diff --git a/plugins/coverage/src/com/intellij/coverage/JavaCoverageEngine.java b/plugins/coverage/src/com/intellij/coverage/JavaCoverageEngine.java index dc789645725a..49be86c13f47 100644 --- a/plugins/coverage/src/com/intellij/coverage/JavaCoverageEngine.java +++ b/plugins/coverage/src/com/intellij/coverage/JavaCoverageEngine.java @@ -255,7 +255,7 @@ public class JavaCoverageEngine extends CoverageEngine { } try { - SourceLineCounterUtil.collectSrcLinesForUntouchedFiles(uncoveredLines, content, suite.isTracingEnabled()); + SourceLineCounterUtil.collectSrcLinesForUntouchedFiles(uncoveredLines, content, suite.isTracingEnabled(), suite.getProject()); } catch (Exception e) { LOG.error("Fail to process class from: " + classFile.getPath(), e); @@ -274,6 +274,7 @@ public class JavaCoverageEngine extends CoverageEngine { } + @NotNull public String getQualifiedName(@NotNull final File outputFile, @NotNull final PsiFile sourceFile) { final String packageFQName = getPackageName(sourceFile); return StringUtil.getQualifiedName(packageFQName, FileUtil.getNameWithoutExtension(outputFile)); @@ -651,6 +652,12 @@ public class JavaCoverageEngine extends CoverageEngine { return "Java Coverage"; } + @Override + public boolean isGeneratedCode(Project project, String qualifiedName, Object lineData) { + PsiClass psiClass = ReadAction.compute(() -> ClassUtil.findPsiClassByJVMName(PsiManager.getInstance(project), qualifiedName)); + return PackageAnnotator.isGeneratedDefaultConstructor(psiClass, ((LineData)lineData).getMethodSignature()); + } + @Override public CoverageViewExtension createCoverageViewExtension(Project project, CoverageSuitesBundle suiteBundle, diff --git a/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java b/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java index 3c48fd63851f..2f90f21a4db0 100644 --- a/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java +++ b/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java @@ -28,10 +28,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiClass; -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.PsiUtilCore; import com.intellij.rt.coverage.data.ClassData; @@ -509,16 +506,23 @@ public class PackageAnnotator { */ public static boolean isGeneratedDefaultConstructor(@Nullable final PsiClass aClass, String nameAndSig) { if (DEFAULT_CONSTRUCTOR_NAME_SIGNATURE.equals(nameAndSig)) { - return hasGeneratedConstructor(aClass); + return hasGeneratedOrEmptyPrivateConstructor(aClass); } return false; } - private static boolean hasGeneratedConstructor(@Nullable final PsiClass aClass) { + private static boolean hasGeneratedOrEmptyPrivateConstructor(@Nullable final PsiClass aClass) { if (aClass == null) { return false; } - return ReadAction.compute(() -> aClass.getConstructors().length == 0); + return ReadAction.compute(() -> { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 1 && constructors[0].hasModifierProperty(PsiModifier.PRIVATE)) { + PsiCodeBlock body = constructors[0].getBody(); + return body != null && body.getStatements().length == 0; + } + return constructors.length == 0; + }); } private static ClassCoverageInfo getOrCreateClassCoverageInfo(final Map toplevelClassCoverage, diff --git a/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java b/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java index be64d6d7368a..e5e8851fbae7 100644 --- a/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java +++ b/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java @@ -15,12 +15,18 @@ */ package com.intellij.coverage; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.project.Project; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiManager; +import com.intellij.psi.util.ClassUtil; import com.intellij.rt.coverage.instrumentation.SourceLineCounter; +import com.intellij.util.containers.HashSet; import gnu.trove.TIntObjectHashMap; import org.jetbrains.org.objectweb.asm.ClassReader; import java.util.List; +import java.util.Set; public class SourceLineCounterUtil { public static boolean collectNonCoveredClassInfo(final PackageAnnotator.ClassCoverageInfo classCoverageInfo, @@ -33,14 +39,20 @@ public class SourceLineCounterUtil { SourceLineCounter counter = new SourceLineCounter(null, excludeLines, null); reader.accept(counter, 0); - classCoverageInfo.totalLineCount += counter.getNSourceLines(); - packageCoverageInfo.totalLineCount += counter.getNSourceLines(); - for (Object nameAndSig : counter.getMethodsWithSourceCode()) { - if (!PackageAnnotator.isGeneratedDefaultConstructor(psiClass, (String) nameAndSig)) { - classCoverageInfo.totalMethodCount++; - packageCoverageInfo.totalMethodCount++; - } - } + Set descriptions = new HashSet<>(); + TIntObjectHashMap lines = counter.getSourceLines(); + lines.forEachEntry((line, description) -> { + if (!PackageAnnotator.isGeneratedDefaultConstructor(psiClass, (String)description)) { + classCoverageInfo.totalLineCount ++; + packageCoverageInfo.totalLineCount ++; + descriptions.add(description); + } + return true; + }); + + classCoverageInfo.totalMethodCount += descriptions.size(); + packageCoverageInfo.totalMethodCount += descriptions.size(); + if (!counter.isInterface()) { packageCoverageInfo.totalClassCount++; } @@ -48,14 +60,21 @@ public class SourceLineCounterUtil { } public static void collectSrcLinesForUntouchedFiles(final List uncoveredLines, - byte[] content, final boolean excludeLines) { + byte[] content, + final boolean excludeLines, + final Project project) { final ClassReader reader = new ClassReader(content); final SourceLineCounter collector = new SourceLineCounter(null, excludeLines, null); reader.accept(collector, 0); - final TIntObjectHashMap lines = collector.getSourceLines(); - lines.forEachKey(line -> { - line--; - uncoveredLines.add(line); + + String qualifiedName = reader.getClassName(); + PsiClass psiClass = ReadAction.compute(() -> ClassUtil.findPsiClassByJVMName(PsiManager.getInstance(project), qualifiedName)); + TIntObjectHashMap lines = collector.getSourceLines(); + lines.forEachEntry((line, description) -> { + if (!PackageAnnotator.isGeneratedDefaultConstructor(psiClass, (String)description)) { + line--; + uncoveredLines.add(line); + } return true; }); } diff --git a/plugins/coverage/testSrc/com/intellij/coverage/CoverageIntegrationTest.java b/plugins/coverage/testSrc/com/intellij/coverage/CoverageIntegrationTest.java index a0e7cc732fde..54c9852b1b4b 100644 --- a/plugins/coverage/testSrc/com/intellij/coverage/CoverageIntegrationTest.java +++ b/plugins/coverage/testSrc/com/intellij/coverage/CoverageIntegrationTest.java @@ -56,7 +56,7 @@ public class CoverageIntegrationTest extends ModuleTestCase { assertEquals(1, barClassCoverage.coveredMethodCount); PackageAnnotator.PackageCoverageInfo barPackageCoverage = consumer.myPackageCoverage.get("foo.bar"); assertEquals(2, barPackageCoverage.coveredLineCount); - assertEquals(9, barPackageCoverage.totalLineCount); + assertEquals(8, barPackageCoverage.totalLineCount); assertEquals(1, barPackageCoverage.coveredMethodCount); assertEquals(7, barPackageCoverage.totalMethodCount); PackageAnnotator.ClassCoverageInfo uncoveredClassInfo = consumer.myClassCoverageInfo.get("foo.bar.UncoveredClass");