Skip to content

Commit a2d434f

Browse files
committed
Issue #25: Added Header Recipe
1 parent cb642ab commit a2d434f

File tree

18 files changed

+327
-12
lines changed

18 files changed

+327
-12
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ _No checks analyzed yet_
4343

4444
### Headers
4545

46-
| Status | Check | Recipe | Coverage Notes |
47-
|--------|---------------------------------------------------------------------------------|------------------|----------------|
48-
| 🟢 | [`Header`](https://checkstyle.sourceforge.io/checks/header/header.html#Header ) | `TBD` | |
46+
| Status | Check | Recipe | Coverage Notes |
47+
|--------|---------------------------------------------------------------------------------|------------------|----------------------------|
48+
| 🟡 | [`Header`](https://checkstyle.sourceforge.io/checks/header/header.html#Header ) | `TBD` | only java files are fixed. |
4949

5050

5151

config/import-control-test.xml

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

config/import-control.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
<allow pkg="javax.xml.stream"/>
1212
<allow pkg="org.checkstyle"/>
1313
<allow pkg="java.util"/>
14+
<allow pkg="com.puppycrawl.tools.checkstyle"/>
1415
</import-control>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public CheckstyleViolation(Integer line, Integer column,
4141
this.fileName = fileName;
4242
}
4343

44-
public int getLine() {
44+
public Integer getLine() {
4545
return line;
4646
}
4747

48-
public int getColumn() {
48+
public Integer getColumn() {
4949
return column;
5050
}
5151

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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.recipe;
19+
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.util.ArrayList;
24+
import java.util.Arrays;
25+
import java.util.List;
26+
import java.util.Optional;
27+
28+
import org.checkstyle.autofix.parser.CheckstyleViolation;
29+
import org.openrewrite.ExecutionContext;
30+
import org.openrewrite.Recipe;
31+
import org.openrewrite.Tree;
32+
import org.openrewrite.TreeVisitor;
33+
import org.openrewrite.java.JavaIsoVisitor;
34+
import org.openrewrite.java.tree.J;
35+
import org.openrewrite.java.tree.JavaSourceFile;
36+
import org.openrewrite.java.tree.Space;
37+
38+
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39+
import com.puppycrawl.tools.checkstyle.api.Configuration;
40+
41+
public class Header extends Recipe {
42+
43+
private static final String HEADER_LITERAL = "header";
44+
45+
private List<CheckstyleViolation> violations;
46+
private Configuration headerConfig;
47+
48+
public Header(List<CheckstyleViolation> violations, Configuration headerConfig) {
49+
this.violations = violations;
50+
this.headerConfig = headerConfig;
51+
}
52+
53+
@Override
54+
public String getDisplayName() {
55+
return "Header recipe";
56+
}
57+
58+
@Override
59+
public String getDescription() {
60+
return "Adds headers to Java source files when missing.";
61+
}
62+
63+
@Override
64+
public TreeVisitor<?, ExecutionContext> getVisitor() {
65+
final String licenseHeader = getLicenseHeader(headerConfig);
66+
return new HeaderVisitor(violations, licenseHeader);
67+
}
68+
69+
private static String getLicenseHeader(Configuration child) {
70+
final String result;
71+
final String[] propertyNames = child.getPropertyNames();
72+
final boolean hasHeaderProperty = Arrays.asList(propertyNames).contains(HEADER_LITERAL);
73+
try {
74+
if (hasHeaderProperty) {
75+
result = child.getProperty(HEADER_LITERAL);
76+
}
77+
else {
78+
final String headerFile = child.getProperty("headerFile");
79+
result = readHeaderFileContent(headerFile);
80+
}
81+
}
82+
catch (CheckstyleException exception) {
83+
throw new IllegalArgumentException("Failed to extract header from config", exception);
84+
}
85+
return result;
86+
}
87+
88+
private static String readHeaderFileContent(String headerFilePath) {
89+
final String content;
90+
try {
91+
content = Files.readString(Path.of(headerFilePath));
92+
}
93+
catch (IOException exception) {
94+
throw new IllegalArgumentException("Failed to read: " + headerFilePath, exception);
95+
}
96+
return content;
97+
}
98+
99+
private static class HeaderVisitor extends JavaIsoVisitor<ExecutionContext> {
100+
private final List<CheckstyleViolation> violations;
101+
private final String licenseHeader;
102+
103+
HeaderVisitor(List<CheckstyleViolation> violations, String licenseHeader) {
104+
this.violations = violations;
105+
this.licenseHeader = licenseHeader;
106+
}
107+
108+
@Override
109+
public J visit(Tree tree, ExecutionContext ctx) {
110+
J result = super.visit(tree, ctx);
111+
112+
if (tree instanceof JavaSourceFile) {
113+
JavaSourceFile sourceFile = (JavaSourceFile) tree;
114+
final Path absolutePath = sourceFile.getSourcePath().toAbsolutePath();
115+
116+
if (isAtViolation(absolutePath)) {
117+
sourceFile = sourceFile.withComments(new ArrayList<>());
118+
sourceFile = sourceFile.withPrefix(
119+
Space.format(licenseHeader
120+
+ System.lineSeparator().repeat(2)));
121+
}
122+
result = super.visit(sourceFile, ctx);
123+
}
124+
return result;
125+
}
126+
127+
private boolean isAtViolation(Path currentFileName) {
128+
final Optional<CheckstyleViolation> match = violations.stream()
129+
.filter(violation -> {
130+
return violation.getColumn() == null
131+
&& Path.of(violation.getFileName())
132+
.toAbsolutePath().equals(currentFileName);
133+
}).findFirst();
134+
135+
match.ifPresent(violations::remove);
136+
137+
return match.isPresent();
138+
}
139+
}
140+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private final class UpperEllVisitor extends JavaIsoVisitor<ExecutionContext> {
7070

7171
@Override
7272
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
73-
this.sourcePath = cu.getSourcePath();
73+
this.sourcePath = cu.getSourcePath().toAbsolutePath();
7474
return super.visitCompilationUnit(cu, ctx);
7575
}
7676

@@ -97,9 +97,10 @@ private boolean isAtViolationLocation(J.Literal literal) {
9797
final int column = computeColumnPosition(cursor, literal, getCursor());
9898

9999
return violations.stream().anyMatch(violation -> {
100+
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
100101
return violation.getLine() == line
101102
&& violation.getColumn() == column
102-
&& Path.of(violation.getFileName()).equals(sourcePath);
103+
&& absolutePath.equals(sourcePath);
103104
});
104105
}
105106

src/test/java/org/checkstyle/autofix/recipe/AbstractRecipeTest.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
import java.io.IOException;
2424
import java.nio.file.Files;
2525
import java.nio.file.Paths;
26+
import java.util.Arrays;
2627

2728
import org.checkstyle.autofix.InputClassRenamer;
2829
import org.openrewrite.Recipe;
2930
import org.openrewrite.test.RewriteTest;
3031

32+
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
33+
import com.puppycrawl.tools.checkstyle.api.Configuration;
34+
3135
public abstract class AbstractRecipeTest implements RewriteTest {
3236

3337
private static final String BASE_TEST_RESOURCES_PATH = "src/test/resources/org"
@@ -37,9 +41,10 @@ private Recipe createPreprocessingRecipe() {
3741
return new InputClassRenamer();
3842
}
3943

40-
protected abstract Recipe getRecipe();
44+
protected abstract Recipe getRecipe() throws CheckstyleException;
4145

42-
protected void testRecipe(String recipePath, String testCaseName) throws IOException {
46+
protected void testRecipe(String recipePath, String testCaseName) throws IOException,
47+
CheckstyleException {
4348
final String testCaseDir = testCaseName.toLowerCase();
4449
final String inputFileName = "Input" + testCaseName + ".java";
4550
final String outputFileName = "Output" + testCaseName + ".java";
@@ -60,4 +65,16 @@ protected void testRecipe(String recipePath, String testCaseName) throws IOExcep
6065
);
6166
});
6267
}
68+
69+
protected Configuration extractCheckConfiguration(Configuration config, String checkName) {
70+
71+
return Arrays.stream(config.getChildren())
72+
.filter(child -> checkName.equals(child.getName()))
73+
.findFirst()
74+
.orElseThrow(() -> {
75+
return new IllegalArgumentException(checkName + "configuration not "
76+
+ "found");
77+
});
78+
}
79+
6380
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.recipe;
19+
20+
import java.io.IOException;
21+
import java.nio.file.Path;
22+
import java.util.List;
23+
import java.util.Properties;
24+
25+
import org.checkstyle.autofix.parser.CheckstyleReportParser;
26+
import org.checkstyle.autofix.parser.CheckstyleViolation;
27+
import org.junit.jupiter.api.Test;
28+
import org.openrewrite.Recipe;
29+
30+
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
31+
import com.puppycrawl.tools.checkstyle.PropertiesExpander;
32+
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
33+
import com.puppycrawl.tools.checkstyle.api.Configuration;
34+
35+
public class HeaderTest extends AbstractRecipeTest {
36+
37+
@Override
38+
protected Recipe getRecipe() throws CheckstyleException {
39+
final String reportPath = "src/test/resources/org/checkstyle/autofix/recipe/header"
40+
+ "/report.xml";
41+
42+
final String configPath = "src/test/resources/org/checkstyle/autofix/recipe/header"
43+
+ "/config.xml";
44+
45+
final Configuration config = ConfigurationLoader.loadConfiguration(
46+
configPath, new PropertiesExpander(new Properties())
47+
);
48+
49+
final List<CheckstyleViolation> violations =
50+
CheckstyleReportParser.parse(Path.of(reportPath));
51+
52+
return new Header(violations, extractCheckConfiguration(config, "Header"));
53+
}
54+
55+
@Test
56+
void headerTest() throws IOException, CheckstyleException {
57+
testRecipe("header", "HeaderBlankLines");
58+
}
59+
60+
@Test
61+
void headerCommentTest() throws IOException, CheckstyleException {
62+
testRecipe("header", "HeaderComments");
63+
}
64+
65+
@Test
66+
void headerIncorrect() throws IOException, CheckstyleException {
67+
testRecipe("header", "HeaderIncorrect");
68+
}
69+
70+
}

src/test/java/org/checkstyle/autofix/recipe/UpperEllTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.junit.jupiter.api.Test;
2727
import org.openrewrite.Recipe;
2828

29+
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
30+
2931
public class UpperEllTest extends AbstractRecipeTest {
3032

3133
@Override
@@ -39,17 +41,17 @@ protected Recipe getRecipe() {
3941
}
4042

4143
@Test
42-
void hexOctalLiteralTest() throws IOException {
44+
void hexOctalLiteralTest() throws IOException, CheckstyleException {
4345
testRecipe("upperell", "HexOctalLiteral");
4446
}
4547

4648
@Test
47-
void complexLongLiterals() throws IOException {
49+
void complexLongLiterals() throws IOException, CheckstyleException {
4850
testRecipe("upperell", "ComplexLongLiterals");
4951
}
5052

5153
@Test
52-
void stringAndCommentTest() throws IOException {
54+
void stringAndCommentTest() throws IOException, CheckstyleException {
5355
testRecipe("upperell", "StringAndComments");
5456
}
5557
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="Checker">
6+
7+
<module name="Header">
8+
<property name="headerFile" value="src/test/resources/org/checkstyle/autofix/recipe/header/header.txt"/>
9+
<property name="fileExtensions" value="java"/>
10+
</module>
11+
12+
</module>

0 commit comments

Comments
 (0)