1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.checkstyle;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Locale;
27
28 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
29 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
30 import com.puppycrawl.tools.checkstyle.api.Configuration;
31 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.maven.doxia.sink.Sink;
34 import org.apache.maven.doxia.tools.SiteTool;
35 import org.apache.maven.doxia.util.DoxiaUtils;
36 import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.reporting.AbstractMavenReportRenderer;
39 import org.codehaus.plexus.i18n.I18N;
40
41
42
43
44
45
46 public class CheckstyleReportRenderer extends AbstractMavenReportRenderer {
47 private static final int NO_TEXT = 0;
48 private static final int TEXT_SIMPLE = 1;
49 private static final int TEXT_TITLE = 2;
50 private static final int TEXT_ABBREV = 3;
51
52 private final I18N i18n;
53
54 private final Locale locale;
55
56 private final MavenProject project;
57
58 private final Configuration checkstyleConfig;
59
60 private final boolean enableRulesSummary;
61
62 private final boolean enableSeveritySummary;
63
64 private final boolean enableFilesSummary;
65
66 private final SiteTool siteTool;
67
68 private String xrefLocation;
69
70 private String xrefTestLocation;
71
72 private List<File> testSourceDirectories;
73
74 private List<String> treeWalkerNames = Collections.singletonList("TreeWalker");
75
76 private final String ruleset;
77
78 private final CheckstyleResults results;
79
80 public CheckstyleReportRenderer(
81 Sink sink,
82 I18N i18n,
83 Locale locale,
84 MavenProject project,
85 SiteTool siteTool,
86 String ruleset,
87 String xrefLocation,
88 String xrefTestLocation,
89 List<File> testSourceDirectories,
90 boolean enableRulesSummary,
91 boolean enableSeveritySummary,
92 boolean enableFilesSummary,
93 CheckstyleResults results) {
94 super(sink);
95 this.i18n = i18n;
96 this.locale = locale;
97 this.project = project;
98 this.siteTool = siteTool;
99 this.ruleset = ruleset;
100 this.xrefLocation = xrefLocation;
101 this.xrefTestLocation = xrefTestLocation;
102 this.testSourceDirectories = testSourceDirectories;
103 this.enableRulesSummary = enableRulesSummary;
104 this.enableSeveritySummary = enableSeveritySummary;
105 this.enableFilesSummary = enableFilesSummary;
106 this.results = results;
107 this.checkstyleConfig = results.getConfiguration();
108 }
109
110 @Override
111 public String getTitle() {
112 return getI18nString("title");
113 }
114
115
116
117
118
119 private String getI18nString(String key) {
120 return i18n.getString("checkstyle-report", locale, "report.checkstyle." + key);
121 }
122
123 protected void renderBody() {
124 startSection(getTitle());
125
126 sink.paragraph();
127 sink.text(getI18nString("checkstylelink") + " ");
128 sink.link("https://checkstyle.org/");
129 sink.text("Checkstyle");
130 sink.link_();
131 String version = getCheckstyleVersion();
132 if (version != null) {
133 sink.text(" ");
134 sink.text(version);
135 }
136 sink.text(" ");
137 sink.text(String.format(getI18nString("ruleset"), ruleset));
138 sink.text(".");
139 sink.paragraph_();
140
141 renderSeveritySummarySection();
142
143 renderFilesSummarySection();
144
145 renderRulesSummarySection();
146
147 renderDetailsSection();
148
149 endSection();
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 private String getConfigAttribute(
166 Configuration config,
167 ChainedItem<Configuration> parentConfiguration,
168 String attributeName,
169 String defaultValue) {
170 String ret;
171 try {
172 ret = config.getAttribute(attributeName);
173 } catch (CheckstyleException e) {
174
175 if (parentConfiguration != null) {
176 ret = getConfigAttribute(
177 parentConfiguration.value, parentConfiguration.parent, attributeName, defaultValue);
178 } else {
179 ret = defaultValue;
180 }
181 }
182 return ret;
183 }
184
185
186
187
188
189
190 private void renderRulesSummarySection() {
191 if (!enableRulesSummary) {
192 return;
193 }
194 if (checkstyleConfig == null) {
195 return;
196 }
197
198 startSection(getI18nString("rules"));
199
200 startTable();
201
202 tableHeader(new String[] {
203 getI18nString("rule.category"),
204 getI18nString("rule"),
205 getI18nString("violations"),
206 getI18nString("column.severity")
207 });
208
209
210 if ("checker".equalsIgnoreCase(checkstyleConfig.getName())) {
211 String category = null;
212 for (ConfReference ref : sortConfiguration(results)) {
213 renderRuleRow(ref, results, category);
214
215 category = ref.category;
216 }
217 } else {
218 tableRow(new String[] {getI18nString("norule")});
219 }
220
221 endTable();
222
223 endSection();
224 }
225
226
227
228
229
230
231
232
233 private void renderRuleRow(ConfReference ref, CheckstyleResults results, String previousCategory) {
234 Configuration checkerConfig = ref.configuration;
235 ChainedItem<Configuration> parentConfiguration = ref.parentConfiguration;
236 String ruleName = checkerConfig.getName();
237
238 sink.tableRow();
239
240
241 sink.tableCell();
242 String category = ref.category;
243 if (!category.equals(previousCategory)) {
244 sink.text(category);
245 }
246 sink.tableCell_();
247
248
249 sink.tableCell();
250 if (!"extension".equals(category)) {
251 sink.link(
252 "https://checkstyle.org/checks/" + category + "/" + ruleName.toLowerCase(Locale.ENGLISH) + ".html");
253 sink.text(ruleName);
254 sink.link_();
255 } else {
256 sink.text(ruleName);
257 }
258
259 List<String> attribnames = new ArrayList<>(Arrays.asList(checkerConfig.getAttributeNames()));
260 attribnames.remove("severity");
261 if (!attribnames.isEmpty()) {
262 sink.list();
263 for (String name : attribnames) {
264 sink.listItem();
265
266 sink.text(name);
267
268 String value = getConfigAttribute(checkerConfig, null, name, "");
269
270 if ("header".equals(name) && ("Header".equals(ruleName) || "RegexpHeader".equals(ruleName))) {
271 String[] lines = StringUtils.split(value, "\\n");
272 int linenum = 1;
273 for (String line : lines) {
274 sink.lineBreak();
275 sink.rawText("<span style=\"color: gray\">");
276 sink.text(linenum + ":");
277 sink.rawText("</span>");
278 sink.nonBreakingSpace();
279 sink.monospaced();
280 sink.text(line);
281 sink.monospaced_();
282 linenum++;
283 }
284 } else if ("headerFile".equals(name) && "RegexpHeader".equals(ruleName)) {
285 sink.text(": ");
286 sink.monospaced();
287 sink.text("\"");
288
289 String path =
290 siteTool.getRelativePath(value, project.getBasedir().getAbsolutePath());
291 sink.text(path.replace('\\', '/'));
292 sink.text(value);
293 sink.text("\"");
294 sink.monospaced_();
295 } else {
296 sink.text(": ");
297 sink.monospaced();
298 sink.text("\"");
299 sink.text(value);
300 sink.text("\"");
301 sink.monospaced_();
302 }
303 sink.listItem_();
304 }
305 sink.list_();
306 }
307
308 sink.tableCell_();
309
310
311 sink.tableCell();
312 sink.text(String.valueOf(ref.violations));
313 sink.tableCell_();
314
315
316 sink.tableCell();
317
318
319 String severity = getConfigAttribute(checkerConfig, parentConfiguration, "severity", "error");
320 iconSeverity(severity, TEXT_SIMPLE);
321 sink.tableCell_();
322
323 sink.tableRow_();
324 }
325
326
327
328
329
330
331
332
333
334
335 public boolean matchRule(AuditEvent event, String ruleName, String expectedMessage, String expectedSeverity) {
336 if (!ruleName.equals(RuleUtil.getName(event))) {
337 return false;
338 }
339
340
341
342 if (expectedMessage != null) {
343
344
345
346 String msgWithoutSingleQuote = StringUtils.replace(expectedMessage, "'", "");
347
348 if (!(expectedMessage.equals(event.getMessage()) || msgWithoutSingleQuote.equals(event.getMessage()))) {
349 return false;
350 }
351 }
352
353
354
355
356 if (expectedSeverity != null) {
357 if (!expectedSeverity.equals(event.getSeverityLevel().getName())) {
358 return false;
359 }
360 }
361 return true;
362 }
363
364 private void renderSeveritySummarySection() {
365 if (!enableSeveritySummary) {
366 return;
367 }
368
369 startSection(getI18nString("summary"));
370
371 startTable();
372
373 sink.tableRow();
374 sink.tableHeaderCell();
375 sink.text(getI18nString("files"));
376 sink.tableHeaderCell_();
377
378 sink.tableHeaderCell();
379 iconSeverity("info", TEXT_TITLE);
380 sink.tableHeaderCell_();
381
382 sink.tableHeaderCell();
383 iconSeverity("warning", TEXT_TITLE);
384 sink.tableHeaderCell_();
385
386 sink.tableHeaderCell();
387 iconSeverity("error", TEXT_TITLE);
388 sink.tableHeaderCell_();
389 sink.tableRow_();
390
391 tableRow(new String[] {
392 String.valueOf(results.getFileCount()),
393 String.valueOf(results.getSeverityCount(SeverityLevel.INFO)),
394 String.valueOf(results.getSeverityCount(SeverityLevel.WARNING)),
395 String.valueOf(results.getSeverityCount(SeverityLevel.ERROR))
396 });
397
398 endTable();
399
400 endSection();
401 }
402
403 private void renderFilesSummarySection() {
404 if (!enableFilesSummary) {
405 return;
406 }
407
408 startSection(getI18nString("files"));
409
410 startTable();
411
412 sink.tableRow();
413 sink.tableHeaderCell();
414 sink.text(getI18nString("file"));
415 sink.tableHeaderCell_();
416 sink.tableHeaderCell();
417 iconSeverity("info", TEXT_ABBREV);
418 sink.tableHeaderCell_();
419 sink.tableHeaderCell();
420 iconSeverity("warning", TEXT_ABBREV);
421 sink.tableHeaderCell_();
422 sink.tableHeaderCell();
423 iconSeverity("error", TEXT_ABBREV);
424 sink.tableHeaderCell_();
425 sink.tableRow_();
426
427
428 List<String> fileList = new ArrayList<>(results.getFiles().keySet());
429 Collections.sort(fileList);
430
431 for (String filename : fileList) {
432 List<AuditEvent> violations = results.getFileViolations(filename);
433 if (violations.isEmpty()) {
434
435 continue;
436 }
437
438 sink.tableRow();
439
440 sink.tableCell();
441 sink.link("#" + DoxiaUtils.encodeId(filename));
442 sink.text(filename);
443 sink.link_();
444 sink.tableCell_();
445
446 sink.tableCell();
447 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.INFO)));
448 sink.tableCell_();
449
450 sink.tableCell();
451 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.WARNING)));
452 sink.tableCell_();
453
454 sink.tableCell();
455 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.ERROR)));
456 sink.tableCell_();
457
458 sink.tableRow_();
459 }
460
461 endTable();
462
463 endSection();
464 }
465
466 private void renderDetailsSection() {
467 startSection(getI18nString("details"));
468
469
470 List<String> fileList = new ArrayList<>(results.getFiles().keySet());
471 Collections.sort(fileList);
472
473 for (String file : fileList) {
474 List<AuditEvent> violations = results.getFileViolations(file);
475
476 if (violations.isEmpty()) {
477
478 continue;
479 }
480
481 startSection(file);
482
483 startTable();
484
485 tableHeader(new String[] {
486 getI18nString("column.severity"),
487 getI18nString("rule.category"),
488 getI18nString("rule"),
489 getI18nString("column.message"),
490 getI18nString("column.line")
491 });
492
493 renderFileEvents(violations, file);
494
495 endTable();
496
497 endSection();
498 }
499
500 endSection();
501 }
502
503 private void renderFileEvents(List<AuditEvent> eventList, String filename) {
504 for (AuditEvent event : eventList) {
505 SeverityLevel level = event.getSeverityLevel();
506
507 sink.tableRow();
508
509 sink.tableCell();
510 iconSeverity(level.getName(), TEXT_SIMPLE);
511 sink.tableCell_();
512
513 sink.tableCell();
514 String category = RuleUtil.getCategory(event);
515 if (category != null) {
516 sink.text(category);
517 }
518 sink.tableCell_();
519
520 sink.tableCell();
521 String ruleName = RuleUtil.getName(event);
522 if (ruleName != null) {
523 sink.text(ruleName);
524 }
525 sink.tableCell_();
526
527 sink.tableCell();
528 sink.text(event.getMessage());
529 sink.tableCell_();
530
531 sink.tableCell();
532
533 int line = event.getLine();
534 String effectiveXrefLocation = getEffectiveXrefLocation(eventList);
535 if (effectiveXrefLocation != null && line != 0) {
536 sink.link(effectiveXrefLocation + "/" + filename.replaceAll("\\.java$", ".html") + "#L" + line);
537 sink.text(String.valueOf(line));
538 sink.link_();
539 } else if (line != 0) {
540 sink.text(String.valueOf(line));
541 }
542 sink.tableCell_();
543
544 sink.tableRow_();
545 }
546 }
547
548 private String getEffectiveXrefLocation(List<AuditEvent> eventList) {
549 String absoluteFilename = eventList.get(0).getFileName();
550 if (isTestSource(absoluteFilename)) {
551 return xrefTestLocation;
552 } else {
553 return xrefLocation;
554 }
555 }
556
557 private boolean isTestSource(final String absoluteFilename) {
558 for (File testSourceDirectory : testSourceDirectories) {
559 if (absoluteFilename.startsWith(testSourceDirectory.getAbsolutePath())) {
560 return true;
561 }
562 }
563
564 return false;
565 }
566
567 public void setTreeWalkerNames(List<String> treeWalkerNames) {
568 this.treeWalkerNames = treeWalkerNames;
569 }
570
571
572
573
574
575
576
577 private void iconSeverity(String level, int textType) {
578 sink.figureGraphics("images/icon_" + level + "_sml.gif");
579
580 if (textType > NO_TEXT) {
581 sink.nonBreakingSpace();
582 String suffix;
583 switch (textType) {
584 case TEXT_TITLE:
585 suffix = "s";
586 break;
587 case TEXT_ABBREV:
588 suffix = "s.abbrev";
589 break;
590 default:
591 suffix = "";
592 }
593 sink.text(getI18nString(level + suffix));
594 }
595 }
596
597
598
599
600
601 private String getCheckstyleVersion() {
602 Package checkstyleApiPackage = Configuration.class.getPackage();
603
604 return (checkstyleApiPackage == null) ? null : checkstyleApiPackage.getImplementationVersion();
605 }
606
607 public List<ConfReference> sortConfiguration(CheckstyleResults results) {
608 List<ConfReference> result = new ArrayList<>();
609
610 sortConfiguration(result, checkstyleConfig, null, results);
611
612 Collections.sort(result);
613
614 return result;
615 }
616
617 private void sortConfiguration(
618 List<ConfReference> result,
619 Configuration config,
620 ChainedItem<Configuration> parent,
621 CheckstyleResults results) {
622 for (Configuration childConfig : config.getChildren()) {
623 String ruleName = childConfig.getName();
624
625 if (treeWalkerNames.contains(ruleName)) {
626
627 sortConfiguration(result, childConfig, new ChainedItem<>(config, parent), results);
628 } else {
629 String fixedmessage = getConfigAttribute(childConfig, null, "message", null);
630
631
632
633 String configSeverity = getConfigAttribute(childConfig, null, "severity", null);
634
635
636 long violations = 0;
637 AuditEvent lastMatchedEvent = null;
638 for (List<AuditEvent> errors : results.getFiles().values()) {
639 for (AuditEvent event : errors) {
640 if (matchRule(event, ruleName, fixedmessage, configSeverity)) {
641 lastMatchedEvent = event;
642 violations++;
643 }
644 }
645 }
646
647 if (violations > 0)
648 {
649 String category = RuleUtil.getCategory(lastMatchedEvent);
650
651 result.add(new ConfReference(category, childConfig, parent, violations, result.size()));
652 }
653 }
654 }
655 }
656
657 private static class ConfReference implements Comparable<ConfReference> {
658 private final String category;
659 private final Configuration configuration;
660 private final ChainedItem<Configuration> parentConfiguration;
661 private final long violations;
662 private final int count;
663
664 ConfReference(
665 String category,
666 Configuration configuration,
667 ChainedItem<Configuration> parentConfiguration,
668 long violations,
669 int count) {
670 this.category = category;
671 this.configuration = configuration;
672 this.parentConfiguration = parentConfiguration;
673 this.violations = violations;
674 this.count = count;
675 }
676
677 public int compareTo(ConfReference o) {
678 int compare = category.compareTo(o.category);
679 if (compare == 0) {
680 compare = configuration.getName().compareTo(o.configuration.getName());
681 }
682 return (compare == 0) ? (o.count - count) : compare;
683 }
684 }
685
686 private static class ChainedItem<T> {
687 private final ChainedItem<T> parent;
688
689 private final T value;
690
691 ChainedItem(T value, ChainedItem<T> parent) {
692 this.parent = parent;
693 this.value = value;
694 }
695 }
696 }