Repository: detekt/detekt (kotlin)

Issue:
`Entity` requires a hand-implemented `toString`, because it's part of `CodeSmell.toString`
## Steps to Reproduce
Put `findings.toString()` anywhere, e.g. test results HTML.

## Expected Behavior
`.toString()` works as before.

I think it should be considered public API and be tested.

## Observed Behavior
```
Found findings:
CodeSmell(issue=Issue(id='UptightFun'), entity=io.gitlab.arturbosch.detekt.api.Entity@5b88b8e, message=Bad code!, references=[], severity=ERROR, id='UptightFun')
CodeSmell(issue=Issue(id='UptightFun'), entity=io.gitlab.arturbosch.detekt.api.Entity@58253c57, message=Bad code!, references=[], severity=ERROR, id='UptightFun')
```
There's no way to know what that entity is without actually debugging the code, or writing a good amount of code to generate external toStrings.

## Context
This change caused the problem: https://github.com/detekt/detekt/pull/6477/files#diff-33451aa64de4e366a232a4eaa18ae7f23aa92e389ca711c85eb3bfb18734d11cR14

cc @3flex @BraisGabin

## Your Environment
<!-- Include as many relevant details about the environment you experienced the bug in -->
* Version of detekt used: `main-SNAPSHOT` (e9423cb)
* Version of Gradle used (if applicable): N/A
* Gradle scan link (add `--scan` option when running the gradle task): N/A
* Operating System and version: N/A
* Link to your project (if it's a public repository): https://github.com/TWiStErRob/net.twisterrob.detekt/pull/11/checks?check_run_id=19158201624
Example: `fails on multiple findings() (net.twisterrob.detekt.testing.VerifyTest$verifyNoFindings function) failed`

Pull request:
* Fixes #6665

See reasoning in #6665.

Patch:
diff --git a/detekt-api/api/detekt-api.api b/detekt-api/api/detekt-api.api
index 06263644c122..e366f16666c7 100644
--- a/detekt-api/api/detekt-api.api
+++ b/detekt-api/api/detekt-api.api
@@ -174,6 +174,7 @@ public final class io/gitlab/arturbosch/detekt/api/Entity : io/gitlab/arturbosch
 	public final fun getLocation ()Lio/gitlab/arturbosch/detekt/api/Location;
 	public final fun getName ()Ljava/lang/String;
 	public final fun getSignature ()Ljava/lang/String;
+	public fun toString ()Ljava/lang/String;
 }
 
 public final class io/gitlab/arturbosch/detekt/api/Entity$Companion {
@@ -266,6 +267,7 @@ public final class io/gitlab/arturbosch/detekt/api/Location : io/gitlab/arturbos
 	public final fun getFilePath ()Lio/github/detekt/psi/FilePath;
 	public final fun getSource ()Lio/gitlab/arturbosch/detekt/api/SourceLocation;
 	public final fun getText ()Lio/gitlab/arturbosch/detekt/api/TextLocation;
+	public fun toString ()Ljava/lang/String;
 }
 
 public final class io/gitlab/arturbosch/detekt/api/Location$Companion {
diff --git a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmell.kt b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmell.kt
index ffcad855550c..3c89ad0711be 100644
--- a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmell.kt
+++ b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmell.kt
@@ -38,7 +38,7 @@ open class CodeSmell(
 }
 
 /**
- * Represents a code smell for that can be auto corrected.
+ * Represents a code smell that can be auto-corrected.
  *
  * @see CodeSmell
  */
@@ -56,7 +56,7 @@ open class CorrectableCodeSmell(
 ) {
     override fun toString(): String {
         return "CorrectableCodeSmell(" +
-            "autoCorrectEnabled=$autoCorrectEnabled," +
+            "autoCorrectEnabled=$autoCorrectEnabled, " +
             "issue=$issue, " +
             "entity=$entity, " +
             "message=$message, " +
diff --git a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Entity.kt b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Entity.kt
index 93651f2e22a9..18a249bcace7 100644
--- a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Entity.kt
+++ b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Entity.kt
@@ -20,6 +20,9 @@ class Entity(
 
     override fun compact(): String = "[$name] at ${location.compact()}"
 
+    override fun toString(): String =
+        "Entity(name=$name, signature=$signature, location=$location, ktElement=$ktElement)"
+
     companion object {
         /**
          * Factory function which retrieves all needed information from the PsiElement itself.
diff --git a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Location.kt b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Location.kt
index 374e2192d3ca..2b206e3ce004 100644
--- a/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Location.kt
+++ b/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/Location.kt
@@ -23,6 +23,9 @@ class Location(
 
     override fun compact(): String = "${filePath.absolutePath}:$source"
 
+    override fun toString(): String =
+        "Location(source=$source, endSource=$endSource, text=$text, filePath=$filePath)"
+
     companion object {
         /**
          * Creates a [Location] from a [PsiElement].
diff --git a/detekt-psi-utils/api/detekt-psi-utils.api b/detekt-psi-utils/api/detekt-psi-utils.api
index f8373dd4df48..d546295a0ecc 100644
--- a/detekt-psi-utils/api/detekt-psi-utils.api
+++ b/detekt-psi-utils/api/detekt-psi-utils.api
@@ -5,6 +5,7 @@ public final class io/github/detekt/psi/FilePath {
 	public final fun getAbsolutePath ()Ljava/nio/file/Path;
 	public final fun getBasePath ()Ljava/nio/file/Path;
 	public final fun getRelativePath ()Ljava/nio/file/Path;
+	public fun toString ()Ljava/lang/String;
 }
 
 public final class io/github/detekt/psi/FilePath$Companion {
diff --git a/detekt-psi-utils/src/main/kotlin/io/github/detekt/psi/KtFiles.kt b/detekt-psi-utils/src/main/kotlin/io/github/detekt/psi/KtFiles.kt
index 1407b88b8290..1c434e58db55 100644
--- a/detekt-psi-utils/src/main/kotlin/io/github/detekt/psi/KtFiles.kt
+++ b/detekt-psi-utils/src/main/kotlin/io/github/detekt/psi/KtFiles.kt
@@ -55,6 +55,9 @@ class FilePath(
         }
     }
 
+    override fun toString(): String =
+        "FilePath(absolutePath=$absolutePath, basePath=$basePath, relativePath=$relativePath)"
+
     companion object {
         fun fromAbsolute(path: Path) = FilePath(absolutePath = path.normalize())
         fun fromRelative(basePath: Path, relativePath: Path) = FilePath(


Test patch:
diff --git a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmellSpec.kt b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmellSpec.kt
new file mode 100644
index 000000000000..72f7d770b708
--- /dev/null
+++ b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CodeSmellSpec.kt
@@ -0,0 +1,23 @@
+package io.gitlab.arturbosch.detekt.api
+
+import io.gitlab.arturbosch.detekt.test.createFindingForRelativePath
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import java.io.File
+
+class CodeSmellSpec {
+
+    @Test
+    fun `toString contains all information`() {
+        val codeSmell: CodeSmell = createFindingForRelativePath()
+        val basePath = "${File.separator}Users${File.separator}tester${File.separator}detekt"
+
+        assertThat(codeSmell.toString()).isEqualTo(
+            "CodeSmell(issue=Issue(id='TestSmell'), entity=Entity(name=TestEntity, signature=TestEntitySignature, " +
+                "location=Location(source=1:1, endSource=1:1, text=0:0, " +
+                "filePath=FilePath(absolutePath=$basePath${File.separator}TestFile.kt, " +
+                "basePath=$basePath, relativePath=TestFile.kt)), ktElement=null), message=TestMessage, " +
+                "references=[], severity=ERROR, id='TestSmell')"
+        )
+    }
+}
diff --git a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CorrectableCodeSmellSpec.kt b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CorrectableCodeSmellSpec.kt
new file mode 100644
index 000000000000..e141ebaeb040
--- /dev/null
+++ b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/CorrectableCodeSmellSpec.kt
@@ -0,0 +1,21 @@
+package io.gitlab.arturbosch.detekt.api
+
+import io.gitlab.arturbosch.detekt.test.createCorrectableFinding
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class CorrectableCodeSmellSpec {
+
+    @Test
+    fun `toString contains all information`() {
+        val codeSmell: CorrectableCodeSmell = createCorrectableFinding()
+
+        assertThat(codeSmell.toString()).isEqualTo(
+            "CorrectableCodeSmell(autoCorrectEnabled=true, issue=Issue(id='TestSmell'), " +
+                "entity=Entity(name=TestEntity, signature=TestEntitySignature, " +
+                "location=Location(source=1:1, endSource=1:1, text=0:0, " +
+                "filePath=FilePath(absolutePath=TestFile.kt, basePath=null, relativePath=null)), " +
+                "ktElement=null), message=TestMessage, references=[], severity=ERROR, id='TestSmell')"
+        )
+    }
+}
diff --git a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/EntitySpec.kt b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/EntitySpec.kt
index 3c1d033a2ea7..906e1b0294aa 100644
--- a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/EntitySpec.kt
+++ b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/EntitySpec.kt
@@ -9,10 +9,14 @@ import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
 import org.junit.jupiter.api.Nested
 import org.junit.jupiter.api.Test
 import kotlin.io.path.Path
+import kotlin.io.path.relativeToOrSelf
+import kotlin.io.path.toPath
 
 class EntitySpec {
 
     private val path = Path("src/test/resources/EntitySpecFixture.kt").toAbsolutePath()
+    private val basePath = EntitySpec::class.java.getResource("/")!!.toURI().toPath()
+    private val relativePath = path.relativeToOrSelf(basePath)
     private val code = compileForTest(path)
 
     @Nested
@@ -43,10 +47,24 @@ class EntitySpec {
             assertThat(Entity.atName(memberFunction).compact())
                 .isEqualTo("[memberFun] at $path:5:17")
         }
+
+        @Test
+        fun `toString gives all details`() {
+            val memberFunction = functions.first { it.name == "memberFun" }
+
+            assertThat(Entity.atName(memberFunction).toString())
+                .isEqualTo(
+                    "Entity(name=memberFun, signature=EntitySpecFixture.kt\$C\$private fun memberFun(): Int, " +
+                        "location=Location(source=5:17, endSource=5:26, text=49:58, " +
+                        "filePath=FilePath(absolutePath=$path, basePath=$basePath, relativePath=$relativePath)), " +
+                        "ktElement=FUN)"
+                )
+        }
     }
 
     @Nested
     inner class Classes {
+
         private val clazz = requireNotNull(code.findDescendantOfType<KtClass>())
 
         @Test
@@ -58,6 +76,17 @@ class EntitySpec {
         fun `includes class name in entity compact`() {
             assertThat(Entity.atName(clazz).compact()).isEqualTo("[C] at $path:3:7")
         }
+
+        @Test
+        fun `toString gives all details`() {
+            assertThat(Entity.atName(clazz).toString())
+                .isEqualTo(
+                    "Entity(name=C, signature=EntitySpecFixture.kt\$C : Any, " +
+                        "location=Location(source=3:7, endSource=3:8, text=20:21, " +
+                        "filePath=FilePath(absolutePath=$path, basePath=$basePath, relativePath=$relativePath)), " +
+                        "ktElement=CLASS)"
+                )
+        }
     }
 
     @Nested
@@ -78,5 +107,16 @@ class EntitySpec {
             assertThat(Entity.from(code).compact()).isEqualTo(expectedResult)
             assertThat(Entity.atPackageOrFirstDecl(code).compact()).isEqualTo(expectedResult)
         }
+
+        @Test
+        fun `toString gives all details`() {
+            assertThat(Entity.from(code).toString())
+                .isEqualTo(
+                    "Entity(name=EntitySpecFixture.kt, signature=EntitySpecFixture.kt\$test.EntitySpecFixture.kt, " +
+                        "location=Location(source=1:1, endSource=9:1, text=0:109, " +
+                        "filePath=FilePath(absolutePath=$path, basePath=$basePath, relativePath=$relativePath)), " +
+                        "ktElement=KtFile: EntitySpecFixture.kt)"
+                )
+        }
     }
 }
diff --git a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/LocationSpec.kt b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/LocationSpec.kt
index b55dda3392bf..614f0d425d76 100644
--- a/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/LocationSpec.kt
+++ b/detekt-api/src/test/kotlin/io/gitlab/arturbosch/detekt/api/LocationSpec.kt
@@ -3,7 +3,10 @@ package io.gitlab.arturbosch.detekt.api
 import io.github.detekt.test.utils.compileContentForTest
 import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.kotlin.psi.KtNamedFunction
+import org.jetbrains.kotlin.psi.KtReturnExpression
+import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
 import org.junit.jupiter.api.Test
+import java.io.File
 
 class LocationSpec {
 
@@ -32,4 +35,20 @@ class LocationSpec {
 
         assertThat("${location.source} - ${location.endSource}").isEqualTo("1:1 - 1:4")
     }
+
+    @Test
+    fun `return keyword's location`() {
+        val code = """
+            fun data(): Int {
+                return 0
+            }
+        """.trimIndent()
+        val psiElement = compileContentForTest(code).findDescendantOfType<KtReturnExpression>()!!
+        val location = Location.from(psiElement.returnKeyword)
+
+        assertThat(location.toString()).isEqualTo(
+            "Location(source=2:5, endSource=2:11, text=22:28, " +
+                "filePath=FilePath(absolutePath=${File.separator}Test.kt, basePath=null, relativePath=null))"
+        )
+    }
 }
diff --git a/detekt-psi-utils/src/test/kotlin/io/github/detekt/psi/KtFilesSpec.kt b/detekt-psi-utils/src/test/kotlin/io/github/detekt/psi/KtFilesSpec.kt
index 4ece96929ff9..e8d3b9c1dffd 100644
--- a/detekt-psi-utils/src/test/kotlin/io/github/detekt/psi/KtFilesSpec.kt
+++ b/detekt-psi-utils/src/test/kotlin/io/github/detekt/psi/KtFilesSpec.kt
@@ -6,6 +6,7 @@ import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.kotlin.com.intellij.psi.PsiFile
 import org.junit.jupiter.api.Nested
 import org.junit.jupiter.api.Test
+import java.io.File
 
 class KtFilesSpec {
 
@@ -48,4 +49,26 @@ class KtFilesSpec {
             every { name } returns filename
         }
     }
+
+    @Nested
+    inner class FilePathSpec {
+
+        // Path separator for the current platform, short name, because lots of usages.
+        private val ps = File.separator
+
+        @Test fun `toString of absolute path`() {
+            val filePath = FilePath.fromAbsolute(File("/a/b/c").toPath())
+
+            assertThat(filePath.toString())
+                .isEqualTo("FilePath(absolutePath=${ps}a${ps}b${ps}c, basePath=null, relativePath=null)")
+        }
+
+        @Test fun `toString of relative path`() {
+            val filePath = FilePath.fromRelative(File("/a/b").toPath(), File("c/d").toPath())
+
+            assertThat(filePath.toString()).isEqualTo(
+                "FilePath(absolutePath=${ps}a${ps}b${ps}c${ps}d, basePath=${ps}a${ps}b, relativePath=c${ps}d)"
+            )
+        }
+    }
 }