diff --git a/src/main/java/io/github/finoid/maven/plugins/codequality/report/AbstractConsoleViolationReporter.java b/src/main/java/io/github/finoid/maven/plugins/codequality/report/AbstractConsoleViolationReporter.java new file mode 100644 index 0000000..d843988 --- /dev/null +++ b/src/main/java/io/github/finoid/maven/plugins/codequality/report/AbstractConsoleViolationReporter.java @@ -0,0 +1,88 @@ +package io.github.finoid.maven.plugins.codequality.report; + +import io.github.finoid.maven.plugins.codequality.ExecutionContext; +import io.github.finoid.maven.plugins.codequality.filter.Violations; +import org.apache.maven.plugin.logging.Log; + +import java.util.List; +import java.util.Locale; + +/** + * A base for console-based {@link ViolationReporter} implementations. + * + *

Handles what every console reporter does the same way: splitting the violations into permissive and + * non-permissive groups, emitting the header of each group, and choosing the log level the group is reported at. + * Subclasses decide how the violations themselves are rendered, through {@link #logViolations}. + */ +abstract class AbstractConsoleViolationReporter implements ViolationReporter { + protected static final String GREEN = "\u001B[32m"; + protected static final String YELLOW = "\u001B[33m"; + protected static final String RESET = "\u001B[0m"; + + /** + * Reports the collected violations by grouping them into permissive and non-permissive categories, + * and logs each group with formatting and category-based log levels. + * + * @param context the context of the current mojo execution + * @param violations the results of executed code analysis steps containing violations + */ + @Override + public void report(final ExecutionContext context, final Violations violations) { + final Log log = context.getLog(); + + logViolationsForType(log, violations.getPermissiveViolations(), PermissiveType.PERMISSIVE); + logViolationsForType(log, violations.getNonPermissiveViolations(), PermissiveType.NON_PERMISSIVE); + } + + /** + * Renders and logs a group of violations. The header of the group has already been logged. + * + * @param log the log of the current mojo execution + * @param violations the violations of the group, never empty + * @param permissiveType the category the violations belong to + */ + protected abstract void logViolations(final Log log, final List violations, final PermissiveType permissiveType); + + /** + * Logs the message as a warning for non-permissive violations, and as info otherwise. + * + * @param log the log of the current mojo execution + * @param permissiveType the category the message relates to + * @param message the message to log + */ + protected static void logWithLevel(final Log log, final PermissiveType permissiveType, final String message) { + if (permissiveType == PermissiveType.NON_PERMISSIVE) { + log.warn(message); + } else { + log.info(message); + } + } + + private void logViolationsForType(final Log log, final List violations, final PermissiveType permissiveType) { + if (violations.isEmpty()) { + log.info(String.format("✅ %s ##### No %s violations found ##### %s ✅ ", GREEN, permissiveType.displayName(), RESET)); + return; + } + + final String message = String.format("%s ##### found %d %s violations ##### %s", + (permissiveType == PermissiveType.NON_PERMISSIVE) ? YELLOW : GREEN, + violations.size(), + permissiveType.displayName(), + RESET + ); + + logWithLevel(log, permissiveType, (permissiveType == PermissiveType.NON_PERMISSIVE ? "⚠ " : "✅ ") + message); + + logViolations(log, violations, permissiveType); + } + + protected enum PermissiveType { + PERMISSIVE, + NON_PERMISSIVE; + + String displayName() { + return name().replace('_', ' ') + .toLowerCase(Locale.ROOT); + } + } +} diff --git a/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporter.java b/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporter.java index be66dc9..5fd6dd4 100644 --- a/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporter.java +++ b/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporter.java @@ -1,7 +1,5 @@ package io.github.finoid.maven.plugins.codequality.report; -import io.github.finoid.maven.plugins.codequality.ExecutionContext; -import io.github.finoid.maven.plugins.codequality.filter.Violations; import io.github.finoid.maven.plugins.codequality.log.ViolationLinkableConsoleLogger; import io.github.finoid.maven.plugins.codequality.util.Precondition; import org.apache.maven.plugin.logging.Log; @@ -10,7 +8,6 @@ import javax.inject.Named; import javax.inject.Singleton; import java.util.List; -import java.util.Locale; /** * A plain console-based implementation of {@link ViolationReporter} that logs code quality violations @@ -21,13 +18,9 @@ */ @Named("console-plain") @Singleton -public class ConsolePlainViolationReporter implements ViolationReporter { +public class ConsolePlainViolationReporter extends AbstractConsoleViolationReporter { public static final String NAME = "CONSOLE_PLAIN"; - private static final String GREEN = "\u001B[32m"; - private static final String YELLOW = "\u001B[33m"; - private static final String RESET = "\u001B[0m"; - private final ViolationLinkableConsoleLogger violationLinkableConsoleLogger; @Inject @@ -35,60 +28,13 @@ public ConsolePlainViolationReporter(final ViolationLinkableConsoleLogger violat this.violationLinkableConsoleLogger = Precondition.nonNull(violationLinkableConsoleLogger, "ViolationLinkableConsoleLogger shouldn't be null"); } - /** - * Reports all violations of at least {@link Severity#MINOR} level by grouping them - * into permissive and non-permissive categories, and logs each group with formatting - * and severity-based log levels. - * - * @param context the context of the current mojo execution - * @param violations the results of executed code analysis steps containing violations - */ - @Override - public void report(final ExecutionContext context, final Violations violations) { - final Log log = context.getLog(); - - logViolationsForType(log, violations.getPermissiveViolations(), PermissiveType.PERMISSIVE); - logViolationsForType(log, violations.getNonPermissiveViolations(), PermissiveType.NON_PERMISSIVE); - } - @Override public String name() { return NAME; } - private void logViolationsForType(final Log log, final List violations, final PermissiveType permissiveType) { - if (violations.isEmpty()) { - log.info(String.format("✅ %s ##### No %s violations found ##### %s ✅ ", GREEN, permissiveType.displayName(), RESET)); - return; - } - - final String message = String.format("%s ##### found %d %s violations ##### %s", - (permissiveType == PermissiveType.NON_PERMISSIVE) ? YELLOW : GREEN, - violations.size(), - permissiveType.displayName(), - RESET - ); - - logWithLevel(log, permissiveType, (permissiveType == PermissiveType.NON_PERMISSIVE ? "⚠ " : "✅ ") + message); - - violations.forEach(v -> logWithLevel(log, permissiveType, violationLinkableConsoleLogger.format(v))); - } - - private static void logWithLevel(final Log log, final PermissiveType permissiveType, final String message) { - if (permissiveType == PermissiveType.NON_PERMISSIVE) { - log.warn(message); - } else { - log.info(message); - } - } - - private enum PermissiveType { - PERMISSIVE, - NON_PERMISSIVE; - - private String displayName() { - return name().replace('_', ' ') - .toLowerCase(Locale.ROOT); - } + @Override + protected void logViolations(final Log log, final List violations, final PermissiveType permissiveType) { + violations.forEach(it -> logWithLevel(log, permissiveType, violationLinkableConsoleLogger.format(it))); } } diff --git a/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporter.java b/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporter.java index 283b22c..097e4f6 100644 --- a/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporter.java +++ b/src/main/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporter.java @@ -4,14 +4,11 @@ import de.vandermeer.asciitable.CWC_LongestLine; import de.vandermeer.asciithemes.TA_GridThemes; import de.vandermeer.skb.interfaces.transformers.textformat.TextAlignment; -import io.github.finoid.maven.plugins.codequality.ExecutionContext; -import io.github.finoid.maven.plugins.codequality.filter.Violations; import org.apache.maven.plugin.logging.Log; import javax.inject.Named; import javax.inject.Singleton; import java.util.List; -import java.util.Locale; /** * A table console-based implementation of {@link ViolationReporter} that logs code quality violations @@ -22,35 +19,30 @@ */ @Named("console-table") @Singleton -public class ConsoleTableViolationReporter implements ViolationReporter { +public class ConsoleTableViolationReporter extends AbstractConsoleViolationReporter { public static final String NAME = "CONSOLE_TABLE"; - private static final String GREEN = "\u001B[32m"; - private static final String YELLOW = "\u001B[33m"; - private static final String RESET = "\u001B[0m"; - /** - * Reports all violations of at least {@link Severity#MINOR} level by grouping them - * into permissive and non-permissive categories, and logs each group with formatting - * and severity-based log levels. - * - * @param context the context of the current mojo execution - * @param violations the results of executed code analysis steps containing violations + * The width, in characters, the table is rendered at. */ - @Override - public void report(final ExecutionContext context, final Violations violations) { - final Log log = context.getLog(); + private static final int TABLE_WIDTH = 200; - logViolationsForType(log, violations.getPermissiveViolations(), PermissiveType.PERMISSIVE); - logViolationsForType(log, violations.getNonPermissiveViolations(), PermissiveType.NON_PERMISSIVE); - } + /** + * The padding, in characters, on either side of every cell. + */ + private static final int CELL_PADDING = 1; @Override public String name() { return NAME; } - private String renderTable(final List violations) { + @Override + protected void logViolations(final Log log, final List violations, final PermissiveType permissiveType) { + logWithLevel(log, permissiveType, System.lineSeparator() + renderTable(violations)); + } + + private static String renderTable(final List violations) { final AsciiTable table = new AsciiTable(); // Add the header @@ -60,8 +52,6 @@ private String renderTable(final List violations) { // Add each individual violation as a row violations.forEach(it -> { - table.setPadding(1); - table.addRow( it.getTool(), it.getRule(), @@ -71,56 +61,23 @@ private String renderTable(final List violations) { table.addRule(); }); + // Applies to the rows added so far, so it has to happen once every row is in place + table.setPadding(CELL_PADDING); table.setTextAlignment(TextAlignment.LEFT); - table.getContext().setGridTheme(TA_GridThemes.FULL); + table.getContext() + .setGridTheme(TA_GridThemes.FULL); final CWC_LongestLine cwc = new CWC_LongestLine(); table.getRenderer() .setCWC(cwc); - // Override specific column width ratios (relative percentages) - cwc.add(10, 15) // Type + // Override the minimum and maximum width of each column + cwc.add(10, 15) // Tool .add(20, 20) // Rule - .add(40, 60) // Description!) + .add(40, 60) // Description .add(25, 50) // Path - .add(12, 20); // Column number - - return table.render(200); - } - - private void logViolationsForType(final Log log, final List violations, final PermissiveType permissiveType) { - if (violations.isEmpty()) { - log.info(String.format("✅ %s ##### No %s violations found ##### %s ✅ ", GREEN, permissiveType.displayName(), RESET)); - return; - } - - final String message = String.format("%s ##### found %d %s violations ##### %s", - (permissiveType == PermissiveType.NON_PERMISSIVE) ? YELLOW : GREEN, - violations.size(), - permissiveType.displayName(), - RESET - ); - - logWithLevel(log, permissiveType, (permissiveType == PermissiveType.NON_PERMISSIVE ? "⚠ " : "✅ ") + message); - - log.info(System.lineSeparator() + renderTable(violations)); - } - - private static void logWithLevel(final Log log, final PermissiveType permissiveType, final String message) { - if (permissiveType == PermissiveType.NON_PERMISSIVE) { - log.warn(message); - } else { - log.info(message); - } - } - - private enum PermissiveType { - PERMISSIVE, - NON_PERMISSIVE; + .add(12, 20); // Line/Column number - private String displayName() { - return name().replace('_', ' ') - .toLowerCase(Locale.ROOT); - } + return table.render(TABLE_WIDTH); } } diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/RecordingLog.java b/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/RecordingLog.java new file mode 100644 index 0000000..d97abf0 --- /dev/null +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/RecordingLog.java @@ -0,0 +1,125 @@ +package io.github.finoid.maven.plugins.codequality.fixtures; + +import org.apache.maven.plugin.logging.Log; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link Log} which records everything written to it, together with the level it was written at. + * + *

Intended for asserting on console output: {@link #render()} returns the recorded log as a single + * readable string, which makes it suitable for snapshotting. + */ +public class RecordingLog implements Log { + private static final String ESCAPE = "\u001B"; + + private final List entries = new ArrayList<>(); + + /** + * The recorded entries, each prefixed with the level it was logged at. + * + * @return the recorded entries, in the order they were logged + */ + public List entries() { + return List.copyOf(entries); + } + + /** + * Renders the recorded entries as a single string. + * + *

ANSI escapes are rendered as their literal, printable escape sequence, to keep the coloring of the + * output both visible and diffable rather than being swallowed by whatever reads the snapshot. + * + * @return the recorded entries, separated by newlines + */ + public String render() { + return String.join("\n", entries) + .replace(ESCAPE, "\\u001B"); + } + + private void record(final String level, @Nullable final CharSequence content) { + entries.add("[" + level + "] " + content); + } + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public void debug(@Nullable final CharSequence content) { + record("DEBUG", content); + } + + @Override + public void debug(@Nullable final CharSequence content, @Nullable final Throwable error) { + record("DEBUG", content); + } + + @Override + public void debug(@Nullable final Throwable error) { + record("DEBUG", String.valueOf(error)); + } + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public void info(@Nullable final CharSequence content) { + record("INFO", content); + } + + @Override + public void info(@Nullable final CharSequence content, @Nullable final Throwable error) { + record("INFO", content); + } + + @Override + public void info(@Nullable final Throwable error) { + record("INFO", String.valueOf(error)); + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public void warn(@Nullable final CharSequence content) { + record("WARN", content); + } + + @Override + public void warn(@Nullable final CharSequence content, @Nullable final Throwable error) { + record("WARN", content); + } + + @Override + public void warn(@Nullable final Throwable error) { + record("WARN", String.valueOf(error)); + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + @Override + public void error(@Nullable final CharSequence content) { + record("ERROR", content); + } + + @Override + public void error(@Nullable final CharSequence content, @Nullable final Throwable error) { + record("ERROR", content); + } + + @Override + public void error(@Nullable final Throwable error) { + record("ERROR", String.valueOf(error)); + } +} diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/UnitTest.java b/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/UnitTest.java index e6a1359..9e2f7ae 100644 --- a/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/UnitTest.java +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/fixtures/UnitTest.java @@ -2,6 +2,7 @@ import au.com.origin.snapshots.Expect; import au.com.origin.snapshots.junit5.SnapshotExtension; +import au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer; import com.fasterxml.jackson.databind.module.SimpleModule; import io.github.finoid.maven.plugins.codequality.fixtures.snapshot.JsonSnapshotSerializer; import org.junit.jupiter.api.Tag; @@ -62,6 +63,22 @@ public void snapshot(final T toBeSnapshotted, final String... maskedFieldPat .toMatchSnapshot(toBeSnapshotted); } + /** + * Takes a snapshot of the string representation of the given object, verbatim. + * + *

Unlike {@link #snapshot(Object)} the snapshot is not serialized as JSON, which keeps multi-line + * output - console output in particular - readable, and reviewable, in the snapshot file. + * + * @param toBeSnapshotted the object to be snapshotted + * @param the type of the object + */ + @SuppressWarnings("SpellCheckingInspection") + public void snapshotText(final T toBeSnapshotted) { + expect + .serializer(new ToStringSnapshotSerializer()) + .toMatchSnapshot(toBeSnapshotted); + } + /** * Captures a snapshot of the given object within a specified test scenario. * This method is useful when running multiple variations of the same test case diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporterUnitTest.java b/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporterUnitTest.java new file mode 100644 index 0000000..fc2860b --- /dev/null +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsolePlainViolationReporterUnitTest.java @@ -0,0 +1,89 @@ +package io.github.finoid.maven.plugins.codequality.report; + +import io.github.finoid.maven.plugins.codequality.ExecutionContext; +import io.github.finoid.maven.plugins.codequality.filter.Violations; +import io.github.finoid.maven.plugins.codequality.fixtures.RecordingLog; +import io.github.finoid.maven.plugins.codequality.fixtures.UnitTest; +import io.github.finoid.maven.plugins.codequality.log.ViolationLinkableConsoleLogger; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ConsolePlainViolationReporterUnitTest extends UnitTest { + private final ConsolePlainViolationReporter unit = new ConsolePlainViolationReporter(new ViolationLinkableConsoleLogger()); + + private final RecordingLog log = new RecordingLog(); + + @Test + void givenNoViolations_whenReport_thenReportsBothCategoriesAsEmpty() { + unit.report(executionContext(), new Violations(List.of(), List.of())); + + snapshotText(log.render()); + } + + @Test + void givenPermissiveViolations_whenReport_thenReportsEveryViolation() { + unit.report(executionContext(), new Violations(violations(), List.of())); + + snapshotText(log.render()); + } + + @Test + void givenNonPermissiveViolations_whenReport_thenReportsEveryViolation() { + unit.report(executionContext(), new Violations(List.of(), violations())); + + snapshotText(log.render()); + } + + @Test + void givenViolationsOfBothCategories_whenReport_thenReportsEachCategoryAtItsOwnLevel() { + unit.report(executionContext(), new Violations(violations(), violations())); + + snapshotText(log.render()); + } + + /** + * Every non-permissive violation, and not only the header of the category, has to be reported as a warning. + * Reported as info they are dropped by a quiet build, leaving behind a warning without the violations it refers to. + */ + @Test + void givenNonPermissiveViolations_whenReport_thenEveryViolationIsReportedAsWarning() { + unit.report(executionContext(), new Violations(List.of(), violations())); + + final List entries = log.entries(); + + Assertions.assertEquals(violations().size() + 2, entries.size(), + "Expected a header per category, plus an entry per violation"); + + entries.stream() + .filter(it -> it.contains("file://")) + .forEach(it -> Assertions.assertTrue(it.startsWith("[WARN]"), () -> "Expected the violation to be reported as a warning: " + it)); + } + + private ExecutionContext executionContext() { + return ExecutionContext.of(new MavenProject(), log); + } + + private static List violations() { + return List.of( + violation("checkstyle", "UnusedImports", "Unused import - java.util.Optional.", "src/main/java/Alpha.java", 3, 8), + violation("NullAway", "NullAway", "Passing @Nullable parameter 'name' where @NonNull is required.", "src/main/java/Gamma.java", 17, 24)); + } + + private static Violation violation(final String tool, final String rule, final String description, final String path, + final int line, final int columnNumber) { + return Violation.builder() + .tool(tool) + .rule(rule) + .description(description) + .fingerprint(tool + ":" + rule + ":" + path) + .severity(Severity.MAJOR) + .relativePath(path) + .fullPath("/workspace/" + path) + .line(line) + .columnNumber(columnNumber) + .build(); + } +} diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporterUnitTest.java b/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporterUnitTest.java new file mode 100644 index 0000000..ec84b80 --- /dev/null +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/report/ConsoleTableViolationReporterUnitTest.java @@ -0,0 +1,135 @@ +package io.github.finoid.maven.plugins.codequality.report; + +import io.github.finoid.maven.plugins.codequality.ExecutionContext; +import io.github.finoid.maven.plugins.codequality.filter.Violations; +import io.github.finoid.maven.plugins.codequality.fixtures.RecordingLog; +import io.github.finoid.maven.plugins.codequality.fixtures.UnitTest; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class ConsoleTableViolationReporterUnitTest extends UnitTest { + private static final String COLUMN_DELIMITER = "│"; + private static final String TABLE_TOP_LEFT_CORNER = "┌"; + + private final ConsoleTableViolationReporter unit = new ConsoleTableViolationReporter(); + + private final RecordingLog log = new RecordingLog(); + + @Test + void givenNoViolations_whenReport_thenReportsBothCategoriesAsEmpty() { + unit.report(executionContext(), new Violations(List.of(), List.of())); + + snapshotText(log.render()); + } + + @Test + void givenPermissiveViolations_whenReport_thenRendersTable() { + unit.report(executionContext(), new Violations(violations(), List.of())); + + snapshotText(log.render()); + } + + @Test + void givenNonPermissiveViolations_whenReport_thenRendersTable() { + unit.report(executionContext(), new Violations(List.of(), violations())); + + snapshotText(log.render()); + } + + /** + * Every row of the table, the last one included, has to be padded. The padding of an + * {@link de.vandermeer.asciitable.AsciiTable} is applied to the rows added so far, which makes it easy to + * leave the row added last unpadded. + */ + @Test + void givenViolations_whenReport_thenEveryRowIsPadded() { + unit.report(executionContext(), new Violations(violations(), List.of())); + + final List rows = tableRowsOf(log.render()); + + Assertions.assertFalse(rows.isEmpty(), "Expected the rendered table to hold rows"); + + rows.forEach(row -> cellsOf(row).forEach(cell -> { + Assertions.assertTrue(cell.startsWith(" "), () -> "Cell isn't padded to the left: '" + cell + "' of row '" + row + "'"); + Assertions.assertTrue(cell.endsWith(" "), () -> "Cell isn't padded to the right: '" + cell + "' of row '" + row + "'"); + })); + } + + /** + * The violations themselves, and not only the header of the category, have to be reported as a warning. + * Reported as info they are dropped by a quiet build, leaving behind a warning without the violations it refers to. + */ + @Test + void givenNonPermissiveViolations_whenReport_thenTableIsReportedAsWarning() { + unit.report(executionContext(), new Violations(List.of(), violations())); + + assertTableReportedAtLevel("[WARN]"); + } + + @Test + void givenPermissiveViolations_whenReport_thenTableIsReportedAsInfo() { + unit.report(executionContext(), new Violations(violations(), List.of())); + + assertTableReportedAtLevel("[INFO]"); + } + + private void assertTableReportedAtLevel(final String level) { + final List tableEntries = log.entries() + .stream() + .filter(it -> it.contains(TABLE_TOP_LEFT_CORNER)) + .toList(); + + Assertions.assertEquals(1, tableEntries.size(), "Expected the table to be reported exactly once"); + + tableEntries.forEach(it -> Assertions.assertTrue(it.startsWith(level), () -> "Expected the table to be reported at " + level)); + } + + private ExecutionContext executionContext() { + return ExecutionContext.of(new MavenProject(), log); + } + + /** + * Extracts the rows, the horizontal rules excluded, of the rendered table. + */ + private static List tableRowsOf(final String rendered) { + return rendered.lines() + .filter(line -> line.startsWith(COLUMN_DELIMITER)) + .toList(); + } + + /** + * Extracts the cells, the leading and trailing delimiter excluded, of a rendered row. + */ + private static List cellsOf(final String row) { + final String[] parts = row.split(COLUMN_DELIMITER, -1); + + return Arrays.asList(parts) + .subList(1, parts.length - 1); + } + + private static List violations() { + return List.of( + violation("checkstyle", "UnusedImports", "Unused import - java.util.Optional.", "src/main/java/Alpha.java", 3, 8), + violation("checkstyle", "LineLength", "Line is longer than 160 characters.", "src/main/java/Beta.java", 42, 1), + violation("NullAway", "NullAway", "Passing @Nullable parameter 'name' where @NonNull is required.", "src/main/java/Gamma.java", 17, 24)); + } + + private static Violation violation(final String tool, final String rule, final String description, final String path, + final int line, final int columnNumber) { + return Violation.builder() + .tool(tool) + .rule(rule) + .description(description) + .fingerprint(tool + ":" + rule + ":" + path) + .severity(Severity.MAJOR) + .relativePath(path) + .fullPath("/workspace/" + path) + .line(line) + .columnNumber(columnNumber) + .build(); + } +} diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsolePlainViolationReporterUnitTest.snap b/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsolePlainViolationReporterUnitTest.snap new file mode 100644 index 0000000..20bf5e3 --- /dev/null +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsolePlainViolationReporterUnitTest.snap @@ -0,0 +1,38 @@ +io.github.finoid.maven.plugins.codequality.report.ConsolePlainViolationReporterUnitTest.givenNoViolations_whenReport_thenReportsBothCategoriesAsEmpty=[ +[INFO] ✅ \u001B[32m ##### No permissive violations found ##### \u001B[0m ✅ +[INFO] ✅ \u001B[32m ##### No non permissive violations found ##### \u001B[0m ✅ +] + + +io.github.finoid.maven.plugins.codequality.report.ConsolePlainViolationReporterUnitTest.givenNonPermissiveViolations_whenReport_thenReportsEveryViolation=[ +[INFO] ✅ \u001B[32m ##### No permissive violations found ##### \u001B[0m ✅ +[WARN] ⚠ \u001B[33m ##### found 2 non permissive violations ##### \u001B[0m +[WARN] file:///workspace/src/main/java/Alpha.java:3:8 + [checkstyle - UnusedImports] Unused import - java.util.Optional. +[WARN] file:///workspace/src/main/java/Gamma.java:17:24 + [NullAway - NullAway] Passing @Nullable parameter 'name' where @NonNull is required. +] + + +io.github.finoid.maven.plugins.codequality.report.ConsolePlainViolationReporterUnitTest.givenPermissiveViolations_whenReport_thenReportsEveryViolation=[ +[INFO] ✅ \u001B[32m ##### found 2 permissive violations ##### \u001B[0m +[INFO] file:///workspace/src/main/java/Alpha.java:3:8 + [checkstyle - UnusedImports] Unused import - java.util.Optional. +[INFO] file:///workspace/src/main/java/Gamma.java:17:24 + [NullAway - NullAway] Passing @Nullable parameter 'name' where @NonNull is required. +[INFO] ✅ \u001B[32m ##### No non permissive violations found ##### \u001B[0m ✅ +] + + +io.github.finoid.maven.plugins.codequality.report.ConsolePlainViolationReporterUnitTest.givenViolationsOfBothCategories_whenReport_thenReportsEachCategoryAtItsOwnLevel=[ +[INFO] ✅ \u001B[32m ##### found 2 permissive violations ##### \u001B[0m +[INFO] file:///workspace/src/main/java/Alpha.java:3:8 + [checkstyle - UnusedImports] Unused import - java.util.Optional. +[INFO] file:///workspace/src/main/java/Gamma.java:17:24 + [NullAway - NullAway] Passing @Nullable parameter 'name' where @NonNull is required. +[WARN] ⚠ \u001B[33m ##### found 2 non permissive violations ##### \u001B[0m +[WARN] file:///workspace/src/main/java/Alpha.java:3:8 + [checkstyle - UnusedImports] Unused import - java.util.Optional. +[WARN] file:///workspace/src/main/java/Gamma.java:17:24 + [NullAway - NullAway] Passing @Nullable parameter 'name' where @NonNull is required. +] \ No newline at end of file diff --git a/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsoleTableViolationReporterUnitTest.snap b/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsoleTableViolationReporterUnitTest.snap new file mode 100644 index 0000000..336acf8 --- /dev/null +++ b/src/test/java/io/github/finoid/maven/plugins/codequality/report/__snapshots__/ConsoleTableViolationReporterUnitTest.snap @@ -0,0 +1,54 @@ +io.github.finoid.maven.plugins.codequality.report.ConsoleTableViolationReporterUnitTest.givenNoViolations_whenReport_thenReportsBothCategoriesAsEmpty=[ +[INFO] ✅ \u001B[32m ##### No permissive violations found ##### \u001B[0m ✅ +[INFO] ✅ \u001B[32m ##### No non permissive violations found ##### \u001B[0m ✅ +] + + +io.github.finoid.maven.plugins.codequality.report.ConsoleTableViolationReporterUnitTest.givenNonPermissiveViolations_whenReport_thenRendersTable=[ +[INFO] ✅ \u001B[32m ##### No permissive violations found ##### \u001B[0m ✅ +[WARN] ⚠ \u001B[33m ##### found 3 non permissive violations ##### \u001B[0m +[WARN] +┌────────────┬────────────────────┬────────────────────────────────────────────────────────────┬──────────────────────────┬────────────────────┐ +│ │ │ │ │ │ +│ Tool │ Rule │ Description │ Path │ Line/Column number │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ checkstyle │ UnusedImports │ Unused import - java.util.Optional. │ src/main/java/Alpha.java │ 3:8 │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ checkstyle │ LineLength │ Line is longer than 160 characters. │ src/main/java/Beta.java │ 42:1 │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ NullAway │ NullAway │ Passing @Nullable parameter 'name' where @NonNull is │ src/main/java/Gamma.java │ 17:24 │ +│ │ │ required. │ │ │ +│ │ │ │ │ │ +└────────────┴────────────────────┴────────────────────────────────────────────────────────────┴──────────────────────────┴────────────────────┘ +] + + +io.github.finoid.maven.plugins.codequality.report.ConsoleTableViolationReporterUnitTest.givenPermissiveViolations_whenReport_thenRendersTable=[ +[INFO] ✅ \u001B[32m ##### found 3 permissive violations ##### \u001B[0m +[INFO] +┌────────────┬────────────────────┬────────────────────────────────────────────────────────────┬──────────────────────────┬────────────────────┐ +│ │ │ │ │ │ +│ Tool │ Rule │ Description │ Path │ Line/Column number │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ checkstyle │ UnusedImports │ Unused import - java.util.Optional. │ src/main/java/Alpha.java │ 3:8 │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ checkstyle │ LineLength │ Line is longer than 160 characters. │ src/main/java/Beta.java │ 42:1 │ +│ │ │ │ │ │ +├────────────┼────────────────────┼────────────────────────────────────────────────────────────┼──────────────────────────┼────────────────────┤ +│ │ │ │ │ │ +│ NullAway │ NullAway │ Passing @Nullable parameter 'name' where @NonNull is │ src/main/java/Gamma.java │ 17:24 │ +│ │ │ required. │ │ │ +│ │ │ │ │ │ +└────────────┴────────────────────┴────────────────────────────────────────────────────────────┴──────────────────────────┴────────────────────┘ +[INFO] ✅ \u001B[32m ##### No non permissive violations found ##### \u001B[0m ✅ +] \ No newline at end of file