Skip to content

Commit 278f60f

Browse files
committed
Issue #25: Added Header Recipe
1 parent 4bf149c commit 278f60f

File tree

18 files changed

+329
-12
lines changed

18 files changed

+329
-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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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.Arrays;
24+
import java.util.List;
25+
import java.util.Optional;
26+
import java.util.stream.Collectors;
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_PROP = "header";
44+
private static final String HEADER_FILE_PROP = "headerFile";
45+
46+
private List<CheckstyleViolation> violations;
47+
private Configuration headerConfig;
48+
49+
public Header(List<CheckstyleViolation> violations, Configuration headerConfig) {
50+
this.violations = violations;
51+
this.headerConfig = headerConfig;
52+
}
53+
54+
@Override
55+
public String getDisplayName() {
56+
return "Header recipe";
57+
}
58+
59+
@Override
60+
public String getDescription() {
61+
return "Adds headers to Java source files when missing.";
62+
}
63+
64+
@Override
65+
public TreeVisitor<?, ExecutionContext> getVisitor() {
66+
final String licenseHeader = getLicenseHeader(headerConfig);
67+
return new HeaderVisitor(violations, licenseHeader);
68+
}
69+
70+
private static String getLicenseHeader(Configuration child) {
71+
final String result;
72+
final String[] propertyNames = child.getPropertyNames();
73+
final boolean hasHeaderProperty = Arrays.asList(propertyNames).contains(HEADER_PROP);
74+
try {
75+
if (hasHeaderProperty) {
76+
result = child.getProperty(HEADER_PROP);
77+
}
78+
else {
79+
final String headerFile = child.getProperty(HEADER_FILE_PROP);
80+
result = readHeaderFileContent(headerFile);
81+
}
82+
}
83+
catch (CheckstyleException exception) {
84+
throw new IllegalArgumentException("Failed to extract header from config", exception);
85+
}
86+
return result;
87+
}
88+
89+
private static String readHeaderFileContent(String headerFilePath) {
90+
final String content;
91+
try {
92+
content = Files.readString(Path.of(headerFilePath));
93+
}
94+
catch (IOException exception) {
95+
throw new IllegalArgumentException("Failed to read: " + headerFilePath, exception);
96+
}
97+
return content;
98+
}
99+
100+
private static class HeaderVisitor extends JavaIsoVisitor<ExecutionContext> {
101+
private final List<CheckstyleViolation> violations;
102+
private final String licenseHeader;
103+
104+
HeaderVisitor(List<CheckstyleViolation> violations, String licenseHeader) {
105+
this.violations = violations;
106+
this.licenseHeader = licenseHeader;
107+
}
108+
109+
@Override
110+
public J visit(Tree tree, ExecutionContext ctx) {
111+
J result = super.visit(tree, ctx);
112+
113+
if (tree instanceof JavaSourceFile) {
114+
JavaSourceFile sourceFile = (JavaSourceFile) tree;
115+
final Path absolutePath = sourceFile.getSourcePath().toAbsolutePath();
116+
117+
final String sourceHeader = sourceFile.getComments().stream()
118+
.map(comment -> comment.printComment(getCursor()))
119+
.collect(Collectors.joining(System.lineSeparator()));
120+
121+
if (!sourceHeader.equals(licenseHeader.stripTrailing())
122+
&& isAtViolation(absolutePath)) {
123+
sourceFile = sourceFile.withPrefix(
124+
Space.format(licenseHeader
125+
+ System.lineSeparator()));
126+
}
127+
result = super.visit(sourceFile, ctx);
128+
}
129+
return result;
130+
}
131+
132+
private boolean isAtViolation(Path currentFileName) {
133+
final Optional<CheckstyleViolation> match = violations.stream()
134+
.filter(violation -> {
135+
return currentFileName.equals(Path.of(violation.getFileName())
136+
.toAbsolutePath());
137+
}).findFirst();
138+
139+
return match.isPresent();
140+
}
141+
}
142+
}

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)