Skip to content

Commit e58b713

Browse files
zyadahmedrdiachenko
authored andcommitted
Issue #118: Implement HexLiterialCase Recipe
1 parent 3f215c6 commit e58b713

File tree

8 files changed

+297
-17
lines changed

8 files changed

+297
-17
lines changed

README.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -219,24 +219,25 @@ This table tracks the auto-fix support status of OpenRewrite recipes for each Ch
219219

220220
### Miscellaneous
221221

222-
| Status | Check | Recipe | Coverage Notes |
223-
|--------|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------|
224-
| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | |
222+
| Status | Check | Recipe | Coverage Notes |
223+
|--------|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------|
224+
| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | |
225225
| 🔴 | [`AvoidEscapedUnicodeCharacters`](https://checkstyle.sourceforge.io/checks/misc/avoidescapedunicodecharacters.html#AvoidEscapedUnicodeCharacters) | | Need to determine appropriate replacements |
226-
| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | |
227-
| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions |
228-
| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | |
229-
| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | |
230-
| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | `TBD` | |
231-
| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file |
232-
| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties |
233-
| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class |
234-
| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments |
235-
| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | |
236-
| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations |
237-
| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | |
238-
| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties |
239-
| 🟢 | [`UpperEll`](https://checkstyle.sourceforge.io/checks/misc/upperell.html#UpperEll) | [`UpperEll`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/UpperEll.java) | |
226+
| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | |
227+
| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions |
228+
| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | |
229+
| 🟢 | [`HexLiteralCase`](https://checkstyle.sourceforge.io/checks/misc/hexliteralcase.html#HexLiteralCase) | [`HexLiteralCase`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java) | |
230+
| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | |
231+
| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | `TBD` | |
232+
| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file |
233+
| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties |
234+
| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class |
235+
| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments |
236+
| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | |
237+
| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations |
238+
| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | |
239+
| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties |
240+
| 🟢 | [`UpperEll`](https://checkstyle.sourceforge.io/checks/misc/upperell.html#UpperEll) | [`UpperEll`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/UpperEll.java) | |
240241

241242

242243
### Modifiers

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public enum CheckstyleCheck {
2424
FINAL_LOCAL_VARIABLE("com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck"),
2525
HEADER("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"),
2626
UPPER_ELL("com.puppycrawl.tools.checkstyle.checks.UpperEllCheck"),
27+
HEX_LITERAL_CASE("com.puppycrawl.tools.checkstyle.checks.HexLiteralCaseCheck"),
2728
REDUNDANT_IMPORT("com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck");
2829

2930
private final String id;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.checkstyle.autofix.parser.CheckstyleViolation;
3030
import org.checkstyle.autofix.recipe.FinalLocalVariable;
3131
import org.checkstyle.autofix.recipe.Header;
32+
import org.checkstyle.autofix.recipe.HexLiteralCase;
3233
import org.checkstyle.autofix.recipe.RedundantImport;
3334
import org.checkstyle.autofix.recipe.UpperEll;
3435
import org.openrewrite.Recipe;
@@ -44,6 +45,7 @@ public final class CheckstyleRecipeRegistry {
4445

4546
static {
4647
RECIPE_MAP.put(CheckstyleCheck.UPPER_ELL, UpperEll::new);
48+
RECIPE_MAP.put(CheckstyleCheck.HEX_LITERAL_CASE, HexLiteralCase::new);
4749
RECIPE_MAP.put(CheckstyleCheck.FINAL_LOCAL_VARIABLE, FinalLocalVariable::new);
4850
RECIPE_MAP_WITH_CONFIG.put(CheckstyleCheck.HEADER, Header::new);
4951
RECIPE_MAP.put(CheckstyleCheck.REDUNDANT_IMPORT, RedundantImport::new);
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.nio.file.Path;
21+
import java.util.List;
22+
import java.util.Locale;
23+
24+
import org.checkstyle.autofix.PositionHelper;
25+
import org.checkstyle.autofix.parser.CheckstyleViolation;
26+
import org.openrewrite.ExecutionContext;
27+
import org.openrewrite.Recipe;
28+
import org.openrewrite.TreeVisitor;
29+
import org.openrewrite.java.JavaIsoVisitor;
30+
import org.openrewrite.java.tree.J;
31+
import org.openrewrite.java.tree.JavaType;
32+
33+
/**
34+
* Fixes Checkstyle HexLiteralCase violations by replacing hexadecimal lowercase literals
35+
* with uppercase literals.
36+
*/
37+
public class HexLiteralCase extends Recipe {
38+
39+
private final List<CheckstyleViolation> violations;
40+
41+
public HexLiteralCase(List<CheckstyleViolation> violations) {
42+
this.violations = violations;
43+
}
44+
45+
@Override
46+
public String getDisplayName() {
47+
return "HexLiteralCase Recipe";
48+
}
49+
50+
@Override
51+
public String getDescription() {
52+
return "Replace HexLiteral lowercase (a-f) with UpperCase (A-F) "
53+
+ "to improve readability.";
54+
}
55+
56+
@Override
57+
public TreeVisitor<?, ExecutionContext> getVisitor() {
58+
return new HexLiteralCase.HexLiteralCaseVisitor();
59+
}
60+
61+
private final class HexLiteralCaseVisitor extends JavaIsoVisitor<ExecutionContext> {
62+
63+
private static final String HEX_PREFIX = "0x";
64+
65+
private Path sourcePath;
66+
67+
@Override
68+
public J.CompilationUnit visitCompilationUnit(
69+
J.CompilationUnit cu, ExecutionContext executionContext) {
70+
this.sourcePath = cu.getSourcePath().toAbsolutePath();
71+
return super.visitCompilationUnit(cu, executionContext);
72+
}
73+
74+
@Override
75+
public J.Literal visitLiteral(J.Literal literal, ExecutionContext executionContext) {
76+
J.Literal result = super.visitLiteral(literal, executionContext);
77+
final String valueSource = result.getValueSource();
78+
79+
if (shouldProcessLiteral(result, valueSource)) {
80+
final String newValueSource = convertLowercaseHexToUppercase(valueSource);
81+
result = result.withValueSource(newValueSource);
82+
}
83+
84+
return result;
85+
}
86+
87+
/**
88+
* Determines whether the given literal should be processed based on its type and format.
89+
*
90+
* @param literal the {@link J.Literal} node to check
91+
* @param valueSource the source value of the literal as a string
92+
* @return {@code true} if the literal meets the criteria for processing,
93+
* {@code false} otherwise
94+
*/
95+
private boolean shouldProcessLiteral(J.Literal literal, String valueSource) {
96+
return valueSource != null
97+
&& (valueSource.startsWith(HEX_PREFIX)
98+
|| valueSource.startsWith(HEX_PREFIX.toUpperCase(Locale.ROOT)))
99+
&& (literal.getType() == JavaType.Primitive.Long
100+
|| literal.getType() == JavaType.Primitive.Int)
101+
&& isAtViolationLocation(literal);
102+
}
103+
104+
/**
105+
* Converts any lowercase hexadecimal letters (a-f) to uppercase (A-F).
106+
*
107+
* @param valueSource the original literal source
108+
* @return a new uppercase version if modified, or {@code null} if no changes were made
109+
*/
110+
private String convertLowercaseHexToUppercase(String valueSource) {
111+
final String prefix = valueSource.substring(0, HEX_PREFIX.length());
112+
String result = prefix
113+
+ valueSource.substring(prefix.length()).toUpperCase(Locale.ROOT);
114+
115+
// Avoid extra cycle by skipping identical replacements
116+
if (result.equals(valueSource)) {
117+
result = valueSource;
118+
}
119+
return result;
120+
}
121+
122+
private boolean isAtViolationLocation(J.Literal literal) {
123+
final J.CompilationUnit cursor = getCursor().firstEnclosing(J.CompilationUnit.class);
124+
125+
final int line = PositionHelper.computeLinePosition(cursor, literal, getCursor());
126+
final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor());
127+
128+
return violations.stream().anyMatch(violation -> {
129+
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
130+
return violation.getLine() == line
131+
&& violation.getColumn() == column
132+
&& absolutePath.endsWith(sourcePath);
133+
});
134+
}
135+
}
136+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 org.junit.jupiter.api.Test;
21+
22+
public class HexLiteralCaseTest extends AbstractRecipeTestSupport {
23+
24+
@Override
25+
protected String getSubpackage() {
26+
return "hexliterialcase";
27+
}
28+
29+
@Test
30+
void hexLiteral() throws Exception {
31+
verify("HexLiteralCase");
32+
}
33+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--- src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/InputHexLiteralCase.java
2+
+++ src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/OutputHexLiteralCase.java
3+
@@ -9,24 +9,21 @@
4+
5+
package org.checkstyle.autofix.recipe.hexliterialcase.hexliteralcase;
6+
7+
-public class InputHexLiteralCase {
8+
- int h = 0xb; // violation 'Should use uppercase hexadecimal letters.'
9+
+public class OutputHexLiteralCase {
10+
+ int h = 0xB;
11+
int w = 0xB;
12+
- long d = 0Xf123L; // violation 'Should use uppercase hexadecimal letters.'
13+
- byte b1 = 0x1b; // violation 'Should use uppercase hexadecimal letters.'
14+
+ long d = 0XF123L;
15+
+ byte b1 = 0x1B;
16+
byte b2 = 0x1B;
17+
- short s2 = 0xF5f; // violation 'Should use uppercase hexadecimal letters.'
18+
- int i1 = 0x11 + 0xabc; // violation 'Should use uppercase hexadecimal letters.'
19+
- long l1 = 0x7fff_ffff_ffff_ffffL;
20+
- // violation above 'Should use uppercase hexadecimal letters.'
21+
- long l2 = 0x7FFF_AAA_bBB_DDDL; // violation 'Should use uppercase hexadecimal letters.'
22+
- // violation below 'Should use uppercase hexadecimal letters.'
23+
- int val = 0x1b, sum = 0xff; // violation 'Should use uppercase hexadecimal letters.'
24+
- int x1 = 223, x2 = 0xa1; // violation 'Should use uppercase hexadecimal letters.'
25+
+ short s2 = 0xF5F;
26+
+ int i1 = 0x11 + 0xABC;
27+
+ long l1 = 0x7FFF_FFFF_FFFF_FFFFL;
28+
+ long l2 = 0x7FFF_AAA_BBB_DDDL;
29+
+ int val = 0x1B, sum = 0xFF;
30+
+ int x1 = 223, x2 = 0xA1;
31+
32+
private static void functionCall() {
33+
- // violation below 'Should use uppercase hexadecimal letters.'
34+
- functionCall2(0x7fff, 0xab); // violation 'Should use uppercase hexadecimal letters.'
35+
+ functionCall2(0x7FFF, 0xAB);
36+
}
37+
private static void functionCall2(int first, int second) {
38+
System.out.println("First: " + first);

0 commit comments

Comments
 (0)