Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ replay_pid*
.ci-temp

# OpenRewrite-generated impl files
*.iml
*.iml

.cache

1 change: 1 addition & 0 deletions config/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
<allow pkg="org.checkstyle"/>
<allow pkg="java.util"/>
<allow pkg="com.puppycrawl.tools.checkstyle"/>
<allow pkg="de.jcup.sarif_2_1_0"/>
</import-control>
7 changes: 4 additions & 3 deletions config/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
<suppress checks="MissingJavadocType" files=".*"/>
<suppress checks="JavadocVariable" files=".*"/>
<suppress checks="MissingJavadocMethod" files=".*"/>
<suppress checks="DesignForExtension" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTest\.java"/>
<suppress checks="DesignForExtension" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTestSupport\.java"/>
<suppress checks="DesignForExtension" files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]CheckstyleAutoFix\.java"/>

<!-- This project is not a library, so there is no benefit in using this check -->
<suppress checks="DesignForExtension" files=".*"/>

<suppress checks="ClassDataAbstractionCoupling" files="[\\/]src[\\/]test[\\/]java[\\/]org[\\/]checkstyle[\\/]autofix[\\/]recipe[\\/]AbstractRecipeTestSupport\.java"/>
</suppressions>
15 changes: 15 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@
<version>7.3.0.202506031305-r</version>
</dependency>

<dependency>
<groupId>de.jcup.sarif.java</groupId>
<artifactId>sarif-2.1.0</artifactId>
<version>1.1.0</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>com.google.truth</groupId>
Expand All @@ -111,6 +117,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down Expand Up @@ -139,6 +151,9 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
</statelessTestsetReporter>
<systemPropertyVariables>
<org.slf4j.simpleLogger.log.org.openrewrite>debug</org.slf4j.simpleLogger.log.org.openrewrite>
</systemPropertyVariables>
Expand Down
24 changes: 21 additions & 3 deletions src/main/java/org/checkstyle/autofix/CheckstyleAutoFix.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import java.util.Map;

import org.checkstyle.autofix.parser.CheckConfiguration;
import org.checkstyle.autofix.parser.CheckstyleReportParser;
import org.checkstyle.autofix.parser.CheckstyleViolation;
import org.checkstyle.autofix.parser.ConfigurationLoader;
import org.checkstyle.autofix.parser.ReportParser;
import org.checkstyle.autofix.parser.SarifReportParser;
import org.checkstyle.autofix.parser.XmlReportParser;
import org.openrewrite.Option;
import org.openrewrite.Recipe;

Expand All @@ -34,7 +36,8 @@
public class CheckstyleAutoFix extends Recipe {

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

Expand Down Expand Up @@ -82,14 +85,29 @@ public String getPropertiesPath() {

@Override
public List<Recipe> getRecipeList() {
final List<CheckstyleViolation> violations = CheckstyleReportParser
final ReportParser reportParser = createReportParser(getViolationReportPath());
final List<CheckstyleViolation> violations = reportParser
.parse(Path.of(getViolationReportPath()));
final Map<CheckstyleCheck,
CheckConfiguration> configuration = loadCheckstyleConfiguration();

return CheckstyleRecipeRegistry.getRecipes(violations, configuration);
}

private ReportParser createReportParser(String path) {
final ReportParser result;
if (path.endsWith(".xml")) {
result = new XmlReportParser();
}
else if (path.endsWith(".sarif") || path.endsWith(".sarif.json")) {
result = new SarifReportParser();
}
else {
throw new IllegalArgumentException("Unsupported report format: " + path);
}
return result;
}

private Map<CheckstyleCheck, CheckConfiguration> loadCheckstyleConfiguration() {
return ConfigurationLoader.loadConfiguration(getConfigurationPath(), getPropertiesPath());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.checkstyle.autofix.parser;

import java.nio.file.Path;

import org.checkstyle.autofix.CheckstyleCheck;

public final class CheckstyleViolation {
Expand All @@ -31,16 +33,21 @@ public final class CheckstyleViolation {

private final String message;

private final String fileName;
private final Path filePath;

public CheckstyleViolation(int line, int column, String severity,
CheckstyleCheck source, String message, String fileName) {
CheckstyleCheck source, String message, Path filePath) {
this.line = line;
this.column = column;
this.severity = severity;
this.source = source;
this.message = message;
this.fileName = fileName;
this.filePath = filePath;
}

public CheckstyleViolation(int line, String severity,
CheckstyleCheck source, String message, Path filePath) {
this(line, -1, severity, source, message, filePath);
}

public Integer getLine() {
Expand All @@ -59,8 +66,8 @@ public String getMessage() {
return message;
}

public String getFileName() {
return fileName;
public Path getFilePath() {
return filePath;
}

public String getSeverity() {
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/checkstyle/autofix/parser/ReportParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///////////////////////////////////////////////////////////////////////////////////////////////

package org.checkstyle.autofix.parser;

import java.nio.file.Path;
import java.util.List;

public interface ReportParser {

List<CheckstyleViolation> parse(Path reportPath);
}
94 changes: 94 additions & 0 deletions src/main/java/org/checkstyle/autofix/parser/SarifReportParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///////////////////////////////////////////////////////////////////////////////////////////////

package org.checkstyle.autofix.parser;

import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.checkstyle.autofix.CheckstyleCheck;

import de.jcup.sarif_2_1_0.SarifSchema210ImportExportSupport;
import de.jcup.sarif_2_1_0.model.PhysicalLocation;
import de.jcup.sarif_2_1_0.model.Region;
import de.jcup.sarif_2_1_0.model.Result;
import de.jcup.sarif_2_1_0.model.Run;
import de.jcup.sarif_2_1_0.model.SarifSchema210;

public class SarifReportParser implements ReportParser {

private static final String FILE_PREFIX = "file:";

private final SarifSchema210ImportExportSupport parser;

public SarifReportParser() {
this.parser = new SarifSchema210ImportExportSupport();
}

@Override
public List<CheckstyleViolation> parse(Path reportPath) {
final SarifSchema210 report;
try {
report = parser.fromFile(reportPath);
}
catch (IOException exception) {
throw new IllegalArgumentException("Failed to parse report: " + reportPath, exception);
}
final List<CheckstyleViolation> result = new ArrayList<>();
for (final Run run: report.getRuns()) {
if (run.getResults() != null) {
run.getResults().forEach(resultEntry -> {
CheckstyleCheck.fromSource(resultEntry.getRuleId()).ifPresent(check -> {
final CheckstyleViolation violation = createViolation(check, resultEntry);
result.add(violation);
});
});
}
}
return result;
}

private CheckstyleViolation createViolation(CheckstyleCheck check, Result result) {
final String severity = result.getLevel().name();
final String message = result.getMessage().getText();
final PhysicalLocation location = result.getLocations().get(0).getPhysicalLocation();
final Path filePath = getFilePath(location);
final Region region = location.getRegion();
final int line = region.getStartLine();
final Optional<Integer> columnMaybe = Optional.ofNullable(region.getStartColumn());
return columnMaybe.map(column -> {
return new CheckstyleViolation(line, column, severity, check, message, filePath);
}).orElse(new CheckstyleViolation(line, severity, check, message, filePath));
}

private Path getFilePath(PhysicalLocation location) {
final Path result;
final String uri = location.getArtifactLocation().getUri();
if (uri.startsWith(FILE_PREFIX)) {
result = Paths.get(URI.create(uri));
}
else {
result = Path.of(uri);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

import org.checkstyle.autofix.CheckstyleCheck;

public final class CheckstyleReportParser {
public class XmlReportParser implements ReportParser {

private static final String FILE_TAG = "file";

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

private static final String SOURCE_ATTR = "source";

private CheckstyleReportParser() {

}

public static List<CheckstyleViolation> parse(Path xmlPath) {
@Override
public List<CheckstyleViolation> parse(Path xmlPath) {

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

Expand Down Expand Up @@ -101,7 +98,7 @@ else if (ERROR_TAG.equals(startElementName)) {
return result;
}

private static String parseFileTag(StartElement startElement) {
private String parseFileTag(StartElement startElement) {
String fileName = null;
final Iterator<Attribute> attributes = startElement.getAttributes();
while (attributes.hasNext()) {
Expand All @@ -114,7 +111,7 @@ private static String parseFileTag(StartElement startElement) {
return fileName;
}

private static Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
private Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
String filename) {
int line = -1;
int column = -1;
Expand Down Expand Up @@ -149,7 +146,7 @@ private static Optional<CheckstyleViolation> parseErrorTag(StartElement startEle
}
if (source.isPresent()) {
violation = new CheckstyleViolation(line, column, severity,
source.get(), message, filename);
source.get(), message, Path.of(filename));
}
return Optional.ofNullable(violation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private boolean isAtViolationLocation(J.VariableDeclarations.NamedVariable varia
.computeColumnPosition(currentCompilationUnit, variable, getCursor());

return violations.removeIf(violation -> {
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
final Path absolutePath = violation.getFilePath().toAbsolutePath();
return violation.getLine() == line
&& violation.getColumn() == column
&& absolutePath.endsWith(sourcePath)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/checkstyle/autofix/recipe/Header.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private String extractCurrentHeader(JavaSourceFile sourceFile) {

private boolean hasViolation(Path filePath) {
return violations.removeIf(violation -> {
return Path.of(violation.getFileName()).endsWith(filePath);
return violation.getFilePath().endsWith(filePath);
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private boolean isAtViolationLocation(J.Literal literal) {
final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor());

return violations.stream().anyMatch(violation -> {
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
final Path absolutePath = violation.getFilePath().toAbsolutePath();
return violation.getLine() == line
&& violation.getColumn() == column
&& absolutePath.endsWith(sourcePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private boolean isAtViolationLocation(J.Import literal) {
final int line = PositionHelper.computeLinePosition(cursor, literal, getCursor());
final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor());
return violations.removeIf(violation -> {
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
final Path absolutePath = violation.getFilePath().toAbsolutePath();
return violation.getLine() == line
&& violation.getColumn() == column
&& absolutePath.endsWith(sourcePath);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/checkstyle/autofix/recipe/UpperEll.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private boolean isAtViolationLocation(J.Literal literal) {
final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor());

return violations.stream().anyMatch(violation -> {
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
final Path absolutePath = violation.getFilePath().toAbsolutePath();
return violation.getLine() == line
&& violation.getColumn() == column
&& absolutePath.endsWith(sourcePath);
Expand Down
Loading