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