Skip to content

Commit 46694a2

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

22 files changed

+449
-165
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
}

src/main/java/org/checkstyle/autofix/parser/CheckstyleViolation.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.checkstyle.autofix.parser;
1919

20+
import java.nio.file.Path;
21+
2022
import org.checkstyle.autofix.CheckstyleCheck;
2123

2224
public final class CheckstyleViolation {
@@ -31,16 +33,16 @@ public final class CheckstyleViolation {
3133

3234
private final String message;
3335

34-
private final String fileName;
36+
private final Path filePath;
3537

3638
public CheckstyleViolation(int line, int column, String severity,
37-
CheckstyleCheck source, String message, String fileName) {
39+
CheckstyleCheck source, String message, Path filePath) {
3840
this.line = line;
3941
this.column = column;
4042
this.severity = severity;
4143
this.source = source;
4244
this.message = message;
43-
this.fileName = fileName;
45+
this.filePath = filePath;
4446
}
4547

4648
public Integer getLine() {
@@ -59,8 +61,8 @@ public String getMessage() {
5961
return message;
6062
}
6163

62-
public String getFileName() {
63-
return fileName;
64+
public Path getFilePath() {
65+
return filePath;
6466
}
6567

6668
public String getSeverity() {
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: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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.net.URI;
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.Optional;
27+
28+
import org.checkstyle.autofix.CheckstyleCheck;
29+
30+
import de.jcup.sarif_2_1_0.SarifSchema210ImportExportSupport;
31+
import de.jcup.sarif_2_1_0.model.PhysicalLocation;
32+
import de.jcup.sarif_2_1_0.model.Region;
33+
import de.jcup.sarif_2_1_0.model.Result;
34+
import de.jcup.sarif_2_1_0.model.Run;
35+
import de.jcup.sarif_2_1_0.model.SarifSchema210;
36+
37+
public class SarifReportParser implements ReportParser {
38+
39+
private static final String NA = "n/a";
40+
private static final String FILE_PREFIX = "file:";
41+
42+
private final SarifSchema210ImportExportSupport parser;
43+
44+
public SarifReportParser() {
45+
this.parser = new SarifSchema210ImportExportSupport();
46+
}
47+
48+
@Override
49+
public List<CheckstyleViolation> parse(Path reportPath) {
50+
final SarifSchema210 report;
51+
try {
52+
report = parser.fromFile(reportPath);
53+
}
54+
catch (IOException exception) {
55+
throw new IllegalArgumentException("Failed to parse report: " + reportPath, exception);
56+
}
57+
final List<CheckstyleViolation> result = new ArrayList<>();
58+
for (final Run run: report.getRuns()) {
59+
if (run.getResults() != null) {
60+
run.getResults().forEach(resultEntry -> {
61+
if (resultEntry.getLocations() != null
62+
&& !resultEntry.getLocations().isEmpty()) {
63+
final PhysicalLocation location =
64+
resultEntry.getLocations().get(0).getPhysicalLocation();
65+
createViolation(resultEntry, location).ifPresent(result::add);
66+
}
67+
});
68+
}
69+
}
70+
return result;
71+
}
72+
73+
private Optional<CheckstyleViolation> createViolation(Result result,
74+
PhysicalLocation location) {
75+
final Region region = location.getRegion();
76+
final int line = getLine(region);
77+
final int column = getColumn(region);
78+
final String severity = getSeverity(result);
79+
final String message = getMessage(result);
80+
final Path filePath = getFilePath(location);
81+
return getSource(result).map(check -> {
82+
return new CheckstyleViolation(
83+
line,
84+
column,
85+
severity,
86+
check,
87+
message,
88+
filePath
89+
);
90+
});
91+
}
92+
93+
private String getSeverity(Result result) {
94+
String severity = NA;
95+
if (result.getLevel() != null) {
96+
severity = result.getLevel().name();
97+
}
98+
return severity;
99+
}
100+
101+
private String getMessage(Result result) {
102+
String message = NA;
103+
if (result.getMessage() != null && result.getMessage().getText() != null) {
104+
message = result.getMessage().getText();
105+
}
106+
return message;
107+
}
108+
109+
private Optional<CheckstyleCheck> getSource(Result result) {
110+
String source = NA;
111+
if (result.getRuleId() != null) {
112+
source = result.getRuleId();
113+
}
114+
return CheckstyleCheck.fromSource(source);
115+
}
116+
117+
private Path getFilePath(PhysicalLocation location) {
118+
Path result = Path.of(NA);
119+
if (location.getArtifactLocation() != null
120+
&& location.getArtifactLocation().getUri() != null) {
121+
final String uri = location.getArtifactLocation().getUri();
122+
if (uri.startsWith(FILE_PREFIX)) {
123+
result = Paths.get(URI.create(uri));
124+
}
125+
else {
126+
result = Path.of(uri);
127+
}
128+
}
129+
return result;
130+
}
131+
132+
private int getLine(Region region) {
133+
int result = -1;
134+
if (region != null && region.getStartLine() != null) {
135+
result = region.getStartLine();
136+
}
137+
return result;
138+
}
139+
140+
private int getColumn(Region region) {
141+
int result = -1;
142+
if (region != null && region.getStartColumn() != null) {
143+
result = region.getStartColumn();
144+
}
145+
return result;
146+
}
147+
}

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

Lines changed: 6 additions & 9 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;
@@ -149,7 +146,7 @@ private static Optional<CheckstyleViolation> parseErrorTag(StartElement startEle
149146
}
150147
if (source.isPresent()) {
151148
violation = new CheckstyleViolation(line, column, severity,
152-
source.get(), message, filename);
149+
source.get(), message, Path.of(filename));
153150
}
154151
return Optional.ofNullable(violation);
155152

src/main/java/org/checkstyle/autofix/recipe/FinalLocalVariable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private boolean isAtViolationLocation(J.VariableDeclarations.NamedVariable varia
130130
.computeColumnPosition(currentCompilationUnit, variable, getCursor());
131131

132132
return violations.removeIf(violation -> {
133-
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
133+
final Path absolutePath = violation.getFilePath().toAbsolutePath();
134134
return violation.getLine() == line
135135
&& violation.getColumn() == column
136136
&& absolutePath.endsWith(sourcePath)

0 commit comments

Comments
 (0)