001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.util.ArrayList; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Locale; 032import java.util.Objects; 033import java.util.Properties; 034import java.util.logging.ConsoleHandler; 035import java.util.logging.Filter; 036import java.util.logging.Level; 037import java.util.logging.LogRecord; 038import java.util.logging.Logger; 039import java.util.regex.Pattern; 040import java.util.stream.Collectors; 041 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044 045import com.puppycrawl.tools.checkstyle.api.AuditEvent; 046import com.puppycrawl.tools.checkstyle.api.AuditListener; 047import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 048import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 049import com.puppycrawl.tools.checkstyle.api.Configuration; 050import com.puppycrawl.tools.checkstyle.api.RootModule; 051import com.puppycrawl.tools.checkstyle.api.Violation; 052import com.puppycrawl.tools.checkstyle.utils.ChainedPropertyUtil; 053import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 054import com.puppycrawl.tools.checkstyle.utils.XpathUtil; 055import picocli.CommandLine; 056import picocli.CommandLine.Command; 057import picocli.CommandLine.Option; 058import picocli.CommandLine.ParameterException; 059import picocli.CommandLine.Parameters; 060import picocli.CommandLine.ParseResult; 061 062/** 063 * Wrapper command line program for the Checker. 064 */ 065public final class Main { 066 067 /** 068 * A key pointing to the error counter 069 * message in the "messages.properties" file. 070 */ 071 public static final String ERROR_COUNTER = "Main.errorCounter"; 072 /** 073 * A key pointing to the load properties exception 074 * message in the "messages.properties" file. 075 */ 076 public static final String LOAD_PROPERTIES_EXCEPTION = "Main.loadProperties"; 077 /** 078 * A key pointing to the create listener exception 079 * message in the "messages.properties" file. 080 */ 081 public static final String CREATE_LISTENER_EXCEPTION = "Main.createListener"; 082 083 /** Logger for Main. */ 084 private static final Log LOG = LogFactory.getLog(Main.class); 085 086 /** Exit code returned when user specified invalid command line arguments. */ 087 private static final int EXIT_WITH_INVALID_USER_INPUT_CODE = -1; 088 089 /** Exit code returned when execution finishes with {@link CheckstyleException}. */ 090 private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2; 091 092 /** 093 * Client code should not create instances of this class, but use 094 * {@link #main(String[])} method instead. 095 */ 096 private Main() { 097 } 098 099 /** 100 * Loops over the files specified checking them for errors. The exit code 101 * is the number of errors found in all the files. 102 * 103 * @param args the command line arguments. 104 * @throws IOException if there is a problem with files access 105 * @noinspection UseOfSystemOutOrSystemErr, CallToPrintStackTrace, CallToSystemExit 106 **/ 107 public static void main(String... args) throws IOException { 108 109 final CliOptions cliOptions = new CliOptions(); 110 final CommandLine commandLine = new CommandLine(cliOptions); 111 commandLine.setUsageHelpWidth(CliOptions.HELP_WIDTH); 112 commandLine.setCaseInsensitiveEnumValuesAllowed(true); 113 114 // provide proper exit code based on results. 115 int exitStatus = 0; 116 int errorCounter = 0; 117 try { 118 final ParseResult parseResult = commandLine.parseArgs(args); 119 if (parseResult.isVersionHelpRequested()) { 120 System.out.println(getVersionString()); 121 } 122 else if (parseResult.isUsageHelpRequested()) { 123 commandLine.usage(System.out); 124 } 125 else { 126 exitStatus = execute(parseResult, cliOptions); 127 errorCounter = exitStatus; 128 } 129 } 130 catch (ParameterException ex) { 131 exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; 132 System.err.println(ex.getMessage()); 133 System.err.println("Usage: checkstyle [OPTIONS]... FILES..."); 134 System.err.println("Try 'checkstyle --help' for more information."); 135 } 136 catch (CheckstyleException ex) { 137 exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE; 138 errorCounter = 1; 139 ex.printStackTrace(); 140 } 141 finally { 142 // return exit code base on validation of Checker 143 if (errorCounter > 0) { 144 final Violation errorCounterViolation = new Violation(1, 145 Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER, 146 new String[] {String.valueOf(errorCounter)}, null, Main.class, null); 147 // print error count statistic to error output stream, 148 // output stream might be used by validation report content 149 System.err.println(errorCounterViolation.getViolation()); 150 } 151 if (exitStatus != 0) { 152 System.exit(exitStatus); 153 } 154 } 155 } 156 157 /** 158 * Returns the version string printed when the user requests version help (--version or -V). 159 * 160 * @return a version string based on the package implementation version 161 */ 162 private static String getVersionString() { 163 return "Checkstyle version: " + Main.class.getPackage().getImplementationVersion(); 164 } 165 166 /** 167 * Validates the user input and returns {@value #EXIT_WITH_INVALID_USER_INPUT_CODE} if 168 * invalid, otherwise executes CheckStyle and returns the number of violations. 169 * 170 * @param parseResult generic access to options and parameters found on the command line 171 * @param options encapsulates options and parameters specified on the command line 172 * @return number of violations 173 * @throws IOException if a file could not be read. 174 * @throws CheckstyleException if something happens processing the files. 175 * @noinspection UseOfSystemOutOrSystemErr 176 */ 177 private static int execute(ParseResult parseResult, CliOptions options) 178 throws IOException, CheckstyleException { 179 180 final int exitStatus; 181 182 // return error if something is wrong in arguments 183 final List<File> filesToProcess = getFilesToProcess(options); 184 final List<String> messages = options.validateCli(parseResult, filesToProcess); 185 final boolean hasMessages = !messages.isEmpty(); 186 if (hasMessages) { 187 messages.forEach(System.out::println); 188 exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; 189 } 190 else { 191 exitStatus = runCli(options, filesToProcess); 192 } 193 return exitStatus; 194 } 195 196 /** 197 * Determines the files to process. 198 * 199 * @param options the user-specified options 200 * @return list of files to process 201 */ 202 private static List<File> getFilesToProcess(CliOptions options) { 203 final List<Pattern> patternsToExclude = options.getExclusions(); 204 205 final List<File> result = new LinkedList<>(); 206 for (File file : options.files) { 207 result.addAll(listFiles(file, patternsToExclude)); 208 } 209 return result; 210 } 211 212 /** 213 * Traverses a specified node looking for files to check. Found files are added to 214 * a specified list. Subdirectories are also traversed. 215 * 216 * @param node 217 * the node to process 218 * @param patternsToExclude The list of patterns to exclude from searching or being added as 219 * files. 220 * @return found files 221 */ 222 private static List<File> listFiles(File node, List<Pattern> patternsToExclude) { 223 // could be replaced with org.apache.commons.io.FileUtils.list() method 224 // if only we add commons-io library 225 final List<File> result = new LinkedList<>(); 226 227 if (node.canRead() && !isPathExcluded(node.getAbsolutePath(), patternsToExclude)) { 228 if (node.isDirectory()) { 229 final File[] files = node.listFiles(); 230 // listFiles() can return null, so we need to check it 231 if (files != null) { 232 for (File element : files) { 233 result.addAll(listFiles(element, patternsToExclude)); 234 } 235 } 236 } 237 else if (node.isFile()) { 238 result.add(node); 239 } 240 } 241 return result; 242 } 243 244 /** 245 * Checks if a directory/file {@code path} should be excluded based on if it matches one of the 246 * patterns supplied. 247 * 248 * @param path The path of the directory/file to check 249 * @param patternsToExclude The list of patterns to exclude from searching or being added as 250 * files. 251 * @return True if the directory/file matches one of the patterns. 252 */ 253 private static boolean isPathExcluded(String path, List<Pattern> patternsToExclude) { 254 boolean result = false; 255 256 for (Pattern pattern : patternsToExclude) { 257 if (pattern.matcher(path).find()) { 258 result = true; 259 break; 260 } 261 } 262 263 return result; 264 } 265 266 /** 267 * Do execution of CheckStyle based on Command line options. 268 * 269 * @param options user-specified options 270 * @param filesToProcess the list of files whose style to check 271 * @return number of violations 272 * @throws IOException if a file could not be read. 273 * @throws CheckstyleException if something happens processing the files. 274 * @noinspection UseOfSystemOutOrSystemErr 275 */ 276 private static int runCli(CliOptions options, List<File> filesToProcess) 277 throws IOException, CheckstyleException { 278 int result = 0; 279 final boolean hasSuppressionLineColumnNumber = options.suppressionLineColumnNumber != null; 280 281 // create config helper object 282 if (options.printAst) { 283 // print AST 284 final File file = filesToProcess.get(0); 285 final String stringAst = AstTreeStringPrinter.printFileAst(file, 286 JavaParser.Options.WITHOUT_COMMENTS); 287 System.out.print(stringAst); 288 } 289 else if (Objects.nonNull(options.xpath)) { 290 final String branch = XpathUtil.printXpathBranch(options.xpath, filesToProcess.get(0)); 291 System.out.print(branch); 292 } 293 else if (options.printAstWithComments) { 294 final File file = filesToProcess.get(0); 295 final String stringAst = AstTreeStringPrinter.printFileAst(file, 296 JavaParser.Options.WITH_COMMENTS); 297 System.out.print(stringAst); 298 } 299 else if (options.printJavadocTree) { 300 final File file = filesToProcess.get(0); 301 final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file); 302 System.out.print(stringAst); 303 } 304 else if (options.printTreeWithJavadoc) { 305 final File file = filesToProcess.get(0); 306 final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file); 307 System.out.print(stringAst); 308 } 309 else if (hasSuppressionLineColumnNumber) { 310 final File file = filesToProcess.get(0); 311 final String stringSuppressions = 312 SuppressionsStringPrinter.printSuppressions(file, 313 options.suppressionLineColumnNumber, options.tabWidth); 314 System.out.print(stringSuppressions); 315 } 316 else { 317 if (options.debug) { 318 final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent(); 319 final ConsoleHandler handler = new ConsoleHandler(); 320 handler.setLevel(Level.FINEST); 321 handler.setFilter(new OnlyCheckstyleLoggersFilter()); 322 parentLogger.addHandler(handler); 323 parentLogger.setLevel(Level.FINEST); 324 } 325 if (LOG.isDebugEnabled()) { 326 LOG.debug("Checkstyle debug logging enabled"); 327 LOG.debug("Running Checkstyle with version: " 328 + Main.class.getPackage().getImplementationVersion()); 329 } 330 331 // run Checker 332 result = runCheckstyle(options, filesToProcess); 333 } 334 335 return result; 336 } 337 338 /** 339 * Executes required Checkstyle actions based on passed parameters. 340 * 341 * @param options user-specified options 342 * @param filesToProcess the list of files whose style to check 343 * @return number of violations of ERROR level 344 * @throws IOException 345 * when output file could not be found 346 * @throws CheckstyleException 347 * when properties file could not be loaded 348 */ 349 private static int runCheckstyle(CliOptions options, List<File> filesToProcess) 350 throws CheckstyleException, IOException { 351 // setup the properties 352 final Properties props; 353 354 if (options.propertiesFile == null) { 355 props = System.getProperties(); 356 } 357 else { 358 props = loadProperties(options.propertiesFile); 359 } 360 361 // create a configuration 362 final ThreadModeSettings multiThreadModeSettings = 363 new ThreadModeSettings(CliOptions.CHECKER_THREADS_NUMBER, 364 CliOptions.TREE_WALKER_THREADS_NUMBER); 365 366 final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions; 367 if (options.executeIgnoredModules) { 368 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE; 369 } 370 else { 371 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT; 372 } 373 374 final Configuration config = ConfigurationLoader.loadConfiguration( 375 options.configurationFile, new PropertiesExpander(props), 376 ignoredModulesOptions, multiThreadModeSettings); 377 378 // create RootModule object and run it 379 final int errorCounter; 380 final ClassLoader moduleClassLoader = Checker.class.getClassLoader(); 381 final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader); 382 383 try { 384 final AuditListener listener; 385 if (options.generateXpathSuppressionsFile) { 386 // create filter to print generated xpath suppressions file 387 final Configuration treeWalkerConfig = getTreeWalkerConfig(config); 388 if (treeWalkerConfig != null) { 389 final DefaultConfiguration moduleConfig = 390 new DefaultConfiguration( 391 XpathFileGeneratorAstFilter.class.getName()); 392 moduleConfig.addAttribute(CliOptions.ATTRIB_TAB_WIDTH_NAME, 393 String.valueOf(options.tabWidth)); 394 ((DefaultConfiguration) treeWalkerConfig).addChild(moduleConfig); 395 } 396 397 listener = new XpathFileGeneratorAuditListener(getOutputStream(options.outputPath), 398 getOutputStreamOptions(options.outputPath)); 399 } 400 else { 401 listener = createListener(options.format, options.outputPath); 402 } 403 404 rootModule.setModuleClassLoader(moduleClassLoader); 405 rootModule.configure(config); 406 rootModule.addListener(listener); 407 408 // run RootModule 409 errorCounter = rootModule.process(filesToProcess); 410 } 411 finally { 412 rootModule.destroy(); 413 } 414 415 return errorCounter; 416 } 417 418 /** 419 * Loads properties from a File. 420 * 421 * @param file 422 * the properties file 423 * @return the properties in file 424 * @throws CheckstyleException 425 * when could not load properties file 426 */ 427 private static Properties loadProperties(File file) 428 throws CheckstyleException { 429 final Properties properties = new Properties(); 430 431 try (InputStream stream = Files.newInputStream(file.toPath())) { 432 properties.load(stream); 433 } 434 catch (final IOException ex) { 435 final Violation loadPropertiesExceptionMessage = new Violation(1, 436 Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, 437 new String[] {file.getAbsolutePath()}, null, Main.class, null); 438 throw new CheckstyleException(loadPropertiesExceptionMessage.getViolation(), ex); 439 } 440 441 return ChainedPropertyUtil.getResolvedProperties(properties); 442 } 443 444 /** 445 * Creates a new instance of the root module that will control and run 446 * Checkstyle. 447 * 448 * @param name The name of the module. This will either be a short name that 449 * will have to be found or the complete package name. 450 * @param moduleClassLoader Class loader used to load the root module. 451 * @return The new instance of the root module. 452 * @throws CheckstyleException if no module can be instantiated from name 453 */ 454 private static RootModule getRootModule(String name, ClassLoader moduleClassLoader) 455 throws CheckstyleException { 456 final ModuleFactory factory = new PackageObjectFactory( 457 Checker.class.getPackage().getName(), moduleClassLoader); 458 459 return (RootModule) factory.createModule(name); 460 } 461 462 /** 463 * Returns {@code TreeWalker} module configuration. 464 * 465 * @param config The configuration object. 466 * @return The {@code TreeWalker} module configuration. 467 */ 468 private static Configuration getTreeWalkerConfig(Configuration config) { 469 Configuration result = null; 470 471 final Configuration[] children = config.getChildren(); 472 for (Configuration child : children) { 473 if ("TreeWalker".equals(child.getName())) { 474 result = child; 475 break; 476 } 477 } 478 return result; 479 } 480 481 /** 482 * This method creates in AuditListener an open stream for validation data, it must be 483 * closed by {@link RootModule} (default implementation is {@link Checker}) by calling 484 * {@link AuditListener#auditFinished(AuditEvent)}. 485 * 486 * @param format format of the audit listener 487 * @param outputLocation the location of output 488 * @return a fresh new {@code AuditListener} 489 * @exception IOException when provided output location is not found 490 */ 491 private static AuditListener createListener(OutputFormat format, Path outputLocation) 492 throws IOException { 493 final OutputStream out = getOutputStream(outputLocation); 494 final AutomaticBean.OutputStreamOptions closeOutputStreamOption = 495 getOutputStreamOptions(outputLocation); 496 return format.createListener(out, closeOutputStreamOption); 497 } 498 499 /** 500 * Create output stream or return System.out 501 * 502 * @param outputPath output location 503 * @return output stream 504 * @throws IOException might happen 505 * @noinspection UseOfSystemOutOrSystemErr 506 */ 507 @SuppressWarnings("resource") 508 private static OutputStream getOutputStream(Path outputPath) throws IOException { 509 final OutputStream result; 510 if (outputPath == null) { 511 result = System.out; 512 } 513 else { 514 result = Files.newOutputStream(outputPath); 515 } 516 return result; 517 } 518 519 /** 520 * Create {@link AutomaticBean.OutputStreamOptions} for the given location. 521 * 522 * @param outputPath output location 523 * @return output stream options 524 */ 525 private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(Path outputPath) { 526 final AutomaticBean.OutputStreamOptions result; 527 if (outputPath == null) { 528 result = AutomaticBean.OutputStreamOptions.NONE; 529 } 530 else { 531 result = AutomaticBean.OutputStreamOptions.CLOSE; 532 } 533 return result; 534 } 535 536 /** 537 * Enumeration over the possible output formats. 538 * 539 * @noinspection PackageVisibleInnerClass 540 */ 541 // Package-visible for tests. 542 enum OutputFormat { 543 /** XML output format. */ 544 XML, 545 /** SARIF output format. */ 546 SARIF, 547 /** Plain output format. */ 548 PLAIN; 549 550 /** 551 * Returns a new AuditListener for this OutputFormat. 552 * 553 * @param out the output stream 554 * @param options the output stream options 555 * @return a new AuditListener for this OutputFormat 556 * @throws IOException if there is any IO exception during logger initialization 557 */ 558 public AuditListener createListener( 559 OutputStream out, 560 AutomaticBean.OutputStreamOptions options) throws IOException { 561 final AuditListener result; 562 if (this == XML) { 563 result = new XMLLogger(out, options); 564 } 565 else if (this == SARIF) { 566 result = new SarifLogger(out, options); 567 } 568 else { 569 result = new DefaultLogger(out, options); 570 } 571 return result; 572 } 573 574 /** 575 * Returns the name in lowercase. 576 * 577 * @return the enum name in lowercase 578 */ 579 @Override 580 public String toString() { 581 return name().toLowerCase(Locale.ROOT); 582 } 583 } 584 585 /** Log Filter used in debug mode. */ 586 private static final class OnlyCheckstyleLoggersFilter implements Filter { 587 /** Name of the package used to filter on. */ 588 private final String packageName = Main.class.getPackage().getName(); 589 590 /** 591 * Returns whether the specified logRecord should be logged. 592 * 593 * @param logRecord the logRecord to log 594 * @return true if the logger name is in the package of this class or a subpackage 595 */ 596 @Override 597 public boolean isLoggable(LogRecord logRecord) { 598 return logRecord.getLoggerName().startsWith(packageName); 599 } 600 } 601 602 /** 603 * Command line options. 604 * 605 * @noinspection unused, FieldMayBeFinal, CanBeFinal, 606 * MismatchedQueryAndUpdateOfCollection, LocalCanBeFinal 607 */ 608 @Command(name = "checkstyle", description = "Checkstyle verifies that the specified " 609 + "source code files adhere to the specified rules. By default violations are " 610 + "reported to standard out in plain format. Checkstyle requires a configuration " 611 + "XML file that configures the checks to apply.", 612 mixinStandardHelpOptions = true) 613 private static class CliOptions { 614 615 /** Width of CLI help option. */ 616 private static final int HELP_WIDTH = 100; 617 618 /** The default number of threads to use for checker and the tree walker. */ 619 private static final int DEFAULT_THREAD_COUNT = 1; 620 621 /** Name for the moduleConfig attribute 'tabWidth'. */ 622 private static final String ATTRIB_TAB_WIDTH_NAME = "tabWidth"; 623 624 /** Default output format. */ 625 private static final OutputFormat DEFAULT_OUTPUT_FORMAT = OutputFormat.PLAIN; 626 627 /** Option name for output format. */ 628 private static final String OUTPUT_FORMAT_OPTION = "-f"; 629 630 /** 631 * The checker threads number. 632 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 633 * This option has been skipped for CLI options intentionally. 634 * 635 * @noinspection CanBeFinal 636 */ 637 private static final int CHECKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT; 638 639 /** 640 * The tree walker threads number. 641 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 642 * This option has been skipped for CLI options intentionally. 643 * 644 * @noinspection CanBeFinal 645 */ 646 private static final int TREE_WALKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT; 647 648 /** List of file to validate. */ 649 @Parameters(arity = "1..*", description = "One or more source files to verify") 650 private List<File> files; 651 652 /** Config file location. */ 653 @Option(names = "-c", description = "Specifies the location of the file that defines" 654 + " the configuration modules. The location can either be a filesystem location" 655 + ", or a name passed to the ClassLoader.getResource() method.") 656 private String configurationFile; 657 658 /** Output file location. */ 659 @Option(names = "-o", description = "Sets the output file. Defaults to stdout.") 660 private Path outputPath; 661 662 /** Properties file location. */ 663 @Option(names = "-p", description = "Sets the property files to load.") 664 private File propertiesFile; 665 666 /** LineNo and columnNo for the suppression. */ 667 @Option(names = "-s", 668 description = "Prints xpath suppressions at the file's line and column position. " 669 + "Argument is the line and column number (separated by a : ) in the file " 670 + "that the suppression should be generated for. The option cannot be used " 671 + "with other options and requires exactly one file to run on to be " 672 + "specified. ATTENTION: generated result will have few queries, joined " 673 + "by pipe(|). Together they will match all AST nodes on " 674 + "specified line and column. You need to choose only one and recheck " 675 + "that it works. Usage of all of them is also ok, but might result in " 676 + "undesirable matching and suppress other issues.") 677 private String suppressionLineColumnNumber; 678 679 /** 680 * Tab character length. 681 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 682 * 683 * @noinspection CanBeFinal 684 */ 685 @Option(names = {"-w", "--tabWidth"}, 686 description = "Sets the length of the tab character. " 687 + "Used only with -s option. Default value is ${DEFAULT-VALUE}.") 688 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; 689 690 /** Switch whether to generate suppressions file or not. */ 691 @Option(names = {"-g", "--generate-xpath-suppression"}, 692 description = "Generates to output a suppression xml to use to suppress all " 693 + "violations from user's config. Instead of printing every violation, " 694 + "all violations will be catched and single suppressions xml file will " 695 + "be printed out. Used only with -c option. Output " 696 + "location can be specified with -o option.") 697 private boolean generateXpathSuppressionsFile; 698 699 /** 700 * Output format. 701 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 702 * 703 * @noinspection CanBeFinal 704 */ 705 @Option(names = "-f", 706 description = "Specifies the output format. Valid values: " 707 + "${COMPLETION-CANDIDATES} for XMLLogger, SarifLogger, " 708 + "and DefaultLogger respectively. Defaults to ${DEFAULT-VALUE}.") 709 private OutputFormat format = DEFAULT_OUTPUT_FORMAT; 710 711 /** Option that controls whether to print the AST of the file. */ 712 @Option(names = {"-t", "--tree"}, 713 description = "Prints Abstract Syntax Tree(AST) of the checked file. The option " 714 + "cannot be used other options and requires exactly one file to run on " 715 + "to be specified.") 716 private boolean printAst; 717 718 /** Option that controls whether to print the AST of the file including comments. */ 719 @Option(names = {"-T", "--treeWithComments"}, 720 description = "Prints Abstract Syntax Tree(AST) with comment nodes " 721 + "of the checked file. The option cannot be used with other options " 722 + "and requires exactly one file to run on to be specified.") 723 private boolean printAstWithComments; 724 725 /** Option that controls whether to print the parse tree of the javadoc comment. */ 726 @Option(names = {"-j", "--javadocTree"}, 727 description = "Prints Parse Tree of the Javadoc comment. " 728 + "The file have to contain only Javadoc comment content without " 729 + "including '/**' and '*/' at the beginning and at the end respectively. " 730 + "The option cannot be used other options and requires exactly one file " 731 + "to run on to be specified.") 732 private boolean printJavadocTree; 733 734 /** Option that controls whether to print the full AST of the file. */ 735 @Option(names = {"-J", "--treeWithJavadoc"}, 736 description = "Prints Abstract Syntax Tree(AST) with Javadoc nodes " 737 + "and comment nodes of the checked file. Attention that line number and " 738 + "columns will not be the same as it is a file due to the fact that each " 739 + "javadoc comment is parsed separately from java file. The option cannot " 740 + "be used with other options and requires exactly one file to run on to " 741 + "be specified.") 742 private boolean printTreeWithJavadoc; 743 744 /** Option that controls whether to print debug info. */ 745 @Option(names = {"-d", "--debug"}, 746 description = "Prints all debug logging of CheckStyle utility.") 747 private boolean debug; 748 749 /** 750 * Option that allows users to specify a list of paths to exclude. 751 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 752 * 753 * @noinspection CanBeFinal 754 */ 755 @Option(names = {"-e", "--exclude"}, 756 description = "Directory/file to exclude from CheckStyle. The path can be the " 757 + "full, absolute path, or relative to the current path. Multiple " 758 + "excludes are allowed.") 759 private List<File> exclude = new ArrayList<>(); 760 761 /** 762 * Option that allows users to specify a regex of paths to exclude. 763 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 764 * 765 * @noinspection CanBeFinal 766 */ 767 @Option(names = {"-x", "--exclude-regexp"}, 768 description = "Directory/file pattern to exclude from CheckStyle. Multiple " 769 + "excludes are allowed.") 770 private List<Pattern> excludeRegex = new ArrayList<>(); 771 772 /** Switch whether to execute ignored modules or not. */ 773 @Option(names = {"-E", "--executeIgnoredModules"}, 774 description = "Allows ignored modules to be run.") 775 private boolean executeIgnoredModules; 776 777 /** Show AST branches that match xpath. */ 778 @Option(names = {"-b", "--branch-matching-xpath"}, 779 description = "Shows Abstract Syntax Tree(AST) branches that match given XPath query.") 780 private String xpath; 781 782 /** 783 * Gets the list of exclusions provided through the command line arguments. 784 * 785 * @return List of exclusion patterns. 786 */ 787 private List<Pattern> getExclusions() { 788 final List<Pattern> result = exclude.stream() 789 .map(File::getAbsolutePath) 790 .map(Pattern::quote) 791 .map(pattern -> Pattern.compile("^" + pattern + "$")) 792 .collect(Collectors.toCollection(ArrayList::new)); 793 result.addAll(excludeRegex); 794 return result; 795 } 796 797 /** 798 * Validates the user-specified command line options. 799 * 800 * @param parseResult used to verify if the format option was specified on the command line 801 * @param filesToProcess the list of files whose style to check 802 * @return list of violations 803 */ 804 // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation 805 private List<String> validateCli(ParseResult parseResult, List<File> filesToProcess) { 806 final List<String> result = new ArrayList<>(); 807 final boolean hasConfigurationFile = configurationFile != null; 808 final boolean hasSuppressionLineColumnNumber = suppressionLineColumnNumber != null; 809 810 if (filesToProcess.isEmpty()) { 811 result.add("Files to process must be specified, found 0."); 812 } 813 // ensure there is no conflicting options 814 else if (printAst || printAstWithComments || printJavadocTree || printTreeWithJavadoc 815 || xpath != null) { 816 if (suppressionLineColumnNumber != null || configurationFile != null 817 || propertiesFile != null || outputPath != null 818 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 819 result.add("Option '-t' cannot be used with other options."); 820 } 821 else if (filesToProcess.size() > 1) { 822 result.add("Printing AST is allowed for only one file."); 823 } 824 } 825 else if (hasSuppressionLineColumnNumber) { 826 if (configurationFile != null || propertiesFile != null 827 || outputPath != null 828 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 829 result.add("Option '-s' cannot be used with other options."); 830 } 831 else if (filesToProcess.size() > 1) { 832 result.add("Printing xpath suppressions is allowed for only one file."); 833 } 834 } 835 else if (hasConfigurationFile) { 836 try { 837 // test location only 838 CommonUtil.getUriByFilename(configurationFile); 839 } 840 catch (CheckstyleException ignored) { 841 final String msg = "Could not find config XML file '%s'."; 842 result.add(String.format(Locale.ROOT, msg, configurationFile)); 843 } 844 result.addAll(validateOptionalCliParametersIfConfigDefined()); 845 } 846 else { 847 result.add("Must specify a config XML file."); 848 } 849 850 return result; 851 } 852 853 /** 854 * Validates optional command line parameters that might be used with config file. 855 * 856 * @return list of violations 857 */ 858 private List<String> validateOptionalCliParametersIfConfigDefined() { 859 final List<String> result = new ArrayList<>(); 860 if (propertiesFile != null && !propertiesFile.exists()) { 861 result.add(String.format(Locale.ROOT, 862 "Could not find file '%s'.", propertiesFile)); 863 } 864 return result; 865 } 866 } 867}