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