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