Skip to content

Commit 35363f0

Browse files
committed
Implemented Input Files based testing
1 parent 4f798b8 commit 35363f0

File tree

11 files changed

+413
-28
lines changed

11 files changed

+413
-28
lines changed

config/import-control-test.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@
77
<allow pkg="org.junit"/>
88
<allow pkg="org.openrewrite"/>
99
<allow pkg="org.checkstyle"/>
10-
</import-control>
10+
<allow pkg="java.io"/>
11+
<allow pkg="java.nio"/>
12+
</import-control>

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@
111111
<activeRecipes>
112112
<recipe>org.checkstyle.autofix.CheckstyleAutoFix</recipe>
113113
</activeRecipes>
114+
<exclusions>
115+
<exclusion>src/test/resources/**</exclusion>
116+
</exclusions>
114117
<recipeArtifactCoordinates>
115118
<coordinate>org.checkstyle.autofix:checkstyle-openrewrite-recipes:1.0.0</coordinate>
116119
</recipeArtifactCoordinates>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package org.checkstyle.autofix.recipe;
2+
3+
import java.util.regex.Pattern;
4+
5+
import org.openrewrite.ExecutionContext;
6+
import org.openrewrite.Recipe;
7+
import org.openrewrite.TreeVisitor;
8+
import org.openrewrite.java.JavaIsoVisitor;
9+
import org.openrewrite.java.tree.J;
10+
import org.openrewrite.java.tree.TypeTree;
11+
12+
/**
13+
* Recipe to rename classes starting with 'Input' to 'Output'.
14+
* For example, a class named InputExample will be renamed to OutputExample.
15+
*/
16+
public class ClassRenameRecipe extends Recipe {
17+
18+
@Override
19+
public String getDisplayName() {
20+
return "Rename Input-prefixed classes";
21+
}
22+
23+
@Override
24+
public String getDescription() {
25+
return "Renames classes from InputXxx to OutputXxx.";
26+
}
27+
28+
@Override
29+
public TreeVisitor<?, ExecutionContext> getVisitor() {
30+
return new ClassRenameVisitor();
31+
}
32+
33+
/**
34+
* A visitor that traverse Java AST nodes and renames classes starting with "Input" to "Output".
35+
*/
36+
private static final class ClassRenameVisitor extends JavaIsoVisitor<ExecutionContext> {
37+
38+
/**
39+
* Regex pattern to match class names that start with "Input".
40+
*/
41+
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^Input(.+)$");
42+
43+
@Override
44+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl,
45+
ExecutionContext executionContext) {
46+
J.ClassDeclaration result = classDecl;
47+
final String newName = renameIfMatch(classDecl.getSimpleName());
48+
if (newName != null) {
49+
result = classDecl.withName(classDecl.getName().withSimpleName(newName));
50+
}
51+
return result;
52+
}
53+
54+
@Override
55+
public J.NewClass visitNewClass(J.NewClass constructorNode,
56+
ExecutionContext executionContext) {
57+
J.NewClass result = constructorNode;
58+
if (constructorNode.getClazz() != null) {
59+
final String oldName = getName(constructorNode.getClazz());
60+
final String newName = renameIfMatch(oldName);
61+
if (newName != null) {
62+
result = constructorNode
63+
.withClazz((TypeTree) rename(constructorNode.getClazz(), newName));
64+
}
65+
}
66+
return result;
67+
}
68+
69+
@Override
70+
public J.Identifier visitIdentifier(J.Identifier identifierNode,
71+
ExecutionContext executionContext) {
72+
J.Identifier result = identifierNode;
73+
if (isInClassNameContext()) {
74+
final String newName = renameIfMatch(identifierNode.getSimpleName());
75+
if (newName != null) {
76+
result = identifierNode.withSimpleName(newName);
77+
}
78+
}
79+
return result;
80+
}
81+
82+
/**
83+
* Checks if a given class name matches the "Input" prefix pattern
84+
* and returns the renamed version.
85+
*
86+
* @param originalName The original class name.
87+
* @return The new class name with "Output" prefix if matched, otherwise null.
88+
*/
89+
private String renameIfMatch(String originalName) {
90+
String renamed = null;
91+
final var matcher = CLASS_NAME_PATTERN.matcher(originalName);
92+
if (matcher.matches()) {
93+
renamed = "Output" + matcher.group(1);
94+
}
95+
return renamed;
96+
}
97+
98+
/**
99+
* Extracts the simple name from an identifier or field access.
100+
*
101+
* @param treeNode The AST node (identifier or field access).
102+
* @return The simple name, or null if the type is not supported.
103+
*/
104+
private String getName(J treeNode) {
105+
String name = null;
106+
if (treeNode instanceof J.Identifier) {
107+
name = ((J.Identifier) treeNode).getSimpleName();
108+
}
109+
else if (treeNode instanceof J.FieldAccess) {
110+
name = ((J.FieldAccess) treeNode).getSimpleName();
111+
}
112+
return name;
113+
}
114+
115+
/**
116+
* Renames a given identifier or field access node.
117+
*
118+
* @param treeNode The node to rename.
119+
* @param newSimpleName The new simple name to apply.
120+
* @return The renamed node.
121+
*/
122+
private J rename(J treeNode, String newSimpleName) {
123+
J renamed = treeNode;
124+
if (treeNode instanceof J.Identifier) {
125+
renamed = ((J.Identifier) treeNode).withSimpleName(newSimpleName);
126+
}
127+
else if (treeNode instanceof J.FieldAccess) {
128+
final J.FieldAccess fieldAccess = (J.FieldAccess) treeNode;
129+
renamed = fieldAccess.withName(fieldAccess.getName().withSimpleName(newSimpleName));
130+
}
131+
return renamed;
132+
}
133+
134+
/**
135+
* Determines whether the current identifier is used in a class context
136+
* (e.g., declaration, constructor, or field access).
137+
*
138+
* @return True if the identifier is in a class name context, false otherwise.
139+
*/
140+
private boolean isInClassNameContext() {
141+
boolean inContext = false;
142+
final var parentCursor = getCursor().getParent();
143+
if (parentCursor != null) {
144+
final Object parentValue = parentCursor.getValue();
145+
inContext = parentValue instanceof J.ClassDeclaration
146+
|| parentValue instanceof J.NewClass
147+
|| parentValue instanceof J.FieldAccess;
148+
}
149+
return inContext;
150+
}
151+
}
152+
}
Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
11
package org.checkstyle.autofix.recipe;
22

3-
import static org.junit.jupiter.api.Assertions.assertTrue;
4-
import static org.openrewrite.java.Assertions.java;
3+
import java.io.IOException;
54

5+
import org.checkstyle.autofix.util.AbstractTestUtil;
66
import org.junit.jupiter.api.Test;
7-
import org.openrewrite.test.RecipeSpec;
8-
import org.openrewrite.test.RewriteTest;
7+
import org.openrewrite.Recipe;
98

10-
public class UpperEllRecipeTest implements RewriteTest {
9+
public class UpperEllRecipeTest extends AbstractTestUtil {
1110

1211
@Override
13-
public void defaults(RecipeSpec spec) {
14-
spec.recipe(new UpperEllRecipe());
12+
protected Recipe getRecipe() {
13+
return new UpperEllRecipe();
1514
}
1615

1716
@Test
18-
void fixesLowercase() {
19-
rewriteRun(
17+
void hexOctalLiteralTest() throws IOException {
18+
testRecipe("upperell", "HexOctalLiteral");
19+
}
2020

21-
java(
22-
"class Test {\n"
23-
+ " int value1 = 123l;\n"
24-
+ " long value2 = 0x123l;\n"
25-
+ " long value3 = 0123l;\n"
26-
+ " long value4 = 0b101l;\n"
27-
+ " String value5 = null;\n"
28-
+ "}\n",
29-
"class Test {\n"
30-
+ " int value1 = 123L;\n"
31-
+ " long value2 = 0x123L;\n"
32-
+ " long value3 = 0123L;\n"
33-
+ " long value4 = 0b101L;\n"
34-
+ " String value5 = null;\n"
35-
+ "}\n"
36-
)
37-
);
38-
assertTrue(true, "Test completed successfully");
21+
@Test
22+
void complexLongLiterals() throws IOException {
23+
testRecipe("upperell", "ComplexLongLiterals");
24+
}
25+
26+
@Test
27+
void stringAndCommentTest() throws IOException {
28+
testRecipe("upperell", "StringAndComments");
3929
}
4030
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.checkstyle.autofix.util;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.openrewrite.java.Assertions.java;
5+
6+
import java.io.IOException;
7+
import java.nio.file.Files;
8+
import java.nio.file.Paths;
9+
10+
import org.checkstyle.autofix.recipe.ClassRenameRecipe;
11+
import org.openrewrite.Recipe;
12+
import org.openrewrite.test.RewriteTest;
13+
14+
/**
15+
* Base test class for recipe testing that provides common functionality
16+
* for reading test files and executing recipe tests with preprocessing.
17+
*/
18+
public abstract class AbstractTestUtil implements RewriteTest {
19+
20+
private static final String BASE_TEST_RESOURCES_PATH = "src/test/resources/org"
21+
+ "/checkstyle/autofix/recipe/";
22+
23+
/**
24+
* Creates a preprocessing recipe that normalizes class names from InputXxx to OutputXxx.
25+
* This allows test files to have descriptive names while maintaining consistent class names.
26+
*
27+
* @return the preprocessing recipe
28+
*/
29+
protected Recipe createPreprocessingRecipe() {
30+
return new ClassRenameRecipe();
31+
}
32+
33+
/**
34+
* Creates the main recipe that should be tested.
35+
* Subclasses must implement this method to provide their specific recipe.
36+
*
37+
* @return the main recipe to test
38+
*/
39+
protected abstract Recipe getRecipe();
40+
41+
/**
42+
* Tests a recipe with the given recipe path and test case name.
43+
* Expects input and output files to follow the naming convention:
44+
* - Input: {recipePath}/{testCaseName}/Input{testCaseName}.java
45+
* - Output: {recipePath}/{testCaseName}/Output{testCaseName}.java
46+
* The method automatically applies preprocessing to normalize class names
47+
* before running the main recipe.
48+
*
49+
* @param recipePath the recipe-specific path.
50+
* @param testCaseName the name of the test case (should match directory and file names)
51+
* @throws IOException if files cannot be read
52+
*/
53+
protected void testRecipe(String recipePath, String testCaseName) throws IOException {
54+
final String testCaseDir = testCaseName.toLowerCase();
55+
final String inputFileName = "Input" + testCaseName + ".java";
56+
final String outputFileName = "Output" + testCaseName + ".java";
57+
58+
final String beforeCode = Files.readString(Paths.get(BASE_TEST_RESOURCES_PATH
59+
+ recipePath + "/" + testCaseDir + "/" + inputFileName));
60+
61+
final String afterCode = Files.readString(Paths.get(BASE_TEST_RESOURCES_PATH
62+
+ recipePath + "/" + testCaseDir + "/" + outputFileName));
63+
64+
final Recipe preprocessing = createPreprocessingRecipe();
65+
final Recipe mainRecipe = getRecipe();
66+
67+
assertDoesNotThrow(() -> {
68+
rewriteRun(
69+
spec -> spec.recipes(preprocessing, mainRecipe),
70+
java(beforeCode, afterCode)
71+
);
72+
});
73+
}
74+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.checkstyle.autofix.recipe.upperell.complexlongliterals;
2+
3+
public class InputComplexLongLiterals {
4+
private long withUnderscores = 1_000_000l;
5+
private long multipleUnderscores = 1_234_567_890l;
6+
7+
private long maxLong = 9223372036854775807l;
8+
private long minLong = -9223372036854775808l;
9+
10+
private Long nullLong = null;
11+
private Long simpleLong = 1234l;
12+
private Long negativeLong = -5678l;
13+
private Long underscoreLong = 1_000_000l;
14+
Long maxLongObject = 9223372036854775807l;
15+
Long minLongObject = -9223372036854775808l;
16+
17+
public long calculate(long input1, long input2) {
18+
return input1 + input2 + 1000l;
19+
}
20+
21+
public void lambdaUsage() {
22+
java.util.function.LongSupplier supplier = () -> 42l;
23+
java.util.Arrays.asList(1l, 2l, 3l).forEach(System.out::println);
24+
}
25+
26+
public java.util.List<Long> getNumbers() {
27+
return java.util.Arrays.asList(100l, 200l, 300l);
28+
}
29+
30+
public void longObjectOperations() {
31+
Long a = null;
32+
Long b = 1234l;
33+
Long c = Long.valueOf(5678l);
34+
Long d = new Long(9999l);
35+
36+
Long conditional = (a != null) ? a : 0l;
37+
long primitive = b != null ? b : 0l;
38+
Long boxed = primitive + 1000l;
39+
}
40+
41+
public Long methodReturningLong(Long param) {
42+
if (param == null) {
43+
return 12345l;
44+
}
45+
return param + 6789l;
46+
}
47+
}

0 commit comments

Comments
 (0)