Skip to content

Commit

Permalink
Issue checkstyle#4530: Add the new option for Checkstyle CLI to gener…
Browse files Browse the repository at this point in the history
…ate the basic suppression xpath
  • Loading branch information
timurt committed Mar 1, 2018
1 parent eb5b2dc commit c66cac1
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 6 deletions.
1 change: 1 addition & 0 deletions config/import-control.xml
Expand Up @@ -45,6 +45,7 @@
<allow class="java.lang.annotation.Retention" local-only="true"/>
<allow class="java.lang.annotation.RetentionPolicy" local-only="true"/>
<allow class="java.lang.annotation.Target" local-only="true"/>
<allow class="com.puppycrawl.tools.checkstyle.xpath.XpathQueryGenerator" local-only="true"/>
<allow class="com.puppycrawl.tools.checkstyle.FileStatefulCheck"/>
<allow class="com.puppycrawl.tools.checkstyle.StatelessCheck"/>

Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Expand Up @@ -2483,11 +2483,13 @@
<param>com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser</param>
<param>com.puppycrawl.tools.checkstyle.DetailNodeTreeStringPrinter</param>
<param>com.puppycrawl.tools.checkstyle.AstTreeStringPrinter</param>
<param>com.puppycrawl.tools.checkstyle.SuppressionsStringPrinter</param>
<param>com.puppycrawl.tools.checkstyle.TreeWalker</param>
</targetClasses>
<targetTests>
<param>com.puppycrawl.tools.checkstyle.DetailNodeTreeStringPrinterTest</param>
<param>com.puppycrawl.tools.checkstyle.AstTreeStringPrinterTest</param>
<param>com.puppycrawl.tools.checkstyle.SuppressionsStringPrinterTest</param>
<param>com.puppycrawl.tools.checkstyle.TreeWalkerTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.PackageDeclarationCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheckTest</param>
Expand Down
48 changes: 46 additions & 2 deletions src/main/java/com/puppycrawl/tools/checkstyle/Main.java
Expand Up @@ -100,12 +100,18 @@ public final class Main {
/** Name for the option 'o'. */
private static final String OPTION_O_NAME = "o";

/** Name for the option 's'. */
private static final String OPTION_S_NAME = "s";

/** Name for the option 't'. */
private static final String OPTION_T_NAME = "t";

/** Name for the option '--tree'. */
private static final String OPTION_TREE_NAME = "tree";

/** Name for the option 'tabWidth'. */
private static final String OPTION_TAB_WIDTH_NAME = "tabWidth";

/** Name for the option '-T'. */
private static final String OPTION_CAPITAL_T_NAME = "T";

Expand Down Expand Up @@ -167,6 +173,9 @@ public final class Main {
/** A string value of 1. */
private static final String ONE_STRING_VALUE = "1";

/** Default distance between tab stops. */
private static final String DEFAULT_TAB_WIDTH = "8";

/** Don't create instance of this class, use {@link #main(String[])} method instead. */
private Main() {
}
Expand Down Expand Up @@ -298,14 +307,24 @@ private static List<String> validateCli(CommandLine cmdLine, List<File> filesToP
// ensure there is no conflicting options
else if (cmdLine.hasOption(OPTION_T_NAME) || cmdLine.hasOption(OPTION_CAPITAL_T_NAME)
|| cmdLine.hasOption(OPTION_J_NAME) || cmdLine.hasOption(OPTION_CAPITAL_J_NAME)) {
if (cmdLine.hasOption(OPTION_C_NAME) || cmdLine.hasOption(OPTION_P_NAME)
|| cmdLine.hasOption(OPTION_F_NAME) || cmdLine.hasOption(OPTION_O_NAME)) {
if (cmdLine.hasOption(OPTION_S_NAME) || cmdLine.hasOption(OPTION_C_NAME)
|| cmdLine.hasOption(OPTION_P_NAME) || cmdLine.hasOption(OPTION_F_NAME)
|| cmdLine.hasOption(OPTION_O_NAME)) {
result.add("Option '-t' cannot be used with other options.");
}
else if (filesToProcess.size() > 1) {
result.add("Printing AST is allowed for only one file.");
}
}
else if (cmdLine.hasOption(OPTION_S_NAME)) {
if (cmdLine.hasOption(OPTION_C_NAME) || cmdLine.hasOption(OPTION_P_NAME)
|| cmdLine.hasOption(OPTION_F_NAME) || cmdLine.hasOption(OPTION_O_NAME)) {
result.add("Option '-s' cannot be used with other options.");
}
else if (filesToProcess.size() > 1) {
result.add("Printing xpath suppressions is allowed for only one file.");
}
}
// ensure a configuration file is specified
else if (cmdLine.hasOption(OPTION_C_NAME)) {
final String configLocation = cmdLine.getOptionValue(OPTION_C_NAME);
Expand Down Expand Up @@ -412,6 +431,15 @@ else if (commandLine.hasOption(OPTION_CAPITAL_J_NAME)) {
final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file);
System.out.print(stringAst);
}
else if (commandLine.hasOption(OPTION_S_NAME)) {
final File file = config.files.get(0);
final String suppressionLineColumnNumber = config.suppressionLineColumnNumber;
final int tabWidth = config.tabWidth;
final String stringSuppressions =
SuppressionsStringPrinter.printSuppressions(file,
suppressionLineColumnNumber, tabWidth);
System.out.print(stringSuppressions);
}
else {
if (commandLine.hasOption(OPTION_D_NAME)) {
final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent();
Expand Down Expand Up @@ -456,6 +484,7 @@ private static CliOptions convertCliToPojo(CommandLine cmdLine, List<File> files
conf.outputLocation = cmdLine.getOptionValue(OPTION_O_NAME);
conf.configLocation = cmdLine.getOptionValue(OPTION_C_NAME);
conf.propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME);
conf.suppressionLineColumnNumber = cmdLine.getOptionValue(OPTION_S_NAME);
conf.files = filesToProcess;
conf.executeIgnoredModules = cmdLine.hasOption(OPTION_EXECUTE_IGNORED_MODULES_NAME);
final String checkerThreadsNumber = cmdLine.getOptionValue(
Expand All @@ -464,6 +493,9 @@ private static CliOptions convertCliToPojo(CommandLine cmdLine, List<File> files
final String treeWalkerThreadsNumber = cmdLine.getOptionValue(
OPTION_CAPITAL_W_NAME, ONE_STRING_VALUE);
conf.treeWalkerThreadsNumber = Integer.parseInt(treeWalkerThreadsNumber);
final String tabWidth =
cmdLine.getOptionValue(OPTION_TAB_WIDTH_NAME, DEFAULT_TAB_WIDTH);
conf.tabWidth = Integer.parseInt(tabWidth);
return conf;
}

Expand Down Expand Up @@ -709,6 +741,14 @@ private static Options buildOptions() {
options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use.");
options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout");
options.addOption(OPTION_P_NAME, true, "Loads the properties file");
options.addOption(OPTION_S_NAME, true,
"Print xpath suppressions at the file's line and column position. "
+ "Argument is the line and column number (separated by a : ) in the file "
+ "that the suppression should be generated for");
options.addOption(OPTION_TAB_WIDTH_NAME, true,
String.format("Sets the length of the tab character. Used only with \"-s\" option. "
+ "Default value is %s",
DEFAULT_TAB_WIDTH));
options.addOption(OPTION_F_NAME, true, String.format(
"Sets the output format. (%s|%s). Defaults to %s",
PLAIN_FORMAT_NAME, XML_FORMAT_NAME, PLAIN_FORMAT_NAME));
Expand Down Expand Up @@ -755,6 +795,10 @@ private static class CliOptions {
private int checkerThreadsNumber;
/** The tree walker threads number. */
private int treeWalkerThreadsNumber;
/** LineNo and columnNo for the suppression. */
private String suppressionLineColumnNumber;
/** Tab character length. */
private int tabWidth;

}

Expand Down
@@ -0,0 +1,100 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2018 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.xpath.XpathQueryGenerator;

/**
* Class for constructing xpath queries to suppress nodes
* with specified line and column number.
* @author Timur Tibeyev.
*/
public final class SuppressionsStringPrinter {

/** Line and column number config value pattern. */
private static final Pattern VALID_SUPPRESSION_LINE_COLUMN_NUMBER_REGEX =
Pattern.compile("^([0-9]+):([0-9]+)$");

/** Prevent instances. */
private SuppressionsStringPrinter() {
// no code
}

/**
* Prints generated suppressions.
* @param file the file to process.
* @param suppressionLineColumnNumber line and column number of the suppression
* @param tabWidth length of the tab character
* @return generated suppressions.
* @throws IOException if the file could not be read.
* @throws CheckstyleException if the file is not a Java source.
*/
public static String printSuppressions(File file, String suppressionLineColumnNumber,
int tabWidth) throws IOException, CheckstyleException {
final Matcher matcher =
VALID_SUPPRESSION_LINE_COLUMN_NUMBER_REGEX.matcher(suppressionLineColumnNumber);
if (matcher.matches()) {
final FileText fileText = new FileText(file.getAbsoluteFile(),
System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
final DetailAST detailAST =
JavaParser.parseFileText(fileText, JavaParser.Options.WITH_COMMENTS);
final int lineNumber = Integer.parseInt(matcher.group(1));
final int columnNumber = Integer.parseInt(matcher.group(2));
return generate(fileText, detailAST, lineNumber, columnNumber, tabWidth);
}
else {
final String exceptionMsg = String.format(Locale.ROOT,
"%s does not match valid format 'line:column'.",
suppressionLineColumnNumber);
throw new IllegalStateException(exceptionMsg);
}
}

/**
* Creates {@code XpathQueryGenerator} instance and generates suppressions.
* @param fileText {@code FileText} object.
* @param detailAST {@code DetailAST} object.
* @param lineNumber line number.
* @param columnNumber column number.
* @param tabWidth length of the tab character.
* @return generated suppressions.
*/
private static String generate(FileText fileText, DetailAST detailAST, int lineNumber,
int columnNumber, int tabWidth) {
final XpathQueryGenerator queryGenerator =
new XpathQueryGenerator(detailAST, lineNumber, columnNumber, fileText,
tabWidth);
final List<String> suppressions = queryGenerator.generate();
return suppressions.stream().collect(Collectors.joining(System.lineSeparator(),
"", System.lineSeparator()));
}
}
125 changes: 125 additions & 0 deletions src/test/java/com/puppycrawl/tools/checkstyle/MainTest.java
Expand Up @@ -91,10 +91,20 @@ public class MainTest {
+ " -J,--treeWithJavadoc Print full Abstract Syntax Tree of the file%n"
+ " -o <arg> Sets the output file. Defaults to stdout%n"
+ " -p <arg> Loads the properties file%n"
+ " -s <arg> Print xpath suppressions at the file's line "
+ "and column%n"
+ " position. Argument is the line and column "
+ "number (separated%n"
+ " by a : ) in the file that the suppression "
+ "should be%n"
+ " generated for%n"
+ " -t,--tree Print Abstract Syntax Tree(AST) of the file%n"
+ " -T,--treeWithComments Print Abstract Syntax Tree(AST) of the file"
+ " including%n"
+ " comments%n"
+ " -tabWidth <arg> Sets the length of the tab character. "
+ "Used only with \"-s\"%n"
+ " option. Default value is 8%n"
+ " -v Print product version and exit%n"
+ " -W,--tree-walker-threads-number <arg> (experimental) The number of TreeWalker threads"
+ " (must be%n"
Expand Down Expand Up @@ -772,6 +782,107 @@ public void testPrintTreeJavadocOption() throws Exception {
Main.main("-j", getPath("InputMainJavadocComment.javadoc"));
}

@Test
public void testPrintSuppressionOption() throws Exception {
final String expected = "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']\n"
+ "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']/MODIFIERS\n"
+ "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']/LITERAL_CLASS\n";

exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log",
expected, systemOut.getLog());
assertEquals("Unexpected system error log",
"", systemErr.getLog());
});
Main.main(getPath("InputMainSuppressionsStringPrinter.java"),
"-s", "3:1");
}

@Test
public void testPrintSuppressionAndTabWidthOption() throws Exception {
final String expected = "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']"
+ "/OBJBLOCK/METHOD_DEF[@text='getName']/SLIST/VARIABLE_DEF[@text='var']\n"
+ "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']/OBJBLOCK"
+ "/METHOD_DEF[@text='getName']/SLIST/VARIABLE_DEF[@text='var']/MODIFIERS\n"
+ "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']/OBJBLOCK"
+ "/METHOD_DEF[@text='getName']/SLIST/VARIABLE_DEF[@text='var']/TYPE\n"
+ "/CLASS_DEF[@text='InputMainSuppressionsStringPrinter']/OBJBLOCK"
+ "/METHOD_DEF[@text='getName']/SLIST/VARIABLE_DEF[@text='var']/TYPE/LITERAL_INT\n";

exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log",
expected, systemOut.getLog());
assertEquals("Unexpected system error log",
"", systemErr.getLog());
});
Main.main(getPath("InputMainSuppressionsStringPrinter.java"),
"-s", "7:9", "-tabWidth", "2");
}

@Test
public void testPrintSuppressionConflictingOptionsTvsC() throws Exception {
exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Option '-s' cannot be used with other options."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-c", "/google_checks.xml",
getPath(""), "-s", "2:4");
}

@Test
public void testPrintSuppressionConflictingOptionsTvsP() throws Exception {
exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Option '-s' cannot be used with other options."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-p", getPath("InputMainMycheckstyle.properties"), "-s", "2:4", getPath(""));
}

@Test
public void testPrintSuppressionConflictingOptionsTvsF() throws Exception {
exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Option '-s' cannot be used with other options."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-f", "plain", "-s", "2:4", getPath(""));
}

@Test
public void testPrintSuppressionConflictingOptionsTvsO() throws Exception {
final File file = temporaryFolder.newFile("file.output");

exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Option '-s' cannot be used with other options."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-o", file.getCanonicalPath(), "-s", "2:4", getPath(""));
}

@Test
public void testPrintSuppressionOnMoreThanOneFile() throws Exception {
exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Printing xpath suppressions is allowed for "
+ "only one file."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-s", "2:4", getPath(""), getPath(""));
}

@Test
public void testPrintFullTreeOption() throws Exception {
final String expected = new String(Files.readAllBytes(Paths.get(
Expand Down Expand Up @@ -822,6 +933,20 @@ public void testConflictingOptionsTvsF() throws Exception {
Main.main("-f", "plain", "-t", getPath(""));
}

@Test
public void testConflictingOptionsTvsS() throws Exception {
final File file = temporaryFolder.newFile("file.output");

exit.expectSystemExitWithStatus(-1);
exit.checkAssertionAfterwards(() -> {
assertEquals("Unexpected output log", "Option '-t' cannot be used with other options."
+ System.lineSeparator(), systemOut.getLog());
assertEquals("Unexpected system error log", "", systemErr.getLog());
});

Main.main("-s", file.getCanonicalPath(), "-t", getPath(""));
}

@Test
public void testConflictingOptionsTvsO() throws Exception {
final File file = temporaryFolder.newFile("file.output");
Expand Down

0 comments on commit c66cac1

Please sign in to comment.