diff --git a/README.md b/README.md index 4be52e1..a2651cd 100644 --- a/README.md +++ b/README.md @@ -219,24 +219,25 @@ This table tracks the auto-fix support status of OpenRewrite recipes for each Ch ### Miscellaneous -| Status | Check | Recipe | Coverage Notes | -|--------|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------| -| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | | +| Status | Check | Recipe | Coverage Notes | +|--------|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------| +| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | | | 🔴 | [`AvoidEscapedUnicodeCharacters`](https://checkstyle.sourceforge.io/checks/misc/avoidescapedunicodecharacters.html#AvoidEscapedUnicodeCharacters) | | Need to determine appropriate replacements | -| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | | -| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions | -| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | | -| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | | -| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | `TBD` | | -| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file | -| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties | -| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class | -| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments | -| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | | -| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations | -| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | | -| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties | -| 🟢 | [`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) | | +| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | | +| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions | +| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | | +| 🟢 | [`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) | | +| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | | +| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | `TBD` | | +| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file | +| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties | +| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class | +| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments | +| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | | +| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations | +| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | | +| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties | +| 🟢 | [`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) | | ### Modifiers diff --git a/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java b/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java index 271e91c..fef6b72 100644 --- a/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java +++ b/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java @@ -24,6 +24,7 @@ public enum CheckstyleCheck { FINAL_LOCAL_VARIABLE("com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck"), HEADER("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"), UPPER_ELL("com.puppycrawl.tools.checkstyle.checks.UpperEllCheck"), + HEX_LITERAL_CASE("com.puppycrawl.tools.checkstyle.checks.HexLiteralCaseCheck"), REDUNDANT_IMPORT("com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck"); private final String id; diff --git a/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java b/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java index 65e2d36..997be2b 100644 --- a/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java +++ b/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java @@ -29,6 +29,7 @@ import org.checkstyle.autofix.parser.CheckstyleViolation; import org.checkstyle.autofix.recipe.FinalLocalVariable; import org.checkstyle.autofix.recipe.Header; +import org.checkstyle.autofix.recipe.HexLiteralCase; import org.checkstyle.autofix.recipe.RedundantImport; import org.checkstyle.autofix.recipe.UpperEll; import org.openrewrite.Recipe; @@ -44,6 +45,7 @@ public final class CheckstyleRecipeRegistry { static { RECIPE_MAP.put(CheckstyleCheck.UPPER_ELL, UpperEll::new); + RECIPE_MAP.put(CheckstyleCheck.HEX_LITERAL_CASE, HexLiteralCase::new); RECIPE_MAP.put(CheckstyleCheck.FINAL_LOCAL_VARIABLE, FinalLocalVariable::new); RECIPE_MAP_WITH_CONFIG.put(CheckstyleCheck.HEADER, Header::new); RECIPE_MAP.put(CheckstyleCheck.REDUNDANT_IMPORT, RedundantImport::new); diff --git a/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java b/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java new file mode 100644 index 0000000..aa2d711 --- /dev/null +++ b/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package org.checkstyle.autofix.recipe; + +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; + +import org.checkstyle.autofix.PositionHelper; +import org.checkstyle.autofix.parser.CheckstyleViolation; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +/** + * Fixes Checkstyle HexLiteralCase violations by replacing hexadecimal lowercase literals + * with uppercase literals. + */ +public class HexLiteralCase extends Recipe { + + private final List violations; + + public HexLiteralCase(List violations) { + this.violations = violations; + } + + @Override + public String getDisplayName() { + return "HexLiteralCase Recipe"; + } + + @Override + public String getDescription() { + return "Replace HexLiteral lowercase (a-f) with UpperCase (A-F) " + + "to improve readability."; + } + + @Override + public TreeVisitor getVisitor() { + return new HexLiteralCase.HexLiteralCaseVisitor(); + } + + private final class HexLiteralCaseVisitor extends JavaIsoVisitor { + + private static final String HEX_PREFIX = "0x"; + + private Path sourcePath; + + @Override + public J.CompilationUnit visitCompilationUnit( + J.CompilationUnit cu, ExecutionContext executionContext) { + this.sourcePath = cu.getSourcePath().toAbsolutePath(); + return super.visitCompilationUnit(cu, executionContext); + } + + @Override + public J.Literal visitLiteral(J.Literal literal, ExecutionContext executionContext) { + J.Literal result = super.visitLiteral(literal, executionContext); + final String valueSource = result.getValueSource(); + + if (shouldProcessLiteral(result, valueSource)) { + final String newValueSource = convertLowercaseHexToUppercase(valueSource); + result = result.withValueSource(newValueSource); + } + + return result; + } + + /** + * Determines whether the given literal should be processed based on its type and format. + * + * @param literal the {@link J.Literal} node to check + * @param valueSource the source value of the literal as a string + * @return {@code true} if the literal meets the criteria for processing, + * {@code false} otherwise + */ + private boolean shouldProcessLiteral(J.Literal literal, String valueSource) { + return valueSource != null + && (valueSource.startsWith(HEX_PREFIX) + || valueSource.startsWith(HEX_PREFIX.toUpperCase(Locale.ROOT))) + && (literal.getType() == JavaType.Primitive.Long + || literal.getType() == JavaType.Primitive.Int) + && isAtViolationLocation(literal); + } + + /** + * Converts any lowercase hexadecimal letters (a-f) to uppercase (A-F). + * + * @param valueSource the original literal source + * @return a new uppercase version if modified, or {@code null} if no changes were made + */ + private String convertLowercaseHexToUppercase(String valueSource) { + final String prefix = valueSource.substring(0, HEX_PREFIX.length()); + String result = prefix + + valueSource.substring(prefix.length()).toUpperCase(Locale.ROOT); + + // Avoid extra cycle by skipping identical replacements + if (result.equals(valueSource)) { + result = valueSource; + } + return result; + } + + private boolean isAtViolationLocation(J.Literal literal) { + final J.CompilationUnit cursor = getCursor().firstEnclosing(J.CompilationUnit.class); + + final int line = PositionHelper.computeLinePosition(cursor, literal, getCursor()); + final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor()); + + return violations.stream().anyMatch(violation -> { + final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath(); + return violation.getLine() == line + && violation.getColumn() == column + && absolutePath.endsWith(sourcePath); + }); + } + } +} diff --git a/src/test/java/org/checkstyle/autofix/recipe/HexLiteralCaseTest.java b/src/test/java/org/checkstyle/autofix/recipe/HexLiteralCaseTest.java new file mode 100644 index 0000000..0c3054f --- /dev/null +++ b/src/test/java/org/checkstyle/autofix/recipe/HexLiteralCaseTest.java @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite. +// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package org.checkstyle.autofix.recipe; + +import org.junit.jupiter.api.Test; + +public class HexLiteralCaseTest extends AbstractRecipeTestSupport { + + @Override + protected String getSubpackage() { + return "hexliterialcase"; + } + + @Test + void hexLiteral() throws Exception { + verify("HexLiteralCase"); + } +} diff --git a/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/DiffHexLiteralCase.diff b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/DiffHexLiteralCase.diff new file mode 100644 index 0000000..ec6c3ac --- /dev/null +++ b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/DiffHexLiteralCase.diff @@ -0,0 +1,38 @@ +--- src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/InputHexLiteralCase.java ++++ src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/OutputHexLiteralCase.java +@@ -9,24 +9,21 @@ + + package org.checkstyle.autofix.recipe.hexliterialcase.hexliteralcase; + +-public class InputHexLiteralCase { +- int h = 0xb; // violation 'Should use uppercase hexadecimal letters.' ++public class OutputHexLiteralCase { ++ int h = 0xB; + int w = 0xB; +- long d = 0Xf123L; // violation 'Should use uppercase hexadecimal letters.' +- byte b1 = 0x1b; // violation 'Should use uppercase hexadecimal letters.' ++ long d = 0XF123L; ++ byte b1 = 0x1B; + byte b2 = 0x1B; +- short s2 = 0xF5f; // violation 'Should use uppercase hexadecimal letters.' +- int i1 = 0x11 + 0xabc; // violation 'Should use uppercase hexadecimal letters.' +- long l1 = 0x7fff_ffff_ffff_ffffL; +- // violation above 'Should use uppercase hexadecimal letters.' +- long l2 = 0x7FFF_AAA_bBB_DDDL; // violation 'Should use uppercase hexadecimal letters.' +- // violation below 'Should use uppercase hexadecimal letters.' +- int val = 0x1b, sum = 0xff; // violation 'Should use uppercase hexadecimal letters.' +- int x1 = 223, x2 = 0xa1; // violation 'Should use uppercase hexadecimal letters.' ++ short s2 = 0xF5F; ++ int i1 = 0x11 + 0xABC; ++ long l1 = 0x7FFF_FFFF_FFFF_FFFFL; ++ long l2 = 0x7FFF_AAA_BBB_DDDL; ++ int val = 0x1B, sum = 0xFF; ++ int x1 = 223, x2 = 0xA1; + + private static void functionCall() { +- // violation below 'Should use uppercase hexadecimal letters.' +- functionCall2(0x7fff, 0xab); // violation 'Should use uppercase hexadecimal letters.' ++ functionCall2(0x7FFF, 0xAB); + } + private static void functionCall2(int first, int second) { + System.out.println("First: " + first); diff --git a/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/InputHexLiteralCase.java b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/InputHexLiteralCase.java new file mode 100644 index 0000000..20260c8 --- /dev/null +++ b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/InputHexLiteralCase.java @@ -0,0 +1,36 @@ +/*xml + + + + + + +*/ + +package org.checkstyle.autofix.recipe.hexliterialcase.hexliteralcase; + +public class InputHexLiteralCase { + int h = 0xb; // violation 'Should use uppercase hexadecimal letters.' + int w = 0xB; + long d = 0Xf123L; // violation 'Should use uppercase hexadecimal letters.' + byte b1 = 0x1b; // violation 'Should use uppercase hexadecimal letters.' + byte b2 = 0x1B; + short s2 = 0xF5f; // violation 'Should use uppercase hexadecimal letters.' + int i1 = 0x11 + 0xabc; // violation 'Should use uppercase hexadecimal letters.' + long l1 = 0x7fff_ffff_ffff_ffffL; + // violation above 'Should use uppercase hexadecimal letters.' + long l2 = 0x7FFF_AAA_bBB_DDDL; // violation 'Should use uppercase hexadecimal letters.' + // violation below 'Should use uppercase hexadecimal letters.' + int val = 0x1b, sum = 0xff; // violation 'Should use uppercase hexadecimal letters.' + int x1 = 223, x2 = 0xa1; // violation 'Should use uppercase hexadecimal letters.' + + private static void functionCall() { + // violation below 'Should use uppercase hexadecimal letters.' + functionCall2(0x7fff, 0xab); // violation 'Should use uppercase hexadecimal letters.' + } + private static void functionCall2(int first, int second) { + System.out.println("First: " + first); + System.out.println("Second: " + second); + } + +} diff --git a/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/OutputHexLiteralCase.java b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/OutputHexLiteralCase.java new file mode 100644 index 0000000..23b4d46 --- /dev/null +++ b/src/test/resources/org/checkstyle/autofix/recipe/hexliterialcase/hexliteralcase/OutputHexLiteralCase.java @@ -0,0 +1,33 @@ +/*xml + + + + + + +*/ + +package org.checkstyle.autofix.recipe.hexliterialcase.hexliteralcase; + +public class OutputHexLiteralCase { + int h = 0xB; + int w = 0xB; + long d = 0XF123L; + byte b1 = 0x1B; + byte b2 = 0x1B; + short s2 = 0xF5F; + int i1 = 0x11 + 0xABC; + long l1 = 0x7FFF_FFFF_FFFF_FFFFL; + long l2 = 0x7FFF_AAA_BBB_DDDL; + int val = 0x1B, sum = 0xFF; + int x1 = 223, x2 = 0xA1; + + private static void functionCall() { + functionCall2(0x7FFF, 0xAB); + } + private static void functionCall2(int first, int second) { + System.out.println("First: " + first); + System.out.println("Second: " + second); + } + +}