Issue #652: New option to print AST with comment nodes.

This commit is contained in:
Baratali Izmailov 2016-02-18 00:44:28 +00:00
parent 84166439f0
commit 25621a3c33
11 changed files with 217 additions and 48 deletions

View File

@ -140,7 +140,7 @@
<!-- Suppressions from PMD configuration-->
<!-- validateCli is not reasonable to split as encapsulation of logic will be damaged -->
<suppress checks="CyclomaticComplexity" files="Main\.java" lines="184"/>
<suppress checks="CyclomaticComplexity" files="Main\.java" lines="200"/>
<!-- JavadocMethodCheck, JavadocStyleCheck, JavadocUtils.getJavadocTags() - deprecated -->
<suppress checks="CyclomaticComplexity" files="JavadocMethodCheck\.java"/>
<suppress checks="CyclomaticComplexity" files="JavadocStyleCheck\.java"/>
@ -161,5 +161,6 @@
lines="105, 143, 174"/>
<!-- Not reasonable to split as encapsulation of logic will be damaged. -->
<suppress checks="ExecutableStatementCount" files="Main\.java" lines="90"/>
<suppress checks="ExecutableStatementCount" files="Main\.java" lines="102"/>
<suppress checks="CyclomaticComplexity" files="Main\.java" lines="102"/>
</suppressions>

View File

@ -45,6 +45,9 @@ public final class AstTreeStringPrinter {
/** Tab pattern. */
private static final Pattern TAB = Pattern.compile("\t");
/** OS specific line separator. */
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
/** Prevent instances. */
private AstTreeStringPrinter() {
// no code
@ -53,12 +56,14 @@ public final class AstTreeStringPrinter {
/**
* Parse a file and print the parse tree.
* @param file the file to print.
* @param withComments true to include comments to AST
* @return the AST of the file in String form.
* @throws IOException if the file could not be read.
* @throws CheckstyleException if the file is not a Java source.
*/
public static String printFileAst(File file) throws IOException, CheckstyleException {
return printTree(parseFile(file));
public static String printFileAst(File file, boolean withComments)
throws IOException, CheckstyleException {
return printTree(parseFile(file, withComments));
}
/**
@ -73,7 +78,8 @@ public final class AstTreeStringPrinter {
messageBuilder.append(getIndentation(node))
.append(TokenUtils.getTokenName(node.getType())).append(" -> ")
.append(excapeAllControlChars(node.getText())).append(" [")
.append(node.getLineNo()).append(':').append(node.getColumnNo()).append("]\n")
.append(node.getLineNo()).append(':').append(node.getColumnNo()).append(']')
.append(LINE_SEPARATOR)
.append(printTree(node.getFirstChild()));
node = node.getNextSibling();
}
@ -127,16 +133,24 @@ public final class AstTreeStringPrinter {
/**
* Parse a file and return the parse tree.
* @param file the file to parse.
* @param withComments true to include comment nodes to the tree
* @return the root node of the parse tree.
* @throws IOException if the file could not be read.
* @throws CheckstyleException if the file is not a Java source.
*/
private static DetailAST parseFile(File file) throws IOException, CheckstyleException {
private static DetailAST parseFile(File file, boolean withComments)
throws IOException, CheckstyleException {
final FileText text = new FileText(file.getAbsoluteFile(),
System.getProperty("file.encoding", "UTF-8"));
final FileContents contents = new FileContents(text);
final DetailAST result;
try {
return TreeWalker.parse(contents);
if (withComments) {
result = TreeWalker.parseWithComments(contents);
}
else {
result = TreeWalker.parse(contents);
}
}
catch (RecognitionException | TokenStreamException ex) {
final String exceptionMsg = String.format(Locale.ROOT,
@ -144,5 +158,7 @@ public final class AstTreeStringPrinter {
ex.getClass().getSimpleName(), file.getPath());
throw new CheckstyleException(exceptionMsg, ex);
}
return result;
}
}

View File

@ -49,6 +49,9 @@ import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
*
**/
public final class Main {
/** Width of CLI help option. */
private static final int HELP_WIDTH = 100;
/** Exit code returned when execution finishes with {@link CheckstyleException}. */
private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2;
@ -70,6 +73,15 @@ public final class Main {
/** 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 '-T'. */
private static final String OPTION_CAPITAL_T_NAME = "T";
/** Name for the option '--treeWithComments'. */
private static final String OPTION_TREE_COMMENT_NAME = "treeWithComments";
/** Name for 'xml' format. */
private static final String XML_FORMAT_NAME = "xml";
@ -120,11 +132,15 @@ public final class Main {
else {
// create config helper object
final CliOptions config = convertCliToPojo(commandLine, filesToProcess);
if (commandLine.hasOption(OPTION_T_NAME)) {
// print AST
final File file = config.files.get(0);
final String stringAst = AstTreeStringPrinter.printFileAst(file);
final String stringAst = AstTreeStringPrinter.printFileAst(file, false);
System.out.print(stringAst);
}
else if (commandLine.hasOption(OPTION_CAPITAL_T_NAME)) {
final File file = config.files.get(0);
final String stringAst = AstTreeStringPrinter.printFileAst(file, true);
System.out.print(stringAst);
}
else {
@ -188,7 +204,7 @@ public final class Main {
result.add("Files to process must be specified, found 0.");
}
// ensure there is no conflicting options
else if (cmdLine.hasOption(OPTION_T_NAME)) {
else if (cmdLine.hasOption(OPTION_T_NAME) || cmdLine.hasOption(OPTION_CAPITAL_T_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 '-t' cannot be used with other options.");
@ -430,6 +446,7 @@ public final class Main {
/** Prints the usage information. **/
private static void printUsage() {
final HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(HELP_WIDTH);
formatter.printHelp(String.format("java %s [options] -c <config.xml> file...",
Main.class.getName()), buildOptions());
}
@ -447,7 +464,10 @@ public final class Main {
"Sets the output format. (%s|%s). Defaults to %s",
PLAIN_FORMAT_NAME, XML_FORMAT_NAME, PLAIN_FORMAT_NAME));
options.addOption(OPTION_V_NAME, false, "Print product version and exit");
options.addOption(OPTION_T_NAME, false, "Print Abstract Syntax Tree(AST) of the file");
options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false,
"Print Abstract Syntax Tree(AST) of the file");
options.addOption(OPTION_CAPITAL_T_NAME, OPTION_TREE_COMMENT_NAME, false,
"Print Abstract Syntax Tree(AST) of the file including comments");
return options;
}

View File

@ -443,6 +443,18 @@ public final class TreeWalker
return (DetailAST) parser.getAST();
}
/**
* Parses Java source file. Result AST contains comment nodes.
* @param contents source file content
* @return DetailAST tree
* @throws RecognitionException if parser failed
* @throws TokenStreamException if lexer failed
*/
public static DetailAST parseWithComments(FileContents contents)
throws RecognitionException, TokenStreamException {
return appendHiddenCommentNodes(parse(contents));
}
@Override
public void destroy() {
for (AbstractCheck check : ordinaryChecks) {

View File

@ -23,8 +23,11 @@ import static com.puppycrawl.tools.checkstyle.internal.TestUtils.assertUtilsClas
import java.io.File;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
public class AstTreeStringPrinterTest {
@ -33,6 +36,10 @@ public class AstTreeStringPrinterTest {
return "src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/" + filename;
}
private static String getPath(String filename) {
return "src/test/resources/com/puppycrawl/tools/checkstyle/astprinter/" + filename;
}
@Test
public void testIsProperUtilsClass() throws ReflectiveOperationException {
assertUtilsClassHasPrivateConstructor(AstTreeStringPrinter.class);
@ -41,6 +48,26 @@ public class AstTreeStringPrinterTest {
@Test(expected = CheckstyleException.class)
public void testParseFileThrowable() throws Exception {
AstTreeStringPrinter.printFileAst(
new File(getNonCompilablePath("InputAstTreeStringPrinter.java")));
new File(getNonCompilablePath("InputAstTreeStringPrinter.java")), false);
}
@Test
public void testParseFile() throws Exception {
final String actual = AstTreeStringPrinter.printFileAst(
new File(getPath("InputAstTreeStringPrinterComments.java")), false);
final String expected = Files.toString(new File(
getPath("expectedInputAstTreeStringPrinter.txt")), Charsets.UTF_8);
Assert.assertEquals(expected, actual);
}
@Test
public void testParseFileWithComments() throws Exception {
final String actual = AstTreeStringPrinter.printFileAst(
new File(getPath("InputAstTreeStringPrinterComments.java")), true)
.replaceAll("\\\\r\\\\n", "\\\\n");
final String expected = Files.toString(new File(
getPath("expectedInputAstTreeStringPrinterComments.txt")), Charsets.UTF_8)
.replaceAll("\\\\r\\\\n", "\\\\n");
Assert.assertEquals(expected, actual);
}
}

View File

@ -96,14 +96,17 @@ public class MainTest {
@Override
public void checkAssertion() {
final String usage = String.format(Locale.ROOT, "Unrecognized option: -w%n"
+ "usage: java com.puppycrawl.tools.checkstyle.Main [options] -c <config.xml>%n"
+ " file...%n"
+ " -c <arg> Sets the check configuration file to use.%n"
+ " -f <arg> Sets the output format. (plain|xml). Defaults to plain%n"
+ " -o <arg> Sets the output file. Defaults to stdout%n"
+ " -p <arg> Loads the properties file%n"
+ " -t Print Abstract Syntax Tree(AST) of the file%n"
+ " -v Print product version and exit%n");
+ "usage: java com.puppycrawl.tools.checkstyle.Main [options] -c <config.xml>"
+ " file...%n"
+ " -c <arg> Sets the check configuration file to use.%n"
+ " -f <arg> Sets the output format. (plain|xml). Defaults to"
+ " plain%n"
+ " -o <arg> Sets the output file. Defaults to stdout%n"
+ " -p <arg> Loads the properties file%n"
+ " -t,--tree Print Abstract Syntax Tree(AST) of the file%n"
+ " -T,--treeWithComments Print Abstract Syntax Tree(AST) of the file"
+ " including comments%n"
+ " -v Print product version and exit%n");
assertEquals(usage, systemOut.getLog());
assertEquals("", systemErr.getLog());
@ -647,31 +650,31 @@ public class MainTest {
@Test
public void testPrintTreeOption() throws Exception {
final String expected = "PACKAGE_DEF -> package [1:0]\n"
+ "|--ANNOTATIONS -> ANNOTATIONS [1:28]\n"
+ "|--DOT -> . [1:28]\n"
+ "| |--DOT -> . [1:22]\n"
+ "| | |--DOT -> . [1:11]\n"
+ "| | | |--IDENT -> com [1:8]\n"
+ "| | | `--IDENT -> puppycrawl [1:12]\n"
+ "| | `--IDENT -> tools [1:23]\n"
+ "| `--IDENT -> checkstyle [1:29]\n"
+ "`--SEMI -> ; [1:39]\n"
+ "CLASS_DEF -> CLASS_DEF [3:0]\n"
+ "|--MODIFIERS -> MODIFIERS [3:0]\n"
+ "| `--LITERAL_PUBLIC -> public [3:0]\n"
+ "|--LITERAL_CLASS -> class [3:7]\n"
+ "|--IDENT -> InputMain [3:13]\n"
+ "`--OBJBLOCK -> OBJBLOCK [3:23]\n"
+ " |--LCURLY -> { [3:23]\n"
+ " `--RCURLY -> } [4:0]\n"
+ "CLASS_DEF -> CLASS_DEF [5:0]\n"
+ "|--MODIFIERS -> MODIFIERS [5:0]\n"
+ "|--LITERAL_CLASS -> class [5:0]\n"
+ "|--IDENT -> InputMainInner [5:6]\n"
+ "`--OBJBLOCK -> OBJBLOCK [5:21]\n"
+ " |--LCURLY -> { [5:21]\n"
+ " `--RCURLY -> } [6:0]\n";
final String expected = String.format(Locale.ROOT, "PACKAGE_DEF -> package [1:0]%n"
+ "|--ANNOTATIONS -> ANNOTATIONS [1:28]%n"
+ "|--DOT -> . [1:28]%n"
+ "| |--DOT -> . [1:22]%n"
+ "| | |--DOT -> . [1:11]%n"
+ "| | | |--IDENT -> com [1:8]%n"
+ "| | | `--IDENT -> puppycrawl [1:12]%n"
+ "| | `--IDENT -> tools [1:23]%n"
+ "| `--IDENT -> checkstyle [1:29]%n"
+ "`--SEMI -> ; [1:39]%n"
+ "CLASS_DEF -> CLASS_DEF [3:0]%n"
+ "|--MODIFIERS -> MODIFIERS [3:0]%n"
+ "| `--LITERAL_PUBLIC -> public [3:0]%n"
+ "|--LITERAL_CLASS -> class [3:7]%n"
+ "|--IDENT -> InputMain [3:13]%n"
+ "`--OBJBLOCK -> OBJBLOCK [3:23]%n"
+ " |--LCURLY -> { [3:23]%n"
+ " `--RCURLY -> } [4:0]%n"
+ "CLASS_DEF -> CLASS_DEF [5:0]%n"
+ "|--MODIFIERS -> MODIFIERS [5:0]%n"
+ "|--LITERAL_CLASS -> class [5:0]%n"
+ "|--IDENT -> InputMainInner [5:6]%n"
+ "`--OBJBLOCK -> OBJBLOCK [5:21]%n"
+ " |--LCURLY -> { [5:21]%n"
+ " `--RCURLY -> } [6:0]%n");
exit.checkAssertionAfterwards(new Assertion() {
@Override
@ -683,6 +686,47 @@ public class MainTest {
Main.main("-t", getPath("InputMain.java"));
}
@Test
public void testPrintTreeCommentsOption() throws Exception {
final String expected = String.format(Locale.ROOT, "PACKAGE_DEF -> package [1:0]%n"
+ "|--ANNOTATIONS -> ANNOTATIONS [1:28]%n"
+ "|--DOT -> . [1:28]%n"
+ "| |--DOT -> . [1:22]%n"
+ "| | |--DOT -> . [1:11]%n"
+ "| | | |--IDENT -> com [1:8]%n"
+ "| | | `--IDENT -> puppycrawl [1:12]%n"
+ "| | `--IDENT -> tools [1:23]%n"
+ "| `--IDENT -> checkstyle [1:29]%n"
+ "`--SEMI -> ; [1:39]%n"
+ "CLASS_DEF -> CLASS_DEF [3:0]%n"
+ "|--MODIFIERS -> MODIFIERS [3:0]%n"
+ "| |--BLOCK_COMMENT_BEGIN -> /* [2:0]%n"
+ "| | |--COMMENT_CONTENT -> comment [2:2]%n"
+ "| | `--BLOCK_COMMENT_END -> */ [2:8]%n"
+ "| `--LITERAL_PUBLIC -> public [3:0]%n"
+ "|--LITERAL_CLASS -> class [3:7]%n"
+ "|--IDENT -> InputMain [3:13]%n"
+ "`--OBJBLOCK -> OBJBLOCK [3:23]%n"
+ " |--LCURLY -> { [3:23]%n"
+ " `--RCURLY -> } [4:0]%n"
+ "CLASS_DEF -> CLASS_DEF [5:0]%n"
+ "|--MODIFIERS -> MODIFIERS [5:0]%n"
+ "|--LITERAL_CLASS -> class [5:0]%n"
+ "|--IDENT -> InputMainInner [5:6]%n"
+ "`--OBJBLOCK -> OBJBLOCK [5:21]%n"
+ " |--LCURLY -> { [5:21]%n"
+ " `--RCURLY -> } [6:0]%n");
exit.checkAssertionAfterwards(new Assertion() {
@Override
public void checkAssertion() {
assertEquals(expected, systemOut.getLog());
assertEquals("", systemErr.getLog());
}
});
Main.main("-T", getPath("InputMain.java"));
}
@Test
public void testConflictingOptionsTvsC() throws Exception {

View File

@ -1,5 +1,5 @@
package com.puppycrawl.tools.checkstyle;
/*comment*/
public class InputMain {
}
class InputMainInner {

View File

@ -0,0 +1,6 @@
package com.puppycrawl.tools.checkstyle;
/**my class*/
class InputAstTreeStringPrinterComments {
// no code
}

View File

@ -0,0 +1,17 @@
PACKAGE_DEF -> package [1:0]
|--ANNOTATIONS -> ANNOTATIONS [1:28]
|--DOT -> . [1:28]
| |--DOT -> . [1:22]
| | |--DOT -> . [1:11]
| | | |--IDENT -> com [1:8]
| | | `--IDENT -> puppycrawl [1:12]
| | `--IDENT -> tools [1:23]
| `--IDENT -> checkstyle [1:29]
`--SEMI -> ; [1:39]
CLASS_DEF -> CLASS_DEF [4:0]
|--MODIFIERS -> MODIFIERS [4:0]
|--LITERAL_CLASS -> class [4:0]
|--IDENT -> InputAstTreeStringPrinterComments [4:6]
`--OBJBLOCK -> OBJBLOCK [4:40]
|--LCURLY -> { [4:40]
`--RCURLY -> } [6:0]

View File

@ -0,0 +1,22 @@
PACKAGE_DEF -> package [1:0]
|--ANNOTATIONS -> ANNOTATIONS [1:28]
|--DOT -> . [1:28]
| |--DOT -> . [1:22]
| | |--DOT -> . [1:11]
| | | |--IDENT -> com [1:8]
| | | `--IDENT -> puppycrawl [1:12]
| | `--IDENT -> tools [1:23]
| `--IDENT -> checkstyle [1:29]
`--SEMI -> ; [1:39]
CLASS_DEF -> CLASS_DEF [4:0]
|--MODIFIERS -> MODIFIERS [4:0]
|--BLOCK_COMMENT_BEGIN -> /* [3:0]
| |--COMMENT_CONTENT -> *my class [3:2]
| `--BLOCK_COMMENT_END -> */ [3:10]
|--LITERAL_CLASS -> class [4:0]
|--IDENT -> InputAstTreeStringPrinterComments [4:6]
`--OBJBLOCK -> OBJBLOCK [4:40]
|--LCURLY -> { [4:40]
|--SINGLE_LINE_COMMENT -> // [5:1]
| `--COMMENT_CONTENT -> no code\r\n [5:3]
`--RCURLY -> } [6:0]

View File

@ -38,7 +38,7 @@ java -D&lt;property&gt;=&lt;value&gt; \
com.puppycrawl.tools.checkstyle.Main \
-c &lt;configurationFile&gt; \
[-f &lt;format&gt;] [-p &lt;propertiesFile&gt;] [-o &lt;file&gt;] \
[-t] [-v] \
[-t | --tree] [-T | --treeWithComments] [-v] \
file...
</source>
</p>
@ -74,7 +74,11 @@ java -D&lt;property&gt;=&lt;value&gt; \
to.
</li>
<li>
<code>-t</code> - print Abstract Syntax Tree(AST) of the checked file. The option
<code>-t, --tree</code> - print Abstract Syntax Tree(AST) of the checked file. The option
cannot be used other options and requires exactly one file to run on to be specified.
</li>
<li>
<code>-T, --treeWithComments</code> - print Abstract Syntax Tree(AST) with comment nodes of the checked file. The option
cannot be used other options and requires exactly one file to run on to be specified.
</li>
<li>