Feature Request: Recipe to Extract Magic Numbers to Type-and-Value-Named Constants per Class#769
Open
boraciner wants to merge 9 commits intoopenrewrite:mainfrom
Conversation
Introduce `ReplaceMagicNumbersWithConstants` recipe implementing Sonar rule java:S109. This recipe extracts numeric literals from method bodies into private static final constants to improve code readability and maintainability. Also add `ReplaceMagicNumbersWithConstantsTest` with test cases verifying: - constants are generated for numeric literals - literals already assigned to variables or fields are ignored - replacements preserve correctness and type consistency
-1, 0, and 1 are not considered magic numbers.
-1, 0, and 1 are not considered magic numbers.
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Outdated
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Outdated
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Outdated
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Show resolved
Hide resolved
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Show resolved
Hide resolved
src/test/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstantsTest.java
Show resolved
Hide resolved
src/test/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstantsTest.java
Show resolved
Hide resolved
src/test/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstantsTest.java
Show resolved
Hide resolved
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
timtebeek
reviewed
Nov 3, 2025
src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java
Outdated
Show resolved
Hide resolved
Comment on lines
+18
to
+19
| import org.openrewrite.*; | ||
| import org.openrewrite.java.JavaTemplate; |
Contributor
There was a problem hiding this comment.
Suggested change
| import org.openrewrite.*; | |
| import org.openrewrite.java.JavaTemplate; | |
| import org.openrewrite.Cursor; | |
| import org.openrewrite.ExecutionContext; | |
| import org.openrewrite.Recipe; | |
| import org.openrewrite.TreeVisitor; | |
| import java.util.ArrayList; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| import static java.util.Collections.singleton; |
Comment on lines
+37
to
+43
| public @NlsRewrite.DisplayName String getDisplayName() { | ||
| return "Replace magic numbers with constants"; | ||
| } | ||
|
|
||
| @Override | ||
| public @NlsRewrite.Description String getDescription() { | ||
| return "Replaces magic number literals in method bodies with named constants to improve code readability and maintainability. " |
Contributor
There was a problem hiding this comment.
Suggested change
| public @NlsRewrite.DisplayName String getDisplayName() { | |
| return "Replace magic numbers with constants"; | |
| } | |
| @Override | |
| public @NlsRewrite.Description String getDescription() { | |
| return "Replaces magic number literals in method bodies with named constants to improve code readability and maintainability. " | |
| public String getDisplayName() { | |
| public String getDescription() { | |
| return "Replaces magic number literals in method bodies with named constants to improve code readability and maintainability. " + | |
| "Magic numbers are replaced by private static final constants declared at the top of the class, following Sonar's java:S109 rule. " + | |
| "The recipe does not create constants for literals that are already assigned to fields or variables, nor for typical non-magic numbers (such as 0, 1, or -1). " + | |
| "Currently, only numeric primitive literals are handled; string and character literals are unaffected. " + | |
| "If a constant for a value already exists, or the constant name would conflict with an existing symbol, the recipe will skip that value."; |
Comment on lines
+68
to
+70
| if (!(cursor.getParent().getParent().getValue() instanceof J.VariableDeclarations.NamedVariable) | ||
| && !isIgnoredMagicNumber(literal)) { | ||
| literals.add(literal); |
Contributor
There was a problem hiding this comment.
Suggested change
| if (!(cursor.getParent().getParent().getValue() instanceof J.VariableDeclarations.NamedVariable) | |
| && !isIgnoredMagicNumber(literal)) { | |
| literals.add(literal); | |
| if (!(cursor.getParent().getParent().getValue() instanceof J.VariableDeclarations.NamedVariable) && | |
| !isIgnoredMagicNumber(literal)) { |
Comment on lines
+129
to
+130
| grandparent.getValue() instanceof J.VariableDeclarations.NamedVariable) | ||
| || isIgnoredMagicNumber(literal)) { |
Contributor
There was a problem hiding this comment.
Suggested change
| grandparent.getValue() instanceof J.VariableDeclarations.NamedVariable) | |
| || isIgnoredMagicNumber(literal)) { | |
| grandparent.getValue() instanceof J.VariableDeclarations.NamedVariable) || | |
| isIgnoredMagicNumber(literal)) { |
Comment on lines
+145
to
+156
| private String printCursorPath(Cursor cursor) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| while (cursor != null) { | ||
| Object val = cursor.getValue(); | ||
| sb.append(val == null ? "null" : val.getClass().getSimpleName()); | ||
| sb.append(" > "); | ||
| cursor = cursor.getParent(); | ||
| } | ||
| sb.append("ROOT"); | ||
| return sb.toString(); | ||
| } | ||
| @Override |
Contributor
There was a problem hiding this comment.
Suggested change
| private String printCursorPath(Cursor cursor) { | |
| StringBuilder sb = new StringBuilder(); | |
| while (cursor != null) { | |
| Object val = cursor.getValue(); | |
| sb.append(val == null ? "null" : val.getClass().getSimpleName()); | |
| sb.append(" > "); | |
| cursor = cursor.getParent(); | |
| } | |
| sb.append("ROOT"); | |
| return sb.toString(); | |
| } | |
| @Override | |
| return singleton("RSPEC-109"); |
| private String getStrValFromLiteral(J.Literal literal) { | ||
| String type = getTypeName(literal).toUpperCase(); | ||
| String valueSource = literal.getValueSource(); | ||
| if (valueSource == null) return null; |
Contributor
There was a problem hiding this comment.
Suggested change
| if (valueSource == null) return null; | |
| if (valueSource == null) { | |
| return null; | |
| } |
| } | ||
|
|
||
| private String getTypeName(J.Literal literal) { | ||
| if (literal.getType() == null) return "Object"; |
Contributor
There was a problem hiding this comment.
Suggested change
| if (literal.getType() == null) return "Object"; | |
| if (literal.getType() == null) { | |
| return "Object"; | |
| } |
Comment on lines
+34
to
+35
| import org.openrewrite.java.JavaParser; | ||
| import org.openrewrite.test.RecipeSpec; |
Contributor
There was a problem hiding this comment.
Suggested change
| import org.openrewrite.java.JavaParser; | |
| import org.openrewrite.test.RecipeSpec; | |
| class ReplaceMagicNumbersWithConstantsTest implements RewriteTest { |
Comment on lines
+107
to
+108
| @DocumentExample | ||
| @Test |
Contributor
There was a problem hiding this comment.
Suggested change
| @DocumentExample | |
| @Test | |
| @Test |
Comment on lines
+136
to
+137
| } | ||
| @DocumentExample |
Contributor
There was a problem hiding this comment.
Suggested change
| } | |
| @DocumentExample | |
| } |
Contributor
|
This PR is stale because it has been open for 90 days with no activity. Remove |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduce
ReplaceMagicNumbersWithConstantsrecipe implementing Sonar rule java:S109. This recipe extracts numeric literals from method bodies into private static final constants to improve code readability and maintainability.Also add
ReplaceMagicNumbersWithConstantsTestwith test cases verifying:What's changed?
Added new recipe: ReplaceMagicNumbersWithConstants
Added new test class: ReplaceMagicNumbersWithConstantsTest
What's your motivation?
To enhance code quality and readability by enforcing Sonar rule java:S109, reducing the use of magic numbers in Java code.
Anything in particular you'd like reviewers to focus on?
Verify correct handling of numeric literals and naming of generated constants
Confirm no false positives for literals already assigned to variables or fields
Ensure the recipe integrates cleanly with existing static analysis infrastructure
Have you considered any alternatives or workarounds?
Manual refactoring or partial Sonar-based analysis, but these approaches lack the automation and reproducibility provided by this recipe.