Repository: detekt/detekt (kotlin)

Issue:
MagicNumber rule does not trigger for unsigned literals
<!-- The bug you're experiencing might have already be reported! -->
<!-- Please search in the [issues](https://github.com/detekt/detekt/issues) before creating one. -->

## Expected Behavior

The `MagicNumber` rule should be reporting 'magic numbers' written as unsigned number literals (for example, `65520U`).

## Observed Behavior

If my code contains a 'magic number' written as an unsigned literal, the rule does not trigger. Example:

```kotlin
    a = 65520U // does not trigger
    b = 0xFFF0U // also 'fine'
    c = 65520.toUInt() // now it triggers
    d = 0xFFF0.toUInt() // triggers on this one too
```

## Steps to Reproduce
<!-- Provide a link to a live example, or an unambiguous set of steps to -->
<!-- reproduce this bug. Include code to reproduce, if relevant -->

See **Observed Behavior** above.

## Context
<!-- How has this issue affected you? What are you trying to accomplish? -->
<!-- Providing context helps us come up with a solution that is most useful in the real world -->

N/A (should be obvious from the rule's purpose)

## Your Environment
<!-- Include as many relevant details about the environment you experienced the bug in -->
* Version of detekt used: 1.23.8
* Version of Gradle used (if applicable): 8.14.1
* Operating System and version: Windows 11 Pro 24H2

My `detekt.yaml` has this:

```yaml
  MagicNumber:
    ignoreCompanionObjectPropertyDeclaration: false
    ignoreNamedArgument: false
    ignoreExtensionFunctions: false
```

Pull request:
- Add support for detecting unsigned integer literals (U suffix)
- Add support for detecting unsigned long literals (UL suffix)
- Add comprehensive test cases for unsigned literals
- Fix false-negative detection for 65520U, 0xFFF0U, etc.

Fixes #8194

Patch:
diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumber.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumber.kt
index 25e4e8f1303e..c3c8a4e7471c 100644
--- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumber.kt
+++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumber.kt
@@ -173,9 +173,11 @@ class MagicNumber(config: Config) : Rule(
         text.trim()
             .lowercase(Locale.US)
             .replace("_", "")
+            .removeSuffix("ul") // Handle UL suffix (unsigned long)
             .removeSuffix("l")
             .removeSuffix("d")
             .removeSuffix("f")
+            .removeSuffix("u")
 
     private fun KtConstantExpression.isNamedArgument(): Boolean {
         val valueArgument = this.getNonStrictParentOfType<KtValueArgument>()


Test patch:
diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumberSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumberSpec.kt
index f5cd19e6d6cd..0d513081501b 100644
--- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumberSpec.kt
+++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MagicNumberSpec.kt
@@ -1005,4 +1005,216 @@ class MagicNumberSpec {
             assertThat(rule.lint(code)).hasSize(1)
         }
     }
+
+    @Nested
+    inner class `unsigned integer literals` {
+
+        @Test
+        fun `should report unsigned integer literal`() {
+            val code = "val myUInt = 65520U"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 14)
+        }
+
+        @Test
+        fun `should not report unsigned integer literal when ignored`() {
+            val code = "val myUInt = 65520U"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("65520U"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned long literal`() {
+            val code = "val myULong = 65520UL"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 15)
+        }
+
+        @Test
+        fun `should not report unsigned long literal when ignored`() {
+            val code = "val myULong = 65520UL"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("65520UL"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned hex literal`() {
+            val code = "val myUHex = 0xFFF0U"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 14)
+        }
+
+        @Test
+        fun `should not report unsigned hex literal when ignored`() {
+            val code = "val myUHex = 0xFFF0U"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("0xFFF0U"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned hex long literal`() {
+            val code = "val myUHexLong = 0xFFF0UL"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 18)
+        }
+
+        @Test
+        fun `should report unsigned literals in function calls`() {
+            val code = """
+                fun someFunction(x: UInt) {}
+                fun test() { someFunction(65520U) }
+            """.trimIndent()
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(2, 27)
+        }
+
+        @Test
+        fun `should report unsigned literals in array initialization`() {
+            val code = "val array = arrayOf(1U, 2U, 65520U)"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 29)
+        }
+
+        @Test
+        fun `should not report unsigned literals in property declarations by default`() {
+            val code = "val myUInt = 65520U"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 14)
+        }
+
+        @Test
+        fun `should not report unsigned literals in property declarations when ignored`() {
+            val code = "val myUInt = 65520U"
+            val findings = MagicNumber(TestConfig(IGNORE_PROPERTY_DECLARATION to "true")).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report multiple unsigned literals`() {
+            val code = """
+                val a = 65520U
+                val b = 0xFFF0UL
+                val c = 12345U
+            """.trimIndent()
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocations(
+                SourceLocation(1, 9),
+                SourceLocation(2, 9),
+                SourceLocation(3, 9)
+            )
+        }
+
+        @Test
+        fun `should report unsigned integer literal with lowercase u`() {
+            val code = "val myUInt = 65520u"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 14)
+        }
+
+        @Test
+        fun `should not report unsigned integer literal with lowercase u when ignored`() {
+            val code = "val myUInt = 65520u"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("65520u"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned long literal with lowercase uL`() {
+            val code = "val myULong = 65520uL"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 15)
+        }
+
+        @Test
+        fun `should not report unsigned long literal with lowercase uL when ignored`() {
+            val code = "val myULong = 65520uL"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("65520uL"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned hex literal with lowercase u`() {
+            val code = "val myUHex = 0xFFF0u"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 14)
+        }
+
+        @Test
+        fun `should not report unsigned hex literal with lowercase u when ignored`() {
+            val code = "val myUHex = 0xFFF0u"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("0xFFF0u"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report unsigned hex long literal with lowercase uL`() {
+            val code = "val myUHexLong = 0xFFF0uL"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 18)
+        }
+
+        @Test
+        fun `should not report unsigned hex long literal with lowercase uL when ignored`() {
+            val code = "val myUHexLong = 0xFFF0uL"
+            val findings = MagicNumber(TestConfig(IGNORE_NUMBERS to listOf("0xFFF0uL"))).lint(code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `should report lowercase unsigned literals in function calls`() {
+            val code = """
+                fun someFunction(x: UInt) {}
+                fun test() { someFunction(65520u) }
+            """.trimIndent()
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(2, 27)
+        }
+
+        @Test
+        fun `should report lowercase unsigned literals in array initialization`() {
+            val code = "val array = arrayOf(1u, 2u, 65520u)"
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocation(1, 29)
+        }
+
+        @Test
+        fun `should report mixed case unsigned literals`() {
+            val code = """
+                val a = 65520u
+                val b = 0xFFF0uL
+                val c = 12345U
+                val d = 0xABCDUL
+            """.trimIndent()
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocations(
+                SourceLocation(1, 9),
+                SourceLocation(2, 9),
+                SourceLocation(3, 9),
+                SourceLocation(4, 9)
+            )
+        }
+
+        @Test
+        fun `should handle binary literals with lowercase unsigned suffixes`() {
+            val code = """
+                val binary1 = 0b1010u
+                val binary2 = 0b1111uL
+            """.trimIndent()
+            val findings = MagicNumber(Config.empty).lint(code)
+            assertThat(findings).hasStartSourceLocations(
+                SourceLocation(1, 15),
+                SourceLocation(2, 15)
+            )
+        }
+
+        @Test
+        fun `should not report lowercase unsigned literals when ignoring property declarations`() {
+            val code = """
+                val myUInt = 65520u
+                val myULong = 0xFFF0uL
+            """.trimIndent()
+            val findings = MagicNumber(TestConfig(IGNORE_PROPERTY_DECLARATION to "true")).lint(code)
+            assertThat(findings).isEmpty()
+        }
+    }
 }