Skip to content

Commit a0ae57d

Browse files
rdiachenkoromani
authored andcommitted
Issue #102: add support for sarif reports
1 parent e58b713 commit a0ae57d

22 files changed

+439
-166
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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
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+
13+
<!-- This project is not a library, so there is no benefit in using this check -->
14+
<suppress checks="DesignForExtension" files=".*"/>
15+
1516
<suppress checks="ClassDataAbstractionCoupling" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTestSupport\.java"/>
1617
</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: 12 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,21 @@ 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;
46+
}
47+
48+
public CheckstyleViolation(int line, String severity,
49+
CheckstyleCheck source, String message, Path filePath) {
50+
this(line, -1, severity, source, message, filePath);
4451
}
4552

4653
public Integer getLine() {
@@ -59,8 +66,8 @@ public String getMessage() {
5966
return message;
6067
}
6168

62-
public String getFileName() {
63-
return fileName;
69+
public Path getFilePath() {
70+
return filePath;
6471
}
6572

6673
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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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 FILE_PREFIX = "file:";
40+
41+
private final SarifSchema210ImportExportSupport parser;
42+
43+
public SarifReportParser() {
44+
this.parser = new SarifSchema210ImportExportSupport();
45+
}
46+
47+
@Override
48+
public List<CheckstyleViolation> parse(Path reportPath) {
49+
final SarifSchema210 report;
50+
try {
51+
report = parser.fromFile(reportPath);
52+
}
53+
catch (IOException exception) {
54+
throw new IllegalArgumentException("Failed to parse report: " + reportPath, exception);
55+
}
56+
final List<CheckstyleViolation> result = new ArrayList<>();
57+
for (final Run run: report.getRuns()) {
58+
if (run.getResults() != null) {
59+
run.getResults().forEach(resultEntry -> {
60+
CheckstyleCheck.fromSource(resultEntry.getRuleId()).ifPresent(check -> {
61+
final CheckstyleViolation violation = createViolation(check, resultEntry);
62+
result.add(violation);
63+
});
64+
});
65+
}
66+
}
67+
return result;
68+
}
69+
70+
private CheckstyleViolation createViolation(CheckstyleCheck check, Result result) {
71+
final String severity = result.getLevel().name();
72+
final String message = result.getMessage().getText();
73+
final PhysicalLocation location = result.getLocations().get(0).getPhysicalLocation();
74+
final Path filePath = getFilePath(location);
75+
final Region region = location.getRegion();
76+
final int line = region.getStartLine();
77+
final Optional<Integer> columnMaybe = Optional.ofNullable(region.getStartColumn());
78+
return columnMaybe.map(column -> {
79+
return new CheckstyleViolation(line, column, severity, check, message, filePath);
80+
}).orElse(new CheckstyleViolation(line, severity, check, message, filePath));
81+
}
82+
83+
private Path getFilePath(PhysicalLocation location) {
84+
final Path result;
85+
final String uri = location.getArtifactLocation().getUri();
86+
if (uri.startsWith(FILE_PREFIX)) {
87+
result = Paths.get(URI.create(uri));
88+
}
89+
else {
90+
result = Path.of(uri);
91+
}
92+
return result;
93+
}
94+
}

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)