diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java new file mode 100644 index 0000000000..5e332c52aa --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceMagicNumbersWithConstants.java @@ -0,0 +1,194 @@ +/* + * Copyright 2024 the original author or authors. + *
+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://docs.moderne.io/licensing/moderne-source-available-license + *
+ * 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.openrewrite.staticanalysis;
+
+import org.openrewrite.*;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.JavaVisitor;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+
+import java.time.Duration;
+import java.util.*;
+
+/**
+ * This recipe replaces magic number literals in method bodies with named constants following the Sonar java:S109 rule.
+ * All detected magic numbers (excluding those explicitly assigned to variables or fields, and -1, 0, 1) will be extracted as
+ * private static final constants at the top of the class.
+ * The original numeric usages are replaced with the new constant name to improve code readability and maintainability.
+ */
+public class ReplaceMagicNumbersWithConstants extends Recipe {
+ private static final String CUSTOM_MODIFIERS = "private static final";
+
+ @Override
+ 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. "
+ + "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.";
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return new JavaVisitor
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.openrewrite.staticanalysis;
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.
+ */
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+import org.openrewrite.test.TypeValidation;
+
+import static org.openrewrite.java.Assertions.java;
+
+public class ReplaceMagicNumbersWithConstantsTest implements RewriteTest {
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new ReplaceMagicNumbersWithConstants());
+ }
+
+ @DocumentExample
+ @Test
+ void assignMagicNumbersToConstantsTest() {
+ rewriteRun(
+ spec -> spec
+ .typeValidationOptions(TypeValidation.none()),
+ java(
+"""
+public class OrderProcessor {
+ private static final double myVariable = 99.99;
+ public double calculateShippingCost(double orderTotal) {
+ int localVar = 5;
+ orderTotal = localVar + 10;
+ if (orderTotal < 51.0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < 51.0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < 51.0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < 51.0) {
+ return 7.99;
+ } else {
+ return 0.0;
+ }
+ }
+}
+""",
+ """
+public class OrderProcessor {
+ private static final int INT_10 = 10;
+ private static final double DOUBLE_51_0 = 51.0;
+ private static final double DOUBLE_7_99 = 7.99;
+ private static final double myVariable = 99.99;
+ public double calculateShippingCost(double orderTotal) {
+ int localVar = 5;
+ orderTotal = localVar + INT_10;
+ if (orderTotal < DOUBLE_51_0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < DOUBLE_51_0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < DOUBLE_51_0) {
+ System.out.println("Order total is less than 51.0");
+ }
+ if (orderTotal < DOUBLE_51_0) {
+ return DOUBLE_7_99;
+ } else {
+ return 0.0;
+ }
+ }
+}
+ """
+ )
+ );
+ }
+
+ @DocumentExample
+ @Test
+ void assignMagicNumbersToConstantsBasicTest() {
+ rewriteRun(
+ spec -> spec
+ .typeValidationOptions(TypeValidation.none()),
+ java(
+ """
+ public class OrderProcessor {
+ public double calculateShippingCost(double orderTotal) {
+ if (orderTotal < 51.0) {
+ return 7.99;
+ }
+ }
+ }
+ """,
+ """
+public class OrderProcessor {
+ private static final double DOUBLE_51_0 = 51.0;
+ private static final double DOUBLE_7_99 = 7.99;
+ public double calculateShippingCost(double orderTotal) {
+ if (orderTotal < DOUBLE_51_0) {
+ return DOUBLE_7_99;
+ }
+ }
+}
+ """
+ )
+ );
+ }
+ @DocumentExample
+ @Test
+ void assignMagicNumbersToConstantsM1_0_1_AreIgnoredTest() {
+ rewriteRun(
+ spec -> spec
+ .typeValidationOptions(TypeValidation.none()),
+ java(
+ """
+ public class OrderProcessor {
+ private static final double myVariable = 99.99;
+ public double calculateShippingCost(double orderTotal) {
+ int localVar0 = 0;
+ orderTotal = localVar0 - 1;
+ orderTotal = localVar0 + 0;
+ orderTotal = localVar0 + 1;
+ if (orderTotal < 51.0) {
+ return 7.99;
+ } else {
+ return 0.0;
+ }
+ }
+ }
+ """,
+ """
+public class OrderProcessor {
+ private static final double DOUBLE_51_0 = 51.0;
+ private static final double DOUBLE_7_99 = 7.99;
+ private static final double myVariable = 99.99;
+ public double calculateShippingCost(double orderTotal) {
+ int localVar0 = 0;
+ orderTotal = localVar0 - 1;
+ orderTotal = localVar0 + 0;
+ orderTotal = localVar0 + 1;
+ if (orderTotal < DOUBLE_51_0) {
+ return DOUBLE_7_99;
+ } else {
+ return 0.0;
+ }
+ }
+}
+ """
+ )
+ );
+ }
+}