Repository: detekt/detekt (kotlin)

Issue:
Add rule to prevent `BigDecimal.valueOf(<some_double_value/some_float_value>)`
As per the comment at https://github.com/detekt/detekt/issues/6668#issuecomment-1834718270 it carried the issue of precision to `BigDecimal` which might be unintended.

We should create the rule to prevent this

Pull request:
<!-- A similar PR may already be submitted! -->
<!-- Please search among the [Pull requests](https://github.com/detekt/detekt/pulls) before creating one. -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. Link to relevant issues if possible. -->

<!-- For more information, see the [CONTRIBUTING guide](https://github.com/detekt/detekt/blob/main/.github/CONTRIBUTING.md). -->

This forbids usages of `BigDecimal` double constructor since it will almost never be the intended behavior of the user. Instead of calling `new BigDecimal(double)`, users should use `BigDecimal.valueOf(double)` or just `BigDecimal(string)`.
 
Resolves #6704

Patch:
diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml
index 97380622b49a..b8a965daa322 100644
--- a/detekt-core/src/main/resources/default-detekt-config.yml
+++ b/detekt-core/src/main/resources/default-detekt-config.yml
@@ -608,6 +608,8 @@ style:
         value: 'kotlin.io.print'
       - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
         value: 'kotlin.io.println'
+      - reason: 'using `BigDecimal.<init>(kotlin.Double)` can result in unexpected float point precision behavior. Use `BigDecimal.valueOf(kotlin.Double)` or `BigDecimal.<init>(kotlin.String)` instead.'
+        value: 'java.math.BigDecimal.<init>(kotlin.Double)'
   ForbiddenSuppress:
     active: false
     rules: []
diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCall.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCall.kt
index c8aaa289115c..a5fadbb15203 100644
--- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCall.kt
+++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCall.kt
@@ -68,6 +68,9 @@ class ForbiddenMethodCall(config: Config = Config.empty) : Rule(config) {
         valuesWithReason(
             "kotlin.io.print" to "print does not allow you to configure the output stream. Use a logger instead.",
             "kotlin.io.println" to "println does not allow you to configure the output stream. Use a logger instead.",
+            "java.math.BigDecimal.<init>(kotlin.Double)" to "using `BigDecimal.<init>(kotlin.Double)` can result in " +
+                "unexpected float point precision behavior. Use `BigDecimal.valueOf(kotlin.Double)` or " +
+                "`BigDecimal.<init>(kotlin.String)` instead.",
         )
     ) { list ->
         list.map { Forbidden(fromFunctionSignature(it.value), it.reason) }


Test patch:
diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCallSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCallSpec.kt
index 431fecd1742f..2a76f079cf02 100644
--- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCallSpec.kt
+++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenMethodCallSpec.kt
@@ -741,6 +741,39 @@ class ForbiddenMethodCallSpec(val env: KotlinCoreEnvironment) {
                 ).compileAndLintWithContext(env, code)
                 assertThat(findings).hasSize(1)
             }
+
+            @Test
+            fun `should report BigDecimal double constructor usages by default`() {
+                val code = """
+                    import java.math.BigDecimal
+
+                    val x = BigDecimal(3.14)
+                """.trimIndent()
+                val findings = ForbiddenMethodCall(TestConfig()).compileAndLintWithContext(env, code)
+                assertThat(findings).hasSize(1)
+            }
+
+            @Test
+            fun `should report nothing when using BigDecimal string constructor`() {
+                val code = """
+                    import java.math.BigDecimal
+
+                    val x = BigDecimal("3.14")
+                """.trimIndent()
+                val findings = ForbiddenMethodCall(TestConfig()).compileAndLintWithContext(env, code)
+                assertThat(findings).isEmpty()
+            }
+
+            @Test
+            fun `should report nothing when using BigDecimal int constructor`() {
+                val code = """
+                    import java.math.BigDecimal
+
+                    val x = BigDecimal(3)
+                """.trimIndent()
+                val findings = ForbiddenMethodCall(TestConfig()).compileAndLintWithContext(env, code)
+                assertThat(findings).isEmpty()
+            }
         }
     }
 }