From 024b9d14b36906591e9d1b984688b63f2aead342 Mon Sep 17 00:00:00 2001 From: yangziwen Date: Sat, 18 Feb 2023 17:32:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7checkstyle=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=88=B09.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/github/yangziwen/checkstyle/Main.java | 1195 ++++++++--------- .../checkstyle/filter/DiffLineFilter.java | 2 +- .../src/main/resources/custom_checks.xml | 16 +- .../src/main/resources/custom_full_checks.xml | 15 +- .../checkstyle/filter/DiffLineFilterTest.java | 9 +- hooks/pre-commit-checkstyle | 2 +- pom.xml | 2 +- 7 files changed, 608 insertions(+), 633 deletions(-) diff --git a/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/Main.java b/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/Main.java index f0059b3..e1b001f 100644 --- a/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/Main.java +++ b/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/Main.java @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. -// Copyright (C) 2001-2018 the original author or authors. +// Copyright (C) 2001-2022 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -24,11 +24,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Locale; +import java.util.Objects; import java.util.Properties; import java.util.logging.ConsoleHandler; import java.util.logging.Filter; @@ -38,12 +40,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -61,6 +57,7 @@ import com.puppycrawl.tools.checkstyle.ModuleFactory; import com.puppycrawl.tools.checkstyle.PackageObjectFactory; import com.puppycrawl.tools.checkstyle.PropertiesExpander; +import com.puppycrawl.tools.checkstyle.SarifLogger; import com.puppycrawl.tools.checkstyle.SuppressionsStringPrinter; import com.puppycrawl.tools.checkstyle.ThreadModeSettings; import com.puppycrawl.tools.checkstyle.XMLLogger; @@ -71,22 +68,25 @@ import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import com.puppycrawl.tools.checkstyle.api.CheckstyleException; import com.puppycrawl.tools.checkstyle.api.Configuration; -import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; import com.puppycrawl.tools.checkstyle.api.RootModule; +import com.puppycrawl.tools.checkstyle.api.Violation; +import com.puppycrawl.tools.checkstyle.utils.ChainedPropertyUtil; import com.puppycrawl.tools.checkstyle.utils.CommonUtil; +import com.puppycrawl.tools.checkstyle.utils.XpathUtil; import io.github.yangziwen.checkstyle.filter.DiffLineFilter; import io.github.yangziwen.diff.calculate.DiffCalculator; import io.github.yangziwen.diff.calculate.DiffEntryWrapper; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.ParseResult; /** * Wrapper command line program for the Checker. - * @noinspection UseOfSystemOutOrSystemErr - **/ -/** - * Wrapper command line program for the Checker. - * @noinspection UseOfSystemOutOrSystemErr - **/ + */ public final class Main { /** @@ -104,197 +104,61 @@ public final class Main { * message in the "messages.properties" file. */ public static final String CREATE_LISTENER_EXCEPTION = "Main.createListener"; + /** Logger for Main. */ private static final Log LOG = LogFactory.getLog(Main.class); - /** Width of CLI help option. */ - private static final int HELP_WIDTH = 100; + /** Exit code returned when user specified invalid command line arguments. */ + private static final int EXIT_WITH_INVALID_USER_INPUT_CODE = -1; /** Exit code returned when execution finishes with {@link CheckstyleException}. */ private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2; - /** Name for the option 'v'. */ - private static final String OPTION_V_NAME = "v"; - - /** Name for the option 'c'. */ - private static final String OPTION_C_NAME = "c"; - - /** Name for the option 'f'. */ - private static final String OPTION_F_NAME = "f"; - - /** Name for the option 'p'. */ - private static final String OPTION_P_NAME = "p"; - - /** Name for the option 'o'. */ - private static final String OPTION_O_NAME = "o"; - - /** Name for the option 's'. */ - private static final String OPTION_S_NAME = "s"; - - /** Name for the option 't'. */ - private static final String OPTION_T_NAME = "t"; - - /** Name for the option '--tree'. */ - private static final String OPTION_TREE_NAME = "tree"; - - /** Name for the option 'tabWidth'. */ - private static final String OPTION_TAB_WIDTH_NAME = "tabWidth"; - - /** Name for the option '-T'. */ - private static final String OPTION_CAPITAL_T_NAME = "T"; - - /** Name for the option '--treeWithComments'. */ - private static final String OPTION_TREE_COMMENT_NAME = "treeWithComments"; - - /** Name for the option '-j'. */ - private static final String OPTION_J_NAME = "j"; - - /** Name for the option '--javadocTree'. */ - private static final String OPTION_JAVADOC_TREE_NAME = "javadocTree"; - - /** Name for the option '-J'. */ - private static final String OPTION_CAPITAL_J_NAME = "J"; - - /** Name for the option '--treeWithJavadoc'. */ - private static final String OPTION_TREE_JAVADOC_NAME = "treeWithJavadoc"; - - /** Name for the option '-d'. */ - private static final String OPTION_D_NAME = "d"; - - /** Name for the option '--debug'. */ - private static final String OPTION_DEBUG_NAME = "debug"; - - /** Name for the option 'e'. */ - private static final String OPTION_E_NAME = "e"; - - /** Name for the option '--exclude'. */ - private static final String OPTION_EXCLUDE_NAME = "exclude"; - - /** Name for the option '--executeIgnoredModules'. */ - private static final String OPTION_EXECUTE_IGNORED_MODULES_NAME = "executeIgnoredModules"; - - /** Name for the option 'x'. */ - private static final String OPTION_X_NAME = "x"; - - /** Name for the option '--exclude-regexp'. */ - private static final String OPTION_EXCLUDE_REGEXP_NAME = "exclude-regexp"; - - /** Name for the option '-C'. */ - private static final String OPTION_CAPITAL_C_NAME = "C"; - - /** Name for the option '--checker-threads-number'. */ - private static final String OPTION_CHECKER_THREADS_NUMBER_NAME = "checker-threads-number"; - - /** Name for the option '-W'. */ - private static final String OPTION_CAPITAL_W_NAME = "W"; - - /** Name for the option '--tree-walker-threads-number'. */ - private static final String OPTION_TREE_WALKER_THREADS_NUMBER_NAME = - "tree-walker-threads-number"; - - /** Name for the option 'gxs'. */ - private static final String OPTION_GXS_NAME = "gxs"; - - /** Name for the option 'generate-xpath-suppression'. */ - private static final String OPTION_GENERATE_XPATH_SUPPRESSION_NAME = - "generate-xpath-suppression"; - - /** Name for the option 'gd'. */ - private static final String OPTION_GD_NAME = "gd"; - - /** Name for the option 'git-directory'. */ - private static final String OPTION_GIT_DIR_NAME = "git-dir"; - - /** Name for the option 'br'. */ - private static final String OPTION_BR_NAME = "br"; - - /** Name for the option 'base-rev' */ - private static final String OPTION_GIT_BASE_REV_NAME = "base-rev"; - - /** Name for the option 'ic'. */ - private static final String OPTION_IS_NAME = "is"; - - /** Name for the option 'include-staged-codes' */ - private static final String OPTION_GIT_INCLUDE_STAGED_CODES_NAME = "include-staged-codes"; - - /** Name for 'xml' format. */ - private static final String XML_FORMAT_NAME = "xml"; - - /** Name for 'plain' format. */ - private static final String PLAIN_FORMAT_NAME = "plain"; - - /** A string value of 1. */ - private static final String ONE_STRING_VALUE = "1"; - - /** Default distance between tab stops. */ - private static final String DEFAULT_TAB_WIDTH = "8"; - private static final List DIFF_ENTRY_LIST = new ArrayList<>(); - - /** Don't create instance of this class, use {@link #main(String[])} method instead. */ + /** + * Client code should not create instances of this class, but use + * {@link #main(String[])} method instead. + */ private Main() { } /** * Loops over the files specified checking them for errors. The exit code * is the number of errors found in all the files. + * * @param args the command line arguments. * @throws IOException if there is a problem with files access - * @noinspection CallToPrintStackTrace, CallToSystemExit + * @noinspection UseOfSystemOutOrSystemErr, CallToPrintStackTrace, CallToSystemExit **/ public static void main(String... args) throws IOException { - int errorCounter = 0; - boolean cliViolations = false; + + final CliOptions cliOptions = new CliOptions(); + final CommandLine commandLine = new CommandLine(cliOptions); + commandLine.setUsageHelpWidth(CliOptions.HELP_WIDTH); + commandLine.setCaseInsensitiveEnumValuesAllowed(true); + // provide proper exit code based on results. - final int exitWithCliViolation = -1; int exitStatus = 0; - + int errorCounter = 0; try { - //parse CLI arguments - final CommandLine commandLine = parseCli(args); - - // show version and exit if it is requested - if (commandLine.hasOption(OPTION_V_NAME)) { - System.out.println("Checkstyle version: " - + Main.class.getPackage().getImplementationVersion()); - exitStatus = 0; + final ParseResult parseResult = commandLine.parseArgs(args); + if (parseResult.isVersionHelpRequested()) { + System.out.println(getVersionString()); + } + else if (parseResult.isUsageHelpRequested()) { + commandLine.usage(System.out); } else { - List filesToProcess = Collections.emptyList(); - - if (commandLine.hasOption(OPTION_GIT_DIR_NAME)) { - filesToProcess = getGitDiffFilesToProcess(getExclusions(commandLine), commandLine); - if (CollectionUtils.isEmpty(filesToProcess)) { - System.out.println("There is no file need to check"); - return; - } - } else { - filesToProcess = getFilesToProcess(getExclusions(commandLine), - commandLine.getArgs()); - } - - // return error if something is wrong in arguments - final List messages = validateCli(commandLine, filesToProcess); - cliViolations = !messages.isEmpty(); - if (cliViolations) { - exitStatus = exitWithCliViolation; - errorCounter = 1; - messages.forEach(System.out::println); - } - else { - errorCounter = runCli(commandLine, filesToProcess); - exitStatus = errorCounter; - } + exitStatus = execute(parseResult, cliOptions); + errorCounter = exitStatus; } } - catch (ParseException pex) { - // something wrong with arguments - print error and manual - cliViolations = true; - exitStatus = exitWithCliViolation; - errorCounter = 1; - System.out.println(pex.getMessage()); - printUsage(); + catch (ParameterException ex) { + exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; + System.err.println(ex.getMessage()); + System.err.println("Usage: checkstyle [OPTIONS]... FILES..."); + System.err.println("Try 'checkstyle --help' for more information."); } catch (CheckstyleException ex) { exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE; @@ -303,222 +167,225 @@ public static void main(String... args) throws IOException { } finally { // return exit code base on validation of Checker - // two ifs exist till https://github.com/hcoles/pitest/issues/377 - if (errorCounter != 0) { - if (!cliViolations) { - final LocalizedMessage errorCounterMessage = new LocalizedMessage(1, - Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER, - new String[] {String.valueOf(errorCounter)}, null, Main.class, null); - System.out.println(errorCounterMessage.getMessage()); - } - } - if (exitStatus != 0) { - System.exit(exitStatus); + if (errorCounter > 0) { + final Violation errorCounterViolation = new Violation(1, + Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER, + new String[] {String.valueOf(errorCounter)}, null, Main.class, null); + // print error count statistic to error output stream, + // output stream might be used by validation report content + System.err.println(errorCounterViolation.getViolation()); } } + Runtime.getRuntime().exit(exitStatus); } /** - * Parses and executes Checkstyle based on passed arguments. - * @param args - * command line parameters - * @return parsed information about passed parameters - * @throws ParseException - * when passed arguments are not valid + * Returns the version string printed when the user requests version help (--version or -V). + * + * @return a version string based on the package implementation version */ - private static CommandLine parseCli(String... args) - throws ParseException { - // parse the parameters - final CommandLineParser clp = new DefaultParser(); - // always returns not null value - return clp.parse(buildOptions(), args); + private static String getVersionString() { + return "Checkstyle version: " + Main.class.getPackage().getImplementationVersion(); } /** - * Gets the list of exclusions provided through the command line argument. - * @param commandLine command line object - * @return List of exclusion patterns. + * Validates the user input and returns {@value #EXIT_WITH_INVALID_USER_INPUT_CODE} if + * invalid, otherwise executes CheckStyle and returns the number of violations. + * + * @param parseResult generic access to options and parameters found on the command line + * @param options encapsulates options and parameters specified on the command line + * @return number of violations + * @throws IOException if a file could not be read. + * @throws CheckstyleException if something happens processing the files. + * @noinspection UseOfSystemOutOrSystemErr */ - private static List getExclusions(CommandLine commandLine) { - final List result = new ArrayList<>(); + private static int execute(ParseResult parseResult, CliOptions options) + throws IOException, CheckstyleException { - if (commandLine.hasOption(OPTION_E_NAME)) { - for (String value : commandLine.getOptionValues(OPTION_E_NAME)) { - result.add(Pattern.compile("^" + Pattern.quote(new File(value).getAbsolutePath()) - + "$")); - } + final int exitStatus; + + // return error if something is wrong in arguments + List filesToProcess = Collections.emptyList(); + if (StringUtils.isEmptyOrNull(options.gitDir)) { + filesToProcess = getFilesToProcess(options); + } else { + filesToProcess = getChangedFilesToProcess(options); } - if (commandLine.hasOption(OPTION_X_NAME)) { - for (String value : commandLine.getOptionValues(OPTION_X_NAME)) { - result.add(Pattern.compile(value)); - } + final List messages = options.validateCli(parseResult, filesToProcess); + final boolean hasMessages = !messages.isEmpty(); + if (hasMessages) { + messages.forEach(System.out::println); + exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; } - - return result; + else { + exitStatus = runCli(options, filesToProcess); + } + return exitStatus; } /** - * Do validation of Command line options. - * @param cmdLine command line object - * @param filesToProcess List of files to process found from the command line. - * @return list of violations + * Determines the files to process. + * + * @param options the user-specified options + * @return list of files to process */ - // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation - private static List validateCli(CommandLine cmdLine, List filesToProcess) { - final List result = new ArrayList<>(); - - if (filesToProcess.isEmpty()) { - result.add("Files to process must be specified, found 0."); - } - // ensure there is no conflicting options - else if (cmdLine.hasOption(OPTION_T_NAME) || cmdLine.hasOption(OPTION_CAPITAL_T_NAME) - || cmdLine.hasOption(OPTION_J_NAME) || cmdLine.hasOption(OPTION_CAPITAL_J_NAME)) { - if (cmdLine.hasOption(OPTION_S_NAME) || cmdLine.hasOption(OPTION_C_NAME) - || cmdLine.hasOption(OPTION_P_NAME) || cmdLine.hasOption(OPTION_F_NAME) - || cmdLine.hasOption(OPTION_O_NAME)) { - result.add("Option '-t' cannot be used with other options."); - } - else if (filesToProcess.size() > 1) { - result.add("Printing AST is allowed for only one file."); - } + private static List getFilesToProcess(CliOptions options) { + final List patternsToExclude = options.getExclusions(); + + final List result = new LinkedList<>(); + for (File file : options.files) { + result.addAll(listFiles(file, patternsToExclude)); } - else if (cmdLine.hasOption(OPTION_S_NAME)) { - if (cmdLine.hasOption(OPTION_C_NAME) || cmdLine.hasOption(OPTION_P_NAME) - || cmdLine.hasOption(OPTION_F_NAME) || cmdLine.hasOption(OPTION_O_NAME)) { - result.add("Option '-s' cannot be used with other options."); - } - else if (filesToProcess.size() > 1) { - result.add("Printing xpath suppressions is allowed for only one file."); - } + return result; + } + + private static List getChangedFilesToProcess(CliOptions options) { + List patternsToExclude = options.getExclusions(); + + String gitDirPath = options.gitDir; + File repoDir = new File(gitDirPath); + if (!repoDir.isDirectory()) { + System.out.println("git directory " + gitDirPath + " is not a directory!"); + System.exit(1); } - // ensure a configuration file is specified - else if (cmdLine.hasOption(OPTION_C_NAME)) { - final String configLocation = cmdLine.getOptionValue(OPTION_C_NAME); - try { - // test location only - CommonUtil.getUriByFilename(configLocation); - } - catch (CheckstyleException ignored) { - result.add(String.format("Could not find config XML file '%s'.", configLocation)); - } + String oldRev = options.gitBaseRev; + boolean includeStagedCodes = options.gitIncludeStagedCodes; + if (StringUtils.isEmptyOrNull(oldRev)) { + oldRev = includeStagedCodes ? "HEAD" : "HEAD~"; + } + String newRev = "HEAD"; + DiffCalculator calculator = DiffCalculator.builder().diffAlgorithm(new HistogramDiff()).build(); + try { + List diffEntryList = calculator.calculateDiff(repoDir, oldRev, newRev, includeStagedCodes) + .stream() + .filter(diffEntry -> !diffEntry.isDeleted()) + .filter(diffEntry -> patternsToExclude.stream() + .noneMatch(p -> p.matcher(diffEntry.getNewPath()).matches())) + .collect(Collectors.toList()); + + DIFF_ENTRY_LIST.addAll(diffEntryList); + + return diffEntryList.stream() + .map(DiffEntryWrapper::getNewFile) + .collect(Collectors.toList()); + + } catch (Exception e) { + e.printStackTrace(); + System.out.println("error happened when calculate git diff"); + return Collections.emptyList(); + } + } + + /** + * Traverses a specified node looking for files to check. Found files are added to + * a specified list. Subdirectories are also traversed. + * + * @param node + * the node to process + * @param patternsToExclude The list of patterns to exclude from searching or being added as + * files. + * @return found files + */ + private static List listFiles(File node, List patternsToExclude) { + // could be replaced with org.apache.commons.io.FileUtils.list() method + // if only we add commons-io library + final List result = new LinkedList<>(); - // validate optional parameters - if (cmdLine.hasOption(OPTION_F_NAME)) { - final String format = cmdLine.getOptionValue(OPTION_F_NAME); - if (!PLAIN_FORMAT_NAME.equals(format) && !XML_FORMAT_NAME.equals(format)) { - result.add(String.format("Invalid output format." - + " Found '%s' but expected '%s' or '%s'.", - format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME)); + if (node.canRead() && !isPathExcluded(node.getAbsolutePath(), patternsToExclude)) { + if (node.isDirectory()) { + final File[] files = node.listFiles(); + // listFiles() can return null, so we need to check it + if (files != null) { + for (File element : files) { + result.addAll(listFiles(element, patternsToExclude)); + } } } - if (cmdLine.hasOption(OPTION_P_NAME)) { - final String propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME); - final File file = new File(propertiesLocation); - if (!file.exists()) { - result.add(String.format("Could not find file '%s'.", propertiesLocation)); - } + else if (node.isFile()) { + result.add(node); } - verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_C_NAME, - "Checker threads number must be greater than zero", - "Invalid Checker threads number"); - verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_W_NAME, - "TreeWalker threads number must be greater than zero", - "Invalid TreeWalker threads number"); } - else { - result.add("Must specify a config XML file."); - } - return result; } /** - * Verifies threads number CLI parameter value. - * @param cmdLine a command line - * @param result a resulting list of errors - * @param cliParameterName a CLI parameter name - * @param mustBeGreaterThanZeroMessage a message which should be reported - * if the number of threads is less than or equal to zero - * @param invalidNumberMessage a message which should be reported if the passed value - * is not a valid number + * Checks if a directory/file {@code path} should be excluded based on if it matches one of the + * patterns supplied. + * + * @param path The path of the directory/file to check + * @param patternsToExclude The list of patterns to exclude from searching or being added as + * files. + * @return True if the directory/file matches one of the patterns. */ - private static void verifyThreadsNumberParameter(CommandLine cmdLine, List result, - String cliParameterName, String mustBeGreaterThanZeroMessage, - String invalidNumberMessage) { - if (cmdLine.hasOption(cliParameterName)) { - final String checkerThreadsNumberStr = - cmdLine.getOptionValue(cliParameterName); - if (CommonUtil.isInt(checkerThreadsNumberStr)) { - final int checkerThreadsNumber = Integer.parseInt(checkerThreadsNumberStr); - if (checkerThreadsNumber < 1) { - result.add(mustBeGreaterThanZeroMessage); - } - } - else { - result.add(invalidNumberMessage); + private static boolean isPathExcluded(String path, List patternsToExclude) { + boolean result = false; + + for (Pattern pattern : patternsToExclude) { + if (pattern.matcher(path).find()) { + result = true; + break; } } + + return result; } /** * Do execution of CheckStyle based on Command line options. - * @param commandLine command line object - * @param filesToProcess List of files to process found from the command line. + * + * @param options user-specified options + * @param filesToProcess the list of files whose style to check * @return number of violations * @throws IOException if a file could not be read. * @throws CheckstyleException if something happens processing the files. + * @noinspection UseOfSystemOutOrSystemErr */ - private static int runCli(CommandLine commandLine, List filesToProcess) + private static int runCli(CliOptions options, List filesToProcess) throws IOException, CheckstyleException { int result = 0; + final boolean hasSuppressionLineColumnNumber = options.suppressionLineColumnNumber != null; // create config helper object - final CliOptions config = convertCliToPojo(commandLine, filesToProcess); - if (commandLine.hasOption(OPTION_T_NAME)) { + if (options.printAst) { // print AST - final File file = config.files.get(0); + final File file = filesToProcess.get(0); final String stringAst = AstTreeStringPrinter.printFileAst(file, JavaParser.Options.WITHOUT_COMMENTS); System.out.print(stringAst); } - else if (commandLine.hasOption(OPTION_CAPITAL_T_NAME)) { - final File file = config.files.get(0); + else if (Objects.nonNull(options.xpath)) { + final String branch = XpathUtil.printXpathBranch(options.xpath, filesToProcess.get(0)); + System.out.print(branch); + } + else if (options.printAstWithComments) { + final File file = filesToProcess.get(0); final String stringAst = AstTreeStringPrinter.printFileAst(file, JavaParser.Options.WITH_COMMENTS); System.out.print(stringAst); } - else if (commandLine.hasOption(OPTION_J_NAME)) { - final File file = config.files.get(0); + else if (options.printJavadocTree) { + final File file = filesToProcess.get(0); final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file); System.out.print(stringAst); } - else if (commandLine.hasOption(OPTION_CAPITAL_J_NAME)) { - final File file = config.files.get(0); + else if (options.printTreeWithJavadoc) { + final File file = filesToProcess.get(0); final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file); System.out.print(stringAst); } - else if (commandLine.hasOption(OPTION_S_NAME)) { - final File file = config.files.get(0); - final String suppressionLineColumnNumber = config.suppressionLineColumnNumber; - final int tabWidth = config.tabWidth; + else if (hasSuppressionLineColumnNumber) { + final File file = filesToProcess.get(0); final String stringSuppressions = SuppressionsStringPrinter.printSuppressions(file, - suppressionLineColumnNumber, tabWidth); + options.suppressionLineColumnNumber, options.tabWidth); System.out.print(stringSuppressions); } else { - if (commandLine.hasOption(OPTION_D_NAME)) { + if (options.debug) { final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent(); final ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.FINEST); - handler.setFilter(new Filter() { - private final String packageName = Main.class.getPackage().getName(); - - @Override - public boolean isLoggable(LogRecord record) { - return record.getLoggerName().startsWith(packageName); - } - }); + handler.setFilter(new OnlyCheckstyleLoggersFilter()); parentLogger.addHandler(handler); parentLogger.setLevel(Level.FINEST); } @@ -529,73 +396,42 @@ public boolean isLoggable(LogRecord record) { } // run Checker - result = runCheckstyle(config); + result = runCheckstyle(options, filesToProcess); } return result; } - /** - * Util method to convert CommandLine type to POJO object. - * @param cmdLine command line object - * @param filesToProcess List of files to process found from the command line. - * @return command line option as POJO object - */ - private static CliOptions convertCliToPojo(CommandLine cmdLine, List filesToProcess) { - final CliOptions conf = new CliOptions(); - conf.format = cmdLine.getOptionValue(OPTION_F_NAME); - if (conf.format == null) { - conf.format = PLAIN_FORMAT_NAME; - } - conf.outputLocation = cmdLine.getOptionValue(OPTION_O_NAME); - conf.configLocation = cmdLine.getOptionValue(OPTION_C_NAME); - conf.propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME); - conf.suppressionLineColumnNumber = cmdLine.getOptionValue(OPTION_S_NAME); - conf.files = filesToProcess; - conf.executeIgnoredModules = cmdLine.hasOption(OPTION_EXECUTE_IGNORED_MODULES_NAME); - final String checkerThreadsNumber = cmdLine.getOptionValue( - OPTION_CAPITAL_C_NAME, ONE_STRING_VALUE); - conf.checkerThreadsNumber = Integer.parseInt(checkerThreadsNumber); - final String treeWalkerThreadsNumber = cmdLine.getOptionValue( - OPTION_CAPITAL_W_NAME, ONE_STRING_VALUE); - conf.treeWalkerThreadsNumber = Integer.parseInt(treeWalkerThreadsNumber); - final String tabWidth = - cmdLine.getOptionValue(OPTION_TAB_WIDTH_NAME, DEFAULT_TAB_WIDTH); - conf.tabWidth = Integer.parseInt(tabWidth); - conf.generateXpathSuppressionsFile = - cmdLine.hasOption(OPTION_GENERATE_XPATH_SUPPRESSION_NAME); - return conf; - } - /** * Executes required Checkstyle actions based on passed parameters. - * @param cliOptions - * pojo object that contains all options + * + * @param options user-specified options + * @param filesToProcess the list of files whose style to check * @return number of violations of ERROR level * @throws IOException * when output file could not be found * @throws CheckstyleException * when properties file could not be loaded */ - private static int runCheckstyle(CliOptions cliOptions) + private static int runCheckstyle(CliOptions options, List filesToProcess) throws CheckstyleException, IOException { // setup the properties final Properties props; - if (cliOptions.propertiesLocation == null) { + if (options.propertiesFile == null) { props = System.getProperties(); } else { - props = loadProperties(new File(cliOptions.propertiesLocation)); + props = loadProperties(options.propertiesFile); } // create a configuration final ThreadModeSettings multiThreadModeSettings = - new ThreadModeSettings( - cliOptions.checkerThreadsNumber, cliOptions.treeWalkerThreadsNumber); + new ThreadModeSettings(CliOptions.CHECKER_THREADS_NUMBER, + CliOptions.TREE_WALKER_THREADS_NUMBER); final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions; - if (cliOptions.executeIgnoredModules) { + if (options.executeIgnoredModules) { ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE; } else { @@ -603,7 +439,7 @@ private static int runCheckstyle(CliOptions cliOptions) } final Configuration config = ConfigurationLoader.loadConfiguration( - cliOptions.configLocation, new PropertiesExpander(props), + options.configurationFile, new PropertiesExpander(props), ignoredModulesOptions, multiThreadModeSettings); // create RootModule object and run it @@ -613,24 +449,23 @@ cliOptions.configLocation, new PropertiesExpander(props), try { final AuditListener listener; - if (cliOptions.generateXpathSuppressionsFile) { + if (options.generateXpathSuppressionsFile) { // create filter to print generated xpath suppressions file final Configuration treeWalkerConfig = getTreeWalkerConfig(config); if (treeWalkerConfig != null) { final DefaultConfiguration moduleConfig = new DefaultConfiguration( XpathFileGeneratorAstFilter.class.getName()); - moduleConfig.addAttribute(OPTION_TAB_WIDTH_NAME, - Integer.toString(cliOptions.tabWidth)); + moduleConfig.addProperty(CliOptions.ATTRIB_TAB_WIDTH_NAME, + String.valueOf(options.tabWidth)); ((DefaultConfiguration) treeWalkerConfig).addChild(moduleConfig); } - listener = new XpathFileGeneratorAuditListener(System.out, - AutomaticBean.OutputStreamOptions.NONE); + listener = new XpathFileGeneratorAuditListener(getOutputStream(options.outputPath), + getOutputStreamOptions(options.outputPath)); } else { - listener = createListener(cliOptions.format, - cliOptions.outputLocation); + listener = createListener(options.format, options.outputPath); } rootModule.setModuleClassLoader(moduleClassLoader); @@ -643,7 +478,7 @@ cliOptions.configLocation, new PropertiesExpander(props), } // run RootModule - errorCounter = rootModule.process(cliOptions.files); + errorCounter = rootModule.process(filesToProcess); } finally { rootModule.destroy(); @@ -653,26 +488,35 @@ cliOptions.configLocation, new PropertiesExpander(props), } /** - * Returns {@code TreeWalker} module configuration. - * @param config The configuration object. - * @return The {@code TreeWalker} module configuration. + * Loads properties from a File. + * + * @param file + * the properties file + * @return the properties in file + * @throws CheckstyleException + * when could not load properties file */ - private static Configuration getTreeWalkerConfig(Configuration config) { - Configuration result = null; + private static Properties loadProperties(File file) + throws CheckstyleException { + final Properties properties = new Properties(); - final Configuration[] children = config.getChildren(); - for (Configuration child : children) { - if ("TreeWalker".equals(child.getName())) { - result = child; - break; - } + try (InputStream stream = Files.newInputStream(file.toPath())) { + properties.load(stream); } - return result; + catch (final IOException ex) { + final Violation loadPropertiesExceptionMessage = new Violation(1, + Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, + new String[] {file.getAbsolutePath()}, null, Main.class, null); + throw new CheckstyleException(loadPropertiesExceptionMessage.getViolation(), ex); + } + + return ChainedPropertyUtil.getResolvedProperties(properties); } /** * Creates a new instance of the root module that will control and run * Checkstyle. + * * @param name The name of the module. This will either be a short name that * will have to be found or the complete package name. * @param moduleClassLoader Class loader used to load the root module. @@ -688,91 +532,71 @@ private static RootModule getRootModule(String name, ClassLoader moduleClassLoad } /** - * Loads properties from a File. - * @param file - * the properties file - * @return the properties in file - * @throws CheckstyleException - * when could not load properties file + * Returns {@code TreeWalker} module configuration. + * + * @param config The configuration object. + * @return The {@code TreeWalker} module configuration. */ - private static Properties loadProperties(File file) - throws CheckstyleException { - final Properties properties = new Properties(); + private static Configuration getTreeWalkerConfig(Configuration config) { + Configuration result = null; - try (InputStream stream = Files.newInputStream(file.toPath())) { - properties.load(stream); - } - catch (final IOException ex) { - final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(1, - Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, - new String[] {file.getAbsolutePath()}, null, Main.class, null); - throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), ex); + final Configuration[] children = config.getChildren(); + for (Configuration child : children) { + if ("TreeWalker".equals(child.getName())) { + result = child; + break; + } } - - return properties; + return result; } /** - * This method creates in AuditListener an open stream for validation data, it must be closed by - * {@link RootModule} (default implementation is {@link Checker}) by calling + * This method creates in AuditListener an open stream for validation data, it must be + * closed by {@link RootModule} (default implementation is {@link Checker}) by calling * {@link AuditListener#auditFinished(AuditEvent)}. + * * @param format format of the audit listener * @param outputLocation the location of output * @return a fresh new {@code AuditListener} * @exception IOException when provided output location is not found */ - private static AuditListener createListener(String format, String outputLocation) + private static AuditListener createListener(OutputFormat format, Path outputLocation) throws IOException { - final AuditListener listener; - if (XML_FORMAT_NAME.equals(format)) { - final OutputStream out = getOutputStream(outputLocation); - final AutomaticBean.OutputStreamOptions closeOutputStreamOption = - getOutputStreamOptions(outputLocation); - listener = new XMLLogger(out, closeOutputStreamOption); - } - else if (PLAIN_FORMAT_NAME.equals(format)) { - final OutputStream out = getOutputStream(outputLocation); - final AutomaticBean.OutputStreamOptions closeOutputStreamOption = - getOutputStreamOptions(outputLocation); - listener = new DefaultLogger(out, closeOutputStreamOption); - } - else { - final LocalizedMessage outputFormatExceptionMessage = new LocalizedMessage(1, - Definitions.CHECKSTYLE_BUNDLE, CREATE_LISTENER_EXCEPTION, - new String[] {format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME}, null, - Main.class, null); - throw new IllegalStateException(outputFormatExceptionMessage.getMessage()); - } - - return listener; + final OutputStream out = getOutputStream(outputLocation); + final AutomaticBean.OutputStreamOptions closeOutputStreamOption = + getOutputStreamOptions(outputLocation); + return format.createListener(out, closeOutputStreamOption); } /** * Create output stream or return System.out - * @param outputLocation output location + * + * @param outputPath output location * @return output stream * @throws IOException might happen + * @noinspection UseOfSystemOutOrSystemErr */ @SuppressWarnings("resource") - private static OutputStream getOutputStream(String outputLocation) throws IOException { + private static OutputStream getOutputStream(Path outputPath) throws IOException { final OutputStream result; - if (outputLocation == null) { + if (outputPath == null) { result = System.out; } else { - result = Files.newOutputStream(Paths.get(outputLocation)); + result = Files.newOutputStream(outputPath); } return result; } /** * Create {@link AutomaticBean.OutputStreamOptions} for the given location. - * @param outputLocation output location + * + * @param outputPath output location * @return output stream options */ - private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(String outputLocation) { + private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(Path outputPath) { final AutomaticBean.OutputStreamOptions result; - if (outputLocation == null) { + if (outputPath == null) { result = AutomaticBean.OutputStreamOptions.NONE; } else { @@ -782,195 +606,350 @@ private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(String o } /** - * Determines the files to process. - * @param patternsToExclude The list of directory patterns to exclude from searching. - * @param filesToProcess - * arguments that were not processed yet but shall be - * @return list of files to process + * Enumeration over the possible output formats. + * + * @noinspection PackageVisibleInnerClass */ - private static List getFilesToProcess(List patternsToExclude, - String... filesToProcess) { - final List files = new LinkedList<>(); - for (String element : filesToProcess) { - files.addAll(listFiles(new File(element), patternsToExclude)); + // Package-visible for tests. + enum OutputFormat { + /** XML output format. */ + XML, + /** SARIF output format. */ + SARIF, + /** Plain output format. */ + PLAIN; + + /** + * Returns a new AuditListener for this OutputFormat. + * + * @param out the output stream + * @param options the output stream options + * @return a new AuditListener for this OutputFormat + * @throws IOException if there is any IO exception during logger initialization + */ + public AuditListener createListener( + OutputStream out, + AutomaticBean.OutputStreamOptions options) throws IOException { + final AuditListener result; + if (this == XML) { + result = new XMLLogger(out, options); + } + else if (this == SARIF) { + result = new SarifLogger(out, options); + } + else { + result = new DefaultLogger(out, options); + } + return result; } - return files; - } - - private static List getGitDiffFilesToProcess(List patternsToExclude, CommandLine commandLine) { - String gitDirPath = commandLine.getOptionValue(OPTION_GIT_DIR_NAME); - File repoDir = new File(gitDirPath); - if (!repoDir.isDirectory()) { - System.out.println("git directory " + gitDirPath + " is not a directory!"); - System.exit(1); - } - String oldRev = commandLine.getOptionValue(OPTION_GIT_BASE_REV_NAME); - boolean includeStagedCodes = commandLine.hasOption(OPTION_GIT_INCLUDE_STAGED_CODES_NAME); - if (StringUtils.isEmptyOrNull(oldRev)) { - oldRev = includeStagedCodes ? "HEAD" : "HEAD~"; + /** + * Returns the name in lowercase. + * + * @return the enum name in lowercase + */ + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); } - String newRev = "HEAD"; - DiffCalculator calculator = DiffCalculator.builder().diffAlgorithm(new HistogramDiff()).build(); - try { - List diffEntryList = calculator.calculateDiff(repoDir, oldRev, newRev, includeStagedCodes) - .stream() - .filter(diffEntry -> !diffEntry.isDeleted()) - .filter(diffEntry -> patternsToExclude.stream() - .noneMatch(p -> p.matcher(diffEntry.getNewPath()).matches())) - .collect(Collectors.toList()); - - DIFF_ENTRY_LIST.addAll(diffEntryList); - - return diffEntryList.stream() - .map(DiffEntryWrapper::getNewFile) - .collect(Collectors.toList()); + } - } catch (Exception e) { - e.printStackTrace(); - System.out.println("error happened when calculate git diff"); - return Collections.emptyList(); + /** Log Filter used in debug mode. */ + private static final class OnlyCheckstyleLoggersFilter implements Filter { + /** Name of the package used to filter on. */ + private final String packageName = Main.class.getPackage().getName(); + + /** + * Returns whether the specified logRecord should be logged. + * + * @param logRecord the logRecord to log + * @return true if the logger name is in the package of this class or a subpackage + */ + @Override + public boolean isLoggable(LogRecord logRecord) { + return logRecord.getLoggerName().startsWith(packageName); } } /** - * Traverses a specified node looking for files to check. Found files are added to a specified - * list. Subdirectories are also traversed. - * @param node - * the node to process - * @param patternsToExclude The list of directory patterns to exclude from searching. - * @return found files + * Command line options. + * + * @noinspection unused, FieldMayBeFinal, CanBeFinal, + * MismatchedQueryAndUpdateOfCollection, LocalCanBeFinal */ - private static List listFiles(File node, List patternsToExclude) { - // could be replaced with org.apache.commons.io.FileUtils.list() method - // if only we add commons-io library - final List result = new LinkedList<>(); + @Command(name = "checkstyle", description = "Checkstyle verifies that the specified " + + "source code files adhere to the specified rules. By default violations are " + + "reported to standard out in plain format. Checkstyle requires a configuration " + + "XML file that configures the checks to apply.", + mixinStandardHelpOptions = true) + private static class CliOptions { - if (node.canRead()) { - if (node.isDirectory()) { - if (!isDirectoryExcluded(node.getAbsolutePath(), patternsToExclude)) { - final File[] files = node.listFiles(); - // listFiles() can return null, so we need to check it - if (files != null) { - for (File element : files) { - result.addAll(listFiles(element, patternsToExclude)); - } - } - } - } - else if (node.isFile()) { - result.add(node); - } - } - return result; - } + /** Width of CLI help option. */ + private static final int HELP_WIDTH = 100; - /** - * Checks if a directory {@code path} should be excluded based on if it matches one of the - * patterns supplied. - * @param path The path of the directory to check - * @param patternsToExclude The list of directory patterns to exclude from searching. - * @return True if the directory matches one of the patterns. - */ - private static boolean isDirectoryExcluded(String path, List patternsToExclude) { - boolean result = false; + /** The default number of threads to use for checker and the tree walker. */ + private static final int DEFAULT_THREAD_COUNT = 1; - for (Pattern pattern : patternsToExclude) { - if (pattern.matcher(path).find()) { - result = true; - break; - } - } + /** Name for the moduleConfig attribute 'tabWidth'. */ + private static final String ATTRIB_TAB_WIDTH_NAME = "tabWidth"; - return result; - } + /** Default output format. */ + private static final OutputFormat DEFAULT_OUTPUT_FORMAT = OutputFormat.PLAIN; - /** Prints the usage information. **/ - private static void printUsage() { - final HelpFormatter formatter = new HelpFormatter(); - formatter.setWidth(HELP_WIDTH); - formatter.printHelp(String.format("java %s [options] -c file...", - Main.class.getName()), buildOptions()); - } + /** Option name for output format. */ + private static final String OUTPUT_FORMAT_OPTION = "-f"; - /** - * Builds and returns list of parameters supported by cli Checkstyle. - * @return available options - */ - private static Options buildOptions() { - final Options options = new Options(); - options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use."); - options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout"); - options.addOption(OPTION_P_NAME, true, "Loads the properties file"); - options.addOption(OPTION_S_NAME, true, - "Print xpath suppressions at the file's line and column position. " - + "Argument is the line and column number (separated by a : ) in the file " - + "that the suppression should be generated for"); - options.addOption(OPTION_TAB_WIDTH_NAME, true, - String.format("Sets the length of the tab character. Used only with \"-s\" option. " - + "Default value is %s", - DEFAULT_TAB_WIDTH)); - options.addOption(OPTION_GXS_NAME, OPTION_GENERATE_XPATH_SUPPRESSION_NAME, false, - "Generates to output a suppression.xml to use to suppress all violations" - + " from user's config"); - options.addOption(OPTION_F_NAME, true, String.format( - "Sets the output format. (%s|%s). Defaults to %s", - PLAIN_FORMAT_NAME, XML_FORMAT_NAME, PLAIN_FORMAT_NAME)); - options.addOption(OPTION_V_NAME, false, "Print product version and exit"); - options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false, - "Print Abstract Syntax Tree(AST) of the file"); - options.addOption(OPTION_CAPITAL_T_NAME, OPTION_TREE_COMMENT_NAME, false, - "Print Abstract Syntax Tree(AST) of the file including comments"); - options.addOption(OPTION_J_NAME, OPTION_JAVADOC_TREE_NAME, false, - "Print Parse tree of the Javadoc comment"); - options.addOption(OPTION_CAPITAL_J_NAME, OPTION_TREE_JAVADOC_NAME, false, - "Print full Abstract Syntax Tree of the file"); - options.addOption(OPTION_D_NAME, OPTION_DEBUG_NAME, false, - "Print all debug logging of CheckStyle utility"); - options.addOption(OPTION_E_NAME, OPTION_EXCLUDE_NAME, true, - "Directory path to exclude from CheckStyle"); - options.addOption(OPTION_X_NAME, OPTION_EXCLUDE_REGEXP_NAME, true, - "Regular expression of directory to exclude from CheckStyle"); - options.addOption(OPTION_EXECUTE_IGNORED_MODULES_NAME, false, - "Allows ignored modules to be run."); - options.addOption(OPTION_CAPITAL_C_NAME, OPTION_CHECKER_THREADS_NUMBER_NAME, true, - "(experimental) The number of Checker threads (must be greater than zero)"); - options.addOption(OPTION_CAPITAL_W_NAME, OPTION_TREE_WALKER_THREADS_NUMBER_NAME, true, - "(experimental) The number of TreeWalker threads (must be greater than zero)"); - options.addOption(OPTION_GD_NAME, OPTION_GIT_DIR_NAME, true, "The git directory"); - options.addOption(OPTION_BR_NAME, OPTION_GIT_BASE_REV_NAME, true, - "The git base revision, will proccess the changed files between this revision and HEAD"); - options.addOption(OPTION_IS_NAME, OPTION_GIT_INCLUDE_STAGED_CODES_NAME, false, - "Whether to include indexed codes when calculating diffs"); - return options; - } + /** + * The checker threads number. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * This option has been skipped for CLI options intentionally. + * + * @noinspection CanBeFinal + */ + private static final int CHECKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT; - /** Helper structure to clear show what is required for Checker to run. **/ - private static class CliOptions { + /** + * The tree walker threads number. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * This option has been skipped for CLI options intentionally. + * + * @noinspection CanBeFinal + */ + private static final int TREE_WALKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT; - /** Properties file location. */ - private String propertiesLocation; - /** Config file location. */ - private String configLocation; - /** Output format. */ - private String format; - /** Output file location. */ - private String outputLocation; /** List of file to validate. */ + @Parameters(arity = "1..*", description = "One or more source files to verify") private List files; - /** Switch whether to execute ignored modules or not. */ - private boolean executeIgnoredModules; - /** The checker threads number. */ - private int checkerThreadsNumber; - /** The tree walker threads number. */ - private int treeWalkerThreadsNumber; + + /** Config file location. */ + @Option(names = "-c", description = "Specifies the location of the file that defines" + + " the configuration modules. The location can either be a filesystem location" + + ", or a name passed to the ClassLoader.getResource() method.") + private String configurationFile; + + /** Output file location. */ + @Option(names = "-o", description = "Sets the output file. Defaults to stdout.") + private Path outputPath; + + /** Properties file location. */ + @Option(names = "-p", description = "Sets the property files to load.") + private File propertiesFile; + /** LineNo and columnNo for the suppression. */ + @Option(names = "-s", + description = "Prints xpath suppressions at the file's line and column position. " + + "Argument is the line and column number (separated by a : ) in the file " + + "that the suppression should be generated for. The option cannot be used " + + "with other options and requires exactly one file to run on to be " + + "specified. ATTENTION: generated result will have few queries, joined " + + "by pipe(|). Together they will match all AST nodes on " + + "specified line and column. You need to choose only one and recheck " + + "that it works. Usage of all of them is also ok, but might result in " + + "undesirable matching and suppress other issues.") private String suppressionLineColumnNumber; - /** Tab character length. */ - private int tabWidth; + + /** + * Tab character length. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * + * @noinspection CanBeFinal + */ + @Option(names = {"-w", "--tabWidth"}, + description = "Sets the length of the tab character. " + + "Used only with -s option. Default value is ${DEFAULT-VALUE}.") + private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; + /** Switch whether to generate suppressions file or not. */ + @Option(names = {"-g", "--generate-xpath-suppression"}, + description = "Generates to output a suppression xml to use to suppress all " + + "violations from user's config. Instead of printing every violation, " + + "all violations will be catched and single suppressions xml file will " + + "be printed out. Used only with -c option. Output " + + "location can be specified with -o option.") private boolean generateXpathSuppressionsFile; + /** + * Output format. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * + * @noinspection CanBeFinal + */ + @Option(names = "-f", + description = "Specifies the output format. Valid values: " + + "${COMPLETION-CANDIDATES} for XMLLogger, SarifLogger, " + + "and DefaultLogger respectively. Defaults to ${DEFAULT-VALUE}.") + private OutputFormat format = DEFAULT_OUTPUT_FORMAT; + + /** Option that controls whether to print the AST of the file. */ + @Option(names = {"-t", "--tree"}, + description = "Prints Abstract Syntax Tree(AST) of the checked file. The option " + + "cannot be used other options and requires exactly one file to run on " + + "to be specified.") + private boolean printAst; + + /** Option that controls whether to print the AST of the file including comments. */ + @Option(names = {"-T", "--treeWithComments"}, + description = "Prints Abstract Syntax Tree(AST) with comment nodes " + + "of the checked file. The option cannot be used with other options " + + "and requires exactly one file to run on to be specified.") + private boolean printAstWithComments; + + /** Option that controls whether to print the parse tree of the javadoc comment. */ + @Option(names = {"-j", "--javadocTree"}, + description = "Prints Parse Tree of the Javadoc comment. " + + "The file have to contain only Javadoc comment content without " + + "including '/**' and '*/' at the beginning and at the end respectively. " + + "The option cannot be used other options and requires exactly one file " + + "to run on to be specified.") + private boolean printJavadocTree; + + /** Option that controls whether to print the full AST of the file. */ + @Option(names = {"-J", "--treeWithJavadoc"}, + description = "Prints Abstract Syntax Tree(AST) with Javadoc nodes " + + "and comment nodes of the checked file. Attention that line number and " + + "columns will not be the same as it is a file due to the fact that each " + + "javadoc comment is parsed separately from java file. The option cannot " + + "be used with other options and requires exactly one file to run on to " + + "be specified.") + private boolean printTreeWithJavadoc; + + /** Option that controls whether to print debug info. */ + @Option(names = {"-d", "--debug"}, + description = "Prints all debug logging of CheckStyle utility.") + private boolean debug; + + /** + * Option that allows users to specify a list of paths to exclude. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * + * @noinspection CanBeFinal + */ + @Option(names = {"-e", "--exclude"}, + description = "Directory/file to exclude from CheckStyle. The path can be the " + + "full, absolute path, or relative to the current path. Multiple " + + "excludes are allowed.") + private List exclude = new ArrayList<>(); + + /** + * Option that allows users to specify a regex of paths to exclude. + * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields + * + * @noinspection CanBeFinal + */ + @Option(names = {"-x", "--exclude-regexp"}, + description = "Directory/file pattern to exclude from CheckStyle. Multiple " + + "excludes are allowed.") + private List excludeRegex = new ArrayList<>(); + + /** Switch whether to execute ignored modules or not. */ + @Option(names = {"-E", "--executeIgnoredModules"}, + description = "Allows ignored modules to be run.") + private boolean executeIgnoredModules; + + /** Show AST branches that match xpath. */ + @Option(names = {"-b", "--branch-matching-xpath"}, + description = "Shows Abstract Syntax Tree(AST) branches that match given XPath query.") + private String xpath; + + /** The directory path of the git repository */ + @Option(names = {"-gd", "--git-dir"}, + description = "the directory path of the git repository") + private String gitDir; + + /** The base revision against which the changed files are collected */ + @Option(names = {"-br", "--base-rev"}, + description = "The git base revision, will proccess the changed files between this revision and HEAD") + private String gitBaseRev; + + /** Switch whether to include the changed codes in the git stage area */ + @Option(names = {"-is", "--include-staged-codes"}, + description = "Whether to include indexed codes when calculating differencies of codes") + private boolean gitIncludeStagedCodes; + + /** + * Gets the list of exclusions provided through the command line arguments. + * + * @return List of exclusion patterns. + */ + private List getExclusions() { + final List result = exclude.stream() + .map(File::getAbsolutePath) + .map(Pattern::quote) + .map(pattern -> Pattern.compile("^" + pattern + "$")) + .collect(Collectors.toCollection(ArrayList::new)); + result.addAll(excludeRegex); + return result; + } + + /** + * Validates the user-specified command line options. + * + * @param parseResult used to verify if the format option was specified on the command line + * @param filesToProcess the list of files whose style to check + * @return list of violations + */ + // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation + private List validateCli(ParseResult parseResult, List filesToProcess) { + final List result = new ArrayList<>(); + final boolean hasConfigurationFile = configurationFile != null; + final boolean hasSuppressionLineColumnNumber = suppressionLineColumnNumber != null; + + if (filesToProcess.isEmpty()) { + result.add("Files to process must be specified, found 0."); + } + // ensure there is no conflicting options + else if (printAst || printAstWithComments || printJavadocTree || printTreeWithJavadoc + || xpath != null) { + if (suppressionLineColumnNumber != null || configurationFile != null + || propertiesFile != null || outputPath != null + || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { + result.add("Option '-t' cannot be used with other options."); + } + else if (filesToProcess.size() > 1) { + result.add("Printing AST is allowed for only one file."); + } + } + else if (hasSuppressionLineColumnNumber) { + if (configurationFile != null || propertiesFile != null + || outputPath != null + || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { + result.add("Option '-s' cannot be used with other options."); + } + else if (filesToProcess.size() > 1) { + result.add("Printing xpath suppressions is allowed for only one file."); + } + } + else if (hasConfigurationFile) { + try { + // test location only + CommonUtil.getUriByFilename(configurationFile); + } + catch (CheckstyleException ignored) { + final String msg = "Could not find config XML file '%s'."; + result.add(String.format(Locale.ROOT, msg, configurationFile)); + } + result.addAll(validateOptionalCliParametersIfConfigDefined()); + } + else { + result.add("Must specify a config XML file."); + } + + return result; + } + + /** + * Validates optional command line parameters that might be used with config file. + * + * @return list of violations + */ + private List validateOptionalCliParametersIfConfigDefined() { + final List result = new ArrayList<>(); + if (propertiesFile != null && !propertiesFile.exists()) { + result.add(String.format(Locale.ROOT, + "Could not find file '%s'.", propertiesFile)); + } + return result; + } } -} +} \ No newline at end of file diff --git a/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/filter/DiffLineFilter.java b/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/filter/DiffLineFilter.java index 9e36385..1eda27e 100644 --- a/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/filter/DiffLineFilter.java +++ b/diff-checkstyle/src/main/java/io/github/yangziwen/checkstyle/filter/DiffLineFilter.java @@ -58,7 +58,7 @@ protected void finishLocalSetup() throws CheckstyleException { } private boolean isEmptyLineSeparatorCheck(AuditEvent event) { - return EmptyLineSeparatorCheck.class.getName().equals(event.getLocalizedMessage().getSourceName()); + return EmptyLineSeparatorCheck.class.getName().equals(event.getSourceName()); } } diff --git a/diff-checkstyle/src/main/resources/custom_checks.xml b/diff-checkstyle/src/main/resources/custom_checks.xml index 9cb8906..9323096 100644 --- a/diff-checkstyle/src/main/resources/custom_checks.xml +++ b/diff-checkstyle/src/main/resources/custom_checks.xml @@ -11,7 +11,13 @@ - + + + + + + + @@ -39,12 +45,11 @@ - + - @@ -98,11 +103,6 @@ - - - - - diff --git a/diff-checkstyle/src/main/resources/custom_full_checks.xml b/diff-checkstyle/src/main/resources/custom_full_checks.xml index c9d09c4..faa1c27 100644 --- a/diff-checkstyle/src/main/resources/custom_full_checks.xml +++ b/diff-checkstyle/src/main/resources/custom_full_checks.xml @@ -11,7 +11,13 @@ - + + + + + + + @@ -30,7 +36,7 @@ - + @@ -88,11 +94,6 @@ - - - - - diff --git a/diff-checkstyle/src/test/java/io/github/yangziwen/checkstyle/filter/DiffLineFilterTest.java b/diff-checkstyle/src/test/java/io/github/yangziwen/checkstyle/filter/DiffLineFilterTest.java index 0044350..35977f1 100644 --- a/diff-checkstyle/src/test/java/io/github/yangziwen/checkstyle/filter/DiffLineFilterTest.java +++ b/diff-checkstyle/src/test/java/io/github/yangziwen/checkstyle/filter/DiffLineFilterTest.java @@ -11,14 +11,12 @@ import org.powermock.modules.junit4.PowerMockRunner; import com.puppycrawl.tools.checkstyle.api.AuditEvent; -import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; import com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck; -import io.github.yangziwen.checkstyle.filter.DiffLineFilter; import io.github.yangziwen.diff.calculate.DiffEntryWrapper; @RunWith(PowerMockRunner.class) -@PrepareForTest({ AuditEvent.class, LocalizedMessage.class, Edit.class }) +@PrepareForTest({ AuditEvent.class, Edit.class }) public class DiffLineFilterTest { @Test @@ -52,13 +50,10 @@ public void testAcceptWithEmptyLineSeparator() { int beginB = 5; int endB = 9; - LocalizedMessage message = PowerMockito.mock(LocalizedMessage.class); - PowerMockito.doReturn(EmptyLineSeparatorCheck.class.getName()).when(message).getSourceName(); - AuditEvent event = PowerMockito.mock(AuditEvent.class); PowerMockito.doReturn(fileName).when(event).getFileName(); PowerMockito.doReturn(lineNum).when(event).getLine(); - PowerMockito.doReturn(message).when(event).getLocalizedMessage(); + PowerMockito.doReturn(EmptyLineSeparatorCheck.class.getName()).when(event).getSourceName(); Edit edit = PowerMockito.mock(Edit.class); PowerMockito.doReturn(beginB).when(edit).getBeginB(); diff --git a/hooks/pre-commit-checkstyle b/hooks/pre-commit-checkstyle index eed8285..4686b12 100755 --- a/hooks/pre-commit-checkstyle +++ b/hooks/pre-commit-checkstyle @@ -43,7 +43,7 @@ if [ -z "$checkstyle_config_file" -o ! -f "$checkstyle_config_file" ]; then fi echo "Check Style" -java $checkstyle_language -jar ${GIT_HOOK_DIR}/diff-checkstyle.jar -c $checkstyle_config_file --git-dir ${GIT_ROOT_DIR} --include-staged-codes $exclude_regexp_opt +java $checkstyle_language -jar ${GIT_HOOK_DIR}/diff-checkstyle.jar -c $checkstyle_config_file ${GIT_ROOT_DIR} --git-dir ${GIT_ROOT_DIR} --include-staged-codes $exclude_regexp_opt result=$? if [ $result -ne 0 ]; then echo "Please fix the checkstyle problems before submit the commit!" diff --git a/pom.xml b/pom.xml index c2a783e..39390bd 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 5.13.0.202109080827-r 1.7.25 - 8.14 + 9.3 5.5.2 1.3.6 2.11.0