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