se.bjurr.violations.main.Runner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of violations-command-line Show documentation
Show all versions of violations-command-line Show documentation
CLI find report files from static code analysis, present and optionally fail.
The newest version!
package se.bjurr.violations.main;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static se.bjurr.violations.git.ViolationsReporterApi.violationsReporterApi;
import static se.bjurr.violations.lib.ViolationsApi.violationsApi;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.script.ScriptException;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import se.bjurr.violations.git.ViolationsGit;
import se.bjurr.violations.git.ViolationsReporterDetailLevel;
import se.bjurr.violations.lib.FilteringViolationsLogger;
import se.bjurr.violations.lib.ViolationsLogger;
import se.bjurr.violations.lib.model.SEVERITY;
import se.bjurr.violations.lib.model.Violation;
import se.bjurr.violations.lib.model.codeclimate.CodeClimateTransformer;
import se.bjurr.violations.lib.model.sarif.SarifTransformer;
import se.bjurr.violations.lib.parsers.JacocoParser;
import se.bjurr.violations.lib.parsers.JacocoParserSettings;
import se.bjurr.violations.lib.parsers.ViolationsParser;
import se.bjurr.violations.lib.reports.Parser;
import se.bjurr.violations.lib.util.Filtering;
import se.bjurr.violations.violationslib.com.google.gson.Gson;
import se.bjurr.violations.violationslib.com.google.gson.GsonBuilder;
@Command(name = "violations-command-line")
public class Runner implements Runnable {
private static final String SHOW_JSON_CONFIG = "-show-json-config";
private static final String VIOLATIONS_CONFIG = "VIOLATIONS_CONFIG";
@Option(
names = {"-v", "--violations"},
hideParamSyntax = true,
description =
"Format: , Example: -v \"JSLINT\" \".\" \".*/jshint.xml$\" \"JSHint\"",
parameterConsumer = ViolationConfigConverter.class,
arity = "3..4")
List violationsArg = new ArrayList();
@Option(
defaultValue = "INFO",
names = {"-severity", "-s"},
description = "Minimum severity level to report. ${COMPLETION-CANDIDATES}")
SEVERITY minSeverityArg;
@Option(
names = {"-code-climate", "-cc"},
description = "Create a CodeClimate file with all the violations.")
File codeClimateFileArg;
@Option(
names = {"-sarif", "-ss"},
description = "Create a Sarif file with all the violations.")
File sarifFileArg;
@Option(
names = {"-violations-file", "-vf"},
description = "Create a JSON file with all the violations.")
File violationsFileArg;
@Option(
defaultValue = Integer.MAX_VALUE + "",
names = {"-max-violations", "-mv"},
description = "Will fail the build if total number of found violations is higher.")
Integer maxViolationsArg;
@Option(
defaultValue = "VERBOSE",
names = {"-detail-level", "-dl"},
description = "Verbosity ${COMPLETION-CANDIDATES}")
ViolationsReporterDetailLevel detailLevelArg;
@Option(
defaultValue = "true",
names = {"-print-violations", "-pv"},
description = "Will print violations found")
Boolean printViolationsArg;
@Option(
defaultValue = ".",
names = {"-git-repo", "-gr"},
description = "Where to look for Git.")
File gitRepoArg;
@Option(
names = {"-diff-from", "-df"},
description = "Can be empty (ignored), Git-commit or any Git-reference")
String diffFrom;
@Option(
names = {"-diff-to", "-dt"},
description = "Can be empty (ignored), Git-commit or any Git-reference")
String diffTo;
@Option(
defaultValue = "INFO",
names = {"-diff-severity", "-ds"},
description = "${COMPLETION-CANDIDATES}")
SEVERITY diffMinSeverity;
@Option(
defaultValue = "false",
names = {"-diff-print-violations", "-dpv"},
description = "Will print violations found in diff")
Boolean diffPrintViolations;
@Option(
defaultValue = Integer.MAX_VALUE + "",
names = {"-diff-max-violations", "-dmv"},
description = "Will fail the build if total number of found violations is higher")
Integer diffMaxViolations;
@Option(
defaultValue = "VERBOSE",
names = {"-diff-detail-level", "-ddl"},
description = "${COMPLETION-CANDIDATES}")
ViolationsReporterDetailLevel diffDetailLevel;
@Option(
defaultValue = "0",
names = {"-max-reporter-column-width", "-mrcw"},
description = "0 means no limit")
Integer maxReporterColumnWidth;
@Option(
defaultValue = "10",
names = {"-max-rule-column-width", "-mrucw"},
description = "0 means no limit")
Integer maxRuleColumnWidth;
@Option(
defaultValue = "0",
names = {"-max-severity-column-width", "-mscw"},
description = "0 means no limit")
Integer maxSeverityColumnWidth;
@Option(
defaultValue = "0",
names = {"-max-line-column-width", "-mlcw"},
description = "0 means no limit")
Integer maxLineColumnWidth;
@Option(
defaultValue = "50",
names = {"-max-message-column-width", "-mmcw"},
description = "0 means no limit")
Integer maxMessageColumnWidth;
@Option(
names = {"-show-debug-info"},
description =
"Please run your command with this parameter and supply output when reporting bugs.")
Boolean showDebugInfo;
@Option(
names = {SHOW_JSON_CONFIG},
description = "Will print the given config as JSON.")
Boolean showJsonConfig;
@Option(
names = {"-config-file", "-cf"},
description =
"Will read config from given file. Can also be configured"
+ " with environment variable "
+ VIOLATIONS_CONFIG
+ ". Format is what you get from "
+ SHOW_JSON_CONFIG
+ ".")
File configFileArg;
@Option(
defaultValue = JacocoParserSettings.DEFAULT_MIN_LINE_COUNT + "",
names = {"-jacoco-min-line-count", "-jmlc"},
description = "Minimum line count in Jacoco that will generate a violation.")
Integer jacocoMinLineCount;
@Option(
defaultValue = "0.7",
names = {"-jacoco-min-coverage", "-jmc"},
description = "Minimum coverage in Jacoco that will generate a violation.")
BigDecimal jacocoMinCoverage;
@Option(names = "--help", usageHelp = true, description = "display this help and exit")
boolean help;
ViolationsConfig violationsConfig = new ViolationsConfig();
@Override
public void run() {
final String violationsConfigPropertyValue = System.getenv(VIOLATIONS_CONFIG);
final boolean violationsConfigPropertyValueGiven = violationsConfigPropertyValue != null;
final boolean shouldUseConfigFile =
this.wasGiven(this.configFileArg) || violationsConfigPropertyValueGiven;
if (shouldUseConfigFile) {
Path jsonFile = null;
if (this.wasGiven(this.configFileArg)) {
jsonFile = this.configFileArg.toPath();
}
if (violationsConfigPropertyValueGiven) {
jsonFile = Paths.get(violationsConfigPropertyValue);
}
String json;
try {
json = new String(Files.readAllBytes(jsonFile), UTF_8);
} catch (final IOException e) {
throw new RuntimeException(jsonFile.toString(), e);
}
this.violationsConfig = new Gson().fromJson(json, ViolationsConfig.class);
} else {
this.violationsConfig.setViolations(this.violationsArg);
this.violationsConfig.setMinSeverity(this.minSeverityArg);
this.violationsConfig.setMaxViolations(this.maxViolationsArg);
this.violationsConfig.setPrintViolations(this.printViolationsArg);
this.violationsConfig.setDetailLevel(this.detailLevelArg);
this.violationsConfig.setPrintViolations(this.printViolationsArg);
this.violationsConfig.setDiffFrom(this.diffFrom);
this.violationsConfig.setDiffTo(this.diffTo);
this.violationsConfig.setDiffMinSeverity(this.diffMinSeverity);
this.violationsConfig.setDiffPrintViolations(this.diffPrintViolations);
this.violationsConfig.setDiffMaxViolations(this.diffMaxViolations);
this.violationsConfig.setDiffDetailLevel(this.diffDetailLevel);
this.violationsConfig.setMaxReporterColumnWidth(this.maxReporterColumnWidth);
this.violationsConfig.setMaxRuleColumnWidth(this.maxRuleColumnWidth);
this.violationsConfig.setMaxSeverityColumnWidth(this.maxSeverityColumnWidth);
this.violationsConfig.setMaxLineColumnWidth(this.maxLineColumnWidth);
this.violationsConfig.setMaxMessageColumnWidth(this.maxMessageColumnWidth);
this.violationsConfig.setGitRepo(this.gitRepoArg);
this.violationsConfig.setJacocoMinCoverage(this.jacocoMinCoverage.doubleValue());
this.violationsConfig.setJacocoMinLineCount(this.jacocoMinLineCount);
if (this.wasGiven(this.codeClimateFileArg)) {
this.violationsConfig.setCodeClimateFile(this.codeClimateFileArg);
}
if (this.wasGiven(this.sarifFileArg)) {
this.violationsConfig.setSarifFile(this.sarifFileArg);
}
if (this.wasGiven(this.violationsFileArg)) {
this.violationsConfig.setViolationsFile(this.violationsFileArg);
}
this.violationsConfig.setShowDebugInfo(this.wasGiven(this.showDebugInfo));
if (this.violationsConfig.isShowDebugInfo()) {
System.out.println("Parsed parameters:\n" + this.toString()); // NOPMD
}
}
if (this.wasGiven(this.showJsonConfig)) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
final String jsonString = gson.toJson(this.violationsConfig);
System.out.println(jsonString); // NOPMD
return;
}
this.violationsConfig.setViolationsLogger(
new ViolationsLogger() {
@Override
public void log(final Level level, final String string) {
System.out.println(level + " " + string); // NOPMD
}
@Override
public void log(final Level level, final String string, final Throwable t) {
final StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
System.out.println(level + " " + string + "\n" + sw.toString()); // NOPMD
}
});
if (!this.violationsConfig.isShowDebugInfo()) {
this.violationsConfig.setViolationsLogger(
FilteringViolationsLogger.filterLevel(this.violationsConfig.getViolationsLogger()));
}
try {
this.performTask();
} catch (final TooManyViolationsException e) {
throw e;
} catch (final Exception e) {
throw new RuntimeException(this.toString(), e);
}
}
private boolean wasGiven(final File it) {
return it != null;
}
private boolean wasGiven(final Boolean it) {
return it != null;
}
public void performTask() throws Exception {
final Set allParsedViolations = new TreeSet<>();
final Set allParsedViolationsInDiff = new TreeSet<>();
for (final ViolationConfig configuredViolation : this.violationsConfig.getViolations()) {
final Set parsedViolations = this.getAllParsedViolations(configuredViolation);
allParsedViolations.addAll(
this.getFiltered(parsedViolations, this.violationsConfig.getMinSeverity()));
allParsedViolationsInDiff.addAll(this.getAllViolationsInDiff(parsedViolations));
}
if (this.violationsConfig.getCodeClimateFile() != null) {
this.createJsonFile(
CodeClimateTransformer.fromViolations(allParsedViolations),
this.violationsConfig.getCodeClimateFile());
}
if (this.violationsConfig.getSarifFile() != null) {
this.createJsonFile(
SarifTransformer.fromViolations(allParsedViolations),
this.violationsConfig.getSarifFile());
}
if (this.violationsConfig.getViolationsFile() != null) {
this.createJsonFile(allParsedViolations, this.violationsConfig.getViolationsFile());
}
this.checkGlobalViolations(allParsedViolations);
this.checkDiffViolations(allParsedViolationsInDiff);
}
private void createJsonFile(final Object object, final String file) throws IOException {
final String codeClimateReport = new GsonBuilder().setPrettyPrinting().create().toJson(object);
Files.write(
Paths.get(file),
codeClimateReport.getBytes(StandardCharsets.UTF_8),
TRUNCATE_EXISTING,
CREATE,
WRITE);
}
private void checkGlobalViolations(final Set violations) throws ScriptException {
final boolean tooManyViolations = violations.size() > this.violationsConfig.getMaxViolations();
if (!tooManyViolations && !this.violationsConfig.isPrintViolations()) {
return;
}
final String report =
violationsReporterApi()
.withViolations(violations)
.withMaxLineColumnWidth(this.violationsConfig.getMaxLineColumnWidth())
.withMaxMessageColumnWidth(this.violationsConfig.getMaxMessageColumnWidth())
.withMaxReporterColumnWidth(this.violationsConfig.getMaxReporterColumnWidth()) //
.withMaxRuleColumnWidth(this.violationsConfig.getMaxRuleColumnWidth()) //
.withMaxSeverityColumnWidth(this.violationsConfig.getMaxSeverityColumnWidth()) //
.getReport(this.violationsConfig.getDetailLevel());
if (tooManyViolations) {
System.err.println("\nViolations in repo\n\n" + report); // NOPMD
throw new TooManyViolationsException(
"Too many violations found, max is "
+ this.violationsConfig.getMaxViolations()
+ " but found "
+ violations.size());
} else {
if (this.violationsConfig.isPrintViolations()) {
System.out.println("\nViolations in repo\n\n" + report); // NOPMD
}
}
}
private void checkDiffViolations(final Set violations) throws ScriptException {
final boolean tooManyViolations =
violations.size() > this.violationsConfig.getDiffMaxViolations();
if (!tooManyViolations && !this.violationsConfig.isDiffPrintViolations()) {
return;
}
final String report =
violationsReporterApi()
.withViolations(violations)
.withMaxLineColumnWidth(this.violationsConfig.getMaxLineColumnWidth())
.withMaxMessageColumnWidth(this.violationsConfig.getMaxMessageColumnWidth())
.withMaxReporterColumnWidth(this.violationsConfig.getMaxReporterColumnWidth()) //
.withMaxRuleColumnWidth(this.violationsConfig.getMaxRuleColumnWidth()) //
.withMaxSeverityColumnWidth(this.violationsConfig.getMaxSeverityColumnWidth()) //
.getReport(this.violationsConfig.getDiffDetailLevel());
if (tooManyViolations) {
System.err.println("\nViolations in repo\n\n" + report); // NOPMD
throw new ScriptException(
"Too many violations found in diff, max is "
+ this.violationsConfig.getMaxViolations()
+ " but found "
+ violations.size());
} else {
if (this.violationsConfig.isDiffPrintViolations()) {
System.out.println("\nViolations in diff\n\n" + report); // NOPMD
}
}
}
private Set getAllViolationsInDiff(final Set unfilteredViolations)
throws Exception {
if (!this.isDefined(this.violationsConfig.getDiffFrom())
|| !this.isDefined(this.violationsConfig.getDiffTo())) {
// No references specified, will not report violations in diff
return new TreeSet<>();
} else {
final Set candidates =
this.getFiltered(unfilteredViolations, this.violationsConfig.getDiffMinSeverity());
return new ViolationsGit(this.violationsConfig.getViolationsLogger(), candidates) //
.getViolationsInChangeset(
new File(this.violationsConfig.getGitRepo()),
this.violationsConfig.getDiffFrom(),
this.violationsConfig.getDiffTo());
}
}
private Set getFiltered(final Set unfiltered, final SEVERITY filter) {
if (filter != null) {
return Filtering.withAtLEastSeverity(unfiltered, filter);
}
return unfiltered;
}
private Set getAllParsedViolations(final ViolationConfig configuredViolation) {
final String reporter =
Optional.ofNullable(configuredViolation.getName())
.orElse(configuredViolation.getParser().name());
ViolationsParser parser = null;
try {
final String parserName = configuredViolation.getParser().name();
if (parserName.equals(Parser.JACOCO.name())) {
final int minLineCount = this.violationsConfig.getJacocoMinLineCount();
final double minCoverage = this.violationsConfig.getJacocoMinCoverage();
final JacocoParserSettings settings = new JacocoParserSettings(minLineCount, minCoverage);
parser = new JacocoParser(settings);
} else {
parser = Parser.valueOf(parserName).getViolationsParser();
}
} catch (final Exception e) {
throw new RuntimeException(
Arrays.asList(Parser.values()).stream()
.map((it) -> it.toString())
.collect(Collectors.joining("\n")),
e);
}
final Set parsedViolations =
violationsApi() //
.withViolationsLogger(this.violationsConfig.getViolationsLogger()) //
.withViolationsParser(parser) //
.inFolder(configuredViolation.getFolder()) //
.withPattern(configuredViolation.getRegexp()) //
.withReporter(reporter) //
.violations();
return parsedViolations;
}
private boolean isDefined(final String str) {
return str != null && !str.isEmpty();
}
@Override
public String toString() {
return "Runner [violations="
+ this.violationsConfig.getViolations()
+ ", minSeverity="
+ this.violationsConfig.getMinSeverity()
+ ", detailLevel="
+ this.violationsConfig.getDetailLevel()
+ ", maxViolations="
+ this.violationsConfig.getMaxViolations()
+ ", printViolations="
+ this.violationsConfig.isPrintViolations()
+ ", diffFrom="
+ this.violationsConfig.getDiffFrom()
+ ", diffTo="
+ this.violationsConfig.getDiffTo()
+ ", diffMinSeverity="
+ this.violationsConfig.getDiffMinSeverity()
+ ", gitRepo="
+ this.violationsConfig.getGitRepo()
+ ", diffPrintViolations="
+ this.violationsConfig.isDiffPrintViolations()
+ ", diffMaxViolations="
+ this.violationsConfig.getDiffMaxViolations()
+ ", diffDetailLevel="
+ this.violationsConfig.getDiffDetailLevel()
+ ", maxReporterColumnWidth="
+ this.violationsConfig.getMaxReporterColumnWidth()
+ ", maxRuleColumnWidth="
+ this.violationsConfig.getMaxRuleColumnWidth()
+ ", maxSeverityColumnWidth="
+ this.violationsConfig.getMaxSeverityColumnWidth()
+ ", maxLineColumnWidth="
+ this.violationsConfig.getMaxLineColumnWidth()
+ ", maxMessageColumnWidth="
+ this.violationsConfig.getMaxMessageColumnWidth()
+ "]";
}
}