Skip to content

Commit 32a5056

Browse files
committed
Issue #122: Support multiple instances of the same Checkstyle check
1 parent a0ae57d commit 32a5056

File tree

12 files changed

+120
-47
lines changed

12 files changed

+120
-47
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public List<Recipe> getRecipeList() {
8888
final ReportParser reportParser = createReportParser(getViolationReportPath());
8989
final List<CheckstyleViolation> violations = reportParser
9090
.parse(Path.of(getViolationReportPath()));
91-
final Map<CheckstyleCheck,
91+
final Map<CheckstyleConfigModule,
9292
CheckConfiguration> configuration = loadCheckstyleConfiguration();
9393

9494
return CheckstyleRecipeRegistry.getRecipes(violations, configuration);
@@ -108,7 +108,7 @@ else if (path.endsWith(".sarif") || path.endsWith(".sarif.json")) {
108108
return result;
109109
}
110110

111-
private Map<CheckstyleCheck, CheckConfiguration> loadCheckstyleConfiguration() {
111+
private Map<CheckstyleConfigModule, CheckConfiguration> loadCheckstyleConfiguration() {
112112
return ConfigurationLoader.loadConfiguration(getConfigurationPath(), getPropertiesPath());
113113
}
114114
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ public String getId() {
3737
return id;
3838
}
3939

40-
public static Optional<CheckstyleCheck> fromSource(String source) {
40+
public static Optional<CheckstyleCheck> fromSource(String checkId) {
4141
return Arrays.stream(values())
42-
.filter(check -> check.getId().contains(source))
42+
.filter(check -> check.getId().contains(checkId))
43+
.findFirst();
44+
}
45+
46+
public static Optional<CheckstyleCheck> fromSourceExact(String checkId) {
47+
return Arrays.stream(values())
48+
.filter(check -> check.getId().equals(checkId))
4349
.findFirst();
4450
}
4551
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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;
19+
20+
public class CheckstyleConfigModule {
21+
private final CheckstyleCheck check;
22+
private final String id;
23+
24+
public CheckstyleConfigModule(CheckstyleCheck check, String id) {
25+
this.check = check;
26+
this.id = id;
27+
}
28+
29+
public boolean matchesId(String input) {
30+
return id != null && id.equals(input);
31+
}
32+
33+
public boolean matchesCheck(String input) {
34+
return CheckstyleCheck.fromSourceExact(input)
35+
.map(checkFromInput -> checkFromInput == check)
36+
.orElse(false);
37+
}
38+
}

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

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public final class CheckstyleRecipeRegistry {
4343
CheckConfiguration, Recipe>> RECIPE_MAP_WITH_CONFIG =
4444
new EnumMap<>(CheckstyleCheck.class);
4545

46+
private static final String HASH_SEPARATOR = "#";
47+
4648
static {
4749
RECIPE_MAP.put(CheckstyleCheck.UPPER_ELL, UpperEll::new);
4850
RECIPE_MAP.put(CheckstyleCheck.HEX_LITERAL_CASE, HexLiteralCase::new);
@@ -65,18 +67,49 @@ private CheckstyleRecipeRegistry() {
6567
* @return a list of generated Recipe objects
6668
*/
6769
public static List<Recipe> getRecipes(List<CheckstyleViolation> violations,
68-
Map<CheckstyleCheck, CheckConfiguration> config) {
70+
Map<CheckstyleConfigModule, CheckConfiguration> config) {
6971
return violations.stream()
70-
.collect(Collectors.groupingBy(CheckstyleViolation::getSource))
72+
.collect(Collectors.groupingBy(CheckstyleViolation::getCheckId))
7173
.entrySet()
7274
.stream()
7375
.map(entry -> {
74-
return createRecipe(entry.getValue(), config.get(entry.getKey()));
76+
final CheckConfiguration configuration =
77+
findMatchingConfiguration(entry.getKey(), config);
78+
return createRecipe(entry.getValue(), configuration);
7579
})
7680
.filter(Objects::nonNull)
7781
.collect(Collectors.toList());
7882
}
7983

84+
private static CheckConfiguration findMatchingConfiguration(String source,
85+
Map<CheckstyleConfigModule, CheckConfiguration> config) {
86+
return config.entrySet().stream()
87+
.filter(configEntry -> matchesSource(configEntry.getKey(), source))
88+
.map(Map.Entry::getValue)
89+
.findFirst()
90+
.orElse(null);
91+
}
92+
93+
private static boolean matchesSource(CheckstyleConfigModule module, String source) {
94+
final boolean matches;
95+
if (source.contains(HASH_SEPARATOR)) {
96+
matches = matchesWithHashSeparator(module, source);
97+
}
98+
else {
99+
matches = module.matchesId(source) || module.matchesCheck(source);
100+
}
101+
return matches;
102+
}
103+
104+
private static boolean matchesWithHashSeparator(CheckstyleConfigModule module, String source) {
105+
final String[] parts = source.split(HASH_SEPARATOR, 2);
106+
final String checkPart = parts[0];
107+
final String idPart = parts[1];
108+
final boolean exactMatch = module.matchesCheck(checkPart) && module.matchesId(idPart);
109+
final boolean individualMatch = module.matchesId(source) || module.matchesCheck(source);
110+
return exactMatch || individualMatch;
111+
}
112+
80113
private static Recipe createRecipe(List<CheckstyleViolation> violations,
81114
CheckConfiguration checkConfig) {
82115
Recipe result = null;

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
import java.nio.file.Path;
2121

22-
import org.checkstyle.autofix.CheckstyleCheck;
23-
2422
public final class CheckstyleViolation {
2523

2624
private final int line;
@@ -29,24 +27,24 @@ public final class CheckstyleViolation {
2927

3028
private final String severity;
3129

32-
private final CheckstyleCheck source;
30+
private final String checkId;
3331

3432
private final String message;
3533

3634
private final Path filePath;
3735

3836
public CheckstyleViolation(int line, int column, String severity,
39-
CheckstyleCheck source, String message, Path filePath) {
37+
String source, String message, Path filePath) {
4038
this.line = line;
4139
this.column = column;
4240
this.severity = severity;
43-
this.source = source;
41+
this.checkId = source;
4442
this.message = message;
4543
this.filePath = filePath;
4644
}
4745

4846
public CheckstyleViolation(int line, String severity,
49-
CheckstyleCheck source, String message, Path filePath) {
47+
String source, String message, Path filePath) {
5048
this(line, -1, severity, source, message, filePath);
5149
}
5250

@@ -58,8 +56,8 @@ public Integer getColumn() {
5856
return column;
5957
}
6058

61-
public CheckstyleCheck getSource() {
62-
return source;
59+
public String getCheckId() {
60+
return checkId;
6361
}
6462

6563
public String getMessage() {

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import java.io.FileInputStream;
2121
import java.io.IOException;
2222
import java.util.HashMap;
23+
import java.util.LinkedHashMap;
2324
import java.util.Map;
2425
import java.util.Optional;
2526
import java.util.Properties;
2627

2728
import org.checkstyle.autofix.CheckstyleCheck;
29+
import org.checkstyle.autofix.CheckstyleConfigModule;
2830

2931
import com.puppycrawl.tools.checkstyle.PropertiesExpander;
3032
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
@@ -36,14 +38,19 @@ private ConfigurationLoader() {
3638
// utility class
3739
}
3840

39-
public static Map<CheckstyleCheck, CheckConfiguration> mapConfiguration(Configuration config) {
40-
final Map<CheckstyleCheck, CheckConfiguration> result = new HashMap<>();
41+
public static Map<CheckstyleConfigModule,
42+
CheckConfiguration> mapConfiguration(Configuration config) {
43+
44+
final Map<CheckstyleConfigModule, CheckConfiguration> result = new LinkedHashMap<>();
4145
final Map<String, String> inherited = getProperties(config);
4246

4347
final Optional<CheckstyleCheck> module = CheckstyleCheck.fromSource(config.getName());
4448
module.ifPresent(checkstyleCheck -> {
45-
result.put(checkstyleCheck, new CheckConfiguration(checkstyleCheck, new HashMap<>(),
46-
getProperties(config)));
49+
final Map<String, String> properties = getProperties(config);
50+
final CheckstyleConfigModule configModule = new CheckstyleConfigModule(
51+
checkstyleCheck, properties.get("id"));
52+
result.put(configModule,
53+
new CheckConfiguration(checkstyleCheck, new HashMap<>(), properties));
4754
});
4855

4956
for (Configuration child : config.getChildren()) {
@@ -69,7 +76,7 @@ private static Map<String, String> getProperties(Configuration config) {
6976
return props;
7077
}
7178

72-
public static Map<CheckstyleCheck, CheckConfiguration> loadConfiguration(
79+
public static Map<CheckstyleConfigModule, CheckConfiguration> loadConfiguration(
7380
String checkstyleConfigurationPath, String propFile) {
7481
Properties props = new Properties();
7582
if (propFile == null) {

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import java.util.List;
2626
import java.util.Optional;
2727

28-
import org.checkstyle.autofix.CheckstyleCheck;
29-
3028
import de.jcup.sarif_2_1_0.SarifSchema210ImportExportSupport;
3129
import de.jcup.sarif_2_1_0.model.PhysicalLocation;
3230
import de.jcup.sarif_2_1_0.model.Region;
@@ -57,17 +55,14 @@ public List<CheckstyleViolation> parse(Path reportPath) {
5755
for (final Run run: report.getRuns()) {
5856
if (run.getResults() != null) {
5957
run.getResults().forEach(resultEntry -> {
60-
CheckstyleCheck.fromSource(resultEntry.getRuleId()).ifPresent(check -> {
61-
final CheckstyleViolation violation = createViolation(check, resultEntry);
62-
result.add(violation);
63-
});
58+
result.add(createViolation(resultEntry.getRuleId(), resultEntry));
6459
});
6560
}
6661
}
6762
return result;
6863
}
6964

70-
private CheckstyleViolation createViolation(CheckstyleCheck check, Result result) {
65+
private CheckstyleViolation createViolation(String checkId, Result result) {
7166
final String severity = result.getLevel().name();
7267
final String message = result.getMessage().getText();
7368
final PhysicalLocation location = result.getLocations().get(0).getPhysicalLocation();
@@ -76,8 +71,8 @@ private CheckstyleViolation createViolation(CheckstyleCheck check, Result result
7671
final int line = region.getStartLine();
7772
final Optional<Integer> columnMaybe = Optional.ofNullable(region.getStartColumn());
7873
return columnMaybe.map(column -> {
79-
return new CheckstyleViolation(line, column, severity, check, message, filePath);
80-
}).orElse(new CheckstyleViolation(line, severity, check, message, filePath));
74+
return new CheckstyleViolation(line, column, severity, checkId, message, filePath);
75+
}).orElse(new CheckstyleViolation(line, severity, checkId, message, filePath));
8176
}
8277

8378
private Path getFilePath(PhysicalLocation location) {

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.Iterator;
2626
import java.util.List;
2727
import java.util.Objects;
28-
import java.util.Optional;
2928

3029
import javax.xml.stream.XMLEventReader;
3130
import javax.xml.stream.XMLInputFactory;
@@ -34,8 +33,6 @@
3433
import javax.xml.stream.events.StartElement;
3534
import javax.xml.stream.events.XMLEvent;
3635

37-
import org.checkstyle.autofix.CheckstyleCheck;
38-
3936
public class XmlReportParser implements ReportParser {
4037

4138
private static final String FILE_TAG = "file";
@@ -78,7 +75,7 @@ public List<CheckstyleViolation> parse(Path xmlPath) {
7875
}
7976
else if (ERROR_TAG.equals(startElementName)) {
8077
Objects.requireNonNull(filename, "File name can not be null");
81-
parseErrorTag(startElement, filename).ifPresent(result::add);
78+
result.add(parseErrorTag(startElement, filename));
8279
}
8380
}
8481
}
@@ -111,14 +108,13 @@ private String parseFileTag(StartElement startElement) {
111108
return fileName;
112109
}
113110

114-
private Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
111+
private CheckstyleViolation parseErrorTag(StartElement startElement,
115112
String filename) {
116113
int line = -1;
117114
int column = -1;
118115
String message = null;
119116
String severity = null;
120-
CheckstyleViolation violation = null;
121-
Optional<CheckstyleCheck> source = Optional.empty();
117+
String source = null;
122118

123119
final Iterator<Attribute> attributes = startElement.getAttributes();
124120
while (attributes.hasNext()) {
@@ -138,17 +134,14 @@ private Optional<CheckstyleViolation> parseErrorTag(StartElement startElement,
138134
message = attribute.getValue();
139135
break;
140136
case SOURCE_ATTR:
141-
source = CheckstyleCheck.fromSource(attribute.getValue());
137+
source = attribute.getValue();
142138
break;
143139
default:
144140
break;
145141
}
146142
}
147-
if (source.isPresent()) {
148-
violation = new CheckstyleViolation(line, column, severity,
149-
source.get(), message, Path.of(filename));
150-
}
151-
return Optional.ofNullable(violation);
143+
return new CheckstyleViolation(line, column, severity,
144+
source, message, Path.of(filename));
152145

153146
}
154147
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.Map;
2626
import java.util.stream.Collectors;
2727

28-
import org.checkstyle.autofix.CheckstyleCheck;
2928
import org.junit.jupiter.api.Test;
3029

3130
public class CheckstyleReportsParserTest {
@@ -49,7 +48,7 @@ public void testParseFromResource() throws Exception {
4948
assertEquals(13, record.getColumn());
5049
assertEquals("error", record.getSeverity());
5150
assertEquals("Example message", record.getMessage());
52-
assertEquals(CheckstyleCheck.UPPER_ELL, record.getSource());
51+
assertEquals("com.puppycrawl.tools.checkstyle.checks.UpperEllCheck", record.getCheckId());
5352
assertEquals(Path.of("Example.java"), record.getFilePath());
5453
}
5554

src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/DiffSingleLocalTest.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
--- src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/InputSingleLocalTest.java
22
+++ src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/OutputSingleLocalTest.java
3-
@@ -15,37 +15,37 @@
3+
@@ -17,37 +17,37 @@
44
import java.util.HashMap;
55
import java.util.List;
66

0 commit comments

Comments
 (0)