Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #12542: new TreeWalker property to skip exceptions #14779

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion config/ant-phase-verify.xml
Expand Up @@ -166,7 +166,8 @@
<exclude name="**/InputMainFrameModelIncorrectClass.java"/>
<exclude name="**/InputBeforeExecutionExclusionFileFilterIncorrectClass.java"/>
<exclude name="**/InputJavaParser.java"/>

<exclude name="**/InputTreeWalkerSkipParsingException.java"/>
<exclude name="**/InputTreeWalkerSkipParsingExceptionConfig.java"/>
</fileset>
</path>
<formatter type="plain"/>
Expand Down Expand Up @@ -203,6 +204,8 @@
<exclude name="**/InputMainFrameModelIncorrectClass.java"/>
<exclude name="**/InputBeforeExecutionExclusionFileFilterIncorrectClass.java"/>
<exclude name="**/InputJavaParser.java"/>
<exclude name="**/InputTreeWalkerSkipParsingException.java"/>
<exclude name="**/InputTreeWalkerSkipParsingExceptionConfig.java"/>
<!-- This check does not apply to grammar input files -->
<exclude name="**/grammar/**/*"/>
<!-- No need to check xpathmapper files -->
Expand Down
Expand Up @@ -851,6 +851,72 @@
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter args of Violation constructor.</message>
<lineContent>new Object[] {ex.getMessage()}, javaParseExceptionSeverity, null,</lineContent>
<details>
found : @Initialized @Nullable Object @Initialized @NonNull []
required: @Initialized @NonNull Object @Initialized @NonNull []
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter ast of TreeWalker.walk.</message>
<lineContent>walk(rootAST, contents, AstState.ORDINARY);</lineContent>
<details>
found : @Initialized @Nullable DetailAST
required: @Initialized @NonNull DetailAST
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter customMessage of Violation constructor.</message>
<lineContent>getClass(), null));</lineContent>
<details>
found : null (NullType)
required: @Initialized @NonNull String
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter moduleId of Violation constructor.</message>
<lineContent>new Object[] {ex.getMessage()}, javaParseExceptionSeverity, null,</lineContent>
<details>
found : null (NullType)
required: @Initialized @NonNull String
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter root of JavaParser.appendHiddenCommentNodes.</message>
<lineContent>final DetailAST astWithComments = JavaParser.appendHiddenCommentNodes(rootAST);</lineContent>
<details>
found : @Initialized @Nullable DetailAST
required: @Initialized @NonNull DetailAST
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>argument</specifier>
<message>incompatible argument for parameter rootAST of TreeWalker.getFilteredViolations.</message>
<lineContent>getFilteredViolations(file.getAbsolutePath(), contents, rootAST);</lineContent>
<details>
found : @Initialized @Nullable DetailAST
required: @Initialized @NonNull DetailAST
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java</fileName>
<specifier>initialization.fields.uninitialized</specifier>
Expand Down
77 changes: 64 additions & 13 deletions src/main/java/com/puppycrawl/tools/checkstyle/TreeWalker.java
Expand Up @@ -42,6 +42,7 @@
import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
import com.puppycrawl.tools.checkstyle.api.Violation;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;

Expand All @@ -53,6 +54,9 @@
@FileStatefulCheck
public final class TreeWalker extends AbstractFileSetCheck implements ExternalResourceHolder {

/** Message to use when an exception occurs and should be printed as a violation. */
public static final String EXCEPTION_MSG = "general.exception";

/** Maps from token name to ordinary checks. */
private final Map<Integer, Set<AbstractCheck>> tokenToOrdinaryChecks =
new HashMap<>();
Expand All @@ -79,6 +83,12 @@ public final class TreeWalker extends AbstractFileSetCheck implements ExternalRe
/** A factory for creating submodules (i.e. the Checks) */
private ModuleFactory moduleFactory;

/** Control whether to skip files with Java parsing errors. */
private boolean skipFileOnJavaParseException;

/** Severity Level to log Java parsing exceptions when they are skipped. */
private SeverityLevel javaParseExceptionSeverity = SeverityLevel.ERROR;

/**
* Creates a new {@code TreeWalker} instance.
*/
Expand All @@ -95,6 +105,27 @@ public void setModuleFactory(ModuleFactory moduleFactory) {
this.moduleFactory = moduleFactory;
}

/**
* Setter to control whether to skip files with Java parsing errors.
*
* @param skipFileOnJavaParseException whether to always check for a trailing comma.
* @since 10.17.0
*/
public void setSkipFileOnJavaParseException(boolean skipFileOnJavaParseException) {
this.skipFileOnJavaParseException = skipFileOnJavaParseException;
}

/**
* Sets severity level to log Java parsing exceptions when they are skipped.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=> Setter to specify the severity level...

*
* @param javaParseExceptionSeverity severity level to log parsing exceptions
* when they are skipped.
* @since 10.17.0
*/
public void setJavaParseExceptionSeverity(SeverityLevel javaParseExceptionSeverity) {
this.javaParseExceptionSeverity = javaParseExceptionSeverity;
}

@Override
public void finishLocalSetup() {
final DefaultContext checkContext = new DefaultContext();
Expand Down Expand Up @@ -149,21 +180,41 @@ protected void processFiltered(File file, FileText fileText) throws CheckstyleEx
// check if already checked and passed the file
if (!ordinaryChecks.isEmpty() || !commentChecks.isEmpty()) {
final FileContents contents = getFileContents();
final DetailAST rootAST = JavaParser.parse(contents);
if (!ordinaryChecks.isEmpty()) {
walk(rootAST, contents, AstState.ORDINARY);
}
if (!commentChecks.isEmpty()) {
final DetailAST astWithComments = JavaParser.appendHiddenCommentNodes(rootAST);
walk(astWithComments, contents, AstState.WITH_COMMENTS);
DetailAST rootAST = null;
// whether skip the procedure after parsing Java files.
boolean skip = false;
try {
rootAST = JavaParser.parse(contents);
rnveach marked this conversation as resolved.
Show resolved Hide resolved
}
if (filters.isEmpty()) {
addViolations(violations);
catch (CheckstyleException ex) {
Copy link
Member

@rnveach rnveach May 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/c6a72a2_2024054348/reports/diff/openjdk17/index.html#A1

This report shows we can get parse errors from more than just CheckstyleExceptions. It seems to me we need to expand it to all throwables for this to work properly.

Edit: We will also need a test case to show this. Let me know what the new test name will be.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (skipFileOnJavaParseException) {
skip = true;
violations.add(new Violation(1, Definitions.CHECKSTYLE_BUNDLE, EXCEPTION_MSG,
new Object[] {ex.getMessage()}, javaParseExceptionSeverity, null,
getClass(), null));
addViolations(violations);
}
else {
throw ex;
}
rnveach marked this conversation as resolved.
Show resolved Hide resolved
}
else {
final SortedSet<Violation> filteredViolations =
getFilteredViolations(file.getAbsolutePath(), contents, rootAST);
addViolations(filteredViolations);

if (!skip) {
if (!ordinaryChecks.isEmpty()) {
walk(rootAST, contents, AstState.ORDINARY);
}
if (!commentChecks.isEmpty()) {
final DetailAST astWithComments = JavaParser.appendHiddenCommentNodes(rootAST);
walk(astWithComments, contents, AstState.WITH_COMMENTS);
}
if (filters.isEmpty()) {
addViolations(violations);
}
else {
final SortedSet<Violation> filteredViolations =
getFilteredViolations(file.getAbsolutePath(), contents, rootAST);
addViolations(filteredViolations);
}
}
violations.clear();
}
Expand Down
129 changes: 129 additions & 0 deletions src/test/java/com/puppycrawl/tools/checkstyle/TreeWalkerTest.java
Expand Up @@ -35,9 +35,12 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -56,7 +59,10 @@
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.api.Violation;
import com.puppycrawl.tools.checkstyle.checks.NoCodeInFileCheck;
import com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck;
import com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck;
import com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck;
Expand Down Expand Up @@ -673,6 +679,129 @@ public void testOrderOfCheckExecution() throws Exception {
}
}

@Test
public void testSkipFileOnJavaParseExceptionTrue() throws Exception {
final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
config.addProperty("skipFileOnJavaParseException", "true");
config.addProperty("javaParseExceptionSeverity", "ignore");
config.addChild(createModuleConfig(ConstantNameCheck.class));

final File[] files = {
new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
new File(getPath("InputTreeWalkerProperFileExtension.java")),
new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
};

final Checker checker = createChecker(config);
final Map<String, List<String>> expectedViolation = new HashMap<>();
expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
Collections.singletonList(
"10:27: " + getCheckMessage(ConstantNameCheck.class,
MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$")));
verify(checker, files, expectedViolation);
}

@Test
public void testSkipFileOnJavaParseExceptionFalse() throws Exception {
final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
config.addProperty("skipFileOnJavaParseException", "false");
config.addChild(createModuleConfig(ConstantNameCheck.class));

final String[] files = {
getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
getPath("InputTreeWalkerProperFileExtension.java"),
getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
};
final Exception ex = TestUtil.getExpectedThrowable(CheckstyleException.class,
() -> execute(config, files),
"Exception is expected");
assertWithMessage("Error message is unexpected")
.that(ex.getMessage())
.contains("Exception was thrown while processing ");
}

@Test
public void testSkipFileOnJavaParseExceptionConfig() throws Exception {
final String path = getNonCompilablePath("InputTreeWalkerSkipParsingExceptionConfig.java");
final String[] expected = {};
verifyWithInlineXmlConfig(path, expected);
}

@Test
public void testSkipFileOnJavaParseExceptionSkipChecks() throws Exception {
final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
config.addProperty("skipFileOnJavaParseException", "true");
config.addProperty("javaParseExceptionSeverity", "ignore");
config.addChild(createModuleConfig(NoCodeInFileCheck.class));

final Checker checker = createChecker(config);

final File[] files = {
new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
new File(getPath("InputTreeWalkerProperFileExtension.java")),
};
final Map<String, List<String>> expectedViolation = new HashMap<>();
expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
new ArrayList<>());

verify(checker, files, expectedViolation);
}

@Test
public void testJavaParseExceptionSeverityDefaultError() throws Exception {
final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
config.addProperty("skipFileOnJavaParseException", "true");
config.addChild(createModuleConfig(NoCodeInFileCheck.class));

final Checker checker = createChecker(config);

final File[] files = {
new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
new File(getPath("InputTreeWalkerProperFileExtension.java")),
};

final Map<String, List<String>> expectedViolation = new HashMap<>();
expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
new ArrayList<>());
expectedViolation.put(getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
List.of("1: Got an exception - IllegalStateException occurred while parsing file "
+ getNonCompilablePath("InputTreeWalkerSkipParsingException.java") + "."));

verify(checker, files, expectedViolation);
}

@Test
public void testJavaParseExceptionSeverity() throws Exception {
final PackageObjectFactory factory = new PackageObjectFactory(
new HashSet<>(), Thread.currentThread().getContextClassLoader());
final File testFile = new File(
getNonCompilablePath("InputTreeWalkerSkipParsingException.java"));

for (SeverityLevel severityLevel : SeverityLevel.values()) {
final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
config.addProperty("skipFileOnJavaParseException", "true");
config.addProperty("javaParseExceptionSeverity", severityLevel.getName());
config.addChild(createModuleConfig(NoCodeInFileCheck.class));

final TreeWalker treeWalker = new TreeWalker();
treeWalker.setModuleFactory(factory);
treeWalker.configure(config);

final SortedSet<Violation> violations = treeWalker.process(testFile,
new FileText(testFile, StandardCharsets.UTF_8.name()));
assertWithMessage("Incorrect length of violation")
.that(violations.size())
.isEqualTo(1);
assertWithMessage("Incorrect severity of violation")
.that(violations.first().getSeverityLevel())
.isEqualTo(severityLevel);
assertWithMessage("Incorrect customized message")
.that(violations.first().getViolation())
.contains("Got an exception - "
+ "IllegalStateException occurred while parsing file ");
}
}

public static class BadJavaDocCheck extends AbstractCheck {

@Override
Expand Down
@@ -0,0 +1,3 @@
//non-compiled syntax: bad file for testing

public clazz InputTreeWalkerSkipParsingException {}
@@ -0,0 +1,12 @@
/*xml
<module name="Checker">
<module name="TreeWalker">
<property name="skipFileOnJavaParseException" value="true"/>
<property name="javaParseExceptionSeverity" value="ignore"/>
<module name="ConstantName"/>
romani marked this conversation as resolved.
Show resolved Hide resolved
</module>
</module>
*/

//non-compiled syntax: bad file for testing
public clazz InputTreeWalkerSkipParsingExceptionConfig {}
14 changes: 14 additions & 0 deletions src/xdocs/config.xml
Expand Up @@ -539,6 +539,20 @@
<td><code>.java</code></td>
<td>3.0</td>
</tr>
<tr>
<td>javaParseExceptionSeverity</td>
<td>Severity level to log Java parsing exceptions when they are skipped.</td>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=> Specify the severity level...

<td><a href="property_types.html#SeverityLevel">SeverityLevel</a></td>
<td><code>error</code></td>
<td>10.17.0</td>
</tr>
<tr>
<td>skipFileOnJavaParseException</td>
<td>Control whether to skip files with Java parsing errors.</td>
<td><a href="property_types.html#boolean">boolean</a></td>
<td><code>false</code></td>
<td>10.17.0</td>
</tr>
</table>
</div>
</subsection>
Expand Down