Skip to content

Commit efb565f

Browse files
committed
Issue #102: add support for sarif reports
1 parent e58b713 commit efb565f

16 files changed

+424
-147
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ replay_pid*
4848
.ci-temp
4949

5050
# OpenRewrite-generated impl files
51-
*.iml
51+
*.iml
52+
53+
.cache
54+

config/import-control.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
<allow pkg="org.checkstyle"/>
1313
<allow pkg="java.util"/>
1414
<allow pkg="com.puppycrawl.tools.checkstyle"/>
15+
<allow pkg="de.jcup.sarif_2_1_0"/>
1516
</import-control>

config/suppressions.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
<suppress checks="MissingJavadocType" files=".*"/>
1010
<suppress checks="JavadocVariable" files=".*"/>
1111
<suppress checks="MissingJavadocMethod" files=".*"/>
12-
<suppress checks="DesignForExtension" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTest\.java"/>
13-
<suppress checks="DesignForExtension" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTestSupport\.java"/>
14-
<suppress checks="DesignForExtension" files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]CheckstyleAutoFix\.java"/>
12+
<suppress checks="DesignForExtension" files=".*"/>
1513
<suppress checks="ClassDataAbstractionCoupling" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTestSupport\.java"/>
1614
</suppressions>

pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@
8686
<version>7.3.0.202506031305-r</version>
8787
</dependency>
8888

89+
<dependency>
90+
<groupId>de.jcup.sarif.java</groupId>
91+
<artifactId>sarif-2.1.0</artifactId>
92+
<version>1.1.0</version>
93+
</dependency>
94+
8995
<!-- Test dependencies -->
9096
<dependency>
9197
<groupId>com.google.truth</groupId>
@@ -111,6 +117,12 @@
111117
<version>${junit.version}</version>
112118
<scope>test</scope>
113119
</dependency>
120+
<dependency>
121+
<groupId>org.junit.jupiter</groupId>
122+
<artifactId>junit-jupiter-params</artifactId>
123+
<version>${junit.version}</version>
124+
<scope>test</scope>
125+
</dependency>
114126
<dependency>
115127
<groupId>org.assertj</groupId>
116128
<artifactId>assertj-core</artifactId>
@@ -139,6 +151,9 @@
139151
<artifactId>maven-surefire-plugin</artifactId>
140152
<version>3.2.2</version>
141153
<configuration>
154+
<statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
155+
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
156+
</statelessTestsetReporter>
142157
<systemPropertyVariables>
143158
<org.slf4j.simpleLogger.log.org.openrewrite>debug</org.slf4j.simpleLogger.log.org.openrewrite>
144159
</systemPropertyVariables>

src/main/java/org/checkstyle/autofix/CheckstyleAutoFix.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import java.util.Map;
2323

2424
import org.checkstyle.autofix.parser.CheckConfiguration;
25-
import org.checkstyle.autofix.parser.CheckstyleReportParser;
2625
import org.checkstyle.autofix.parser.CheckstyleViolation;
2726
import org.checkstyle.autofix.parser.ConfigurationLoader;
27+
import org.checkstyle.autofix.parser.ReportParser;
28+
import org.checkstyle.autofix.parser.SarifReportParser;
29+
import org.checkstyle.autofix.parser.XmlReportParser;
2830
import org.openrewrite.Option;
2931
import org.openrewrite.Recipe;
3032

@@ -34,7 +36,8 @@
3436
public class CheckstyleAutoFix extends Recipe {
3537

3638
@Option(displayName = "Violation report path",
37-
description = "Path to the checkstyle violation report XML file.",
39+
description = "Path to the checkstyle violation report file."
40+
+ " Supported formats: XML, SARIF.",
3841
example = "target/checkstyle/checkstyle-report.xml")
3942
private String violationReportPath;
4043

@@ -82,14 +85,29 @@ public String getPropertiesPath() {
8285

8386
@Override
8487
public List<Recipe> getRecipeList() {
85-
final List<CheckstyleViolation> violations = CheckstyleReportParser
88+
final ReportParser reportParser = createReportParser(getViolationReportPath());
89+
final List<CheckstyleViolation> violations = reportParser
8690
.parse(Path.of(getViolationReportPath()));
8791
final Map<CheckstyleCheck,
8892
CheckConfiguration> configuration = loadCheckstyleConfiguration();
8993

9094
return CheckstyleRecipeRegistry.getRecipes(violations, configuration);
9195
}
9296

97+
private ReportParser createReportParser(String path) {
98+
final ReportParser result;
99+
if (path.endsWith(".xml")) {
100+
result = new XmlReportParser();
101+
}
102+
else if (path.endsWith(".sarif") || path.endsWith(".sarif.json")) {
103+
result = new SarifReportParser();
104+
}
105+
else {
106+
throw new IllegalArgumentException("Unsupported report format: " + path);
107+
}
108+
return result;
109+
}
110+
93111
private Map<CheckstyleCheck, CheckConfiguration> loadCheckstyleConfiguration() {
94112
return ConfigurationLoader.loadConfiguration(getConfigurationPath(), getPropertiesPath());
95113
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
///////////////////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
3+
// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
///////////////////////////////////////////////////////////////////////////////////////////////
17+
18+
package org.checkstyle.autofix.parser;
19+
20+
import java.nio.file.Path;
21+
import java.util.List;
22+
23+
public interface ReportParser {
24+
25+
List<CheckstyleViolation> parse(Path reportPath);
26+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
///////////////////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
3+
// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
///////////////////////////////////////////////////////////////////////////////////////////////
17+
18+
package org.checkstyle.autofix.parser;
19+
20+
import java.io.IOException;
21+
import java.nio.file.Path;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Optional;
25+
26+
import org.checkstyle.autofix.CheckstyleCheck;
27+
28+
import de.jcup.sarif_2_1_0.SarifSchema210ImportExportSupport;
29+
import de.jcup.sarif_2_1_0.model.PhysicalLocation;
30+
import de.jcup.sarif_2_1_0.model.Region;
31+
import de.jcup.sarif_2_1_0.model.Result;
32+
import de.jcup.sarif_2_1_0.model.Run;
33+
import de.jcup.sarif_2_1_0.model.SarifSchema210;
34+
35+
public class SarifReportParser implements ReportParser {
36+
37+
private static final String NA = "n/a";
38+
private static final String FILE_PREFIX = "file:";
39+
40+
private final SarifSchema210ImportExportSupport parser;
41+
42+
public SarifReportParser() {
43+
this.parser = new SarifSchema210ImportExportSupport();
44+
}
45+
46+
@Override
47+
public List<CheckstyleViolation> parse(Path reportPath) {
48+
final SarifSchema210 report;
49+
try {
50+
report = parser.fromFile(reportPath);
51+
}
52+
catch (IOException exception) {
53+
throw new IllegalArgumentException("Failed to parse report: " + reportPath, exception);
54+
}
55+
final List<CheckstyleViolation> result = new ArrayList<>();
56+
for (final Run run: report.getRuns()) {
57+
if (run.getResults() != null) {
58+
run.getResults().forEach(resultEntry -> {
59+
if (resultEntry.getLocations() != null
60+
&& !resultEntry.getLocations().isEmpty()) {
61+
final PhysicalLocation location =
62+
resultEntry.getLocations().get(0).getPhysicalLocation();
63+
createViolation(resultEntry, location).ifPresent(result::add);
64+
}
65+
});
66+
}
67+
}
68+
return result;
69+
}
70+
71+
private Optional<CheckstyleViolation> createViolation(Result result,
72+
PhysicalLocation location) {
73+
final Region region = location.getRegion();
74+
final int line = getLine(region);
75+
final int column = getColumn(region);
76+
final String severity = getSeverity(result);
77+
final String message = getMessage(result);
78+
final String filePath = getFilePath(location);
79+
return getSource(result).map(check -> {
80+
return new CheckstyleViolation(
81+
line,
82+
column,
83+
severity,
84+
check,
85+
message,
86+
filePath
87+
);
88+
});
89+
}
90+
91+
private String getSeverity(Result result) {
92+
String severity = NA;
93+
if (result.getLevel() != null) {
94+
severity = result.getLevel().name();
95+
}
96+
return severity;
97+
}
98+
99+
private String getMessage(Result result) {
100+
String message = NA;
101+
if (result.getMessage() != null && result.getMessage().getText() != null) {
102+
message = result.getMessage().getText();
103+
}
104+
return message;
105+
}
106+
107+
private Optional<CheckstyleCheck> getSource(Result result) {
108+
String source = NA;
109+
if (result.getRuleId() != null) {
110+
source = result.getRuleId();
111+
}
112+
return CheckstyleCheck.fromSource(source);
113+
}
114+
115+
private String getFilePath(PhysicalLocation location) {
116+
String uri = NA;
117+
if (location.getArtifactLocation() != null
118+
&& location.getArtifactLocation().getUri() != null) {
119+
uri = location.getArtifactLocation().getUri();
120+
if (uri.startsWith(FILE_PREFIX)) {
121+
uri = uri.substring(FILE_PREFIX.length());
122+
}
123+
}
124+
return uri;
125+
}
126+
127+
private int getLine(Region region) {
128+
int line = -1;
129+
if (region != null && region.getStartLine() != null) {
130+
line = region.getStartLine();
131+
}
132+
return line;
133+
}
134+
135+
private int getColumn(Region region) {
136+
int column = -1;
137+
if (region != null && region.getStartColumn() != null) {
138+
column = region.getStartColumn();
139+
}
140+
return column;
141+
}
142+
}

src/main/java/org/checkstyle/autofix/parser/CheckstyleReportParser.java renamed to src/main/java/org/checkstyle/autofix/parser/XmlReportParser.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
import org.checkstyle.autofix.CheckstyleCheck;
3838

39-
public final class CheckstyleReportParser {
39+
public class XmlReportParser implements ReportParser {
4040

4141
private static final String FILE_TAG = "file";
4242

@@ -54,11 +54,8 @@ public final class CheckstyleReportParser {
5454

5555
private static final String SOURCE_ATTR = "source";
5656

57-
private CheckstyleReportParser() {
58-
59-
}
60-
61-
public static List<CheckstyleViolation> parse(Path xmlPath) {
57+
@Override
58+
public List<CheckstyleViolation> parse(Path xmlPath) {
6259

6360
final List<CheckstyleViolation> result = new ArrayList<>();
6461

@@ -101,7 +98,7 @@ else if (ERROR_TAG.equals(startElementName)) {
10198
return result;
10299
}
103100

104-
private static String parseFileTag(StartElement startElement) {
101+
private String parseFileTag(StartElement startElement) {
105102
String fileName = null;
106103
final Iterator<Attribute> attributes = startElement.getAttributes();
107104
while (attributes.hasNext()) {
@@ -114,7 +111,7 @@ private static String parseFileTag(StartElement startElement) {
114111
return fileName;
115112
}
116113

117-
private static Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
114+
private Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
118115
String filename) {
119116
int line = -1;
120117
int column = -1;

src/test/java/org/checkstyle/autofix/parser/CheckstyleReportsParserTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@
3030

3131
public class CheckstyleReportsParserTest {
3232

33+
private final ReportParser reportParser = new XmlReportParser();
34+
3335
private static String getPath(String path) {
3436
return "src/test/resources/org/checkstyle/autofix/parser/" + path;
3537
}
3638

3739
@Test
3840
public void testParseFromResource() throws Exception {
3941
final Path xmlPath = Path.of(getPath("checkstyle-report.xml"));
40-
final List<CheckstyleViolation> records = CheckstyleReportParser.parse(xmlPath);
42+
final List<CheckstyleViolation> records = reportParser.parse(xmlPath);
4143

4244
assertNotNull(records);
4345
assertEquals(1, records.size());
@@ -54,7 +56,7 @@ public void testParseFromResource() throws Exception {
5456
@Test
5557
public void testParseMultipleFilesReport() throws Exception {
5658
final Path xmlPath = Path.of(getPath("checkstyle-multiple-files.xml"));
57-
final List<CheckstyleViolation> records = CheckstyleReportParser.parse(xmlPath);
59+
final List<CheckstyleViolation> records = reportParser.parse(xmlPath);
5860

5961
assertNotNull(records);
6062
assertEquals(3, records.size());

0 commit comments

Comments
 (0)