25
25
* This plugin provides a simple way to view coverage metrics directly in the console
26
26
* without needing to generate HTML or XML reports.
27
27
*/
28
- @ Mojo (
29
- name = "report" ,
30
- defaultPhase = LifecyclePhase .VERIFY ,
31
- threadSafe = true
32
- )
28
+ @ Mojo (name = "report" , defaultPhase = LifecyclePhase .VERIFY , threadSafe = true )
33
29
public class JacocoConsoleReporterMojo extends AbstractMojo {
34
30
private final Pattern PACKAGE_PATTERN = Pattern .compile ("(?:^|\\ */)\\ s*package\\ s+([^;]+);" , Pattern .DOTALL | Pattern .MULTILINE );
35
31
@@ -105,6 +101,12 @@ public class JacocoConsoleReporterMojo extends AbstractMojo {
105
101
@ Parameter (defaultValue = "true" , property = "ignoreFilesInBuildDirectory" )
106
102
boolean ignoreFilesInBuildDirectory ;
107
103
104
+ /**
105
+ * When true, ignore the files in the build directory. 99.9% of the time these are automatically generated files.
106
+ */
107
+ @ Parameter (defaultValue = "true" , property = "interpretSonarIgnorePatterns" )
108
+ boolean interpretSonarIgnorePatterns ;
109
+
108
110
/**
109
111
* Base directory for compiled output.
110
112
*/
@@ -139,6 +141,7 @@ public class JacocoConsoleReporterMojo extends AbstractMojo {
139
141
static final Set <File > collectedExecFilePaths = new HashSet <>();
140
142
static final Set <File > collectedClassesPaths = new HashSet <>();
141
143
static final Set <Pattern > collectedExcludePatterns = new HashSet <>();
144
+ static final Set <SonarExclusionPattern > collectedSonarExcludePatterns = new HashSet <>();
142
145
143
146
FileReader fileReader = new FileReader ();
144
147
@@ -172,6 +175,7 @@ public void execute() throws MojoExecutionException {
172
175
void loadExclusionPatterns () {
173
176
addBuildDirExclusion ();
174
177
addJacocoExclusions ();
178
+ addSonarExclusions ();
175
179
}
176
180
177
181
/**
@@ -230,6 +234,46 @@ void addJacocoExclusions() {
230
234
});
231
235
}
232
236
237
+ /**
238
+ * Extracts exclusion patterns from Sonar properties
239
+ */
240
+ void addSonarExclusions () {
241
+ if (!interpretSonarIgnorePatterns ) return ;
242
+
243
+ // Check for Sonar exclusions in project properties
244
+ Properties projectProperties = project .getProperties ();
245
+
246
+ // Handle sonar.exclusions (file-based patterns)
247
+ String sonarExclusions = projectProperties .getProperty ("sonar.exclusions" );
248
+ if (sonarExclusions != null && !sonarExclusions .trim ().isEmpty ()) {
249
+ addSonarFileExclusions (sonarExclusions );
250
+ }
251
+
252
+ // Handle sonar.coverage.exclusions (file-based patterns)
253
+ String sonarCoverageExclusions = projectProperties .getProperty ("sonar.coverage.exclusions" );
254
+ if (sonarCoverageExclusions != null && !sonarCoverageExclusions .trim ().isEmpty ()) {
255
+ addSonarFileExclusions (sonarCoverageExclusions );
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Processes Sonar file-based exclusions and stores them for later evaluation
261
+ */
262
+ void addSonarFileExclusions (@ NotNull String exclusions ) {
263
+ String [] patterns = exclusions .split ("," );
264
+ for (String pattern : patterns ) {
265
+ pattern = pattern .trim ();
266
+ if (pattern .isEmpty ()) {
267
+ continue ;
268
+ }
269
+
270
+ // Store the original pattern with project context for later evaluation
271
+ SonarExclusionPattern sonarPattern = new SonarExclusionPattern (pattern , project );
272
+ collectedSonarExcludePatterns .add (sonarPattern );
273
+ getLog ().debug ("Added Sonar file exclusion pattern: " + pattern );
274
+ }
275
+ }
276
+
233
277
/**
234
278
* Converts a JaCoCo exclude pattern to a Java regex Pattern
235
279
* <p/>
@@ -247,17 +291,13 @@ Pattern convertExclusionToPattern(@NotNull String jacocoPattern) {
247
291
}
248
292
249
293
// Use temporary placeholders to avoid interference between replacements
250
- String regex = jacocoPattern
251
- .replace ("**/" , "__DOUBLE_STAR__" ) // Any directory
294
+ String regex = jacocoPattern .replace ("**/" , "__DOUBLE_STAR__" ) // Any directory
252
295
.replace ("**" , "__DOUBLE_STAR__" ) // Any directory
253
296
.replace ("*" , "__STAR__" ) // Any character (but not a directory)
254
297
.replace ("." , "__DOT__" );
255
298
256
299
// Now perform the actual replacements
257
- regex = regex
258
- .replace ("__DOUBLE_STAR__" , "(?:[^/]*/)*" )
259
- .replace ("__STAR__" , "[^/]*" )
260
- .replace ("__DOT__" , "\\ ." );
300
+ regex = regex .replace ("__DOUBLE_STAR__" , "(?:[^/]*/)*" ).replace ("__STAR__" , "[^/]*" ).replace ("__DOT__" , "\\ ." );
261
301
262
302
regex = "^" + regex + "$" ;
263
303
@@ -272,14 +312,34 @@ void addExclusion(@NotNull String jacocoPattern) {
272
312
}
273
313
274
314
/**
275
- * Checks if a class should be excluded based on its name
315
+ * Checks if a class should be excluded based on its name and file path
276
316
*/
277
317
boolean isExcluded (String className ) {
278
- if (collectedExcludePatterns .isEmpty ()) {
279
- return false ;
318
+ return isExcluded (className , null );
319
+ }
320
+
321
+ /**
322
+ * Checks if a class should be excluded based on its name and optional file path
323
+ */
324
+ boolean isExcluded (String className , String filePath ) {
325
+ // Check JaCoCo-style package exclusions
326
+ if (!collectedExcludePatterns .isEmpty ()) {
327
+ if (collectedExcludePatterns .stream ().anyMatch (p -> p .matcher (className ).matches ())) {
328
+ return true ;
329
+ }
330
+ }
331
+
332
+ // Check Sonar-style file exclusions if we have a file path
333
+ if (filePath != null && !collectedSonarExcludePatterns .isEmpty ()) {
334
+ for (SonarExclusionPattern sonarPattern : collectedSonarExcludePatterns ) {
335
+ if (sonarPattern .matches (filePath , project )) {
336
+ getLog ().debug ("Excluded by Sonar pattern '" + sonarPattern .getOriginalPattern () + "': " + filePath );
337
+ return true ;
338
+ }
339
+ }
280
340
}
281
341
282
- return collectedExcludePatterns . stream (). anyMatch ( p -> p . matcher ( className ). matches ()) ;
342
+ return false ;
283
343
}
284
344
285
345
void generateReport () throws MojoExecutionException {
@@ -546,13 +606,21 @@ void printTree(@NotNull DirectoryNode root) {
546
606
void buildDirectoryTreeAddNode (DirectoryNode root , @ NotNull IPackageCoverage packageCoverage , @ NotNull ISourceFileCoverage sourceFileCoverage ) {
547
607
String filename = sourceFileCoverage .getName ();
548
608
String className = filename .substring (0 , filename .lastIndexOf ('.' ));
609
+ String packageName = packageCoverage .getName ();
610
+ String classPath = packageName + "/" + className ;
549
611
550
- String p = packageCoverage .getName () + "/" + className ;
551
- if (isExcluded (p )) {
612
+ // Construct potential file paths for Sonar pattern matching
613
+ String javaFilePath = packageName .replace ("/" , "/" ) + "/" + filename ;
614
+ String srcMainJavaPath = "src/main/java/" + javaFilePath ;
615
+ String srcTestJavaPath = "src/test/java/" + javaFilePath ;
616
+
617
+ // Check exclusions with both package-style and file-style paths
618
+ if (isExcluded (classPath ) || isExcluded (classPath , javaFilePath ) || isExcluded (classPath , srcMainJavaPath ) || isExcluded (classPath , srcTestJavaPath )) {
619
+ getLog ().debug ("Excluded source file: " + javaFilePath );
552
620
return ;
553
621
}
554
622
555
- String [] pathComponents = packageCoverage . getName () .split ("/" );
623
+ String [] pathComponents = packageName .split ("/" );
556
624
DirectoryNode current = root ;
557
625
for (String component : pathComponents ) {
558
626
current = current .getSubdirectories ().computeIfAbsent (component , DirectoryNode ::new );
@@ -577,7 +645,6 @@ void buildDirectoryTreeAddNode(DirectoryNode root, @NotNull IPackageCoverage pac
577
645
metrics .setCoveredBranches (sourceFileCoverage .getBranchCounter ().getCoveredCount ());
578
646
579
647
current .getSourceFiles ().add (new SourceFileNode (sourceFileName , metrics ));
580
-
581
648
}
582
649
583
650
void buildDirectoryTreeAddNode (DirectoryNode root , @ NotNull IPackageCoverage packageCoverage ) {
0 commit comments