diff --git a/.github/workflows/detekt-with-type-resolution.yaml b/.github/workflows/detekt-with-type-resolution.yaml index 5a570999a4cb..94981df76b5a 100644 --- a/.github/workflows/detekt-with-type-resolution.yaml +++ b/.github/workflows/detekt-with-type-resolution.yaml @@ -37,7 +37,7 @@ jobs: arguments: :detekt-cli:runWithArgsFile - name: Upload SARIF to GitHub using the upload-sarif action - uses: github/codeql-action/upload-sarif@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2 + uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2 if: success() || failure() with: sarif_file: build/detekt-report.sarif @@ -65,7 +65,7 @@ jobs: arguments: detektMain detektTest detektFunctionalTest detektTestFixtures detektReportMergeSarif --continue - name: Upload SARIF to GitHub using the upload-sarif action - uses: github/codeql-action/upload-sarif@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2 + uses: github/codeql-action/upload-sarif@0225834cc549ee0ca93cb085b92954821a145866 # v2 if: success() || failure() with: sarif_file: build/reports/detekt/merge.sarif diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 2276603083af..a10738d2140b 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -10,6 +10,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b # v4 + - uses: actions/labeler@0776a679364a9a16110aac8d0f40f5e11009e327 # v4 with: repo-token: "${{ secrets.DETEKT_CI_GITHUB_USER_TOKEN }}" diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 8e14f4013a4e..548c2ccde574 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -48,7 +48,7 @@ jobs: run: yarn build - name: Deploy GitHub Pages (only on main) - uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 # tag=v4 + uses: JamesIves/github-pages-deploy-action@22a6ee251d6f13c6ab1ecb200d974f1a6feb1b8d # v4 if: github.event_name == 'push' && github.ref == 'refs/heads/main' with: branch: gh-pages diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index ff1ff80017c1..31637b3a0e27 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget object Versions { - const val DETEKT: String = "1.23.0-RC3" + const val DETEKT: String = "1.23.0" const val SNAPSHOT_NAME: String = "main" val JVM_TARGET: JvmTarget = JvmTarget.JVM_1_8 diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index bd2faadadc52..464c833b16fd 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -155,12 +155,17 @@ style: active: true ForbiddenComment: active: true - values: - - 'TODO:' - - 'FIXME:' - - 'STOPSHIP:' - - '@author' - - '@requiresTypeResolution' + comments: + - value: 'FIXME:' + reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + - value: 'STOPSHIP:' + reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + - value: 'TODO:' + reason: 'Forbidden TODO todo marker in comment, please do the changes.' + - value: '@author' + reason: 'Authors are not recorded in KDoc.' + - value: '@requiresTypeResolution' + reason: 'Use @RequiresTypeResolution annotation on the class instead.' excludes: ['**/detekt-rules-style/**/ForbiddenComment.kt'] ForbiddenImport: active: true diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 1e76fd53b4f9..9a38d539d4e6 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -572,12 +572,14 @@ style: value: 'java.lang.annotation.Inherited' ForbiddenComment: active: true - values: - - 'FIXME:' - - 'STOPSHIP:' - - 'TODO:' + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' allowedPatterns: '' - customMessage: '' ForbiddenImport: active: false imports: [] diff --git a/detekt-core/src/main/resources/deprecation.properties b/detekt-core/src/main/resources/deprecation.properties index ff63eaade672..68db34364f71 100644 --- a/detekt-core/src/main/resources/deprecation.properties +++ b/detekt-core/src/main/resources/deprecation.properties @@ -15,6 +15,8 @@ potential-bugs>IgnoredReturnValue>restrictToAnnotatedMethods=Use `restrictToConf potential-bugs>LateinitUsage>excludeAnnotatedProperties=Use `ignoreAnnotated` instead potential-bugs>MissingWhenCase=Rule deprecated as compiler performs this check by default potential-bugs>RedundantElseInWhen=Rule deprecated as compiler performs this check by default +style>ForbiddenComment>customMessage=Use `comments` and provide `reason` against each `value`. +style>ForbiddenComment>values=Use `comments` instead, make sure you escape your text for Regular Expressions. style>ForbiddenPublicDataClass=Rule migrated to `libraries` ruleset plugin style>FunctionOnlyReturningConstant>excludeAnnotatedFunction=Use `ignoreAnnotated` instead style>LibraryCodeMustSpecifyReturnType=Rule migrated to `libraries` ruleset plugin diff --git a/detekt-core/src/test/resources/cases/ComplexClass.kt b/detekt-core/src/test/resources/cases/ComplexClass.kt deleted file mode 100644 index fb036beebaca..000000000000 --- a/detekt-core/src/test/resources/cases/ComplexClass.kt +++ /dev/null @@ -1,145 +0,0 @@ -package cases - -import org.jetbrains.kotlin.utils.sure - -@Suppress("unused") -class ComplexClass {// McCabe: 44, LLOC: 20 + 20 + 4x4 - - class NestedClass { //14 - fun complex() { //1 + - try {//4 - while (true) { - if (true) { - when ("string") { - "" -> println() - else -> println() - } - } - } - } catch (ex: Exception) { //1 + 3 - try { - println() - } catch (ex: Exception) { - while (true) { - if (false) { - println() - } else { - println() - } - } - } - } finally { // 3 - try { - println() - } catch (ex: Exception) { - while (true) { - if (false) { - println() - } else { - println() - } - } - } - } - (1..10).forEach { - //1 - println() - } - for (i in 1..10) { //1 - println() - } - } - } - - fun complex() { //1 + - try {//4 - while (true) { - if (true) { - when ("string") { - "" -> println() - else -> println() - } - } - } - } catch (ex: Exception) { //1 + 3 - try { - println() - } catch (ex: Exception) { - while (true) { - if (false) { - println() - } else { - println() - } - } - } - } finally { // 3 - try { - println() - } catch (ex: Exception) { - while (true) { - if (false) { - println() - } else { - println() - } - } - } - } - (1..10).forEach { - //1 - println() - } - for (i in 1..10) { //1 - println() - } - } - - fun manyClosures() {//4 - true.let { - true.apply { - true.run { - true.sure { - "" - } - } - } - } - } - - fun manyClosures2() {//4 - true.let { - true.apply { - true.run { - true.sure { - "" - } - } - } - } - } - - fun manyClosures3() {//4 - true.let { - true.apply { - true.run { - true.sure { - "" - } - } - } - } - } - - fun manyClosures4() {//4 - true.let { - true.apply { - true.run { - true.sure { - "" - } - } - } - } - } -} diff --git a/detekt-formatting/src/main/kotlin/io/gitlab/arturbosch/detekt/formatting/wrappers/NoSingleLineBlockComment.kt b/detekt-formatting/src/main/kotlin/io/gitlab/arturbosch/detekt/formatting/wrappers/NoSingleLineBlockComment.kt new file mode 100644 index 000000000000..142fdfa671a4 --- /dev/null +++ b/detekt-formatting/src/main/kotlin/io/gitlab/arturbosch/detekt/formatting/wrappers/NoSingleLineBlockComment.kt @@ -0,0 +1,28 @@ +package io.gitlab.arturbosch.detekt.formatting.wrappers + +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY +import com.pinterest.ktlint.ruleset.standard.rules.NoSingleLineBlockCommentRule +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.config +import io.gitlab.arturbosch.detekt.api.internal.AutoCorrectable +import io.gitlab.arturbosch.detekt.api.internal.Configuration +import io.gitlab.arturbosch.detekt.formatting.FormattingRule + +/** + * See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#no-single-line-block-comments) for documentation. + */ +@AutoCorrectable(since = "1.23.0") +class NoSingleLineBlockComment(config: Config) : FormattingRule(config) { + + override val wrapping = NoSingleLineBlockCommentRule() + override val issue = issueFor("Reports single block line comments") + + @Configuration("indentation size") + private val indentSize by config(4) + + override fun overrideEditorConfigProperties(): Map, String> = + mapOf( + INDENT_SIZE_PROPERTY to indentSize.toString(), + ) +} diff --git a/detekt-formatting/src/main/resources/config/config.yml b/detekt-formatting/src/main/resources/config/config.yml index b5f363076813..45aa01a1ced5 100644 --- a/detekt-formatting/src/main/resources/config/config.yml +++ b/detekt-formatting/src/main/resources/config/config.yml @@ -147,6 +147,10 @@ formatting: NoSemicolons: active: true autoCorrect: true + NoSingleLineBlockComment: + active: false + autoCorrect: true + indentSize: 4 NoTrailingSpaces: active: true autoCorrect: true diff --git a/detekt-formatting/src/test/kotlin/io/gitlab/arturbosch/detekt/formatting/NoSingleLineBlockCommentSpec.kt b/detekt-formatting/src/test/kotlin/io/gitlab/arturbosch/detekt/formatting/NoSingleLineBlockCommentSpec.kt new file mode 100644 index 000000000000..6c1f90eb45f0 --- /dev/null +++ b/detekt-formatting/src/test/kotlin/io/gitlab/arturbosch/detekt/formatting/NoSingleLineBlockCommentSpec.kt @@ -0,0 +1,37 @@ +package io.gitlab.arturbosch.detekt.formatting + +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.formatting.wrappers.CommentWrapping +import io.gitlab.arturbosch.detekt.formatting.wrappers.NoSingleLineBlockComment +import io.gitlab.arturbosch.detekt.test.assertThat +import org.junit.jupiter.api.Test + +class NoSingleLineBlockCommentSpec { + @Test + fun `Given a single documentation comment that start starts and end on a same line`() { + val code = """ + /** Some comment */ + """.trimIndent() + assertThat(NoSingleLineBlockComment(Config.empty).lint(code)).isEmpty() + } + + @Test + fun `Given a single block comment that start starts and end on a same line`() { + val code = """ + /* Some comment */ + """.trimIndent() + assertThat(NoSingleLineBlockComment(Config.empty).lint(code)).hasSize(1) + } + + @Test + fun `Given a block comment followed by a code element on the same line as the block`() { + val code = """ + /* Some comment 1 */ val foo1 = "foo1" + /* Some comment 2 */val foo2 = "foo2" + /* Some comment 3 */ fun foo3() = "foo3" + /* Some comment 4 */fun foo4() = "foo4" + """.trimIndent() + + assertThat(CommentWrapping(Config.empty).lint(code)).hasSize(4) + } +} diff --git a/detekt-gradle-plugin/build.gradle.kts b/detekt-gradle-plugin/build.gradle.kts index dc1f4fa9e70b..d22ca2c6be2b 100644 --- a/detekt-gradle-plugin/build.gradle.kts +++ b/detekt-gradle-plugin/build.gradle.kts @@ -12,7 +12,7 @@ plugins { idea alias(libs.plugins.pluginPublishing) // We use this published version of the detekt plugin to self analyse this project. - id("io.gitlab.arturbosch.detekt") version "1.22.0" + id("io.gitlab.arturbosch.detekt") version "1.23.0" } repositories { @@ -70,16 +70,13 @@ dependencies { compileOnly(libs.kotlin.gradle) compileOnly(libs.kotlin.gradlePluginApi) testFixturesCompileOnly("org.jetbrains:annotations:24.0.1") - implementation(libs.sarif4k) { - exclude("org.jetbrains.kotlin") - } - compileOnly("io.gitlab.arturbosch.detekt:detekt-cli:1.22.0") + compileOnly("io.gitlab.arturbosch.detekt:detekt-cli:1.23.0") testKitRuntimeOnly(libs.kotlin.gradle) testKitJava17RuntimeOnly(libs.android.gradle.maxSupported) // We use this published version of the detekt-formatting to self analyse this project. - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.0") } gradlePlugin { diff --git a/detekt-gradle-plugin/settings.gradle.kts b/detekt-gradle-plugin/settings.gradle.kts index c2318686c948..832e85313d10 100644 --- a/detekt-gradle-plugin/settings.gradle.kts +++ b/detekt-gradle-plugin/settings.gradle.kts @@ -13,5 +13,5 @@ dependencyResolutionManagement { } plugins { - id("com.gradle.enterprise") version "3.13.2" + id("com.gradle.enterprise") version "3.13.3" } diff --git a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/report/SarifReportMerger.kt b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/report/SarifReportMerger.kt index c3c65d4f9ac4..fbd1851d4494 100644 --- a/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/report/SarifReportMerger.kt +++ b/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/report/SarifReportMerger.kt @@ -1,8 +1,11 @@ package io.gitlab.arturbosch.detekt.report -import io.github.detekt.sarif4k.SarifSerializer +import groovy.json.JsonOutput +import groovy.json.JsonSlurper import java.io.File +private typealias JsonObject = MutableMap + /** * A naive implementation to merge SARIF assuming all inputs are written by detekt. */ @@ -10,10 +13,22 @@ object SarifReportMerger { fun merge(inputs: Collection, output: File) { val sarifs = inputs.filter { it.exists() }.map { - SarifSerializer.fromJson(it.readText()) + @Suppress("UNCHECKED_CAST") + (JsonSlurper().parse(it) as JsonObject) } val mergedResults = sarifs.flatMap { it.runs.single().results.orEmpty() } - val mergedSarif = sarifs[0].copy(runs = listOf(sarifs[0].runs.single().copy(results = mergedResults))) - output.writeText(SarifSerializer.toJson(mergedSarif)) + val mergedSarif = sarifs[0].apply { this.runs.single().results = mergedResults } + output.writeText(JsonOutput.prettyPrint(JsonOutput.toJson(mergedSarif))) } } + +private val JsonObject.runs: List + @Suppress("UNCHECKED_CAST") + get() = this["runs"] as List + +private var JsonObject.results: List? + @Suppress("UNCHECKED_CAST") + get() = this["results"] as List? + set(value) { + this["results"] = value + } diff --git a/detekt-gradle-plugin/src/test/resources/output.sarif.json b/detekt-gradle-plugin/src/test/resources/output.sarif.json index b5de5acc309d..ef653c62a105 100644 --- a/detekt-gradle-plugin/src/test/resources/output.sarif.json +++ b/detekt-gradle-plugin/src/test/resources/output.sarif.json @@ -1,156 +1,157 @@ { - "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", - "version": "2.1.0", - "runs": [ - { - "originalUriBaseIds": { - "%SRCROOT%": { - "uri": "file:///Users/tester/detekt/" - } - }, - "results": [ + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [ { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" - }, - "region": { - "startColumn": 1, - "startLine": 1 + "originalUriBaseIds": { + "%SRCROOT%": { + "uri": "file:///Users/tester/detekt/" } - } - } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellA.TestSmellA" - }, - { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" + }, + "results": [ + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellA.TestSmellA" }, - "region": { - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellB.TestSmellB" - }, - { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellB.TestSmellB" }, - "region": { - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellC.TestSmellC" - }, - { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellC.TestSmellC" }, - "region": { - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellD.TestSmellD" - }, - { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellD.TestSmellD" }, - "region": { - "startColumn": 1, - "startLine": 1 - } - } - } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellE.TestSmellE" - }, - { - "level": "warning", - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "TestFile.kt", - "uriBaseId": "%SRCROOT%" + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellE.TestSmellE" }, - "region": { - "startColumn": 1, - "startLine": 1 + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestFile.kt", + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "TestMessage" + }, + "ruleId": "detekt.TestSmellF.TestSmellF" + } + ], + "tool": { + "driver": { + "downloadUri": "https://github.com/detekt/detekt/releases/download/v1.0.0/detekt", + "fullName": "detekt", + "guid": "022ca8c2-f6a2-4c95-b107-bb72c43263f3", + "informationUri": "https://detekt.dev", + "language": "en", + "name": "detekt", + "organization": "detekt", + "rules": [ + + ], + "semanticVersion": "1.0.0", + "version": "1.0.0" } - } } - ], - "message": { - "text": "TestMessage" - }, - "ruleId": "detekt.TestSmellF.TestSmellF" - } - ], - "tool": { - "driver": { - "downloadUri": "https://github.com/detekt/detekt/releases/download/v1.0.0/detekt", - "fullName": "detekt", - "guid": "022ca8c2-f6a2-4c95-b107-bb72c43263f3", - "informationUri": "https://detekt.dev", - "language": "en", - "name": "detekt", - "organization": "detekt", - "rules": [ - ], - "semanticVersion": "1.0.0", - "version": "1.0.0" } - } - } - ] + ] } \ No newline at end of file diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt index 3549386ad0a7..5ec25d2804fa 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.complexity -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.api.SourceLocation import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat @@ -117,7 +116,56 @@ class CyclomaticComplexMethodSpec { @Nested inner class `several complex methods` { - val path = resourceAsPath("ComplexMethods.kt") + val code = """ + // reports 1 - only if ignoreSingleWhenExpression = false + fun complexMethodWithSingleWhen1(i: Int) = + when (i) { + 1 -> print("one") + 2 -> print("two") + 3 -> print("three") + else -> print(i) + } + + // reports 1 - only if ignoreSingleWhenExpression = false + fun complexMethodWithSingleWhen2(i: Int) { + when (i) { + 1 -> print("one") + 2 -> print("two") + 3 -> print("three") + else -> print(i) + } + } + + // reports 1 - only if ignoreSingleWhenExpression = false + fun complexMethodWithSingleWhen3(i: Int): String { + return when (i) { + 1 -> "one" + 2 -> "two" + 3 -> "three" + else -> "" + } + } + + // reports 1 - only if ignoreSingleWhenExpression = false + fun complexMethodWithSingleWhen4(i: Int) = when (i) { + 1 -> "one" + 2 -> "two" + 3 -> "three" + else -> "" + } + + // reports 1 + fun complexMethodWith2Statements(i: Int) { + when (i) { + 1 -> print("one") + 2 -> print("two") + 3 -> print("three") + else -> print(i) + } + if (i == 1) { + } + } + """.trimIndent() @Test fun `does not report complex methods with a single when expression`() { @@ -127,7 +175,7 @@ class CyclomaticComplexMethodSpec { ) val subject = CyclomaticComplexMethod(config) - assertThat(subject.lint(path)).hasStartSourceLocations(SourceLocation(43, 5)) + assertThat(subject.lint(code)).hasStartSourceLocations(SourceLocation(39, 5)) } @Test @@ -135,12 +183,12 @@ class CyclomaticComplexMethodSpec { val config = TestConfig("threshold" to "4") val subject = CyclomaticComplexMethod(config) - assertThat(subject.lint(path)).hasStartSourceLocations( - SourceLocation(6, 5), - SourceLocation(15, 5), - SourceLocation(25, 5), - SourceLocation(35, 5), - SourceLocation(43, 5) + assertThat(subject.lint(code)).hasStartSourceLocations( + SourceLocation(2, 5), + SourceLocation(11, 5), + SourceLocation(21, 5), + SourceLocation(31, 5), + SourceLocation(39, 5) ) } diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/LargeClassSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/LargeClassSpec.kt index 7bc3df149625..7aad1cc724d4 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/LargeClassSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/LargeClassSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.complexity -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.api.SourceLocation import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat @@ -13,10 +12,34 @@ private fun subject(threshold: Int) = LargeClass(TestConfig("threshold" to thres class LargeClassSpec { @Test - fun `should detect only the nested large class which exceeds threshold 70`() { - val findings = subject(threshold = 70).lint(resourceAsPath("NestedClasses.kt")) + fun `should detect only the nested large class which exceeds the threshold`() { + val code = """ + class NestedClasses { + + private val i = 0 + + class InnerClass { + + class NestedInnerClass { + + fun nestedMethod() { + fun nestedLocalMethod() { + println() + } + nestedLocalMethod() + } + } + } + } + + /** + * Top level members must be skipped for LargeClass rule + */ + val aTopLevelPropertyOfNestedClasses = 0 + """.trimIndent() + val findings = subject(threshold = 4).lint(code) assertThat(findings).hasSize(1) - assertThat(findings).hasStartSourceLocations(SourceLocation(12, 15)) + assertThat(findings).hasStartSourceLocations(SourceLocation(7, 15)) } @Test diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/NestedBlockDepthSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/NestedBlockDepthSpec.kt index 6462109ba7bf..3ab7b5106049 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/NestedBlockDepthSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/NestedBlockDepthSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.complexity -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat @@ -16,8 +15,48 @@ class NestedBlockDepthSpec { val subject = NestedBlockDepth(defaultConfig) @Test - fun `should detect only the nested large class`() { - subject.lint(resourceAsPath("NestedClasses.kt")) + fun `should ignore class nesting levels`() { + val code = """ + class NestedClasses { + + class InnerClass { + + class NestedInnerClass { + + fun nestedLongMethod() { + if (true) { + if (true) { + if (true) { + 5.run { + this.let { + listOf(1, 2, 3).map { it * 2 } + .groupBy(Int::toString, Int::toString) + } + } + } + } + } + + try { + for (i in 1..5) { + when (i) { + 1 -> print(1) + } + } + } finally { + + } + + fun nestedLocalMethod() { + println() + } + nestedLocalMethod() + } + } + } + } + """.trimIndent() + subject.lint(code) assertThat(subject.findings).hasSize(1) assertThat((subject.findings[0] as ThresholdedCodeSmell).value).isEqualTo(5) } diff --git a/detekt-rules-complexity/src/test/resources/ComplexMethods.kt b/detekt-rules-complexity/src/test/resources/ComplexMethods.kt deleted file mode 100644 index 56679186d5fa..000000000000 --- a/detekt-rules-complexity/src/test/resources/ComplexMethods.kt +++ /dev/null @@ -1,52 +0,0 @@ -@file:Suppress("unused") - -package cases - -// reports 1 - only if ignoreSingleWhenExpression = false -fun complexMethodWithSingleWhen1(i: Int) = - when (i) { - 1 -> print("one") - 2 -> print("two") - 3 -> print("three") - else -> print(i) - } - -// reports 1 - only if ignoreSingleWhenExpression = false -fun complexMethodWithSingleWhen2(i: Int) { - when (i) { - 1 -> print("one") - 2 -> print("two") - 3 -> print("three") - else -> print(i) - } -} - -// reports 1 - only if ignoreSingleWhenExpression = false -fun complexMethodWithSingleWhen3(i: Int): String { - return when (i) { - 1 -> "one" - 2 -> "two" - 3 -> "three" - else -> "" - } -} - -// reports 1 - only if ignoreSingleWhenExpression = false -fun complexMethodWithSingleWhen4(i: Int) = when (i) { - 1 -> "one" - 2 -> "two" - 3 -> "three" - else -> "" -} - -// reports 1 -fun complexMethodWith2Statements(i: Int) { - when (i) { - 1 -> print("one") - 2 -> print("two") - 3 -> print("three") - else -> print(i) - } - if (i == 1) { - } -} diff --git a/detekt-rules-complexity/src/test/resources/NestedClasses.kt b/detekt-rules-complexity/src/test/resources/NestedClasses.kt deleted file mode 100644 index 820d23a37d04..000000000000 --- a/detekt-rules-complexity/src/test/resources/NestedClasses.kt +++ /dev/null @@ -1,107 +0,0 @@ -@file:Suppress("EqualsOrHashCode", "ConstantConditionIf") - -package cases - -@Suppress("unused") -class NestedClasses { - - private val i = 0 - - class InnerClass { - - class NestedInnerClass { - - fun nestedLongMethod() { - if (true) { - if (true) { - if (true) { - 5.run { - this.let { - listOf(1, 2, 3).map { it * 2 } - .groupBy(Int::toString, Int::toString) - } - } - } - } - } - - try { - for (i in 1..5) { - when (i) { - 1 -> print(1) - } - } - } finally { - - } - - fun nestedLocalMethod() { - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - println() - } - nestedLocalMethod() - } - } - } - -} - -@Suppress("unused") - /** - * Top level members must be skipped for LargeClass rule - */ -val aTopLevelPropertyOfNestedClasses = 0 diff --git a/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/AbsentOrWrongFileLicenseSpec.kt b/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/AbsentOrWrongFileLicenseSpec.kt index 4faeb695c027..7657706b8315 100644 --- a/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/AbsentOrWrongFileLicenseSpec.kt +++ b/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/AbsentOrWrongFileLicenseSpec.kt @@ -10,6 +10,7 @@ import io.gitlab.arturbosch.detekt.api.UnstableApi import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.lint import io.gitlab.arturbosch.detekt.test.yamlConfig +import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.resolve.BindingContext import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -181,7 +182,7 @@ class AbsentOrWrongFileLicenseSpec { } @OptIn(UnstableApi::class) -private fun checkLicence(content: String, isRegexLicense: Boolean = false): List { +private fun checkLicence(@Language("kotlin") content: String, isRegexLicense: Boolean = false): List { val file = compileContentForTest(content.trimIndent()) val configFileName = if (isRegexLicense) "license-config-regex.yml" else "license-config.yml" diff --git a/detekt-rules-empty/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/empty/EmptyCodeSpec.kt b/detekt-rules-empty/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/empty/EmptyCodeSpec.kt index 3021fab6147d..2304501dc287 100644 --- a/detekt-rules-empty/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/empty/EmptyCodeSpec.kt +++ b/detekt-rules-empty/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/empty/EmptyCodeSpec.kt @@ -1,7 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.empty -import io.github.detekt.test.utils.compileForTest -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.api.Config import io.gitlab.arturbosch.detekt.api.Rule import io.gitlab.arturbosch.detekt.test.TestConfig @@ -154,8 +152,66 @@ class EmptyCodeSpec { } } +// Each Empty* Rule is tested on the same code to make sure they're all detecting distinct problems. +@Suppress("LongMethod") private fun test(block: () -> Rule) { val rule = block() - rule.lint(compileForTest(resourceAsPath("Empty.kt"))) + rule.lint( + """ + class Empty : Runnable { + + init { + + } + + constructor() { + + } + + override fun run() { + + } + + fun stuff() { + try { + + } catch (e: Exception) { + + } catch (e: Exception) { + //no-op + } catch (e: Exception) { + println() + } catch (ignored: Exception) { + + } catch (expected: Exception) { + + } catch (_: Exception) { + + } finally { + + } + if (true) { + + } else { + + } + when (true) { + + } + for (i in 1..10) { + + } + while (true) { + + } + do { + + } while (true) + } + } + + class EmptyClass() {} + """.trimIndent() + ) assertThat(rule.findings).hasSize(1) } diff --git a/detekt-rules-empty/src/test/resources/Empty.kt b/detekt-rules-empty/src/test/resources/Empty.kt deleted file mode 100644 index 3fb9ce6271df..000000000000 --- a/detekt-rules-empty/src/test/resources/Empty.kt +++ /dev/null @@ -1,57 +0,0 @@ -@file:Suppress("unused", "ConstantConditionIf", "ConvertSecondaryConstructorToPrimary") - -package cases - -class Empty : Runnable { - - init { - - } - - constructor() { - - } - - override fun run() { - - } - - fun stuff() { - try { - - } catch (e: Exception) { - - } catch (e: Exception) { - //no-op - } catch (e: Exception) { - println() - } catch (ignored: Exception) { - - } catch (expected: Exception) { - - } catch (_: Exception) { - - } finally { - - } - if (true) { - - } else { - - } - when (true) { - - } - for (i in 1..10) { - - } - while (true) { - - } - do { - - } while (true) - } -} - -class EmptyClass() {} diff --git a/detekt-rules-errorprone/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclaration.kt b/detekt-rules-errorprone/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclaration.kt index 8ef537283fc2..7e46e2cbf94a 100644 --- a/detekt-rules-errorprone/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclaration.kt +++ b/detekt-rules-errorprone/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclaration.kt @@ -76,7 +76,7 @@ class PropertyUsedBeforeDeclaration(config: Config = Config.empty) : Rule(config member.forEachDescendantOfType { val property = allProperties[it.text] if (property != null && property !in declaredProperties && property == it.descriptor()) { - report(CodeSmell(issue, Entity.from(it), "'${it.text}' is before declaration.")) + report(CodeSmell(issue, Entity.from(it), "'${it.text}' is used before declaration.")) } } if (member is KtProperty) { diff --git a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/EqualsAlwaysReturnsTrueOrFalseSpec.kt b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/EqualsAlwaysReturnsTrueOrFalseSpec.kt index 48ff2b89f7ad..f730617d3495 100644 --- a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/EqualsAlwaysReturnsTrueOrFalseSpec.kt +++ b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/EqualsAlwaysReturnsTrueOrFalseSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.bugs -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.api.Config import io.gitlab.arturbosch.detekt.test.compileAndLint import io.gitlab.arturbosch.detekt.test.lint @@ -12,12 +11,104 @@ class EqualsAlwaysReturnsTrueOrFalseSpec { @Test fun `reports equals() methods`() { - assertThat(subject.lint(resourceAsPath("EqualsAlwaysReturnsTrueOrFalsePositive.kt"))).hasSize(6) + @Suppress("EqualsOrHashCode") + val code = """ + // reports 1 for every equals method + class EqualsReturnsTrue { + + override fun equals(other: Any?): Boolean { + return true + } + } + + class EqualsReturnsFalse { + + override fun equals(other: Any?): Boolean { + return false + } + } + + class EqualsReturnsFalseWithUnreachableReturnStatement { + + override fun equals(other: Any?): Boolean { + return false + return true + } + } + + class EqualsReturnsFalseWithUnreachableCode { + + override fun equals(other: Any?): Boolean { + return false + val i = 0 + } + } + + class EqualsReturnsConstantExpression { + + override fun equals(other: Any?) = false + } + + class EqualsWithTwoReturnExpressions { + + override fun equals(other: Any?): Boolean { + if (other is Int) { + return true + } + return true + } + } + """.trimIndent() + assertThat(subject.lint(code)).hasSize(6) } @Test fun `does not report equals() methods`() { - assertThat(subject.lint(resourceAsPath("EqualsAlwaysReturnsTrueOrFalseNegative.kt"))).isEmpty() + @Suppress("EqualsOrHashCode") + val code = """ + class EqualsReturnsTrueOrFalse { + + override fun equals(other: Any?): Boolean { + if (other is Int) { + return true + } + return false + } + } + + class CorrectEquals { + + override fun equals(other: Any?): Boolean { + return this.toString() == other.toString() + } + } + + class ReferentialEquality { + + override fun equals(other: Any?): Boolean { + return this === other + } + } + + fun equals(other: Any?): Boolean { + return false + } + + class NotOverridingEquals { + + fun equal(other: Any?): Boolean { + return true + } + } + + class WrongEqualsParameterList { + + fun equals(other: Any, i: Int): Boolean { + return true + } + } + """.trimIndent() + assertThat(subject.lint(code)).isEmpty() } @Test diff --git a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorHasNextCallsNextMethodSpec.kt b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorHasNextCallsNextMethodSpec.kt index b54324f91367..e75a6d9eca70 100644 --- a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorHasNextCallsNextMethodSpec.kt +++ b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorHasNextCallsNextMethodSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.bugs -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.test.lint import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -10,13 +9,87 @@ class IteratorHasNextCallsNextMethodSpec { @Test fun `reports wrong iterator implementation`() { - val path = resourceAsPath("IteratorImplPositive.kt") - assertThat(subject.lint(path)).hasSize(4) + val code = """ + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + class IteratorImpl2 : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + return "" + } + } + + class IteratorImplContainer { + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + object IteratorImplNegative3 : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + throw IllegalStateException() + } + } + } + + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + interface InterfaceIterator : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + return "" + } + } + + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + abstract class AbstractIterator : Iterator { + override fun hasNext(): Boolean { + if (true) { + next() + } + return true + } + + override fun next(): String { + return "" + } + } + """.trimIndent() + assertThat(subject.lint(code)).hasSize(4) } @Test fun `does not report correct iterator implementations`() { - val path = resourceAsPath("IteratorImplNegative.kt") - assertThat(subject.lint(path)).isEmpty() + val code = """ + import java.util.NoSuchElementException + + class IteratorImplOk : Iterator { + + override fun hasNext(): Boolean { + return true + } + + override fun next(): String { + if (!hasNext()) throw NoSuchElementException() + return "" + } + + // next method overload should not be reported + private fun next(i: Int) { + } + } + + class NoIteratorImpl + + abstract class AbstractIteratorNotOverridden : Iterator + """.trimIndent() + assertThat(subject.lint(code)).isEmpty() } } diff --git a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorNotThrowingNoSuchElementExceptionSpec.kt b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorNotThrowingNoSuchElementExceptionSpec.kt index 9c4f0f36f0c2..86012c32eb60 100644 --- a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorNotThrowingNoSuchElementExceptionSpec.kt +++ b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/IteratorNotThrowingNoSuchElementExceptionSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.bugs -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.test.lint import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -10,13 +9,87 @@ class IteratorNotThrowingNoSuchElementExceptionSpec { @Test fun `reports invalid next() implementations`() { - val path = resourceAsPath("IteratorImplPositive.kt") - assertThat(subject.lint(path)).hasSize(4) + val code = """ + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + class IteratorImpl2 : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + return "" + } + } + + class IteratorImplContainer { + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + object IteratorImplNegative3 : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + throw IllegalStateException() + } + } + } + + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + interface InterfaceIterator : Iterator { + override fun hasNext(): Boolean { + next() + return true + } + + override fun next(): String { + return "" + } + } + + // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod + abstract class AbstractIterator : Iterator { + override fun hasNext(): Boolean { + if (true) { + next() + } + return true + } + + override fun next(): String { + return "" + } + } + """.trimIndent() + assertThat(subject.lint(code)).hasSize(4) } @Test - fun `does not report correct next() implemenations`() { - val path = resourceAsPath("IteratorImplNegative.kt") - assertThat(subject.lint(path)).isEmpty() + fun `does not report correct next() implementations`() { + val code = """ + import java.util.NoSuchElementException + + class IteratorImplOk : Iterator { + + override fun hasNext(): Boolean { + return true + } + + override fun next(): String { + if (!hasNext()) throw NoSuchElementException() + return "" + } + + // next method overload should not be reported + private fun next(i: Int) { + } + } + + class NoIteratorImpl + + abstract class AbstractIteratorNotOverridden : Iterator + """.trimIndent() + assertThat(subject.lint(code)).isEmpty() } } diff --git a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclarationSpec.kt b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclarationSpec.kt index 6116a52da79c..9a9b8198f125 100644 --- a/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclarationSpec.kt +++ b/detekt-rules-errorprone/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/bugs/PropertyUsedBeforeDeclarationSpec.kt @@ -25,7 +25,7 @@ class PropertyUsedBeforeDeclarationSpec(private val env: KotlinCoreEnvironment) val findings = subject.compileAndLintWithContext(env, code) assertThat(findings).hasSize(1) assertThat(findings).hasTextLocations(45 to 52) - assertThat(findings.first()).hasMessage("'isValid' is before declaration.") + assertThat(findings.first()).hasMessage("'isValid' is used before declaration.") } @Test diff --git a/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalseNegative.kt b/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalseNegative.kt deleted file mode 100644 index 6986853f891e..000000000000 --- a/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalseNegative.kt +++ /dev/null @@ -1,45 +0,0 @@ -@file:Suppress("EqualsOrHashCode", "unused", "UNREACHABLE_CODE") - -package cases - -class EqualsReturnsTrueOrFalse { - - override fun equals(other: Any?): Boolean { - if (other is Int) { - return true - } - return false - } -} - -class CorrectEquals { - - override fun equals(other: Any?): Boolean { - return this.toString() == other.toString() - } -} - -class ReferentialEquality { - - override fun equals(other: Any?): Boolean { - return this === other - } -} - -fun equals(other: Any?): Boolean { - return false -} - -class NotOverridingEquals { - - fun equal(other: Any?): Boolean { - return true - } -} - -class WrongEqualsParameterList { - - fun equals(other: Any, i: Int): Boolean { - return true - } -} diff --git a/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalsePositive.kt b/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalsePositive.kt deleted file mode 100644 index 87ce8d150ff7..000000000000 --- a/detekt-rules-errorprone/src/test/resources/EqualsAlwaysReturnsTrueOrFalsePositive.kt +++ /dev/null @@ -1,49 +0,0 @@ -@file:Suppress("EqualsOrHashCode", "unused", "UNREACHABLE_CODE") - -package cases - -// reports 1 for every equals method -class EqualsReturnsTrue { - - override fun equals(other: Any?): Boolean { - return true - } -} - -class EqualsReturnsFalse { - - override fun equals(other: Any?): Boolean { - return false - } -} - -class EqualsReturnsFalseWithUnreachableReturnStatement { - - override fun equals(other: Any?): Boolean { - return false - return true - } -} - -class EqualsReturnsFalseWithUnreachableCode { - - override fun equals(other: Any?): Boolean { - return false - val i = 0 - } -} - -class EqualsReturnsConstantExpression { - - override fun equals(other: Any?) = false -} - -class EqualsWithTwoReturnExpressions { - - override fun equals(other: Any?): Boolean { - if (other is Int) { - return true - } - return true - } -} diff --git a/detekt-rules-errorprone/src/test/resources/IteratorImplNegative.kt b/detekt-rules-errorprone/src/test/resources/IteratorImplNegative.kt deleted file mode 100644 index 4958d88a1fc0..000000000000 --- a/detekt-rules-errorprone/src/test/resources/IteratorImplNegative.kt +++ /dev/null @@ -1,25 +0,0 @@ -@file:Suppress("unused", "UNUSED_PARAMETER") - -package cases - -import java.util.NoSuchElementException - -class IteratorImplOk : Iterator { - - override fun hasNext(): Boolean { - return true - } - - override fun next(): String { - if (!hasNext()) throw NoSuchElementException() - return "" - } - - // next method overload should not be reported - private fun next(i: Int) { - } -} - -class NoIteratorImpl - -abstract class AbstractIteratorNotOverridden : Iterator diff --git a/detekt-rules-errorprone/src/test/resources/IteratorImplPositive.kt b/detekt-rules-errorprone/src/test/resources/IteratorImplPositive.kt deleted file mode 100644 index 517301c589aa..000000000000 --- a/detekt-rules-errorprone/src/test/resources/IteratorImplPositive.kt +++ /dev/null @@ -1,60 +0,0 @@ -@file:Suppress("unused", "ConstantConditionIf") - -package cases - -// reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod -class IteratorImpl2 : Iterator { - - override fun hasNext(): Boolean { - next() - return true - } - - override fun next(): String { - return "" - } -} - -class IteratorImplContainer { - - // reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod - object IteratorImplNegative3 : Iterator { - - override fun hasNext(): Boolean { - next() - return true - } - - override fun next(): String { - throw IllegalStateException() - } - } -} - -// reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod -interface InterfaceIterator : Iterator { - - override fun hasNext(): Boolean { - next() - return true - } - - override fun next(): String { - return "" - } -} - -// reports IteratorNotThrowingNoSuchElementException, IteratorHasNextCallsNextMethod -abstract class AbstractIterator : Iterator { - - override fun hasNext(): Boolean { - if (true) { - next() - } - return true - } - - override fun next(): String { - return "" - } -} diff --git a/detekt-rules-exceptions/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/exceptions/ExceptionRaisedInUnexpectedLocationSpec.kt b/detekt-rules-exceptions/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/exceptions/ExceptionRaisedInUnexpectedLocationSpec.kt index fc9c7ec50166..f5d157127c40 100644 --- a/detekt-rules-exceptions/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/exceptions/ExceptionRaisedInUnexpectedLocationSpec.kt +++ b/detekt-rules-exceptions/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/exceptions/ExceptionRaisedInUnexpectedLocationSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.exceptions -import io.github.detekt.test.utils.resourceAsPath import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.compileAndLint import io.gitlab.arturbosch.detekt.test.lint @@ -12,14 +11,82 @@ class ExceptionRaisedInUnexpectedLocationSpec { @Test fun `reports methods raising an unexpected exception`() { - val path = resourceAsPath("ExceptionRaisedInMethodsPositive.kt") - assertThat(subject.lint(path)).hasSize(5) + val code = """ + open class ExceptionRaisedInMethods { + + // reports 1 - method should not throw an exception + override fun toString(): String { + throw IllegalStateException() + } + + // reports 1 - method should not throw an exception + override fun hashCode(): Int { + throw IllegalStateException() + } + + // reports 1 - method should not throw an exception + override fun equals(other: Any?): Boolean { + throw IllegalStateException() + } + + // reports 1 - method should not throw an exception + @Suppress("ConstantConditionIf", "RedundantSuppression") + protected fun finalize() { + if (true) { + throw IllegalStateException() + } + } + } + + @Suppress("EqualsOrHashCode", "RedundantSuppression") + object ExceptionRaisedInMethodsObject { + + // reports 1 - method should not throw an exception + override fun equals(other: Any?): Boolean { + throw IllegalStateException() + } + } + """.trimIndent() + assertThat(subject.lint(code)).hasSize(5) } @Test fun `does not report methods raising no exception`() { - val path = resourceAsPath("ExceptionRaisedInMethodsNegative.kt") - assertThat(subject.lint(path)).isEmpty() + val code = """ + @Suppress("RedundantOverride", "RedundantSuppression") + open class NoExceptionRaisedInMethods { + + init { + throw IllegalStateException() + } + + override fun toString(): String { + return super.toString() + } + + override fun hashCode(): Int { + return super.hashCode() + } + + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + companion object { + init { + throw IllegalStateException() + } + } + + fun doSomeEqualsComparison() { + throw IllegalStateException() + } + + protected fun finalize() { + } + } + """.trimIndent() + assertThat(subject.lint(code)).isEmpty() } @Test diff --git a/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsNegative.kt b/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsNegative.kt deleted file mode 100644 index 58051583bf9b..000000000000 --- a/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsNegative.kt +++ /dev/null @@ -1,35 +0,0 @@ -@file:Suppress("unused", "ConstantConditionIf", "RedundantOverride") - -package cases - -open class NoExceptionRaisedInMethods { - - init { - throw IllegalStateException() - } - - override fun toString(): String { - return super.toString() - } - - override fun hashCode(): Int { - return super.hashCode() - } - - override fun equals(other: Any?): Boolean { - return super.equals(other) - } - - companion object { - init { - throw IllegalStateException() - } - } - - fun doSomeEqualsComparison() { - throw IllegalStateException() - } - - protected fun finalize() { - } -} diff --git a/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsPositive.kt b/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsPositive.kt deleted file mode 100644 index 26e89b8eaece..000000000000 --- a/detekt-rules-exceptions/src/test/resources/ExceptionRaisedInMethodsPositive.kt +++ /dev/null @@ -1,36 +0,0 @@ -@file:Suppress("unused", "ConstantConditionIf", "RedundantOverride", "EqualsOrHashCode") - -package cases - -open class ExceptionRaisedInMethods { - - // reports 1 - method should not throw an exception - override fun toString(): String { - throw IllegalStateException() - } - - // reports 1 - method should not throw an exception - override fun hashCode(): Int { - throw IllegalStateException() - } - - // reports 1 - method should not throw an exception - override fun equals(other: Any?): Boolean { - throw IllegalStateException() - } - - // reports 1 - method should not throw an exception - protected fun finalize() { - if (true) { - throw IllegalStateException() - } - } -} - -object ExceptionRaisedInMethodsObject { - - // reports 1 - method should not throw an exception - override fun equals(other: Any?): Boolean { - throw IllegalStateException() - } -} diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenComment.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenComment.kt index 675eb09a8208..58f93769ebc5 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenComment.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenComment.kt @@ -10,21 +10,80 @@ import io.gitlab.arturbosch.detekt.api.Severity import io.gitlab.arturbosch.detekt.api.config import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault import io.gitlab.arturbosch.detekt.api.internal.Configuration +import io.gitlab.arturbosch.detekt.api.valuesWithReason import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +// Note: ​ (zero-width-space) is used to prevent the Kotlin parser getting confused by talking about comments in a comment. /** * This rule allows to set a list of comments which are forbidden in the codebase and should only be used during * development. Offending code comments will then be reported. * + * The regular expressions in `comments` list will have the following behaviors while matching the comments: + * * **Each comment will be handled individually.** + * * single line comments are always separate, consecutive lines are not merged. + * * multi line comments are not split up, the regex will be applied to the whole comment. + * * KDoc comments are not split up, the regex will be applied to the whole comment. + * * **The following comment delimiters (and indentation before them) are removed** before applying the regex: + * `//`, `// `, `/​*`, `/​* `, `/​**`, `*` aligners, `*​/`, ` *​/` + * * **The regex is applied as a multiline regex**, + * see [Anchors](https://www.regular-expressions.info/anchors.html) for more info. + * To match the start and end of each line, use `^` and `$`. + * To match the start and end of the whole comment, use `\A` and `\Z`. + * To turn off multiline, use `(?-m)` at the start of your regex. + * * **The regex is applied with dotall semantics**, meaning `.` will match any character including newlines, + * this is to ensure that freeform line-wrapping doesn't mess with simple regexes. + * To turn off this behavior, use `(?-s)` at the start of your regex, or use `[^\r\n]*` instead of `.*`. + * * **The regex will be searched using "contains" semantics** not "matches", + * so partial comment matches will flag forbidden comments. + * In practice this means there's no need to start and end the regex with `.*`. + * + * The rule can be configured to add extra comments to the list of forbidden comments, here are some examples: + * ```yaml + * ForbiddenComment: + * comments: + * # Repeat the default configuration if it's still needed. + * - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + * value: 'FIXME:' + * - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + * value: 'STOPSHIP:' + * - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + * value: 'TODO:' + * # Add additional patterns to the list. + * + * - reason: 'Authors are not recorded in KDoc.' + * value: '@author' + * + * - reason: 'REVIEW markers are not allowed in production code, only use before PR is merged.' + * value: '^\s*(?i)REVIEW\b' + * # Non-compliant: // REVIEW this code before merging. + * # Compliant: // Preview will show up here. + * + * - reason: 'Use @androidx.annotation.VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) instead.' + * value: '^private$' + * # Non-compliant: /*private*/ fun f() { } + * + * - reason: 'KDoc tag should have a value.' + * value: '^\s*@(?!suppress|hide)\w+\s*$' + * # Non-compliant: /** ... @see */ + * # Compliant: /** ... @throws IOException when there's a network problem */ + * + * - reason: 'include an issue link at the beginning preceded by a space' + * value: 'BUG:(?! https://github\.com/company/repo/issues/\d+).*' + * ``` + * + * By default the commonly used todo markers are forbidden: `TODO:`, `FIXME:` and `STOPSHIP:`. + * * * val a = "" // TODO: remove please - * // FIXME: this is a hack + * /** + * * FIXME: this is a hack + * */ * fun foo() { } - * // STOPSHIP: + * /* STOPSHIP: */ * */ @ActiveByDefault(since = "1.0.0") @@ -38,17 +97,31 @@ class ForbiddenComment(config: Config = Config.empty) : Rule(config) { ) @Configuration("forbidden comment strings") - private val values: List by config(listOf("FIXME:", "STOPSHIP:", "TODO:")) + @Deprecated("Use `comments` instead, make sure you escape your text for Regular Expressions.") + private val values: List by config(emptyList()) + + @Configuration("forbidden comment string patterns") + private val comments: List by config( + valuesWithReason( + "FIXME:" to "Forbidden FIXME todo marker in comment, please fix the problem.", + "STOPSHIP:" to "Forbidden STOPSHIP todo marker in comment, " + + "please address the problem before shipping the code.", + "TODO:" to "Forbidden TODO todo marker in comment, please do the changes.", + ) + ) { list -> + list.map { Comment(it.value.toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.MULTILINE)), it.reason) } + } @Configuration("ignores comments which match the specified regular expression. For example `Ticket|Task`.") private val allowedPatterns: Regex by config("", String::toRegex) @Configuration("error message which overrides the default one") + @Deprecated("Use `comments` and provide `reason` against each `value`.") private val customMessage: String by config("") override fun visitComment(comment: PsiComment) { super.visitComment(comment) - val text = comment.text + val text = comment.getContent() checkForbiddenComment(text, comment) } @@ -63,24 +136,87 @@ class ForbiddenComment(config: Config = Config.empty) : Rule(config) { private fun checkForbiddenComment(text: String, comment: PsiElement) { if (allowedPatterns.pattern.isNotEmpty() && allowedPatterns.containsMatchIn(text)) return + @Suppress("DEPRECATION") values.forEach { if (text.contains(it, ignoreCase = true)) { - report( - CodeSmell( - issue, - Entity.from(comment), - getErrorMessage(it) - ) - ) + reportIssue(comment, getErrorMessage(it)) + } + } + + comments.forEach { + if (it.value.containsMatchIn(text)) { + reportIssue(comment, getErrorMessage(it)) } } } + private fun reportIssue(comment: PsiElement, msg: String) { + report( + CodeSmell( + issue, + Entity.from(comment), + msg + ) + ) + } + + private fun PsiComment.getContent(): String = text.getCommentContent() + + private fun getErrorMessage(comment: Comment): String = + comment.reason ?: String.format(DEFAULT_ERROR_MESSAGE, comment.value.pattern) + + @Suppress("DEPRECATION") private fun getErrorMessage(value: String): String = - customMessage.takeUnless { it.isEmpty() } ?: String.format(DEFAULT_ERROR_MESSAGE, value) + customMessage.takeUnless { it.isEmpty() } + ?: String.format(DEFAULT_ERROR_MESSAGE, value) + + private data class Comment(val value: Regex, val reason: String?) companion object { - const val DEFAULT_ERROR_MESSAGE = "This comment contains '%s' " + - "that has been defined as forbidden in detekt." + const val DEFAULT_ERROR_MESSAGE = "This comment contains '%s' that has been defined as forbidden." } } + +internal fun String.getCommentContent(): String { + return if (this.startsWith("//")) { + this.removePrefix("//").removePrefix(" ") + } else { + this + .trimIndentIgnoringFirstLine() + // Process line by line. + .lineSequence() + // Remove starting, aligning and ending markers. + .map { + it + .let { fullLine -> + val trimmedStartLine = fullLine.trimStart() + if (trimmedStartLine.startsWith("/*")) { + trimmedStartLine.removePrefix("/*").removePrefix(" ") + } else if (trimmedStartLine.startsWith("*") && trimmedStartLine.startsWith("*/").not()) { + trimmedStartLine.removePrefix("*").removePrefix(" ") + } else { + fullLine + } + } + .let { lineWithoutStartMarker -> + if (lineWithoutStartMarker.endsWith("*/")) { + lineWithoutStartMarker.removeSuffix("*/").removeSuffix(" ") + } else { + lineWithoutStartMarker + } + } + } + // Trim trailing empty lines. + .dropWhile(String::isEmpty) + // Reconstruct the comment contents. + .joinToString("\n") + } +} + +private fun String.trimIndentIgnoringFirstLine(): String = + if ('\n' !in this) { + this + } else { + val lines = this.lineSequence() + lines.first() + "\n" + lines.drop(1).joinToString("\n").trimIndent() + } diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatements.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatements.kt index 0bfd7a6ff632..af81c54300c8 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatements.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatements.kt @@ -11,11 +11,17 @@ import io.gitlab.arturbosch.detekt.api.Severity import io.gitlab.arturbosch.detekt.api.config import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault import io.gitlab.arturbosch.detekt.api.internal.Configuration +import org.jetbrains.kotlin.com.intellij.psi.PsiElement +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtBreakExpression import org.jetbrains.kotlin.psi.KtContinueExpression +import org.jetbrains.kotlin.psi.KtDoWhileExpression import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.psi.KtLoopExpression +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.KtWhileExpression /** * Loops which contain multiple `break` or `continue` statements are hard to read and understand. @@ -48,7 +54,7 @@ class LoopWithTooManyJumpStatements(config: Config = Config.empty) : Rule(config override fun visitLoopExpression(loopExpression: KtLoopExpression) { if (countBreakAndReturnStatements(loopExpression.body) > maxJumpCount) { - report(CodeSmell(issue, Entity.from(loopExpression), issue.description)) + report(CodeSmell(issue, Entity.from(loopExpression.keyword ?: loopExpression), issue.description)) } super.visitLoopExpression(loopExpression) } @@ -71,3 +77,22 @@ class LoopWithTooManyJumpStatements(config: Config = Config.empty) : Rule(config return count } } + +/** + * For some reason not all keyword properties are exposed on [KtLoopExpression] subclasses, so we have to do it manually. + */ +@Suppress("CommentOverPrivateProperty") +private val KtLoopExpression.keyword: PsiElement? + get() = + when (this) { + is KtForExpression -> this.forKeyword + is KtWhileExpression -> this.whileKeyword + is KtDoWhileExpression -> this.doKeyword + else -> null + } + +private val KtDoWhileExpression.doKeyword: PsiElement? + get() = KtPsiUtil.findChildByType(this, KtTokens.DO_KEYWORD) + +private val KtWhileExpression.whileKeyword: PsiElement? + get() = KtPsiUtil.findChildByType(this, KtTokens.WHILE_KEYWORD) diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClass.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClass.kt index be960fc744d7..2364b94975be 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClass.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClass.kt @@ -13,6 +13,7 @@ import io.gitlab.arturbosch.detekt.rules.isConstant import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtConstantExpression +import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtPrefixExpression import org.jetbrains.kotlin.psi.KtProperty @@ -54,47 +55,71 @@ class SerialVersionUIDInSerializableClass(config: Config = Config.empty) : Rule( ) override fun visitClass(klass: KtClass) { + super.visitClass(klass) if (!klass.isInterface() && isImplementingSerializable(klass)) { val companionObject = klass.companionObject() - if (companionObject == null || !hasCorrectSerialVersionUUID(companionObject)) { + if (companionObject == null) { report( CodeSmell( issue, - Entity.from(klass), - "The class ${klass.nameAsSafeName} implements" + - " the `Serializable` interface and should thus define a `serialVersionUID`." + Entity.atName(klass), + klass.getIssueMessage("class") ) ) + } else { + val finding = searchSerialVersionUIDFinding(companionObject, klass) ?: return + reportFinding(finding) } } - super.visitClass(klass) } override fun visitObjectDeclaration(declaration: KtObjectDeclaration) { - if (!declaration.isCompanion() && - isImplementingSerializable(declaration) && - !hasCorrectSerialVersionUUID(declaration) - ) { - report( - CodeSmell( - issue, - Entity.from(declaration), - "The object ${declaration.nameAsSafeName} " + - "implements the `Serializable` interface and should thus define a `serialVersionUID`." - ) - ) - } super.visitObjectDeclaration(declaration) + if (!declaration.isCompanion() && isImplementingSerializable(declaration)) { + val finding = searchSerialVersionUIDFinding(declaration) ?: return + reportFinding(finding) + } + } + + private fun reportFinding(finding: SerialVersionUIDFindings) { + report( + CodeSmell( + issue, + Entity.atName(finding.violatingElement), + finding.issueMsg + ) + ) } private fun isImplementingSerializable(classOrObject: KtClassOrObject) = classOrObject.superTypeListEntries.any { it.text == "Serializable" } - private fun hasCorrectSerialVersionUUID(declaration: KtObjectDeclaration): Boolean { - val property = declaration.body?.properties?.firstOrNull { it.name == "serialVersionUID" } ?: return false - return property.isConstant() && isLongProperty(property) && property.isPrivate() + private fun searchSerialVersionUIDFinding( + declaration: KtObjectDeclaration, + parentDeclaration: KtNamedDeclaration = declaration, + ): SerialVersionUIDFindings? { + val property = declaration.body?.properties?.firstOrNull { it.name == "serialVersionUID" } + ?: return SerialVersionUIDFindings( + parentDeclaration, + parentDeclaration.getIssueMessage( + if (parentDeclaration is KtClass) "class" else "object" + ) + ) + return if (property.isConstant() && isLongProperty(property) && property.isPrivate()) { + null + } else { + SerialVersionUIDFindings( + property, + "The property `serialVersionUID` signature is not correct. `serialVersionUID` should be " + + "`private` and `constant` and its type should be `Long`" + ) + } } + private fun KtNamedDeclaration.getIssueMessage(typeOfDeclaration: String) = + "The $typeOfDeclaration ${this.nameAsSafeName} " + + "implements the `Serializable` interface and should thus define a `serialVersionUID`." + private fun isLongProperty(property: KtProperty) = hasLongType(property) || hasLongAssignment(property) private fun hasLongType(property: KtProperty) = property.typeReference?.text == "Long" @@ -106,4 +131,9 @@ class SerialVersionUIDInSerializableClass(config: Config = Config.empty) : Rule( return assignmentText != null && assignmentText.last() == 'L' && assignmentText.substring(0, assignmentText.length - 1).toLongOrNull() != null } + + private data class SerialVersionUIDFindings( + val violatingElement: KtNamedDeclaration, + val issueMsg: String, + ) } diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/Case.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/Case.kt deleted file mode 100644 index 6478cf80ba84..000000000000 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/Case.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.gitlab.arturbosch.detekt.rules - -import io.github.detekt.test.utils.resourceAsPath -import java.nio.file.Path - -/* Do not add new elements to this file. Instead, use inline code snippets within the tests. - See https://github.com/detekt/detekt/issues/1089 */ -enum class Case(val file: String) { - FunctionReturningConstantPositive("/FunctionReturningConstantPositive.kt"), - FunctionReturningConstantNegative("/FunctionReturningConstantNegative.kt"), - LoopWithTooManyJumpStatementsNegative("LoopWithTooManyJumpStatementsNegative.kt"), - LoopWithTooManyJumpStatementsPositive("LoopWithTooManyJumpStatementsPositive.kt"), - MaxLineLength("/MaxLineLength.kt"), - MaxLineLengthSuppressed("/MaxLineLengthSuppressed.kt"), - MaxLineLengthWithLongComments("/MaxLineLengthWithLongComments.kt"), - UtilityClassesPositive("/UtilityClassesPositive.kt"), - UtilityClassesNegative("/UtilityClassesNegative.kt"), - NoTabsNegative("/NoTabsNegative.kt"), - NoTabsPositive("/NoTabsPositive.kt"); - - fun path(): Path = resourceAsPath(file) -} diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenCommentSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenCommentSpec.kt index 2a4fa008b248..dd20491f7d3b 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenCommentSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/ForbiddenCommentSpec.kt @@ -1,66 +1,69 @@ +@file:Suppress("ClassName") + package io.gitlab.arturbosch.detekt.rules.style +import io.gitlab.arturbosch.detekt.api.ValueWithReason import io.gitlab.arturbosch.detekt.test.TestConfig +import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.compileAndLint import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource private const val VALUES = "values" +private const val COMMENTS = "comments" private const val ALLOWED_PATTERNS = "allowedPatterns" private const val MESSAGE = "customMessage" class ForbiddenCommentSpec { - - val todoColon = "// TODO: I need to fix this." - val todo = "// TODO I need to fix this." - - val fixmeColon = "// FIXME: I need to fix this." - val fixme = "// FIXME I need to fix this." - - val stopShipColon = "// STOPSHIP: I need to fix this." - val stopShip = "// STOPSHIP I need to fix this." - @Nested inner class `the default values are configured` { @Test @DisplayName("should report TODO: usages") fun reportTodoColon() { - val findings = ForbiddenComment().compileAndLint(todoColon) + val findings = ForbiddenComment().compileAndLint("// TODO: I need to fix this.") assertThat(findings).hasSize(1) + assertThat(findings[0]).hasMessage("Forbidden TODO todo marker in comment, please do the changes.") } @Test fun `should not report TODO usages`() { - val findings = ForbiddenComment().compileAndLint(todo) + val findings = ForbiddenComment().compileAndLint("// TODO I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should report FIXME: usages") fun reportFixMe() { - val findings = ForbiddenComment().compileAndLint(fixmeColon) + val findings = ForbiddenComment().compileAndLint("// FIXME: I need to fix this.") assertThat(findings).hasSize(1) } @Test fun `should not report FIXME usages`() { - val findings = ForbiddenComment().compileAndLint(fixme) + val findings = ForbiddenComment().compileAndLint("// FIXME I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should report STOPSHIP: usages") fun reportStopShipColon() { - val findings = ForbiddenComment().compileAndLint(stopShipColon) + val findings = ForbiddenComment().compileAndLint("// STOPSHIP: I need to fix this.") assertThat(findings).hasSize(1) + assertThat(findings[0]).hasMessage( + "Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code." + ) } @Test fun `should not report STOPSHIP usages`() { - val findings = ForbiddenComment().compileAndLint(stopShip) + val findings = ForbiddenComment().compileAndLint("// STOPSHIP I need to fix this.") assertThat(findings).isEmpty() } @@ -75,6 +78,15 @@ class ForbiddenCommentSpec { assertThat(findings).hasSize(1) } + @Test + fun `should report violation in single line block comment`() { + val code = """ + /*TODO: I need to fix this.*/ + """.trimIndent() + val findings = ForbiddenComment().compileAndLint(code) + assertThat(findings).hasSize(1) + } + @Test fun `should report violation in KDoc`() { val code = """ @@ -90,6 +102,18 @@ class ForbiddenCommentSpec { val findings = ForbiddenComment().compileAndLint(code) assertThat(findings).hasSize(2) } + + @Test + fun `should report violation in star aligned comment`() { + val code = """ + /* + * TODO: I need to fix this. + */ + class A + """.trimIndent() + val findings = ForbiddenComment().compileAndLint(code) + assertThat(findings).hasSize(1) + } } @Nested @@ -98,26 +122,26 @@ class ForbiddenCommentSpec { @Nested inner class `when given Banana` { - val config = TestConfig(VALUES to "Banana") + val config = TestConfig(COMMENTS to listOf("Banana")) @Test @DisplayName("should not report TODO: usages") fun todoColon() { - val findings = ForbiddenComment(config).compileAndLint(todoColon) + val findings = ForbiddenComment(config).compileAndLint("// TODO: I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should not report FIXME: usages") fun fixmeColon() { - val findings = ForbiddenComment(config).compileAndLint(fixmeColon) + val findings = ForbiddenComment(config).compileAndLint("// FIXME: I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should not report STOPME: usages") fun stopShipColon() { - val findings = ForbiddenComment(config).compileAndLint(stopShipColon) + val findings = ForbiddenComment(config).compileAndLint("// STOPSHIP: I need to fix this.") assertThat(findings).isEmpty() } @@ -138,26 +162,26 @@ class ForbiddenCommentSpec { @Nested @DisplayName("when given listOf(\"banana\")") inner class ListOfBanana { - val config = TestConfig(VALUES to listOf("Banana")) + val config = TestConfig(COMMENTS to listOf("Banana")) @Test @DisplayName("should not report TODO: usages") fun todoColon() { - val findings = ForbiddenComment(config).compileAndLint(todoColon) + val findings = ForbiddenComment(config).compileAndLint("// TODO: I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should not report FIXME: usages") fun fixmeColon() { - val findings = ForbiddenComment(config).compileAndLint(fixmeColon) + val findings = ForbiddenComment(config).compileAndLint("// FIXME: I need to fix this.") assertThat(findings).isEmpty() } @Test @DisplayName("should not report STOPME: usages") fun stopShipColon() { - val findings = ForbiddenComment(config).compileAndLint(stopShipColon) + val findings = ForbiddenComment(config).compileAndLint("// STOPSHIP: I need to fix this.") assertThat(findings).isEmpty() } @@ -225,6 +249,9 @@ class ForbiddenCommentSpec { @Nested inner class `custom message is not configured` { private val messageConfig = TestConfig(VALUES to "Comment") + private val messageConfigWithReason = ForbiddenComment( + ValueWithReason("Comment", "Comment is disallowed") + ) @Test fun `should report a Finding with default Message`() { @@ -234,5 +261,771 @@ class ForbiddenCommentSpec { assertThat(findings).hasSize(1) assertThat(findings.first().message).isEqualTo(expectedMessage) } + + @Test + fun `should report a Finding with reason`() { + val comment = "// Comment" + val findings = ForbiddenComment(messageConfigWithReason).compileAndLint(comment) + assertThat(findings).hasSize(1) + assertThat(findings.first().message).isEqualTo("Comment is disallowed") + } + } + + @Nested + inner class `custom value pattern is configured` { + private val patternStr = """^(?i)REVIEW\b""" + private val messageConfig = TestConfig( + COMMENTS to listOf("STOPSHIP", patternStr), + ) + + @Test + fun `should not report a finding when review doesn't match the pattern`() { + val comment = "// to express in the preview that it's not a normal TextView." + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding when STOPSHIP is present`() { + val comment = "// STOPSHIP to express in the preview that it's not a normal TextView." + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + assertThat(findings[0]) + .hasMessage(String.format(ForbiddenComment.DEFAULT_ERROR_MESSAGE, "STOPSHIP")) + } + + @Test + fun `should report a finding when review pattern is matched with comment with leading space`() { + val comment = "// REVIEW foo -> flag" + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + assertThat(findings[0]) + .hasMessage(String.format(ForbiddenComment.DEFAULT_ERROR_MESSAGE, patternStr)) + } + + @Test + fun `should report a finding when review pattern is matched with comment with out leading space`() { + val comment = "//REVIEW foo -> flag" + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + assertThat(findings[0]) + .hasMessage(String.format(ForbiddenComment.DEFAULT_ERROR_MESSAGE, patternStr)) + } + + @Test + fun `should report a finding matching two patterns`() { + val comment = "// REVIEW foo -> flag STOPSHIP" + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(2) + } + + @Test + fun `should report a finding matching a pattern contained in the comment`() { + val comment = "// foo STOPSHIP bar" + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding matching a pattern contained in multiple single line comments`() { + val comment = """ + // foo STOPSHIP bar + // foo STOPSHIP bar + // foo STOPSHIP bar + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(3) + } + } + + @Nested + inner class `comment on indented code` { + private val patternStr = "^ " + private val messageConfig = TestConfig( + COMMENTS to listOf(patternStr), + ) + + @Test + fun `should report a finding when leading extra space is not allowed`() { + val comment = """ + class A { + // comment + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report a finding when leading extra space is not allowed with no leading space`() { + val comment = """ + class A { + // comment with space in between + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should not report a finding when leading extra space is not allowed in multiline comment`() { + val comment = """ + class A { + /* + * comment with space in between + * comment with space in between + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding when leading extra space is not allowed in multiline comment with leading space`() { + val comment = """ + class A { + /* + * comment + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report a finding when leading extra space is not allowed in star aligned comment`() { + val comment = """ + class A { + /* + * comment + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding when leading space is not allowed in star aligned comment with leading space`() { + val comment = """ + class A { + /* + * comment + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment(messageConfig).compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when leading space is not allowed in multiline block comment with leading space`() { + val comment = """ + class A { + /* + comment + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment("^ comment").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when negative leading space is not allowed in multiline block comment with leading space`() { + val comment = """ + class A { + /* + comment + */ + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment("^comment").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding start space contained in a single line comment`() { + val comment = """ + class a { + fun test() { + // foo + val a = 0 + } + } + """.trimIndent() + val findings = ForbiddenComment("^ foo").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report when not finding start contained in a single line comment`() { + val comment = """ + class a { + fun test() { + // foo + val a = 0 + } + } + """.trimIndent() + val findings = ForbiddenComment("^ foo").compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding when whole comment is not allowed`() { + val comment = """ + class A { + // stopship + val a = 0 + } + """.trimIndent() + val findings = ForbiddenComment("stopship").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + } + + @Nested + inner class `mixed code and comment lines` { + + @Test + fun `should report a finding in trailing single line comment`() { + val comment = """ + fun f() {} // TODO implement + """.trimIndent() + val findings = ForbiddenComment("TODO").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when multiline comment exists before code`() { + val comment = """ + class A { + /*public*/ fun f() {} + } + """.trimIndent() + val findings = ForbiddenComment("public").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when multiline comment exists inside code`() { + val comment = """ + class A { + fun f() /*: String*/ {} + } + """.trimIndent() + val findings = ForbiddenComment("^: .+$").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when multiline comment exists after code`() { + val comment = """ + class A { + fun f(): String = /*error("foo")*/ + TODO() + } + """.trimIndent() + val findings = ForbiddenComment("^error").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when kdoc comment exists before code`() { + val comment = """ + class A { + /**public*/ fun f() {} + } + """.trimIndent() + val findings = ForbiddenComment("public").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when kdoc comment exists inside code`() { + val comment = """ + class A { + fun f() /**: String*/ {} + } + """.trimIndent() + val findings = ForbiddenComment("^: .+$").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should report a finding when kdoc comment exists after code`() { + val comment = """ + class A { + fun f(): String = /**error("foo")*/ + TODO() + } + """.trimIndent() + val findings = ForbiddenComment("^error").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + } + + @Nested + inner class `regex semantics for comments` { + + @Test + fun `should report a finding at the beginning of lines`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("^baz").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report a finding at the beginning of lines when the flag is off`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("(?-m)^baz").compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding at the end of lines`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("bar$").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report a finding at the end of lines when flag is off`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("(?-m)bar$").compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report a finding at the beginning of a comment`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings1 = ForbiddenComment("^foo").compileAndLint(comment) + assertThat(findings1).hasSize(1) + val findings2 = ForbiddenComment("\\Afoo").compileAndLint(comment) + assertThat(findings2).hasSize(1) + } + + @Test + fun `should report a finding at the end of a comment`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings1 = ForbiddenComment("qux$").compileAndLint(comment) + assertThat(findings1).hasSize(1) + val findings2 = ForbiddenComment("qux\\Z").compileAndLint(comment) + assertThat(findings2).hasSize(1) + } + + @Test + fun `should report a finding across lines`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("^foo.*qux$").compileAndLint(comment) + assertThat(findings).hasSize(1) + } + + @Test + fun `should not report a finding across lines when the flag is off`() { + val comment = """ + /* + * foo bar + * baz qux + */ + """.trimIndent() + val findings = ForbiddenComment("(?-s)^foo.*qux$").compileAndLint(comment) + assertThat(findings).isEmpty() + } + + @Test + fun `should report all separate findings at once`() { + val comment = """ + /* + * foo baz + * bar qux + */ + """.trimIndent() + val findings = ForbiddenComment("^foo", "^bar").compileAndLint(comment) + assertThat(findings).hasSize(2) + } + } + + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @Nested + inner class `comment getContent` { + + @Suppress("LongMethod", "UnusedPrivateMember") + private fun getCommentsContentArguments() = listOf( + Arguments.of("// comment", "comment"), + Arguments.of("// comment", " comment"), + Arguments.of("//comment", "comment"), + Arguments.of("// ", ""), + Arguments.of("// ", " "), + Arguments.of("/* comment */", "comment"), + Arguments.of("/* comment */", " comment "), + Arguments.of("/* */", ""), + Arguments.of("/* */", ""), + Arguments.of("/*comment*/", "comment"), + Arguments.of("/*** comment ***/", "** comment **"), + Arguments.of( + """ + /* + * good + * good + */ + """.trimIndent(), + """ + good + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + *bad + * good + */ + """.trimIndent(), + """ + bad + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + *bad + * good + */ + """.trimIndent(), + """ + bad + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + comment + * a + * b + * c*/ + """.trimIndent(), + """ + comment + a + b + c + """.trimIndent() + ), + Arguments.of( + """ + /*comment + * a + * b + * c*/ + """.trimIndent(), + """ + comment + a + b + c + """.trimIndent() + ), + Arguments.of( + """ + /* comment + * a + * b + * c */ + """.trimIndent(), + """ + comment + a + b + c + """.trimIndent() + ), + Arguments.of( + """ + /* + * good + * good + */ + """.trimIndent(), + """ + good + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + *bad + * good + */ + """.trimIndent(), + """ + bad + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + *bad + * good + */ + """.trimIndent(), + """ + bad + good + + """.trimIndent() + ), + Arguments.of( + """ + /* + comment + * a + * b + * c*/ + """.trimIndent(), + """ + comment + a + b + c + """.trimIndent() + ), + Arguments.of( + """ + /*comment + * a + * b + * c*/ + """.trimIndent(), + """ + comment + a + b + c + """.trimIndent() + ), + Arguments.of( + """ + /* + a + b + c + */ + """.trimIndent(), + """ + a + b + c + + """.trimIndent() + ), + Arguments.of( + """ + /* + a + b + + c + */ + """.trimIndent(), + """ + a + b + + c + + """.trimIndent() + ), + Arguments.of( + """ + /* + + + */ + """.trimIndent(), + "".trimIndent() + ), + Arguments.of( + """ + /* + + a + + */ + """.trimIndent(), + "a\n\n" + ), + Arguments.of( + """ + /* + + + */ + """.trimIndent(), + "".trimIndent() + ), + Arguments.of( + """ + /* + + a + + */ + """.trimIndent(), + " a\n\n" + ), + Arguments.of( + """ + /* + a + b + c + */ + """.trimIndent(), + """ + a + b + c + + """.trimIndent() + ), + Arguments.of( + """ + /* + + * a + + */ + """.trimIndent(), + "a\n\n" + ), + Arguments.of( + """ + /* + * foo + * bar + * baz + */ + """.trimIndent(), + """ + foo + bar + baz + + """.trimIndent() + ), + Arguments.of( + """ + /* + * foo + * bar + * baz + */ + """.trimIndent(), + """ + foo + bar + baz + + """.trimIndent() + ), + Arguments.of( + """ + /* + + a + b + c + d + *e + * f + * g + * h + * i + */ + """.trimIndent(), + """ + a + b + c + d + e + f + g + h + i + + """.trimIndent() + ), + ) + + @ParameterizedTest(name = "Given {0} comment, getContent return {1}") + @MethodSource("getCommentsContentArguments") + fun test(comment: String, content: String) { + assertThat(comment.getCommentContent()).isEqualTo(content) + } } } + +@Suppress("TestFunctionName") // This is a factory function for ForbiddenComment +private fun ForbiddenComment(vararg comments: String): ForbiddenComment = + ForbiddenComment(TestConfig(COMMENTS to comments.toList())) + +@Suppress("TestFunctionName") +private fun ForbiddenComment(vararg comments: ValueWithReason): ForbiddenComment = + ForbiddenComment(TestConfig(COMMENTS to comments.map { mapOf("value" to it.value, "reason" to it.reason) })) diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/FunctionOnlyReturningConstantSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/FunctionOnlyReturningConstantSpec.kt index 828bc75cfdfa..ed7fdc4e64ff 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/FunctionOnlyReturningConstantSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/FunctionOnlyReturningConstantSpec.kt @@ -1,6 +1,5 @@ package io.gitlab.arturbosch.detekt.rules.style -import io.gitlab.arturbosch.detekt.rules.Case import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.compileAndLint import io.gitlab.arturbosch.detekt.test.lint @@ -20,15 +19,44 @@ class FunctionOnlyReturningConstantSpec { @Nested inner class `FunctionOnlyReturningConstant rule - positive cases` { - val path = Case.FunctionReturningConstantPositive.path() + private val code = """ + fun functionReturningConstantString() = "1" // reports 1 + + fun functionReturningConstantString(str: String) = "str: ${'$'}${'$'}" // reports 1 + + fun functionReturningConstantEscapedString(str: String) = "str: \${'$'}str" // reports 1 + + fun functionReturningConstantChar() = '1' // reports 1 + + fun functionReturningConstantInt(): Int { // reports 1 + return 1 + } + + @Suppress("EqualsOrHashCode", "RedundantSuppression") + open class FunctionReturningConstant { + + open fun f() = 1 // reports 1 + override fun hashCode() = 1 // reports 1 + } + + interface InterfaceFunctionReturningConstant { + + fun interfaceFunctionWithImplementation() = 1 // reports 1 + + class NestedClassFunctionReturningConstant { + + fun interfaceFunctionWithImplementation() = 1 // reports 1 + } + } + """.trimIndent() - val actualFunctionCode = """ + private val actualFunctionCode = """ actual class ActualFunctionReturningConstant { actual fun f() = 1 } """.trimIndent() - val code = """ + private val sinceKotlinCode = """ import kotlin.SinceKotlin class Test { @SinceKotlin("1.0.0") @@ -40,14 +68,14 @@ class FunctionOnlyReturningConstantSpec { @Test fun `reports functions which return constants`() { - assertThat(subject.lint(path)).hasSize(6) + assertThat(subject.lint(code)).hasSize(6) } @Test fun `reports overridden functions which return constants`() { val config = TestConfig(IGNORE_OVERRIDABLE_FUNCTION to "false") val rule = FunctionOnlyReturningConstant(config) - assertThat(rule.lint(path)).hasSize(9) + assertThat(rule.lint(code)).hasSize(9) } @Test @@ -93,7 +121,7 @@ class FunctionOnlyReturningConstantSpec { fun ignoreAnnotatedFunctionWhichReturnsConstantWhenGivenKotlinSinceKotlin() { val config = TestConfig(EXCLUDE_ANNOTATED_FUNCTION to "kotlin.SinceKotlin") val rule = FunctionOnlyReturningConstant(config) - assertThat(rule.compileAndLint(code)).isEmpty() + assertThat(rule.compileAndLint(sinceKotlinCode)).isEmpty() } @Test @@ -103,7 +131,7 @@ class FunctionOnlyReturningConstantSpec { fun ignoreAnnotatedFunctionWhichReturnsConstantWhenGivenListOfKotlinSinceKotlin() { val config = TestConfig(EXCLUDE_ANNOTATED_FUNCTION to listOf("kotlin.SinceKotlin")) val rule = FunctionOnlyReturningConstant(config) - assertThat(rule.compileAndLint(code)).isEmpty() + assertThat(rule.compileAndLint(sinceKotlinCode)).isEmpty() } } @@ -112,8 +140,16 @@ class FunctionOnlyReturningConstantSpec { @Test fun `does not report functions which do not return constants`() { - val path = Case.FunctionReturningConstantNegative.path() - assertThat(subject.lint(path)).isEmpty() + val code = """ + fun functionNotReturningConstant1() = 1 + 1 + + fun functionNotReturningConstant2(): Int { + return 1 + 1 + } + + fun functionNotReturningConstantString1(str: String) = "str: ${'$'}str" + """.trimIndent() + assertThat(subject.lint(code)).isEmpty() } } } diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatementsSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatementsSpec.kt index 7617a94ff5a2..09fe8b4bfa2a 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatementsSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/LoopWithTooManyJumpStatementsSpec.kt @@ -1,32 +1,124 @@ package io.gitlab.arturbosch.detekt.rules.style -import io.gitlab.arturbosch.detekt.rules.Case import io.gitlab.arturbosch.detekt.test.TestConfig -import io.gitlab.arturbosch.detekt.test.lint -import org.assertj.core.api.Assertions.assertThat +import io.gitlab.arturbosch.detekt.test.assertThat +import io.gitlab.arturbosch.detekt.test.compileAndLint import org.junit.jupiter.api.Test private const val MAX_JUMP_COUNT = "maxJumpCount" class LoopWithTooManyJumpStatementsSpec { - val subject = LoopWithTooManyJumpStatements() - val path = Case.LoopWithTooManyJumpStatementsPositive.path() @Test - fun `reports loops with more than 1 break or continue statement`() { - assertThat(subject.lint(path)).hasSize(3) + fun `reports for loops with more than 1 break or continue statement`() { + val code = """ + fun f(i: Int) { + for (j in 1..2) { + if (i > 1) { + break + } else { + continue + } + } + } + """.trimIndent() + assertThat(LoopWithTooManyJumpStatements().compileAndLint(code)).hasTextLocations(20 to 23) } @Test - fun `does not report when max count configuration is set to 2`() { - val config = TestConfig(MAX_JUMP_COUNT to "2") - val findings = LoopWithTooManyJumpStatements(config).lint(path) + fun `reports while loops with more than 1 break or continue statement`() { + val code = """ + fun f(i: Int) { + while (i < 3) { + if (i > 1) break else continue + } + } + """.trimIndent() + assertThat(LoopWithTooManyJumpStatements().compileAndLint(code)).hasTextLocations(20 to 25) + } + + @Test + fun `reports do loops with more than 1 break or continue statement`() { + val code = """ + fun f(i: Int) { + do { + if (i > 2) break else continue + } while (i < 1) + } + """.trimIndent() + assertThat(LoopWithTooManyJumpStatements().compileAndLint(code)).hasTextLocations(20 to 22) + } + + @Test + fun `does not report for loops when max count configuration is set to 2`() { + val code = """ + fun f(i: Int) { + for (j in 1..2) { + if (i > 1) { + break + } else { + continue + } + } + } + """.trimIndent() + val findings = LoopWithTooManyJumpStatements(TestConfig(MAX_JUMP_COUNT to "2")).compileAndLint(code) + assertThat(findings).isEmpty() + } + + @Test + fun `does not report while loops when max count configuration is set to 2`() { + val code = """ + fun f(i: Int) { + while (i < 3) { + if (i > 1) break else continue + } + } + """.trimIndent() + val findings = LoopWithTooManyJumpStatements(TestConfig(MAX_JUMP_COUNT to "2")).compileAndLint(code) + assertThat(findings).isEmpty() + } + + @Test + fun `does not report do loops when max count configuration is set to 2`() { + val code = """ + fun f(i: Int) { + do { + if (i > 2) break else continue + } while (i < 1) + } + """.trimIndent() + val findings = LoopWithTooManyJumpStatements(TestConfig(MAX_JUMP_COUNT to "2")).compileAndLint(code) + assertThat(findings).isEmpty() + } + + @Test + fun `does not report for loop with less than 1 break statement`() { + val code = """ + fun onlyOneJump() { + for (i in 1..2) { + if (i > 1) break + } + } + """.trimIndent() + val findings = LoopWithTooManyJumpStatements().compileAndLint(code) assertThat(findings).isEmpty() } @Test - fun `does not report loop with less than 1 break or continue statement`() { - val findings = subject.lint(Case.LoopWithTooManyJumpStatementsNegative.path()) + fun `does not report nested loop with less than 1 break or continue statement`() { + val code = """ + fun jumpsInNestedLoops() { + for (i in 1..10) { + if (i > 5) break + // jump statements of the inner loop must not be counted in the outer loop + while (i < 3) { + if (i > 1) continue + } + } + } + """.trimIndent() + val findings = LoopWithTooManyJumpStatements().compileAndLint(code) assertThat(findings).isEmpty() } } diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MaxLineLengthSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MaxLineLengthSpec.kt index 6554cd0aeee7..43db6fb0aac6 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MaxLineLengthSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MaxLineLengthSpec.kt @@ -1,9 +1,7 @@ package io.gitlab.arturbosch.detekt.rules.style import io.github.detekt.test.utils.compileContentForTest -import io.github.detekt.test.utils.compileForTest import io.gitlab.arturbosch.detekt.api.SourceLocation -import io.gitlab.arturbosch.detekt.rules.Case import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.compileAndLint @@ -21,7 +19,61 @@ class MaxLineLengthSpec { @Nested inner class `a kt file with some long lines` { - private val file = compileForTest(Case.MaxLineLength.path()) + private val file = compileContentForTest( + """ + class MaxLineLength { + companion object { + val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + + val A_VERY_LONG_MULTI_LINE = $TQ + This is another very very very very very very very very, very long multiline String that will break the MaxLineLength" + $TQ.trimIndent() + } + + val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + + val longMultiLineField = $TQ + This is another very very very very very very very very + very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very + very long multiline String that will break the MaxLineLength + $TQ.trimIndent() + + val longMultiLineFieldWithLineBreaks = + $TQ + This is another very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very + very long multiline String with Line Break that will break the MaxLineLength + $TQ.trimIndent() + + val longMultiLineFieldWithLeadingQuote = + $TQ + "This is yet another very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very" + "very long multiline String with Line Break that will break the MaxLineLength" + $TQ.trimIndent() + + fun main() { + val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" + + if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { + println("It's indeed a very long String") + } + + val hello = anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot() + val loremIpsum = getLoremIpsum() + + println(hello) + println(loremIpsum) + + } + + // https://longurlmaker.com/go?id=z4Is.gd8c11rangy50farawayRedirx1drawn%2Bout60c3protractedTinyLinkstringylingeringfar%2BreachinglMyURLSHurl86018lengthy7frunningoutstretched1111ilengthy2xSimURLSmallr800xSimURL361juEasyURLSimURL0022faraway1095xhighfar%2Boff1618sustained0Shima8961toutstretchedexpanded0stretch611220drawn%2BoutdwkTightURL8kDoioplongish10Xil14b101ShredURLTraceURLbptoweringB6512TinyURL6towering0rGetShorty004bm5301URLprotracted0prolonged61MooURLy1948jspread%2Bout428u0t3stretchingfarawaylasting11ShredURLc2bDigBigexpandedX.se90a20TinyURL26WapURLr1cprolongedkelongatedc1f2c01loftylengthycontinuede7WapURLgGetShorty2NutshellURLcontinued6a2lastingr5protracted1expandeddistantspread%2BoutURl.iersustainedNotLongSHurl3w2SimURL011xSnipURL02GetShorty2prolonged0f02f60blingeringIs.gd301URLTinyLinktowering3d200t01osustained2WapURL90ShortURL11spread%2Boute02URLPieFly2toweringDwarfurl70elongated9s070SnipURL6Is.gd7spread%2Boutc0hy210vtcnf43Redirxb9148n1lingering6PiURL16URLcutaspread%2BoutYATUCoutstretchede70lUlimita1e610ShortenURL1lnk.inenduringUlimit0U760l8m72011793v7020TightURLelongatedYATUCt6UrlTeaetc91e5kspun%2Bout010d1e1b1Dwarfurl6Shortlinksb0sustained0enlarged6great1187e5e690URLCutter1spun%2Bout10drawn%2Bouttall4EasyURLDecentURLenduringd1eTraceURL5yGetShortyTinyLinkfar%2Boff1prolonged4cc0stretcheddeepprotracted3f001elongate9018ystretchinglastingi7TinyURL7expanded910continuedremotef8sustainedz175lingeringcbloftyprolonged10079running0UlimitB6515Shrinkr00LiteURL1loftyoutstretchedclnk.in3farawayg5runningTinyLinkspread%2Bout1stringy11c036greatfarawaystretchingefar%2Boff31spread%2Bout4kDoiopMooURL53m19Beam.tolastingShredURL1s25ShimBeam.to8nstretchtowering80StartURLShortURL4lengthened018Is.gdNotLongzWapURLNutshellURLe2spun%2Bout119elongated7elongated5outstretchedh8k1stringyloftyShredURL84running06308d071Minilien3wg3UrlTealoftystretchedwCanURLfar%2Boff7atf104083towering820ganglingw35m1a063LiteURLt081NanoRef361lnk.in0deep0Shrinkr6e80far%2Boff9170Redirxy6btspread%2Boutsustained10UlimitShortlinks2toweringGetShorty3ShrinkrDecentURLsustaineddbg1nfShortURL331a001enlargedB65RedirxelongatedMinilien809UrlT + fun anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot(): String { + return "Hello" + } + + fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + """.trimIndent() + ) @Test fun `should report no errors when maxLineLength is set to 200`() { @@ -67,7 +119,76 @@ class MaxLineLengthSpec { @Nested inner class `a kt file with long but suppressed lines` { - private val file = compileForTest(Case.MaxLineLengthSuppressed.path()) + + private val file = compileContentForTest( + """ + class MaxLineLengthSuppressed { + companion object { + @Suppress("MaxLineLength") + val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + + @Suppress("MaxLineLength") + val A_VERY_LONG_MULTI_LINE = $TQ + This is anotehr very very very very very very very very, very long multiline String that will break the MaxLineLength" + $TQ.trimIndent() + } + + @Suppress("MaxLineLength") + val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + + @Suppress("MaxLineLength") + val longMultiLineField = $TQ + This is anotehr very very very very very very very very + very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very + very long multiline String that will break the MaxLineLength + $TQ.trimIndent() + + @Suppress("MaxLineLength") + val longMultiLineFieldWithLineBreaks = + $TQ + This is anotehr very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very + very long multiline String with Line Break that will break the MaxLineLength + $TQ.trimIndent() + + fun main() { + val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" + + if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { + println("It's indeed a very long String") + } + + @Suppress("MaxLineLength") + val hello = anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot() + val loremIpsum = getLoremIpsum() + + println(hello) + println(loremIpsum) + + } + + fun anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot(): String { + return "Hello" + } + + @Suppress("MaxLineLength") + fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + + @Suppress("MaxLineLength") + class AClassWithSuperLongNameItIsSooooLongThatIHaveTroubleThinkingAboutAVeryLongNameManThisIsReallyHardToFillAllTheNecessaryCharacters + + @Suppress("MaxLineLength") + class AClassWithReallyLongCommentsInside { + /* + a really long line that is inside a normal comment ------------------------------------------------------------------------------------------------> + */ + + /** + a really long line that is inside a KDoc comment ------------------------------------------------------------------------------------------------> + */ + } + """.trimIndent() + ) @Test fun `should not report as lines are suppressed`() { @@ -134,7 +255,38 @@ class MaxLineLengthSpec { @Nested inner class `a kt file with a long package name, long import statements, a long line and long comments` { - private val file = compileForTest(Case.MaxLineLengthWithLongComments.path()) + private val file = compileContentForTest( + """ + class MaxLineLengthWithLongComments { + // Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + /* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. */ + + /* + * Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + */ + companion object { + val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + + val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + + fun main() { + val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" + + if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { + println("It's indeed a very long String") + } + + val loremIpsum = getLoremIpsum() + + println(loremIpsum) + + } + + fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." + } + """.trimIndent() + ) @Test fun `should report the package statement, import statements, line and comments by default`() { @@ -262,10 +414,10 @@ class MaxLineLengthSpec { compileContentForTest( """ // some other content - val x = Regex(${"\"\"\""} + val x = Regex($TQ Text (.*?)\(in parens\) this is too long to be valid. The regex/raw string continues down another line . - ${"\"\"\""}.trimIndent()) + $TQ.trimIndent()) // that is the right length """.trimIndent() ) @@ -287,7 +439,7 @@ class MaxLineLengthSpec { compileContentForTest( """ // some other content - val x = "Foo".matches(${"\"\"\""}...too long\(parens\) and some more${"\"\"\""}.toRegex()) + val x = "Foo".matches($TQ...too long\(parens\) and some more$TQ.toRegex()) // that is the right length """.trimIndent() ) @@ -440,3 +592,5 @@ class MaxLineLengthSpec { } } } + +private const val TQ = "\"\"\"" diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/NoTabsSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/NoTabsSpec.kt index 4ce97bbe12ea..1f8728cdaf6a 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/NoTabsSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/NoTabsSpec.kt @@ -1,7 +1,6 @@ package io.gitlab.arturbosch.detekt.rules.style -import io.github.detekt.test.utils.compileForTest -import io.gitlab.arturbosch.detekt.rules.Case +import io.github.detekt.test.utils.compileContentForTest import io.gitlab.arturbosch.detekt.test.assertThat import org.junit.jupiter.api.Test @@ -10,15 +9,42 @@ class NoTabsSpec { @Test fun `should flag a line that contains a tab`() { - val file = compileForTest(Case.NoTabsPositive.path()) + val file = compileContentForTest( + """ + class NoTabsPositive { + ${TAB}fun methodOk() { // reports 3 + ${TAB}${TAB}println("A message") + + $TAB} + + val str = "${'$'}{${TAB}${TAB}methodOk()}" // reports 1 + val multiStr = $TQ${'$'}{${TAB}methodOk()}$TQ // reports 1 + } + """.trimIndent() + ) subject.visitFile(file) assertThat(subject.findings).hasSize(5) } @Test fun `should not flag a line that does not contain a tab`() { - val file = compileForTest(Case.NoTabsNegative.path()) + val file = compileContentForTest( + """ + class NoTabsNegative { + + fun methodOk() { + println("A message") + } + + val str = "A \t tab " + val multiStr = ""${'"'}A \t tab ""${'"'} + } + """.trimIndent() + ) subject.visitFile(file) assertThat(subject.findings).isEmpty() } } + +private const val TQ = "\"\"\"" +private const val TAB = "\t" diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClassSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClassSpec.kt index efd241bf4d32..1a0924e36a6c 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClassSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/SerialVersionUIDInSerializableClassSpec.kt @@ -1,6 +1,8 @@ package io.gitlab.arturbosch.detekt.rules.style import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.SourceLocation +import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.compileAndLint import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -15,7 +17,16 @@ class SerialVersionUIDInSerializableClassSpec { class C : Serializable """.trimIndent() - assertThat(subject.compileAndLint(code)).hasSize(1) + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(1) + .hasStartSourceLocation(3, 7) + .hasEndSourceLocation(3, 8) + assertThat(findings[0]) + .hasMessage( + "The class C implements the `Serializable` interface and should thus define " + + "a `serialVersionUID`." + ) } @Test @@ -29,7 +40,14 @@ class SerialVersionUIDInSerializableClassSpec { } } """.trimIndent() - assertThat(subject.compileAndLint(code)).hasSize(1) + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(1) + .hasStartSourceLocation(5, 27) + .hasEndSourceLocation(5, 43) + assertThat(findings[0]).hasMessage( + WRONG_SERIAL_VERSION_UID_MESSAGE + ) } @Test @@ -43,7 +61,13 @@ class SerialVersionUIDInSerializableClassSpec { } } """.trimIndent() - assertThat(subject.compileAndLint(code)).hasSize(1) + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(1) + .hasStartSourceLocation(5, 27) + .hasEndSourceLocation(5, 43) + + assertThat(findings[0]).hasMessage(WRONG_SERIAL_VERSION_UID_MESSAGE) } @Test @@ -57,7 +81,13 @@ class SerialVersionUIDInSerializableClassSpec { } } """.trimIndent() - assertThat(subject.compileAndLint(code)).hasSize(1) + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(1) + .hasStartSourceLocation(5, 19) + .hasEndSourceLocation(5, 35) + + assertThat(findings[0]).hasMessage(WRONG_SERIAL_VERSION_UID_MESSAGE) } @Test @@ -75,7 +105,89 @@ class SerialVersionUIDInSerializableClassSpec { } } """.trimIndent() - assertThat(subject.compileAndLint(code)).hasSize(2) + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(2) + .hasStartSourceLocations(SourceLocation(3, 7), SourceLocation(8, 12)) + .hasEndSourceLocations(SourceLocation(3, 8), SourceLocation(8, 43)) + assertThat(findings.map { it.message }).containsOnly( + "The class C implements the `Serializable` interface and should thus define " + + "a `serialVersionUID`.", + "The object NestedIncorrectSerialVersionUID implements the `Serializable` interface and should thus " + + "define a `serialVersionUID`." + ) + } + + @Test + fun `reports nested object without const modifier`() { + val code = """ + import java.io.Serializable + + object A : Serializable { + object B : Serializable { + private val serialVersionUID = 1L + } + + private val serialVersionUID = 1L + } + """.trimIndent() + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(2) + .hasStartSourceLocations(SourceLocation(5, 21), SourceLocation(8, 17)) + .hasEndSourceLocations(SourceLocation(5, 37), SourceLocation(8, 33)) + assertThat(findings.map { it.message }).containsOnly( + WRONG_SERIAL_VERSION_UID_MESSAGE, + WRONG_SERIAL_VERSION_UID_MESSAGE + ) + } + + @Test + fun `reports nested class without const modifier`() { + val code = """ + import java.io.Serializable + + class A : Serializable { + class B : Serializable { + companion object { + private val serialVersionUID = 1L + } + } + + companion object { + private val serialVersionUID = 1L + } + } + """.trimIndent() + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(2) + .hasStartSourceLocations(SourceLocation(6, 25), SourceLocation(11, 21)) + .hasEndSourceLocations(SourceLocation(6, 41), SourceLocation(11, 37)) + assertThat(findings.map { it.message }).containsOnly( + WRONG_SERIAL_VERSION_UID_MESSAGE, + WRONG_SERIAL_VERSION_UID_MESSAGE + ) + } + + @Test + fun `reports nested class without serialVersionUID property`() { + val code = """ + import java.io.Serializable + + class A : Serializable { + class B : Serializable + } + """.trimIndent() + val findings = subject.compileAndLint(code) + assertThat(findings) + .hasSize(2) + .hasStartSourceLocations(SourceLocation(3, 7), SourceLocation(4, 11)) + .hasEndSourceLocations(SourceLocation(3, 8), SourceLocation(4, 12)) + assertThat(findings.map { it.message }).containsOnly( + "The class A implements the `Serializable` interface and should thus define a `serialVersionUID`.", + "The class B implements the `Serializable` interface and should thus define a `serialVersionUID`." + ) } @Test @@ -135,4 +247,10 @@ class SerialVersionUIDInSerializableClassSpec { """.trimIndent() assertThat(subject.compileAndLint(code)).isEmpty() } + + companion object { + private const val WRONG_SERIAL_VERSION_UID_MESSAGE = + "The property `serialVersionUID` signature is not correct. `serialVersionUID` should be " + + "`private` and `constant` and its type should be `Long`" + } } diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UtilityClassWithPublicConstructorSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UtilityClassWithPublicConstructorSpec.kt index 24045fdb7a7c..603680948caf 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UtilityClassWithPublicConstructorSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UtilityClassWithPublicConstructorSpec.kt @@ -2,7 +2,6 @@ package io.gitlab.arturbosch.detekt.rules.style import io.gitlab.arturbosch.detekt.api.Config import io.gitlab.arturbosch.detekt.api.Finding -import io.gitlab.arturbosch.detekt.rules.Case import io.gitlab.arturbosch.detekt.test.lint import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -20,7 +19,63 @@ class UtilityClassWithPublicConstructorSpec { @BeforeEach fun beforeEachTest() { - findings = subject.lint(Case.UtilityClassesPositive.path()) + findings = subject.lint( + """ + class UtilityClassWithDefaultConstructor { // violation + companion object { + val C = 0 + } + } + + class UtilityClassWithPrimaryConstructor1 constructor() { // violation + companion object { + val C = 0 + } + } + + class UtilityClassWithPrimaryConstructor2() { // violation + companion object { + val C = 0 + } + } + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class UtilityClassWithSecondaryConstructor { // violation + constructor() + + companion object { + val C = 0 + } + } + + class UtilityClassWithEmptyCompanionObj { // violation + companion object + } + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + open class OpenUtilityClass { // violation - utility class should be final + internal constructor() + + companion object { + val C = 0 + } + } + + sealed class SealedParent { + companion object { + fun create(foo: Int?, bar: String?): SealedParent? = + when { + foo != null -> FooChild(foo) + bar != null -> BarChild(bar) + else -> null + } + } + } + + data class FooChild(val foo: Int) : SealedParent() + data class BarChild(val bar: String) : SealedParent() + """.trimIndent() + ) } @Test @@ -39,9 +94,116 @@ class UtilityClassWithPublicConstructorSpec { @Nested inner class `several classes which adhere to the UtilityClassWithPublicConstructor rule` { + @Suppress("LongMethod") // TODO split this up into multiple test case functions. @Test fun `does not report given classes`() { - val findings = subject.lint(Case.UtilityClassesNegative.path()) + val findings = subject.lint( + """ + class UtilityClassWithPrimaryPrivateConstructorOk private constructor() { + companion object { + val C = 0 + } + } + + class UtilityClassWithPrimaryInternalConstructorOk internal constructor() { + companion object { + val C = 0 + } + } + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class UtilityClassWithSecondaryConstructorOk { + private constructor() + + companion object { + val C = 0 + } + } + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class NoUtilityClassBecauseOfInterface : InterfaceWithCompanionObject { + constructor() + + companion object { + val C = 0 + } + } + + open class UtilityClassesNegativeParent(val i: Int) + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class NoUtilityClassBecauseOfInheritance : UtilityClassesNegativeParent { + constructor(i: Int) : super(i) + + companion object { + val C = 0 + } + } + + class NoUtilityClasses { + private val i = 0 + + class EmptyClass1 {} + class EmptyClass2 + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class ClassWithSecondaryConstructor { + constructor() + } + + class ClassWithInstanceFunc { + + fun f() {} + + companion object { + val C = 0 + } + } + + class ClassWithPrimaryConstructorParameter1(val i: Int) { + + companion object { + val C = 0 + } + } + + class ClassWithPrimaryConstructorParameter2 constructor(val i: Int) { + + companion object { + val C = 0 + } + } + + @Suppress("ConvertSecondaryConstructorToPrimary", "RedundantSuppression") + class ClassWithSecondaryConstructorParameter { + + constructor(i: Int) + + companion object { + val C = 0 + } + } + + companion object { + val C = 0 + } + } + + interface InterfaceWithCompanionObject { + companion object { + val C = 0 + } + } + + interface SomeInterface + class SomeImplementation : SomeInterface + class NotUtilityClass : SomeInterface by SomeImplementation() { + // Issue#682 - Class with delegate is no utility class + companion object { + val C = 0 + } + } + """.trimIndent() + ) assertThat(findings).isEmpty() } } diff --git a/detekt-rules-style/src/test/resources/FunctionReturningConstantNegative.kt b/detekt-rules-style/src/test/resources/FunctionReturningConstantNegative.kt deleted file mode 100644 index 6dc323780310..000000000000 --- a/detekt-rules-style/src/test/resources/FunctionReturningConstantNegative.kt +++ /dev/null @@ -1,11 +0,0 @@ -@file:Suppress("unused") - -package cases - -fun functionNotReturningConstant1() = 1 + 1 - -fun functionNotReturningConstant2(): Int { - return 1 + 1 -} - -fun functionNotReturningConstantString1(str: String) = "str: $str" diff --git a/detekt-rules-style/src/test/resources/FunctionReturningConstantPositive.kt b/detekt-rules-style/src/test/resources/FunctionReturningConstantPositive.kt deleted file mode 100644 index 241b17dd3f90..000000000000 --- a/detekt-rules-style/src/test/resources/FunctionReturningConstantPositive.kt +++ /dev/null @@ -1,32 +0,0 @@ -@file:Suppress("unused", "UNUSED_PARAMETER") - -package cases - -fun functionReturningConstantString() = "1" // reports 1 - -fun functionReturningConstantString(str: String) = "str: $$" // reports 1 - -fun functionReturningConstantEscapedString(str: String) = "str: \$str" // reports 1 - -fun functionReturningConstantChar() = '1' // reports 1 - -fun functionReturningConstantInt(): Int { // reports 1 - return 1 -} - -@Suppress("EqualsOrHashCode") -open class FunctionReturningConstant { - - open fun f() = 1 // reports 1 - override fun hashCode() = 1 // reports 1 -} - -interface InterfaceFunctionReturningConstant { - - fun interfaceFunctionWithImplementation() = 1 // reports 1 - - class NestedClassFunctionReturningConstant { - - fun interfaceFunctionWithImplementation() = 1 // reports 1 - } -} diff --git a/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsNegative.kt b/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsNegative.kt deleted file mode 100644 index 6dff44e9205c..000000000000 --- a/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsNegative.kt +++ /dev/null @@ -1,19 +0,0 @@ -@file:Suppress("unused") - -package cases - -fun onlyOneJump() { - for (i in 1..2) { - if (i > 1) break - } -} - -fun jumpsInNestedLoops() { - for (i in 1..2) { - if (i > 1) break - // jump statements of the inner loop must not be counted in the outer loop - while (i > 1) { - if (i > 1) continue - } - } -} diff --git a/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsPositive.kt b/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsPositive.kt deleted file mode 100644 index 740f7a6d12ff..000000000000 --- a/detekt-rules-style/src/test/resources/LoopWithTooManyJumpStatementsPositive.kt +++ /dev/null @@ -1,25 +0,0 @@ -package cases - -@Suppress("unused", "ConstantConditionIf") -fun tooManyJumpStatements() { - val i = 0 - - // reports 1 - too many jump statements - for (j in 1..2) { - if (i > 1) { - break - } else { - continue - } - } - - // reports 1 - too many jump statements - while (i < 2) { - if (i > 1) break else continue - } - - // reports 1 - too many jump statements - do { - if (i > 1) break else continue - } while (i < 2) -} diff --git a/detekt-rules-style/src/test/resources/MaxLineLength.kt b/detekt-rules-style/src/test/resources/MaxLineLength.kt index 567a100731ee..e69de29bb2d1 100644 --- a/detekt-rules-style/src/test/resources/MaxLineLength.kt +++ b/detekt-rules-style/src/test/resources/MaxLineLength.kt @@ -1,54 +0,0 @@ -package cases - -@Suppress("unused") -class MaxLineLength { - companion object { - val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - - val A_VERY_LONG_MULTI_LINE = """ - This is another very very very very very very very very, very long multiline String that will break the MaxLineLength" - """.trimIndent() - } - - val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - - val longMultiLineField = """ - This is another very very very very very very very very - very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very - very long multiline String that will break the MaxLineLength - """.trimIndent() - - val longMultiLineFieldWithLineBreaks = - """ - This is another very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very - very long multiline String with Line Break that will break the MaxLineLength - """.trimIndent() - - val longMultiLineFieldWithLeadingQuote = - """ - "This is yet another very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very" - "very long multiline String with Line Break that will break the MaxLineLength" - """.trimIndent() - - fun main() { - val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" - - if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { - println("It's indeed a very long String") - } - - val hello = anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot() - val loremIpsum = getLoremIpsum() - - println(hello) - println(loremIpsum) - - } - - // https://longurlmaker.com/go?id=z4Is.gd8c11rangy50farawayRedirx1drawn%2Bout60c3protractedTinyLinkstringylingeringfar%2BreachinglMyURLSHurl86018lengthy7frunningoutstretched1111ilengthy2xSimURLSmallr800xSimURL361juEasyURLSimURL0022faraway1095xhighfar%2Boff1618sustained0Shima8961toutstretchedexpanded0stretch611220drawn%2BoutdwkTightURL8kDoioplongish10Xil14b101ShredURLTraceURLbptoweringB6512TinyURL6towering0rGetShorty004bm5301URLprotracted0prolonged61MooURLy1948jspread%2Bout428u0t3stretchingfarawaylasting11ShredURLc2bDigBigexpandedX.se90a20TinyURL26WapURLr1cprolongedkelongatedc1f2c01loftylengthycontinuede7WapURLgGetShorty2NutshellURLcontinued6a2lastingr5protracted1expandeddistantspread%2BoutURl.iersustainedNotLongSHurl3w2SimURL011xSnipURL02GetShorty2prolonged0f02f60blingeringIs.gd301URLTinyLinktowering3d200t01osustained2WapURL90ShortURL11spread%2Boute02URLPieFly2toweringDwarfurl70elongated9s070SnipURL6Is.gd7spread%2Boutc0hy210vtcnf43Redirxb9148n1lingering6PiURL16URLcutaspread%2BoutYATUCoutstretchede70lUlimita1e610ShortenURL1lnk.inenduringUlimit0U760l8m72011793v7020TightURLelongatedYATUCt6UrlTeaetc91e5kspun%2Bout010d1e1b1Dwarfurl6Shortlinksb0sustained0enlarged6great1187e5e690URLCutter1spun%2Bout10drawn%2Bouttall4EasyURLDecentURLenduringd1eTraceURL5yGetShortyTinyLinkfar%2Boff1prolonged4cc0stretcheddeepprotracted3f001elongate9018ystretchinglastingi7TinyURL7expanded910continuedremotef8sustainedz175lingeringcbloftyprolonged10079running0UlimitB6515Shrinkr00LiteURL1loftyoutstretchedclnk.in3farawayg5runningTinyLinkspread%2Bout1stringy11c036greatfarawaystretchingefar%2Boff31spread%2Bout4kDoiopMooURL53m19Beam.tolastingShredURL1s25ShimBeam.to8nstretchtowering80StartURLShortURL4lengthened018Is.gdNotLongzWapURLNutshellURLe2spun%2Bout119elongated7elongated5outstretchedh8k1stringyloftyShredURL84running06308d071Minilien3wg3UrlTealoftystretchedwCanURLfar%2Boff7atf104083towering820ganglingw35m1a063LiteURLt081NanoRef361lnk.in0deep0Shrinkr6e80far%2Boff9170Redirxy6btspread%2Boutsustained10UlimitShortlinks2toweringGetShorty3ShrinkrDecentURLsustaineddbg1nfShortURL331a001enlargedB65RedirxelongatedMinilien809UrlT - fun anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot(): String { - return "Hello" - } - - fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." -} diff --git a/detekt-rules-style/src/test/resources/MaxLineLengthSuppressed.kt b/detekt-rules-style/src/test/resources/MaxLineLengthSuppressed.kt index 2220b3d239e8..e69de29bb2d1 100644 --- a/detekt-rules-style/src/test/resources/MaxLineLengthSuppressed.kt +++ b/detekt-rules-style/src/test/resources/MaxLineLengthSuppressed.kt @@ -1,68 +0,0 @@ -package cases - -@Suppress("unused") -class MaxLineLengthSuppressed { - companion object { - @Suppress("MaxLineLength") - val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - - @Suppress("MaxLineLength") - val A_VERY_LONG_MULTI_LINE = """ - This is anotehr very very very very very very very very, very long multiline String that will break the MaxLineLength" - """.trimIndent() - } - - @Suppress("MaxLineLength") - val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - - @Suppress("MaxLineLength") - val longMultiLineField = """ - This is anotehr very very very very very very very very - very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very - very long multiline String that will break the MaxLineLength - """.trimIndent() - - @Suppress("MaxLineLength") - val longMultiLineFieldWithLineBreaks = - """ - This is anotehr very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very - very long multiline String with Line Break that will break the MaxLineLength - """.trimIndent() - - fun main() { - val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" - - if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { - println("It's indeed a very long String") - } - - @Suppress("MaxLineLength") - val hello = anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot() - val loremIpsum = getLoremIpsum() - - println(hello) - println(loremIpsum) - - } - - fun anIncrediblyLongAndComplexMethodNameThatProbablyShouldBeMuchShorterButForTheSakeOfTheTestItsNot(): String { - return "Hello" - } - - @Suppress("MaxLineLength") - fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." -} - -@Suppress("MaxLineLength") -class AClassWithSuperLongNameItIsSooooLongThatIHaveTroubleThinkingAboutAVeryLongNameManThisIsReallyHardToFillAllTheNecessaryCharacters - -@Suppress("MaxLineLength") -class AClassWithReallyLongCommentsInside { - /* - a really long line that is inside a normal comment ------------------------------------------------------------------------------------------------> - */ - - /** - a really long line that is inside a KDoc comment ------------------------------------------------------------------------------------------------> - */ -} diff --git a/detekt-rules-style/src/test/resources/MaxLineLengthWithLongComments.kt b/detekt-rules-style/src/test/resources/MaxLineLengthWithLongComments.kt index 19fa858f6699..e69de29bb2d1 100644 --- a/detekt-rules-style/src/test/resources/MaxLineLengthWithLongComments.kt +++ b/detekt-rules-style/src/test/resources/MaxLineLengthWithLongComments.kt @@ -1,31 +0,0 @@ -package cases - -@Suppress("unused") -class MaxLineLengthWithLongComments { - // Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. - /* Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. */ - - /* - * Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. - */ - companion object { - val LOREM_IPSUM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - } - - val loremIpsumField = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." - - fun main() { - val thisIsAVeryLongValName = "This is a very, very long String that will break the MaxLineLength" - - if (thisIsAVeryLongValName.length > "This is not quite as long of a String".length) { - println("It's indeed a very long String") - } - - val loremIpsum = getLoremIpsum() - - println(loremIpsum) - - } - - fun getLoremIpsum() = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua." -} diff --git a/detekt-rules-style/src/test/resources/NoTabsNegative.kt b/detekt-rules-style/src/test/resources/NoTabsNegative.kt deleted file mode 100644 index 79e0dc2f4b33..000000000000 --- a/detekt-rules-style/src/test/resources/NoTabsNegative.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cases - -class NoTabsNegative { - - fun methodOk() { - println("A message") - } - - val str = "A \t tab " - val multiStr = """A \t tab """ -} diff --git a/detekt-rules-style/src/test/resources/NoTabsPositive.kt b/detekt-rules-style/src/test/resources/NoTabsPositive.kt deleted file mode 100644 index bdfedc9a00d9..000000000000 --- a/detekt-rules-style/src/test/resources/NoTabsPositive.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cases - -class NoTabsPositive { - fun methodOk() { // reports 3 - println("A message") - - } - - val str = "${ methodOk()}" // reports 1 - val multiStr = """${ methodOk()}""" // reports 1 -} diff --git a/detekt-rules-style/src/test/resources/UtilityClassesNegative.kt b/detekt-rules-style/src/test/resources/UtilityClassesNegative.kt deleted file mode 100644 index c80d87ee49a1..000000000000 --- a/detekt-rules-style/src/test/resources/UtilityClassesNegative.kt +++ /dev/null @@ -1,109 +0,0 @@ -@file:Suppress("unused", "ConvertSecondaryConstructorToPrimary", "UNUSED_PARAMETER") - -package cases - -class UtilityClassWithPrimaryPrivateConstructorOk private constructor() { - - companion object { - val C = 0 - } -} - -class UtilityClassWithPrimaryInternalConstructorOk internal constructor() { - - companion object { - val C = 0 - } -} - -class UtilityClassWithSecondaryConstructorOk { - - private constructor() - - companion object { - val C = 0 - } -} - -class NoUtilityClassBecauseOfInterface : InterfaceWithCompanionObject { - - constructor() - - companion object { - val C = 0 - } -} - -open class UtilityClassesNegativeParent(val i: Int) -class NoUtilityClassBecauseOfInheritance : UtilityClassesNegativeParent { - - constructor(i: Int) : super(i) - - companion object { - val C = 0 - } -} - -class NoUtilityClasses { - - private val i = 0 - - class EmptyClass1 {} - class EmptyClass2 - - class ClassWithSecondaryConstructor { - constructor() - } - - class ClassWithInstanceFunc { - - fun f() {} - - companion object { - val C = 0 - } - } - - class ClassWithPrimaryConstructorParameter1(val i: Int) { - - companion object { - val C = 0 - } - } - - class ClassWithPrimaryConstructorParameter2 constructor(val i: Int) { - - companion object { - val C = 0 - } - } - - class ClassWithSecondaryConstructorParameter { - - constructor(i: Int) - - companion object { - val C = 0 - } - } - - companion object { - val C = 0 - } -} - -interface InterfaceWithCompanionObject { - - companion object { - val C = 0 - } -} - -interface SomeInterface -class SomeImplementation : SomeInterface -class NotUtilityClass : SomeInterface by SomeImplementation() { - // Issue#682 - Class with delegate is no utility class - companion object { - val C = 0 - } -} diff --git a/detekt-rules-style/src/test/resources/UtilityClassesPositive.kt b/detekt-rules-style/src/test/resources/UtilityClassesPositive.kt deleted file mode 100644 index 54802a3cebf9..000000000000 --- a/detekt-rules-style/src/test/resources/UtilityClassesPositive.kt +++ /dev/null @@ -1,62 +0,0 @@ -@file:Suppress("unused", "ConvertSecondaryConstructorToPrimary", "RemoveEmptyPrimaryConstructor") - -package cases - -class UtilityClassWithDefaultConstructor { // violation - - companion object { - val C = 0 - } -} - -class UtilityClassWithPrimaryConstructor1 constructor() { // violation - - companion object { - val C = 0 - } -} - -class UtilityClassWithPrimaryConstructor2() { // violation - - companion object { - val C = 0 - } -} - -class UtilityClassWithSecondaryConstructor { // violation - - constructor() - - companion object { - val C = 0 - } -} - -class UtilityClassWithEmptyCompanionObj { // violation - - companion object -} - -open class OpenUtilityClass { // violation - utility class should be final - - internal constructor() - - companion object { - val C = 0 - } -} - -sealed class SealedParent { - companion object { - fun create(foo: Int?, bar: String?): SealedParent? = - when { - foo != null -> FooChild(foo) - bar != null -> BarChild(bar) - else -> null - } - } -} - -data class FooChild(val foo: Int) : SealedParent() - -data class BarChild(val bar: String) : SealedParent() diff --git a/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/SuppressingSpec.kt b/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/SuppressingSpec.kt index b260fe3f54e1..b399317ef8bf 100644 --- a/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/SuppressingSpec.kt +++ b/detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/SuppressingSpec.kt @@ -1,7 +1,6 @@ package io.gitlab.arturbosch.detekt.rules -import io.github.detekt.test.utils.compileForTest -import io.github.detekt.test.utils.resourceAsPath +import io.github.detekt.test.utils.compileContentForTest import io.gitlab.arturbosch.detekt.api.RuleSet import io.gitlab.arturbosch.detekt.core.rules.visitFile import io.gitlab.arturbosch.detekt.rules.complexity.ComplexCondition @@ -18,7 +17,57 @@ class SuppressingSpec { @Test fun `all findings are suppressed on element levels`() { - val ktFile = compileForTest(resourceAsPath("SuppressedByElementAnnotation.kt")) + @Suppress("KotlinConstantConditions") + val code = """ + @SuppressWarnings("LongParameterList") + fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { + assert(false) { "FAILED TEST" } + } + + @SuppressWarnings("ComplexCondition") + class SuppressedElements { + + @SuppressWarnings("LongParameterList") + fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { + assert(false) { "FAILED TEST" } + } + + @SuppressWarnings("ComplexCondition") + fun cc() { + if (this is SuppressedElements && this !is Any && this is Nothing && this is SuppressedElements) { + assert(false) { "FAIL" } + } + } + + @SuppressWarnings("LongMethod") + fun lm() { + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + assert(false) { "FAILED TEST" } + } + + } + """.trimIndent() + val ktFile = compileContentForTest(code) val ruleSet = RuleSet("Test", listOf(LongMethod(), LongParameterList(), ComplexCondition())) val findings = ruleSet.visitFile(ktFile) @@ -28,7 +77,53 @@ class SuppressingSpec { @Test fun `all findings are suppressed on file levels`() { - val ktFile = compileForTest(resourceAsPath("SuppressedElementsByFileAnnotation.kt")) + @Suppress("KotlinConstantConditions") + val code = """ + @file:Suppress("LongMethod", "LongParameterList", "ComplexCondition") + + fun lpl2(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { + assert(false) { "FAILED TEST" } + } + + class SuppressedElements2 { + + fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { + assert(false) { "FAILED TEST" } + } + + fun cc() { + if (this is SuppressedElements2 && this !is Any && this is Nothing && this is SuppressedElements2) { + assert(false) { "FAIL" } + } + } + + fun lm() { + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + assert(false) { "FAILED TEST" } + } + } + """.trimIndent() + val ktFile = compileContentForTest(code) val ruleSet = RuleSet("Test", listOf(LongMethod(), LongParameterList(), ComplexCondition())) val findings = ruleSet.visitFile(ktFile) @@ -38,7 +133,49 @@ class SuppressingSpec { @Test fun `all findings are suppressed on class levels`() { - val ktFile = compileForTest(resourceAsPath("SuppressedElementsByClassAnnotation.kt")) + @Suppress("KotlinConstantConditions") + val code = """ + @Suppress("LongMethod", "LongParameterList", "ComplexCondition") + class SuppressedElements3 { + + fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { + assert(false) { "FAILED TEST" } + } + + fun cc() { + if (this is SuppressedElements3 && this !is Any && this is Nothing && this is SuppressedElements3) { + assert(false) { "FAIL" } + } + } + + fun lm() { + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + lpl(1, 2, 3, 4, 5, 6) + assert(false) { "FAILED TEST" } + } + } + """.trimIndent() + + val ktFile = compileContentForTest(code) val ruleSet = RuleSet("Test", listOf(LongMethod(), LongParameterList(), ComplexCondition())) val findings = ruleSet.visitFile(ktFile) @@ -48,17 +185,32 @@ class SuppressingSpec { @Test fun `should suppress TooManyFunctionsRule on class level`() { - val rule = TooManyFunctions(TestConfig("thresholdInClass" to "0")) + val rule = TooManyFunctions(TestConfig("thresholdInClasses" to "0")) + val code = """ + @Suppress("TooManyFunctions") + class OneIsTooMany { + fun f() {} + } + """.trimIndent() - val findings = rule.lint(resourceAsPath("SuppressedElementsByClassAnnotation.kt")) + val findings = rule.lint(code) assertThat(findings).isEmpty() } @Test fun `should suppress StringLiteralDuplication on class level`() { - val path = resourceAsPath("SuppressStringLiteralDuplication.kt") - - assertThat(StringLiteralDuplication().lint(path)).isEmpty() + @Suppress("UnusedEquals") + val code = """ + @Suppress("StringLiteralDuplication") + class Duplication { + var s1 = "lorem" + fun f(s: String = "lorem") { + s1 == "lorem" + } + } + """.trimIndent() + + assertThat(StringLiteralDuplication().lint(code)).isEmpty() } } diff --git a/detekt-rules/src/test/resources/SuppressStringLiteralDuplication.kt b/detekt-rules/src/test/resources/SuppressStringLiteralDuplication.kt deleted file mode 100644 index 50397d75bec7..000000000000 --- a/detekt-rules/src/test/resources/SuppressStringLiteralDuplication.kt +++ /dev/null @@ -1,9 +0,0 @@ -@file:Suppress("unused", "UNUSED_PARAMETER", "UnusedEquals") - -@Suppress("StringLiteralDuplication") -class Duplication { - var s1 = "lorem" - fun f(s: String = "lorem") { - s1 == "lorem" - } -} diff --git a/detekt-rules/src/test/resources/SuppressedByElementAnnotation.kt b/detekt-rules/src/test/resources/SuppressedByElementAnnotation.kt deleted file mode 100644 index 069be9bd42c6..000000000000 --- a/detekt-rules/src/test/resources/SuppressedByElementAnnotation.kt +++ /dev/null @@ -1,49 +0,0 @@ -@file:Suppress("unused") - -@SuppressWarnings("LongParameterList") -fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { - assert(false) { "FAILED TEST" } -} - -@SuppressWarnings("ComplexCondition") -class SuppressedElements { - - @SuppressWarnings("LongParameterList") - fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { - assert(false) { "FAILED TEST" } - } - - @SuppressWarnings("ComplexCondition") - fun cc() { - if (this is SuppressedElements && this !is Any && this is Nothing && this is SuppressedElements) { - assert(false) { "FAIL" } - } - } - - @SuppressWarnings("LongMethod") - fun lm() { - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - assert(false) { "FAILED TEST" } - } - -} diff --git a/detekt-rules/src/test/resources/SuppressedElementsByClassAnnotation.kt b/detekt-rules/src/test/resources/SuppressedElementsByClassAnnotation.kt deleted file mode 100644 index e61d23a80afa..000000000000 --- a/detekt-rules/src/test/resources/SuppressedElementsByClassAnnotation.kt +++ /dev/null @@ -1,39 +0,0 @@ -@Suppress("unused", "LongMethod", "LongParameterList", "ComplexCondition", "TooManyFunctions") -class SuppressedElements3 { - - fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { - assert(false) { "FAILED TEST" } - } - - fun cc() { - if (this is SuppressedElements3 && this !is Any && this is Nothing && this is SuppressedElements3) { - assert(false) { "FAIL" } - } - } - - fun lm() { - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - assert(false) { "FAILED TEST" } - } - -} diff --git a/detekt-rules/src/test/resources/SuppressedElementsByFileAnnotation.kt b/detekt-rules/src/test/resources/SuppressedElementsByFileAnnotation.kt deleted file mode 100644 index 019212e7c775..000000000000 --- a/detekt-rules/src/test/resources/SuppressedElementsByFileAnnotation.kt +++ /dev/null @@ -1,44 +0,0 @@ -@file:Suppress("unused", "LongMethod", "LongParameterList", "ComplexCondition") - -fun lpl2(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { - assert(false) { "FAILED TEST" } -} - -class SuppressedElements2 { - - fun lpl(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) = (a + b + c + d + e + f).apply { - assert(false) { "FAILED TEST" } - } - - fun cc() { - if (this is SuppressedElements2 && this !is Any && this is Nothing && this is SuppressedElements2) { - assert(false) { "FAIL" } - } - } - - fun lm() { - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - lpl(1, 2, 3, 4, 5, 6) - assert(false) { "FAILED TEST" } - } - -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08d68128ead5..478c33f5e926 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ android-gradle-minSupported = "com.android.tools.build:gradle:7.0.0" # This version of AGP is used for testing and should be updated when new AGP versions are released to ensure detekt's # Gradle plugin remains compatible. -android-gradle-maxSupported = "com.android.tools.build:gradle:8.0.1" +android-gradle-maxSupported = "com.android.tools.build:gradle:8.0.2" ktlintRulesetStandard = { module = "com.pinterest.ktlint:ktlint-ruleset-standard", version.ref = "ktlint" } slf4j-nop = { module = "org.slf4j:slf4j-nop", version = "1.7.36" } @@ -36,7 +36,7 @@ slf4j-simple = { module = "org.slf4j:slf4j-simple", version = "1.7.36" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } -sarif4k = "io.github.detekt.sarif4k:sarif4k:0.2.0" +sarif4k = "io.github.detekt.sarif4k:sarif4k:0.4.0" assertj = "org.assertj:assertj-core:3.24.2" reflections = "org.reflections:reflections:0.10.2" mockk = "io.mockk:mockk:1.13.5" diff --git a/settings.gradle.kts b/settings.gradle.kts index 41de0c0b65d1..e4f2bc66e2c0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,7 +43,7 @@ enableFeaturePreview("STABLE_CONFIGURATION_CACHE") // build scan plugin can only be applied in settings file plugins { - id("com.gradle.enterprise") version "3.13.2" + id("com.gradle.enterprise") version "3.13.3" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.10" } diff --git a/website/docs/gettingstarted/compilerplugin.mdx b/website/docs/gettingstarted/compilerplugin.mdx new file mode 100644 index 000000000000..ff681283d76f --- /dev/null +++ b/website/docs/gettingstarted/compilerplugin.mdx @@ -0,0 +1,105 @@ +--- +title: "Run detekt using the Compiler Plugin" +keywords: [detekt, static, analysis, code, kotlin] +sidebar: +permalink: compilerplugin.html +folder: gettingstarted +summary: +sidebar_position: 7 +--- + +You can integrate detekt in your project using the Detekt Compiler Plugin instead of the classic [Detekt Gradle Plugin](/docs/gettingstarted/gradle). Detekt offers a compiler plugin for K1 which allows you to run detekt as part of the Kotlin compilation process. This allows you to run detekt on your code without having separate tasks to invoke and results in much faster execution of detekt, especially if you're using [type resolution](/docs/gettingstarted/type-resolution). + +:::caution + +Please note that Detekt Compiler Plugin is an **experimental extension** of detekt. We expect it to be stable with the upcoming release of detekt (2.x) + +::: + +## Using the Compiler Plugin + +To use the detekt Compiler Plugin, you will have to add the following Gradle Plugin: + +```kotlin +plugins { + id("io.github.detekt.gradle.compiler-plugin") version "[detekt_version]" +} +``` + +## Configuring the Compiler Plugin + +The compiler plugin can be configured using the `detekt{}` block in your gradle file. + +The following options are allowed: + +```kotlin +detekt { + // Define the detekt configuration(s) you want to use. + // Defaults to the default detekt configuration. + config.setFrom("path/to/config.yml") + + // Applies the config files on top of detekt's default config file. `false` by default. + buildUponDefaultConfig = false + + // Turns on all the rules. `false` by default. + allRules = false + + // Specifying a baseline file. All findings stored in this file in subsequent runs of detekt. + baseline = file("path/to/baseline.xml") + + // Disables all default detekt rulesets and will only run detekt with custom rules + // defined in plugins passed in with `detektPlugins` configuration. `false` by default. + disableDefaultRuleSets = false + + // Adds debug output during task execution. `false` by default. + debug = false + + // Kill switch to turn off the Compiler Plugin execution entirely. + enableCompilerPlugin = true +} +``` + +Moreover, detekt reports can be configured at the Kotlin Compilation tasks level as follows + +```kotlin +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + detekt { + reports { + xml.enabled.set(true) + txt.enabled.set(false) + create("custom") { + enabled.set(false) + } + } + } +} +``` + +## Adding third party plugins + +As for the Detekt Gradle Plugin, you can add third party plugins to the Compiler Plugin using the `detektPlugins` configuration. + +```kotlin +dependencies { + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:[detekt_version]") +} +``` + +## Running the Compiler Plugin + +The compiler plugin will run during your `compileKotlin` tasks execution: + +```shell +$ ./gradlew compileKotlin + +> Task :example:compileKotlin +w: Analysis failed with 1 weighted issues. +w: file:///.../example/src/main/java/Sample.kt:4:17 MagicNumber: This expression contains a magic number. Consider defining it to a well named constant. + +BUILD SUCCESSFUL in 1s +5 actionable tasks: 1 executed, 4 up-to-date +``` + +## Known Issues + +1. The rule `InvalidPackageDeckaration` is known to not be working well with the Compiler Plugin [#5747](https://github.com/detekt/detekt/issues/5747). diff --git a/website/src/data/marketplace.js b/website/src/data/marketplace.js index 607a45ad7378..26f8292e46d1 100644 --- a/website/src/data/marketplace.js +++ b/website/src/data/marketplace.js @@ -97,24 +97,23 @@ export const extensions = [ tags: ["ruleset"], }, { - title: "Compose by Twitter", + title: "Jetpack Compose Rules", description: "Static checks to aid with a healthy adoption of Jetpack Compose.", - repo: "https://github.com/twitter/compose-rules/", - docs: "https://twitter.github.io/compose-rules/", - ruleset: "TwitterCompose", + repo: "https://github.com/mrmans0n/compose-rules/", + docs: "https://mrmans0n.github.io/compose-rules/", + ruleset: "Compose", rules: [ - "ComposableNaming", - "ComposableParamOrder", "CompositionLocalAllowlist", - "CompositionLocalNaming", "ContentEmitterReturningValues", "ModifierComposable", "ModifierMissing", "ModifierReused", "ModifierWithoutDefault", - "MultipleEmitters", - "MutableParams", + "MultipleContentEmitters", + "MutableParameters", + "Naming", + "ParameterOrder", "PreviewNaming", "PreviewPublic", "RememberMissing", diff --git a/website/src/pages/changelog.md b/website/src/pages/changelog.md index f44fa1e22c97..685834af1496 100644 --- a/website/src/pages/changelog.md +++ b/website/src/pages/changelog.md @@ -6,30 +6,37 @@ keywords: [changelog, release-notes, migration] # Changelog and Migration Guide -#### 1.23.0-RC3 - 2023-04-29 +#### 1.23.0 - 2023-05-22 ##### Notable Changes -- This is the first version of Detekt that ships with the `detekt-compiler-plugin`. The Detekt Compiler plugin is still experimental but we're moving it closer to Detekt to make it easier to integrate. From now on the compiler plugin will follow the same versioning schema as Detekt. We invite you to try it and provide feedback till we stabilize it. - [#5492](https://github.com/detekt/detekt/pull/5492) -- We added **20** new Rules to detekt +- This is the first version of Detekt that ships with the `detekt-compiler-plugin`. The [Detekt Compiler plugin](/docs/gettingstarted/compilerplugin) is still experimental, but we're moving it closer to Detekt to make it easier to integrate. From now on the compiler plugin will follow the same versioning schema as Detekt. We invite you to try it and provide feedback till we stabilize it. You can read more about it in the [official documentation page](/docs/gettingstarted/compilerplugin) - [#5492](https://github.com/detekt/detekt/pull/5492) +- We added **25** new Rules to detekt - `BracesOnIfStatements` - [#5700](https://github.com/detekt/detekt/pull/5700) - `BracesOnWhenStatements` - [#5838](https://github.com/detekt/detekt/pull/5838) - `CastNullableToNonNullableType` - [#5653](https://github.com/detekt/detekt/pull/5653) - `DoubleNegativeLambda` - [#5937](https://github.com/detekt/detekt/pull/5937) - `ForbiddenAnnotation` - [#5515](https://github.com/detekt/detekt/pull/5515) + - `PropertyUsedBeforeDeclaration` - [#6062](https://github.com/detekt/detekt/pull/6062) - `StringShouldBeRawString` - [#5705](https://github.com/detekt/detekt/pull/5705) - `SuspendFunSwallowedCancellation` - [#5666](https://github.com/detekt/detekt/pull/5666) - `UnusedParameter` - [#5722](https://github.com/detekt/detekt/pull/5722) - `UnusedPrivateProperty` - [#5722](https://github.com/detekt/detekt/pull/5722) - - Plus the bump to KtLint 0.49.0 added the following rules to the `detekt-formatting` ruleset: + - `UseLet` - [#6091](https://github.com/detekt/detekt/pull/6091) + - `UnnecessaryBracesAroundTrailingLambda` - [#6029](https://github.com/detekt/detekt/pull/6029) + - Plus the bump to KtLint 0.49.1 added the following rules to the `detekt-formatting` ruleset: + - `ClassName` - [#6037](https://github.com/detekt/detekt/pull/6037) - `EnumWrapping` - [#6028](https://github.com/detekt/detekt/pull/6028) + - `FunctionName` - [#6037](https://github.com/detekt/detekt/pull/6037) - `IfElseBracing` - [#6028](https://github.com/detekt/detekt/pull/6028) - `IfElseWrapping` - [#6028](https://github.com/detekt/detekt/pull/6028) - `MultilineExpressionWrapping` - [#6028](https://github.com/detekt/detekt/pull/6028) - `NoBlankLineInList` - [#6028](https://github.com/detekt/detekt/pull/6028) - `NoConsecutiveComments` - [#6028](https://github.com/detekt/detekt/pull/6028) - `NoEmptyFirstLineInClassBody` - [#6028](https://github.com/detekt/detekt/pull/6028) + - `NoSingleLineBlockCommentRule` - [#6104](https://github.com/detekt/detekt/pull/6104) - `ParameterWrapping` - [#6028](https://github.com/detekt/detekt/pull/6028) + - `PropertyName` - [#6037](https://github.com/detekt/detekt/pull/6037) - `PropertyWrapping` - [#6028](https://github.com/detekt/detekt/pull/6028) - `StringTemplateIndent` - [#6028](https://github.com/detekt/detekt/pull/6028) - `TryCatchFinallySpacing` - [#6028](https://github.com/detekt/detekt/pull/6028) @@ -47,11 +54,22 @@ keywords: [changelog, release-notes, migration] - We fixed the `includes`/`excludes` logic on the config file as they were overriding each other - [#5782](https://github.com/detekt/detekt/pull/5782) - We fully removed support for Spek from `detekt-test-utils`. The recommended testing framework is JUnit - [#5785](https://github.com/detekt/detekt/pull/5785) - The minimum supported Gradle version is now `v6.8.3` - [#5616](https://github.com/detekt/detekt/pull/5616) -- This version of detekt is built with Gradle `v8.0.2`, AGP `8.0.0`, Kotlin `1.8.21` and KtLint `0.49.0` (see [#5893](https://github.com/detekt/detekt/pull/5893) [#5723](https://github.com/detekt/detekt/pull/5723) [#5877](https://github.com/detekt/detekt/pull/5877) [#6028](https://github.com/detekt/detekt/pull/6028) [#6043](https://github.com/detekt/detekt/pull/6043) [#5995](https://github.com/detekt/detekt/pull/5995)) +- This version of detekt is built with Gradle `v8.1`, AGP `8.0.1`, Kotlin `1.8.21` and KtLint `0.49.1` (see [#5893](https://github.com/detekt/detekt/pull/5893) [#5723](https://github.com/detekt/detekt/pull/5723) [#5877](https://github.com/detekt/detekt/pull/5877) [#6028](https://github.com/detekt/detekt/pull/6028) [#6043](https://github.com/detekt/detekt/pull/6043) [#5995](https://github.com/detekt/detekt/pull/5995) [#5996](https://github.com/detekt/detekt/pull/5996)) - We now added a [Code of Conduct](https://github.com/detekt/detekt/blob/main/.github/CODE_OF_CONDUCT.md) to our repo. Please read it and follow it when interacting with our community on our channels. ##### Changelog +- SerialVersionUIDInSerializableClass - Update the error location - [#6114](https://github.com/detekt/detekt/pull/6114) +- Reduce LoopWithTooManyJumpStatements finding scope - [#6110](https://github.com/detekt/detekt/pull/6110) +- Add alias for SuspendFunWithCoroutineScopeReceiver - [#6089](https://github.com/detekt/detekt/pull/6089) +- CastNullableToNonNullableType - Check the SimpleType instead of typeElement - [#6071](https://github.com/detekt/detekt/pull/6071) +- Update plugin com.gradle.enterprise to v3.13.1 - [#6069](https://github.com/detekt/detekt/pull/6069) +- CanBeNonNullable: Check parent condition for checking if nullability info is used or not - [#6064](https://github.com/detekt/detekt/pull/6064) +- Add configuration to add alternate trimming methods - [#6063](https://github.com/detekt/detekt/pull/6063) +- Check if property is documented at class header - [#6061](https://github.com/detekt/detekt/pull/6061) +- OutdatedDocumentation - Check if only public property is documented - [#6057](https://github.com/detekt/detekt/pull/6057) +- UnnecessaryLet: fix false positive in call chains - [#6052](https://github.com/detekt/detekt/pull/6052) +- Add `comments` with a list of regexes to `ForbiddenComment` - [#5981](https://github.com/detekt/detekt/pull/5981) - Fix incomplete `requireRootInDeclaration` check in `InvalidPackageDeclaration` - [#6045](https://github.com/detekt/detekt/pull/6045) - BracesOnWhenStatements: fix false positive for necessary braces - [#6042](https://github.com/detekt/detekt/pull/6042) - Fix redundant ClassOrdering violations using maximum increasing section - [#6003](https://github.com/detekt/detekt/pull/6003) @@ -144,6 +162,10 @@ keywords: [changelog, release-notes, migration] ##### Dependency Updates +- Update dependency io.github.detekt.sarif4k:sarif4k to v0.4.0 - [#6113](https://github.com/detekt/detekt/pull/6113) +- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.7.1 - [#6097](https://github.com/detekt/detekt/pull/6097) +- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.7.0 - [#6074](https://github.com/detekt/detekt/pull/6074) +- Update com.android.tools.build - [#6065](https://github.com/detekt/detekt/pull/6065) - Update JaCoCo to v0.8.10 - [#6044](https://github.com/detekt/detekt/pull/6044) - Update plugin pluginPublishing to v1.2.0 - [#5975](https://github.com/detekt/detekt/pull/5975) - Update ktlint to v0.48.1 - [#5661](https://github.com/detekt/detekt/pull/5661) @@ -160,6 +182,10 @@ keywords: [changelog, release-notes, migration] ##### Housekeeping & Refactorings +- Inline Cases enum and inline other external test code into the test classes - [#6107](https://github.com/detekt/detekt/pull/6107) +- Update codecov/codecov-action digest to eaaf4be - [#6102](https://github.com/detekt/detekt/pull/6102) +- Remove unnecessary baselines - [#6092](https://github.com/detekt/detekt/pull/6092) +- Remove unused `dependenciesAsNames` - [#6059](https://github.com/detekt/detekt/pull/6059) - Reduce eager POM task creation - [#6041](https://github.com/detekt/detekt/pull/6041) - Improve our configuration of `ClassNaming` and `FunctionNaming` - [#6019](https://github.com/detekt/detekt/pull/6019) - Comment text in the Issue/PR Template - [#5992](https://github.com/detekt/detekt/pull/5992) @@ -191,7 +217,7 @@ keywords: [changelog, release-notes, migration] ##### Contributors -We would like to thank the following contributors that made this release possible: @3flex, @BeBAKE, @BraisGabin, @Goooler, @SaumyaBhushan, @TWiStErRob, @VitalyVPinchuk, @adef145, @asomov, @atulgpt, @chao2zhang, @cketti, @cortinico, @drawers, @dzirbel, @igorwojda, @lexa-diky, @luanpotter, @marschwar, @mjovanc, @mmorozkov, @ncteisen, @osipxd, @ov7a, @schalkms, @t-kameyama +We would like to thank the following contributors that made this release possible: @3flex, @BeBAKE, @BraisGabin, @Goooler, @SaumyaBhushan, @TWiStErRob, @VitalyVPinchuk, @adef145, @asomov, @atulgpt, @chao2zhang, @cketti, @cortinico, @drawers, @dzirbel, @igorwojda, @lexa-diky, @luanpotter, @marschwar, @mjovanc, @mmorozkov, @ncteisen, @osipxd, @ov7a, @schalkms, @t-kameyama, @tresni See all issues at: [1.23.0](https://github.com/detekt/detekt/milestone/88) diff --git a/website/src/remark/detektVersionReplace.js b/website/src/remark/detektVersionReplace.js index e94318441ff7..a5f7aafb54cd 100644 --- a/website/src/remark/detektVersionReplace.js +++ b/website/src/remark/detektVersionReplace.js @@ -3,7 +3,7 @@ const visit = require("unist-util-visit"); // Remark plugin that is replacing the [detekt_version] with the latest // released version. Please note that this field is updated automatically // by the `applyDocVersion` task. -const detektVersion = "1.23.0-RC3"; +const detektVersion = "1.23.0"; const plugin = (options) => { const transformer = async (ast) => { diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/_category_.json b/website/versioned_docs/version-1.23.0/gettingstarted/_category_.json new file mode 100644 index 000000000000..c1998c5e4c8a --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Getting Started", + "position": 3 +} diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/_cli-generator-options.md b/website/versioned_docs/version-1.23.0/gettingstarted/_cli-generator-options.md new file mode 100644 index 000000000000..4b49776c0a9d --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/_cli-generator-options.md @@ -0,0 +1,10 @@ +``` +Usage: java -jar detekt-generator-[detekt_version]-all.jar [options] + Options: + --generate-custom-rule-config, -gcrc + Generate custom rules configuration files. The files will be + placed under 'resources' folder of each rule respectively + (e.g. 'custom-rule/src/main/resources/config/config.yml'). + --input, -i + Input paths to rules to analyze. Multiple paths are separated by comma. +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/_cli-options.md b/website/versioned_docs/version-1.23.0/gettingstarted/_cli-options.md new file mode 100644 index 000000000000..b6062f798449 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/_cli-options.md @@ -0,0 +1,89 @@ +``` +Usage: detekt [options] + Options: + --all-rules + Activates all available (even unstable) rules. + Default: false + --auto-correct, -ac + Allow rules to auto correct code if they support it. The default rule + sets do NOT support auto correcting and won't change any line in the + users code base. However custom rules can be written to support auto + correcting. The additional 'formatting' rule set, added with + '--plugins', does support it and needs this flag. + Default: false + --base-path, -bp + Specifies a directory as the base path.Currently it impacts all file + paths in the formatted reports. File paths in console output and txt + report are not affected and remain as absolute paths. + --baseline, -b + If a baseline xml file is passed in, only new code smells not in the + baseline are printed in the console. + --build-upon-default-config + Preconfigures detekt with a bunch of rules and some opinionated defaults + for you. Allows additional provided configurations to override the + defaults. + Default: false + --classpath, -cp + EXPERIMENTAL: Paths where to find user class files and depending jar + files. Used for type resolution. + --config, -c + Path to the config file (path/to/config.yml). Multiple configuration + files can be specified with ',' or ';' as separator. + --config-resource, -cr + Path to the config resource on detekt's classpath (path/to/config.yml). + --create-baseline, -cb + Treats current analysis findings as a smell baseline for future detekt + runs. + Default: false + --debug + Prints extra information about configurations and extensions. + Default: false + --disable-default-rulesets, -dd + Disables default rule sets. + Default: false + --excludes, -ex + Globbing patterns describing paths to exclude from the analysis. + --generate-config, -gc + Export default config. Path can be specified with --config option + (default path: default-detekt-config.yml) + Default: false + --help, -h + Shows the usage. + --includes, -in + Globbing patterns describing paths to include in the analysis. Useful in + combination with 'excludes' patterns. + --input, -i + Input paths to analyze. Multiple paths are separated by comma. If not + specified the current working directory is used. + --jdk-home + EXPERIMENTAL: Use a custom JDK home directory to include into the + classpath + --jvm-target + EXPERIMENTAL: Target version of the generated JVM bytecode that was + generated during compilation and is now being used for type resolution + (1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 or 19) + Default: 1.8 + --language-version + EXPERIMENTAL: Compatibility mode for Kotlin language version X.Y, + reports errors for all language features that came out later + Possible Values: [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0] + --max-issues + Return exit code 0 only when found issues count does not exceed + specified issues count. + --parallel + Enables parallel compilation and analysis of source files. Do some + benchmarks first before enabling this flag. Heuristics show performance + benefits starting from 2000 lines of Kotlin code. + Default: false + --plugins, -p + Extra paths to plugin jars separated by ',' or ';'. + --report, -r + Generates a report for given 'report-id' and stores it on given 'path'. + Entry should consist of: [report-id:path]. Available 'report-id' values: + 'txt', 'xml', 'html', 'md', 'sarif'. These can also be used in + combination with each other e.g. '-r txt:reports/detekt.txt -r + xml:reports/detekt.xml' + --version + Prints the detekt CLI version. + Default: false +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/cli.mdx b/website/versioned_docs/version-1.23.0/gettingstarted/cli.mdx new file mode 100644 index 000000000000..9ece91f07424 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/cli.mdx @@ -0,0 +1,69 @@ +--- +title: "Run detekt using Command Line Interface" +keywords: [cli] +sidebar: +permalink: cli.html +folder: gettingstarted +summary: +sidebar_position: 1 +--- + +import CliOptions from "./_cli-options.md"; +import CliGeneratorOptions from "./_cli-generator-options.md"; + +## Install the cli + +There are different ways to install the Command Line Interface (CLI): + +### MacOS, with [Homebrew](https://brew.sh/): + +```sh +brew install detekt +detekt [options] +``` + +### Windows, with [Scoop](https://scoop.sh/) + +```powershell +scoop install detekt +detekt [options] +``` + +### Any OS: + +```sh +curl -sSLO https://github.com/detekt/detekt/releases/download/v[detekt_version]/detekt-cli-[detekt_version].zip +unzip detekt-cli-[detekt_version].zip +./detekt-cli-[detekt_version]/bin/detekt-cli --help +``` + +### NixOS + +As a prerequisite, you have to add the unstable channel via `nix-channel` and then execute the following command. + +```sh +nix-shell -I nixpkgs=channel:nixpkgs-unstable -p detekt +``` + +## Use the cli + +detekt will exit with one of the following exit codes: + +| Exit code | Description | +| --------- | ------------------------------------------------------------------------------ | +| 0 | detekt ran normally and maxIssues count was not reached in BuildFailureReport. | +| 1 | An unexpected error occurred | +| 2 | MaxIssues count was reached in BuildFailureReport. | +| 3 | Invalid detekt configuration file detected. | + +The following parameters are shown when `--help` is entered. + + + +## Use the cli to generate configuration for custom rules + + + +```sh +java -jar detekt-generator-[detekt_version]-all.jar -gcrc -i /path/to/rule1, /path/to/rule2 +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/compilerplugin.mdx b/website/versioned_docs/version-1.23.0/gettingstarted/compilerplugin.mdx new file mode 100644 index 000000000000..ff681283d76f --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/compilerplugin.mdx @@ -0,0 +1,105 @@ +--- +title: "Run detekt using the Compiler Plugin" +keywords: [detekt, static, analysis, code, kotlin] +sidebar: +permalink: compilerplugin.html +folder: gettingstarted +summary: +sidebar_position: 7 +--- + +You can integrate detekt in your project using the Detekt Compiler Plugin instead of the classic [Detekt Gradle Plugin](/docs/gettingstarted/gradle). Detekt offers a compiler plugin for K1 which allows you to run detekt as part of the Kotlin compilation process. This allows you to run detekt on your code without having separate tasks to invoke and results in much faster execution of detekt, especially if you're using [type resolution](/docs/gettingstarted/type-resolution). + +:::caution + +Please note that Detekt Compiler Plugin is an **experimental extension** of detekt. We expect it to be stable with the upcoming release of detekt (2.x) + +::: + +## Using the Compiler Plugin + +To use the detekt Compiler Plugin, you will have to add the following Gradle Plugin: + +```kotlin +plugins { + id("io.github.detekt.gradle.compiler-plugin") version "[detekt_version]" +} +``` + +## Configuring the Compiler Plugin + +The compiler plugin can be configured using the `detekt{}` block in your gradle file. + +The following options are allowed: + +```kotlin +detekt { + // Define the detekt configuration(s) you want to use. + // Defaults to the default detekt configuration. + config.setFrom("path/to/config.yml") + + // Applies the config files on top of detekt's default config file. `false` by default. + buildUponDefaultConfig = false + + // Turns on all the rules. `false` by default. + allRules = false + + // Specifying a baseline file. All findings stored in this file in subsequent runs of detekt. + baseline = file("path/to/baseline.xml") + + // Disables all default detekt rulesets and will only run detekt with custom rules + // defined in plugins passed in with `detektPlugins` configuration. `false` by default. + disableDefaultRuleSets = false + + // Adds debug output during task execution. `false` by default. + debug = false + + // Kill switch to turn off the Compiler Plugin execution entirely. + enableCompilerPlugin = true +} +``` + +Moreover, detekt reports can be configured at the Kotlin Compilation tasks level as follows + +```kotlin +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + detekt { + reports { + xml.enabled.set(true) + txt.enabled.set(false) + create("custom") { + enabled.set(false) + } + } + } +} +``` + +## Adding third party plugins + +As for the Detekt Gradle Plugin, you can add third party plugins to the Compiler Plugin using the `detektPlugins` configuration. + +```kotlin +dependencies { + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:[detekt_version]") +} +``` + +## Running the Compiler Plugin + +The compiler plugin will run during your `compileKotlin` tasks execution: + +```shell +$ ./gradlew compileKotlin + +> Task :example:compileKotlin +w: Analysis failed with 1 weighted issues. +w: file:///.../example/src/main/java/Sample.kt:4:17 MagicNumber: This expression contains a magic number. Consider defining it to a well named constant. + +BUILD SUCCESSFUL in 1s +5 actionable tasks: 1 executed, 4 up-to-date +``` + +## Known Issues + +1. The rule `InvalidPackageDeckaration` is known to not be working well with the Compiler Plugin [#5747](https://github.com/detekt/detekt/issues/5747). diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/git-pre-commit-hook.md b/website/versioned_docs/version-1.23.0/gettingstarted/git-pre-commit-hook.md new file mode 100644 index 000000000000..5c1d6a989dfd --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/git-pre-commit-hook.md @@ -0,0 +1,82 @@ +--- +title: "Run detekt using a Git pre-commit hook" +keywords: [detekt, static, analysis, code, kotlin] +sidebar: +permalink: git-pre-commit-hook.html +folder: gettingstarted +summary: +sidebar_position: 6 +--- + +detekt can be integrated into your development workflow by using a Git pre-commit hook. +For that reason Git supports to run custom scripts automatically, when a specific action occurs. +The mentioned pre-commit hook can be setup locally on your dev-machine. +The following client-side detekt hook is triggered by a commit operation, and checks all files via the gradle task. + +```bash +#!/usr/bin/env bash +echo "Running detekt check..." +OUTPUT="/tmp/detekt-$(date +%s)" +./gradlew detekt > $OUTPUT +EXIT_CODE=$? +if [ $EXIT_CODE -ne 0 ]; then + cat $OUTPUT + rm $OUTPUT + echo "***********************************************" + echo " detekt failed " + echo " Please fix the above issues before committing " + echo "***********************************************" + exit $EXIT_CODE +fi +rm $OUTPUT +``` + +The shell script can be installed by copying the content over to `<>/.git/hooks/pre-commit`. +This pre-commit hook needs to be executable, so you may need to change the permission (`chmod +x pre-commit`). +More information about Git hooks and how to install them can be found in +[Atlassian's tutorial](https://www.atlassian.com/git/tutorials/git-hooks). + +A special thanks goes to Mohit Sarveiya for providing this shell script. +You can watch his excellent talk about **Static Code Analysis For Kotlin** on +[YouTube](https://www.youtube.com/watch?v=LT6m5_LO2DQ). + +## Only run on staged files + +It is also possible to use [the CLI](/docs/gettingstarted/cli) to create a hook that only runs on staged files. This has the advantage of speedier execution, by running on fewer files and avoiding the warm-up time of the gradle daemon. + +Please note, however, that a handful of checks requiring [type resolution](/docs/gettingstarted/type-resolution) will not work correctly with this approach. If you do adopt a partial hook, it is recommended that you still implement a full `detekt` check as part of your CI pipeline. + +This example has been put together using [pre-commit](https://pre-commit.com/), but the same principle can be applied to any kind of hook. + +Hook definition in pre-commit: + +```yml +- id: detekt + name: detekt check + description: Runs `detekt` on modified .kt files. + language: script + entry: detekt.sh + files: \.kt +``` + +Script `detekt.sh`: + +```bash +#!/bin/bash + +echo "Running detekt check..." +fileArray=($@) +detektInput=$(IFS=,;printf "%s" "${fileArray[*]}") +echo "Input files: $detektInput" + +OUTPUT=$(detekt --input "$detektInput" 2>&1) +EXIT_CODE=$? +if [ $EXIT_CODE -ne 0 ]; then + echo $OUTPUT + echo "***********************************************" + echo " detekt failed " + echo " Please fix the above issues before committing " + echo "***********************************************" + exit $EXIT_CODE +fi +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/gradle.mdx b/website/versioned_docs/version-1.23.0/gettingstarted/gradle.mdx new file mode 100644 index 000000000000..d2be0515e26d --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/gradle.mdx @@ -0,0 +1,529 @@ +--- +title: "Run detekt using the Detekt Gradle Plugin" +keywords: [detekt, static, analysis, code, kotlin] +sidebar: +permalink: gradle.html +folder: gettingstarted +summary: +sidebar_position: 2 +--- + +detekt requires **Gradle 6.8.3** or higher. We, however, recommend using the version of Gradle that is [listed in this table](/docs/introduction/compatibility). + +## Available plugin tasks + +The Detekt Gradle Plugin will generate multiple tasks: + +- `detekt` - Runs a detekt analysis and complexity report on your source files. Configure the analysis inside the +`detekt` closure. + - By default, the standard rule set without any ignore list is executed on sources files located + in `src/main/java`, `src/test/java`, `src/main/kotlin` and `src/test/kotlin`. + - Reports are automatically generated in xml, + html, txt, md, and sarif format and can be found in `build/reports/detekt/detekt.[xml|html|txt|md|sarif]` respectively. + - Please note that the `detekt` task is automatically run when executing `gradle check`. + - You may specify Gradle task CLI option for auto correction, such as `gradle detekt --auto-correct`. +- `detektGenerateConfig` - Generates a default detekt configuration file into your project directory. +- `detektBaseline` - Similar to `detekt`, but creates a code smell baseline. Further detekt runs will only feature new smells not in this list. + +In addition to these standard tasks, the plugin will also generate a set of experimental tasks that have +[type resolution](type-resolution.md) enabled. This happens for both, pure JVM projects and Android projects that have +the Android Gradle Plugin applied: + +- `detektMain` - Similar to `detekt`, but runs only on the `main` source set + (Android: all production source sets) +- `detektTest` - Similar to `detekt`, but runs only on the `test` source set + (Android: all JVM and Android Test source sets) +- `detektBaselineMain` - Similar to `detektBaseline`, but creates a baseline only for the `main` source set + (Android: multiple baselines for all production source sets) +- `detektBaselineTest` - Similar to `detektBaseline`, but creates a baseline only for the `test` source set + (Android: multiple baselines for all JVM and Android Test source sets) +- Android-only: `detekt` - Similar to `detekt`, but runs only on the specific (test) build variant +- Android-only: `detektBaseline` - Similar to `detektBaseline`, but creates a baseline only for the + specific (test) build variant + +Baseline files that are generated for these specific source sets / build variants contain the name of the source set / +the name of the build variant in their name, unless otherwise configured, such as `detekt-main.xml` or +`detekt-productionDebug.xml`. + +If both, a `detekt-main.xml` and a `detekt.xml` baseline file exists in place, the more specific one - `detekt-main.xml` - +takes precedence when the `detektMain` task is executed, likewise for Android variant-specific baseline files. + +_NOTE:_ When analyzing Android projects that make use of specific code generators, such as Data Binding, Kotlin synthetic +view accessors or else, you might see warnings output while detekt runs. This is due to the inability to gather the +complete compile classpath from the Android Gradle Plugin ([upstream ticket](https://issuetracker.google.com/issues/158777988)) +and can safely be ignored. + +Use the Groovy or Kotlin DSL of Gradle to apply the Detekt Gradle Plugin. You can further configure the Plugin +using the detekt closure as described [here](#closure). + +### Configuration + +Using the plugins DSL: + +#### Groovy DSL + +```groovy +plugins { + id "io.gitlab.arturbosch.detekt" version "[detekt_version]" +} + +repositories { + mavenCentral() +} +``` + +#### Kotlin DSL + +```kotlin +plugins { + id("io.gitlab.arturbosch.detekt").version("[detekt_version]") +} + +repositories { + mavenCentral() +} +``` + +Using legacy plugin application (`buildscript{}`): + +#### Groovy DSL + +```groovy +buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:[detekt_version]" + } +} + +apply plugin: "io.gitlab.arturbosch.detekt" + +repositories { + mavenCentral() +} +``` + +#### Kotlin DSL + +```kotlin +buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:[detekt_version]") + } +} + +apply(plugin = "io.gitlab.arturbosch.detekt") + +repositories { + mavenCentral() +} +``` + +### Configuration for Android projects + +When using Android make sure to have detekt configured in the app/module level `build.gradle` file. + +You can configure the plugin in the same way as indicated above. + +#### Groovy DSL + +```groovy +buildscript { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath "com.android.tools.build:gradle:" + } +} + +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.android" version "" + id "io.gitlab.arturbosch.detekt" version "[detekt_version]" +} + +repositories { + mavenCentral() +} +``` + +#### Kotlin DSL + +```kotlin +buildscript { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:") + } +} + +plugins { + id("com.android.application") + kotlin("android") version "" + id("io.gitlab.arturbosch.detekt") version "[detekt_version]" +} + +repositories { + mavenCentral() +} +``` + +### Options for detekt configuration closure + +#### Groovy DSL + +```groovy +detekt { + // Version of detekt that will be used. When unspecified the latest detekt + // version found will be used. Override to stay on the same version. + toolVersion = "[detekt_version]" + + // The directories where detekt looks for source files. + // Defaults to `files("src/main/java", "src/test/java", "src/main/kotlin", "src/test/kotlin")`. + source = files( + "src/main/kotlin", + "gensrc/main/kotlin" + ) + + // Builds the AST in parallel. Rules are always executed in parallel. + // Can lead to speedups in larger projects. `false` by default. + parallel = false + + // Define the detekt configuration(s) you want to use. + // Defaults to the default detekt configuration. + config.setFrom("path/to/config.yml") + + // Applies the config files on top of detekt's default config file. `false` by default. + buildUponDefaultConfig = false + + // Turns on all the rules. `false` by default. + allRules = false + + // Specifying a baseline file. All findings stored in this file in subsequent runs of detekt. + baseline = file("path/to/baseline.xml") + + // Disables all default detekt rulesets and will only run detekt with custom rules + // defined in plugins passed in with `detektPlugins` configuration. `false` by default. + disableDefaultRuleSets = false + + // Adds debug output during task execution. `false` by default. + debug = false + + // If set to `true` the build does not fail when the + // maxIssues count was reached. Defaults to `false`. + ignoreFailures = false + + // Android: Don't create tasks for the specified build types (e.g. "release") + ignoredBuildTypes = ["release"] + + // Android: Don't create tasks for the specified build flavor (e.g. "production") + ignoredFlavors = ["production"] + + // Android: Don't create tasks for the specified build variants (e.g. "productionRelease") + ignoredVariants = ["productionRelease"] + + // Specify the base path for file paths in the formatted reports. + // If not set, all file paths reported will be absolute file path. + basePath = projectDir +} +``` + +#### Kotlin DSL + +```kotlin +detekt { + // Version of detekt that will be used. When unspecified the latest detekt + // version found will be used. Override to stay on the same version. + toolVersion = "[detekt_version]" + + // The directories where detekt looks for source files. + // Defaults to `files("src/main/java", "src/test/java", "src/main/kotlin", "src/test/kotlin")`. + source.setFrom("src/main/java", "src/main/kotlin") + + // Builds the AST in parallel. Rules are always executed in parallel. + // Can lead to speedups in larger projects. `false` by default. + parallel = false + + // Define the detekt configuration(s) you want to use. + // Defaults to the default detekt configuration. + config.setFrom("path/to/config.yml") + + // Applies the config files on top of detekt's default config file. `false` by default. + buildUponDefaultConfig = false + + // Turns on all the rules. `false` by default. + allRules = false + + // Specifying a baseline file. All findings stored in this file in subsequent runs of detekt. + baseline = file("path/to/baseline.xml") + + // Disables all default detekt rulesets and will only run detekt with custom rules + // defined in plugins passed in with `detektPlugins` configuration. `false` by default. + disableDefaultRuleSets = false + + // Adds debug output during task execution. `false` by default. + debug = false + + // If set to `true` the build does not fail when the + // maxIssues count was reached. Defaults to `false`. + ignoreFailures = false + + // Android: Don't create tasks for the specified build types (e.g. "release") + ignoredBuildTypes = listOf("release") + + // Android: Don't create tasks for the specified build flavor (e.g. "production") + ignoredFlavors = listOf("production") + + // Android: Don't create tasks for the specified build variants (e.g. "productionRelease") + ignoredVariants = listOf("productionRelease") + + // Specify the base path for file paths in the formatted reports. + // If not set, all file paths reported will be absolute file path. + basePath = projectDir.absolutePath +} +``` + +### Options for detekt Gradle properties + +```groovy +// If set to true, enables Gradle Worker API for Detekt tasks. `false` by default. +// See the doc for the Worker API at https://docs.gradle.org/8.1/userguide/worker_api.html +detekt.use.worker.api = false +``` + +### Reports + +Report output can be customized for each task. The DSL is the same in both Groovy and Kotlin: + +```kotlin +tasks.named("detekt").configure { + reports { + // Enable/Disable XML report (default: true) + xml.required.set(true) + xml.outputLocation.set(file("build/reports/detekt.xml")) + // Enable/Disable HTML report (default: true) + html.required.set(true) + html.outputLocation.set(file("build/reports/detekt.html")) + // Enable/Disable TXT report (default: true) + txt.required.set(true) + txt.outputLocation.set(file("build/reports/detekt.txt")) + // Enable/Disable SARIF report (default: false) + sarif.required.set(true) + sarif.outputLocation.set(file("build/reports/detekt.sarif")) + // Enable/Disable MD report (default: false) + md.required.set(true) + md.outputLocation.set(file("build/reports/detekt.md")) + custom { + // The simple class name of your custom report. + reportId = "CustomJsonReport" + outputLocation.set(file("build/reports/detekt.json")) + } + } +} +``` + +### Using Type Resolution + +Type resolution is experimental and works only for [predefined tasks listed above](#available-plugin-tasks) +or when implementing a custom detekt task with the `classpath` and `jvmTarget` properties present. + +`jdkHome` is also available as an input. When this is unset, analysis is performed using the JDK classes of the JDK that +Gradle is running with (shown by the `./gradlew --version` command). This can be an issue if the Gradle JDK and the +project JDK doesn't match e.g. if Gradle runs under Java 8 but the project uses classes only available in Java 9 or +higher. Setting `jdkHome` to the Java 9 JDK path will allow for more correct analysis. + +`jdkHome` and `jvmTarget` are set automatically when applying a toolchain using either +[`java`](https://docs.gradle.org/current/userguide/toolchains.html) or +[`kotlin`](https://kotlinlang.org/docs/gradle.html#gradle-java-toolchains-support). + +More information on type resolution are available on the [type resolution](type-resolution.md) page. + +#### Groovy DSL + +```groovy +tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach { + jvmTarget = '1.8' + jdkHome.set(file('path/to/jdkHome')) +} +tasks.withType(io.gitlab.arturbosch.detekt.DetektCreateBaselineTask).configureEach { + jvmTarget = '1.8' + jdkHome.set(file('path/to/jdkHome')) +} +``` + +#### Kotlin DSL + +```kotlin +tasks.withType().configureEach { + this.jvmTarget = "1.8" + jdkHome.set(file("path/to/jdkHome")) +} +tasks.withType().configureEach { + this.jvmTarget = "1.8" + jdkHome.set(file("path/to/jdkHome")) +} +``` + +### Leveraging Gradle's SourceTask - Excluding and including source files + +A detekt task extends the Gradle `SourceTask` to be only scheduled when watched source files are changed. +It also allows to match files that should be excluded from the analysis. +To do this introduce a query on detekt tasks and define include and exclude patterns outside the detekt closure: + +#### Groovy DSL + +```groovy +detekt { + ... +} + +tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/special/package/internal/**") // but exclude our legacy internal package +} + +tasks.withType(io.gitlab.arturbosch.detekt.DetektCreateBaselineTask).configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/special/package/internal/**") // but exclude our legacy internal package +} +``` + +#### Kotlin DSL + +```kotlin +detekt { + ... +} + +tasks.withType().configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/special/package/internal/**") // but exclude our legacy internal package +} + +tasks.withType().configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/special/package/internal/**") // but exclude our legacy internal package +} +``` + +### Defining custom detekt task + +Custom tasks for alternative configurations or different source sets can be defined by creating a custom task that +uses the type `Detekt`. + +#### Groovy DSL + +```groovy +tasks.register(name: myDetekt, type: io.gitlab.arturbosch.detekt.Detekt) { + description = "Runs a custom detekt build." + setSource(files("src/main/kotlin", "src/test/kotlin")) + config.setFrom(files("$rootDir/config.yml")) + debug = true + reports { + xml { + destination = file("build/reports/mydetekt.xml") + } + html.destination = file("build/reports/mydetekt.html") + } + include '**/*.kt' + include '**/*.kts' + exclude 'resources/' + exclude 'build/' +} +``` + +#### Kotlin DSL + +```kotlin +tasks.register("myDetekt") { + description = "Runs a custom detekt build." + setSource(files("src/main/kotlin", "src/test/kotlin")) + config.setFrom(files("$rootDir/config.yml")) + debug = true + reports { + xml { + destination = file("build/reports/mydetekt.xml") + } + html.destination = file("build/reports/mydetekt.html") + } + include("**/*.kt") + include("**/*.kts") + exclude("resources/") + exclude("build/") +} +``` + +### Disabling detekt from the check task + +detekt tasks by default are verification tasks. They get executed whenever the Gradle check task gets executed. +This aligns with the behavior of other code analysis plugins for Gradle. + +If you are adding detekt to an already long running project you may want to increase the code quality incrementally and therefore +exclude detekt from the check task. + +#### Groovy DSL + +```groovy +check.configure { + dependsOn = dependsOn.findAll { it.name != 'detekt' } +} +``` + +#### Kotlin DSL + +```kotlin +tasks.named("check").configure { + this.setDependsOn(this.dependsOn.filterNot { + it is TaskProvider<*> && it.name == "detekt" + }) +} +``` + +Instead of disabling detekt for the check task, you may want to increase the build failure threshold in the [configuration file](/docs/introduction/configurations). + +## Integrating detekt inside your IntelliJ IDEA + +detekt comes with an [IntelliJ Plugin](https://plugins.jetbrains.com/plugin/10761-detekt) that you can install directly from the IDE. The plugin offers warning highlight directly inside the IDE as well as support for code formatting. + +The source code of the plugin is available here: [detekt/detekt-intellij-plugin](https://github.com/detekt/detekt-intellij-plugin) + +## Gradle runtime dependencies + +detekt is tightly coupled to the Kotlin compiler and requires a specific version to be available at runtime to perform +its analysis. + +If detekt is run with an unexpected version of the Kotlin compiler on its classpath, you will see an error like this +when you try to run detekt via Gradle: + +``` +detekt was compiled with Kotlin 1.8.0 but is currently running with 1.7.0 +``` + +This happens when the version of `kotlin-compiler-embeddable` is overridden on detekt's classpath (in the `detekt` +dependency configuration). This can happen when build scripts use things like this which override all dependency +configurations: + +```kotlin +configurations.all { + resolutionStrategy.eachDependency { + if (requested.group == "org.jetbrains.kotlin") { + useVersion("1.7.0") + } + } +} +``` + +If Kotlin dependencies are being aligned like this then exclude the `detekt` dependency configuration with something +like `configurations.matching { it.name != "detekt" }.all`. diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/gradletask.md b/website/versioned_docs/version-1.23.0/gettingstarted/gradletask.md new file mode 100644 index 000000000000..297bb5b492dd --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/gradletask.md @@ -0,0 +1,72 @@ +--- +title: "Run detekt using Gradle Task" +keywords: [gradle, task] +sidebar: +permalink: gradletask.html +folder: gettingstarted +summary: +sidebar_position: 3 +--- + +1. Add following lines to your build.gradle file. +2. Run `gradle detekt` + +###### Groovy DSL +```groovy +repositories { + mavenCentral() +} + +configurations { + detekt +} + +def detektTask = tasks.register("detekt", JavaExec) { + main = "io.gitlab.arturbosch.detekt.cli.Main" + classpath = configurations.detekt + + def input = "$projectDir" + def config = "$projectDir/detekt.yml" + def exclude = ".*/build/.*,.*/resources/.*" + def params = [ '-i', input, '-c', config, '-ex', exclude] + + args(params) +} + +dependencies { + detekt 'io.gitlab.arturbosch.detekt:detekt-cli:[detekt_version]' +} + +// Remove this line if you don't want to run detekt on every build +check.dependsOn detektTask +``` + +###### Kotlin DSL +```kotlin +repositories { + mavenCentral() +} + +val detekt by configurations.creating + +val detektTask = tasks.register("detekt") { + main = "io.gitlab.arturbosch.detekt.cli.Main" + classpath = detekt + + val input = projectDir + val config = "$projectDir/detekt.yml" + val exclude = ".*/build/.*,.*/resources/.*" + val params = listOf("-i", input, "-c", config, "-ex", exclude) + + args(params) +} + +dependencies { + detekt("io.gitlab.arturbosch.detekt:detekt-cli:[detekt_version]") +} + +// Remove this block if you don't want to run detekt on every build +tasks.check { + dependsOn(detektTask) +} +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/mavenanttask.md b/website/versioned_docs/version-1.23.0/gettingstarted/mavenanttask.md new file mode 100644 index 000000000000..b832a9bb7c7a --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/mavenanttask.md @@ -0,0 +1,57 @@ +--- +title: "Run detekt using Maven Ant Task" +keywords: [maven, anttask] +sidebar: +permalink: mavenanttask.html +folder: gettingstarted +summary: +sidebar_position: 4 +--- + +1. Add following lines to your pom.xml. +2. Run `mvn verify` (when using the verify phase as we are doing here) + +```xml + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + + detekt + verify + + + + + + + + + + + + + + + run + + + + + io.gitlab.arturbosch.detekt + detekt-cli + [detekt_version] + + + + + +``` diff --git a/website/versioned_docs/version-1.23.0/gettingstarted/type-resolution.md b/website/versioned_docs/version-1.23.0/gettingstarted/type-resolution.md new file mode 100644 index 000000000000..d5008ae7f3b1 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/gettingstarted/type-resolution.md @@ -0,0 +1,126 @@ +--- +title: "Using Type Resolution" +keywords: [detekt, static, analysis, code, kotlin] +sidebar: +permalink: type-resolution.html +folder: gettingstarted +summary: +sidebar_position: 5 +--- + +This page describes how to use detekt's **type resolution** feature. + +:::caution + +Please note that type resolution is still an **experimental feature** of detekt. We expect it to be stable with the upcoming release of detekt (2.x) + +::: + +## What is type resolution + +Type resolution is a feature that allows detekt to perform more **advanced** static analysis on your Kotlin source code. + +Normally, detekt doesn't have access to the types and symbols that are available to the compiler during the compilation. This restricts the inspection capability. +By enabling type resolution, you provide to detekt all the information to understand types and symbols in your code needed to perform more accurate analysis. This extends detekt's inspection capability to ones of the Kotlin **compiler**. + +### An example + +detekt has a rule called [MagicNumber](/docs/rules/style#magicnumber) to detect usages of magic numbers in your code. + +In the following code: + +```kotlin +val user = getUserById(42)?.toString() +``` + +detekt is able to report the usage of the number `42` as a magic number, **without** type resolution. All the information needed to run this inspection is already available in the source code. + +Similarly, detekt has another rule called [UnnecessarySafeCall](/docs/rules/potential-bugs#unnecessarysafecall) to detect unnecessary usages of safe call operators (`?.`). + +In the previous example, detekt is able to determine if the safe call in `getUserById(42)?.toString()` is required **only with** type resolution. + +This is because detekt needs to know what is the **return type** of `getUserById()` in order to correctly perform the inspection. If the return type is a nullable type, then the code is valid. If the return type is a non-nullable type, detekt will report an `UnnecessarySafeCall` as the `?.` is actually not needed. + +With type resolution, detekt has access to all the symbols and types of your codebase. Type resolution can be enabled by providing the **classpath** that is used during compilation. This will give detekt access to all the code used to compile your project (both first and third party code) and will allow more advanced analysis. + +## Is my rule using type resolution? + +If you're running detekt **without** type resolution, all the rules that require type resolution **will not run**. + +All the rules that require type resolution are annotated with [`@RequiresTypeResolution`](https://github.com/detekt/detekt/search?q=%40RequiresTypeResolution). + +Moreover, their official documentation in the detekt website will mention _Requires Type Resolution_ ([like here](/docs/rules/potential-bugs#unnecessarysafecall)). + +:::caution + +Please note that we do have some rules that have mixed behavior whether type resolution is enabled or not. Those rules are listed here: [#2994](https://github.com/detekt/detekt/issues/2994) + +::: + +Before opening an issue that you're rule is not working, please verify, whether your rule requires type resolution and check if you have type resolution enabled. + +Issues and proposals for rules that require type resolution are labelled with [needs type and symbol solving](https://github.com/detekt/detekt/labels/needs%20type%20and%20symbol%20solving) on the Issue tracker. + +## Enabling on a JVM project + +The easiest way to use type resolution is to use the Detekt Gradle Plugin. On a JVM project, the following tasks will be created: + +- `detekt` - Runs detekt WITHOUT type resolution +- `detektMain` - Runs detekt with type resolution on the `main` source set +- `detektTest` - Runs detekt with type resolution on the `test` source set + +Moreover, you can use `detektBaselineMain` and `detektBaselineTest` to create baselines starting from runs of detekt with type resolution enabled. + +Alternatively, you can create a **custom detekt task**, making sure to specify the `classpath` and `jvmTarget` properties correctly. See the [Run detekt using the Detekt Gradle Plugin](/docs/gettingstarted/gradle) and the [Run detekt using Gradle Task](/docs/gettingstarted/gradletask) for further readings on this. + +## Enabling on an Android project + +Other than the aforementioned tasks for JVM projects, you can use the following Android-specific gradle tasks: + +- `detekt` - Runs detekt with type resolution on the specific build variant +- `detektBaseline` - Creates a detekt baselines starting from a run of detekt with type resolution enabled on the specific build variant. + +Alternatively, you can create a **custom detekt task**, making sure to specify the `classpath` and `jvmTarget` properties correctly. +Doing this on Android is more complicated due to build types/flavors (see [#2259](https://github.com/detekt/detekt/issues/2259) for further context). +Therefore, we recommend using the `detekt` tasks offered by the Gradle plugins. + +In case of build related issues, you may try `detekt.android.disabled=true` in `gradle.properties` to prevent detekt +Gradle plugins from configuring Android-specific gradle tasks. + +## Enabling on Detekt CLI + +If you're using [detekt via CLI](/docs/gettingstarted/cli), type resolution will be enabled only if you provide the `--classpath` and +`--jvm-target` flags. See the list of [CLI options](/docs/gettingstarted/cli#use-the-cli) for details. + +## Writing a rule that uses type resolution + +If you're [writing a custom rule](/docs/introduction/extensions) or if you're willing to write a rule to contribute to detekt, you might want to leverage type resolution. + +Rules that are using type resolution, access the [bindingContext](https://github.com/JetBrains/kotlin/blob/master/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java) from the `BaseRule` class ([source](https://github.com/detekt/detekt/blob/cd659ce8737fb177caf140f46f73a1a86b22be56/detekt-api/src/main/kotlin/io/gitlab/arturbosch/detekt/api/internal/BaseRule.kt#L30)). + +By default, the `bindingContext` is initialized as `BindingContext.EMPTY`. This is the **default value** that the rule receives if type resolution is **not enabled**. + +Therefore, is generally advised to annotate your `Rule` with `@RequiresTypeResolution` to ensure that your rule doesn't run if you don't have a proper `BindingContext`. + +If your rule is annotated with `@RequiresTypeResolution` you are free to use it to resolve types and get access to all the information needed for your rules. As a rule of thumb, we recommend to get inspiration from other rules on how they're using the `bindingContext`. + +## Testing a rule that uses type resolution + +To test a rule that uses type resolution, you can use the [`lintWithContext`](https://github.com/detekt/detekt/blob/d3546ff0d539d57e7a502dacbf66e91587fff098/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt#L40-L44) and [`compileAndLintWithContext`](https://github.com/detekt/detekt/blob/cd659ce8737fb177caf140f46f73a1a86b22be56/detekt-test/src/main/kotlin/io/gitlab/arturbosch/detekt/test/RuleExtensions.kt#L63-L72) extension functions. + +If you're using JUnit 5 for testing, you can use the `@KotlinCoreEnvironmentTest` annotation on your test class, and +accept a parameter of type `KotlinCoreEnvironment` in the class constructor. You can then access the environment by +referencing the parameter specified in the constructor: + +```kotlin +@KotlinCoreEnvironmentTest +class MyRuleSpec(private val env: KotlinCoreEnvironment) { + @Test + fun `reports cast that cannot succeed`() { + val code = """/* The code you want to test */""" + assertThat(MyRuleSpec().compileAndLintWithContext(env, code)).hasSize(1) + } +} +``` + +If you're using another testing framework (e.g. JUnit 4), you can use the [`createEnvironment()`](https://github.com/detekt/detekt/blob/cd659ce8737fb177caf140f46f73a1a86b22be56/detekt-test-utils/src/main/kotlin/io/github/detekt/test/utils/KotlinCoreEnvironmentWrapper.kt#L26-L31) method from `detekt-test-utils`. diff --git a/website/versioned_docs/version-1.23.0/intro.mdx b/website/versioned_docs/version-1.23.0/intro.mdx new file mode 100644 index 000000000000..ea29105d4018 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/intro.mdx @@ -0,0 +1,95 @@ +--- +title: "Welcome" +keywords: [detekt, static, analysis, code, kotlin] +sidebar_position: 1 +summary: +--- + +![detekt logo](/img/logo.svg "detekt logo") +![detekt in action](/img/tutorial/detekt_in_action.png "detekt in action") + +### Features + +- Code smell analysis for your [Kotlin](https://kotlinlang.org/) projects. +- Highly configurable rule sets. +- Generate baselines to suppress existing issues for legacy projects while making sure no new issues are introduced. +- Suppress issues in source files using `@Suppress` annotations. +- Support for different report formats: HTML, Markdown, [SARIF](https://sarifweb.azurewebsites.net/), XML (Checkstyle) and custom reports. +- [Extend detekt](introduction/extensions) with custom rule sets and reports. +- Complexity reports based on lines of code, cyclomatic complexity and number of code smells. +- First party integration with Gradle with our [Gradle plugin](#with-gradle). +- A community of [third party plugins](https://github.com/topics/detekt-plugin) that adds more rules and features to detekt. + +### Quick Start with Gradle + +Apply the following configuration to your Gradle project build file: + +```kotlin +plugins { + id("io.gitlab.arturbosch.detekt") version("[detekt_version]") +} + +repositories { + mavenCentral() +} +``` + +You can find what is the **latest version of detekt** in the [release notes](/changelog). + +Once you have set up detekt in your project, simply run `gradlew detekt`. + +To change the default behaviour of detekt rules, first generate yourself a detekt configuration file by running +`gradlew detektGenerateConfig` task and applying any changes to the generated file. + +Don't forget to reference the newly generated config inside the `detekt { }` closure. Optionally, it is possible to +slim down the configuration file to only the changes from the default configuration, by applying the +`buildUponDefaultConfig` option: + +```kotlin +detekt { + toolVersion = "[detekt_version]" + config.setFrom(file("config/detekt/detekt.yml")) + buildUponDefaultConfig = true +} +``` + +To enable/disable detekt reports use the `withType` method to set defaults for all detekt tasks at once: + +```kotlin +// Kotlin DSL +tasks.withType().configureEach { + reports { + xml.required.set(true) + html.required.set(true) + txt.required.set(true) + sarif.required.set(true) + md.required.set(true) + } +} +``` + +```groovy +// Groovy DSL +tasks.withType(Detekt).configureEach { + reports { + xml.required.set(true) + html.required.set(true) + txt.required.set(true) + sarif.required.set(true) + md.required.set(true) + } +} +``` + +See [reporting](introduction/reporting) docs for more details on configuring reports. + +### Adding more rule sets + +detekt itself provides a wrapper over [ktlint](https://github.com/pinterest/ktlint) as the `formatting` rule set +which can be easily added to the Gradle configuration: + +```gradle +dependencies { + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:[detekt_version]") +} +``` diff --git a/website/versioned_docs/version-1.23.0/introduction/_category_.json b/website/versioned_docs/version-1.23.0/introduction/_category_.json new file mode 100644 index 000000000000..34d319ff6a94 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Introduction", + "position": 2 +} diff --git a/website/versioned_docs/version-1.23.0/introduction/baseline.md b/website/versioned_docs/version-1.23.0/introduction/baseline.md new file mode 100644 index 000000000000..add7a082a2ff --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/baseline.md @@ -0,0 +1,92 @@ +--- +id: baseline +title: "Code Smell Baseline" +keywords: [baseline, suppressing, smells] +sidebar_position: 7 +--- + +With the cli option `--baseline` or the detekt-gradle-plugin closure-property `baseline` you can specify a file which is used to generate a `baseline.xml`. +It is a file where ignored code smells are defined. + +The intention of `CurrentIssues` is that only new code smells are printed on further analysis. +The `ManuallySuppressedIssues` can be used to write down false positive detections (instead of suppressing them and pollute your code base). + +The `ID` node has the following structure: `:`. +When adding a custom issue to the xml file, make sure the `RuleID` should be self-explaining. +The `Codesmell_Signature` is not printed to the console but can be retrieved from the **txt** output file when using +the `--report txt:path/to/report` cli flag. + +```xml + + + CatchRuntimeException:Junk.kt$e: RuntimeException + + + NestedBlockDepth:Indentation.kt$Indentation$override fun procedure(node: ASTNode) + TooManyFunctions:LargeClass.kt$io.gitlab.arturbosch.detekt.rules.complexity.LargeClass.kt + ComplexMethod:DetektExtension.kt$DetektExtension$fun convertToArguments(): MutableList<String> + + +``` + +#### Gradle + +If you are using the gradle-plugin run the `detektBaseline` task to generate yourself a `baseline.xml`. +This will create one baseline file per Gradle module. +As this might not be the desired behavior for a multi module project, think about implementing +a custom meta baseline task: + +###### Groovy DSL +```groovy +subprojects { + detekt { + // ... + baseline = file("${rootProject.projectDir}/config/baseline.xml") + // ... + } +} + +task detektProjectBaseline(type: io.gitlab.arturbosch.detekt.DetektCreateBaselineTask) { + description = "Overrides current baseline." + ignoreFailures.set(true) + parallel.set(true) + buildUponDefaultConfig.set(true) + setSource(files(rootDir)) + config.setFrom(files("$rootDir/config/detekt/detekt.yml")) + baseline.set(file("$rootDir/config/detekt/baseline.xml")) + include("**/*.kt") + include("**/*.kts") + exclude("**/resources/**") + exclude("**/build/**") +} +``` + +###### Kotlin DSL +```kotlin +subprojects { + detekt { + // ... + baseline = file("${rootProject.projectDir}/config/baseline.xml") + // ... + } +} + +val detektProjectBaseline by tasks.registering(DetektCreateBaselineTask::class) { + description = "Overrides current baseline." + buildUponDefaultConfig.set(true) + ignoreFailures.set(true) + parallel.set(true) + setSource(files(rootDir)) + config.setFrom(files("$rootDir/config/detekt/detekt.yml")) + baseline.set(file("$rootDir/config/detekt/baseline.xml")) + include("**/*.kt") + include("**/*.kts") + exclude("**/resources/**") + exclude("**/build/**") +} +``` + +#### FAQ + +Be aware that auto formatting cannot be combined with the `baseline`. +The signatures for a `;` for example would be too ambiguous. diff --git a/website/versioned_docs/version-1.23.0/introduction/compatibility.md b/website/versioned_docs/version-1.23.0/introduction/compatibility.md new file mode 100644 index 000000000000..73eb407d20b0 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/compatibility.md @@ -0,0 +1,43 @@ +--- +id: compatibility +title: "Compatibility Table" +keywords: [detekt, kotlin, gradle, compatibility, android] +summary: This page lists the version of the Gradle plugins have been used to build detekt. +sidebar_position: 11 +--- + +## Detekt Support Commitment + +detekt is developed by open-source contributors as a volunteer effort. +Due to our limited resources, we commit to support only the **latest stable versions** and related RC versions. + +When opening Issues and Discussions, consider first updating to the latest version and align your tool versions +with the one listed below. This allows us to offer you better support. + +## Tool Versions + +When shipping the Detekt Gradle Plugin, we depend on both the **Kotlin Gradle Plugin** and the **Android Gradle Plugin**. + +Those dependencies are applied as `compileOnly` ([see here](https://github.com/detekt/detekt/blob/75622d3ba88b0ae0357aec5f2d82a55aa6c6d157/detekt-gradle-plugin/build.gradle.kts#L17-L18)) to allow our users to pick the version of the Gradle plugin they prefer and don't impose the one we use inside detekt. + +We try to provide **backward compatibility** when possible, although that's not always trivial (especially with AGP or across minor versions of Kotlin). + +The following table lists the version of the Gradle plugin we used to compile the Detekt Gradle Plugin. The `Java Target Level` entry specifies the level of the generated bytecode (i.e. the `-target` flag used when generating bytecode). The `JDK Version` represents the highest version of the JDK we test our code against. + +Consider **aligning** your Gradle plugin versions with the one listed below, as we can offer better support on Issues and Discussions for the listed versions of those tools. + +| detekt Version | Gradle Version | Kotlin Version | AGP Version | Java Target Level | JDK Version | +|----------------|----------------|----------------|-------------|-------------------|-------------| +| `1.22.0` | `7.5.1` | `1.7.21` | `7.3.1` | `1.8` | `17` | +| `1.21.0` | `7.5` | `1.6.21` | `7.2.1` | `1.8` | `17` | +| `1.20.0` | `7.4.2` | `1.6.20` | `7.1.3` | `1.8` | `17` | +| `1.19.0` | `7.3.0` | `1.5.31` | `4.2.2` | `1.8` | `17` | +| `1.18.0` | `7.0.1` | `1.5.21` | `4.2.0` | `1.8` | `16` | +| `1.17.0` | `7.0.1` | `1.4.32` | `4.2.0` | `1.8` | `15` | +| `1.16.0` | `6.8.0` | `1.4.21` | `4.1.2` | `1.8` | `15` | +| `1.15.0` | `6.8.0` | `1.4.10` | `4.0.1` | `1.8` | `15` | +| `1.14.2` | `6.7.0` | `1.4.10` | `4.0.1` | `1.8` | `14` | +| `1.14.0` | `6.7-rc-2` | `1.4.10` | `4.0.1` | `1.8` | `14` | +| `1.13.1` | `6.6.1` | `1.4.0` | `4.0.1` | `1.8` | `14` | + +_(older versions are omitted for brevity)_ diff --git a/website/versioned_docs/version-1.23.0/introduction/compose.md b/website/versioned_docs/version-1.23.0/introduction/compose.md new file mode 100644 index 000000000000..b0a25fa0d55e --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/compose.md @@ -0,0 +1,102 @@ +--- +id: compose +title: "Configuration for Compose" +keywords: [compose, config, configuration, jetpack-compose, rules] +summary: This page describes each reporting format and explains how to leverage them. +sidebar_position: 5 +--- + +Relevant rule sets and their configuration options for Compose styles & usage. The following are being used as reference for Compose usage: +- [Compose API Guidelines](https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md) +- [Compose source](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose) + +### FunctionNaming for Compose + +See [FunctionNaming](/docs/rules/naming#functionnaming). + +`@Composable` functions that return `Unit` are named using `PascalCase`. detekt may see this as a violation: + +```kotlin +@Composable +fun FooButton(text: String, onClick: () -> Unit) { // Violation for FooButton() +``` + +#### Recommended configuration +Choose _either_ of the following options: + +* Augment default `functionPattern` to `'[a-zA-Z][a-zA-Z0-9]*'` (default is: `'[a-z][a-zA-Z0-9]*'`) +* Set `ignoreAnnotated` to `['Composable']` + +### TopLevelPropertyNaming for Compose + +See [TopLevelPropertyNaming](/docs/rules/naming#toplevelpropertynaming). + +Compose guidelines prescribe `CamelCase` for top-level constants. + +##### Default Style: + +```kotlin +private val FOO_PADDING = 16.dp +``` + +##### Compose Style: + +```kotlin +private val FooPadding = 16.dp +``` + +#### Recommended configuration + +* Set `constantPattern` to `'[A-Z][A-Za-z0-9]*'` (default is: `'[A-Z][_A-Z0-9]*'`) + + +### LongParameterList for Compose + +See [LongParameterList](/docs/rules/complexity#longparameterlist). + +Composables may boast more than the typical number of function arguments (albeit mostly with default values). For example, see [OutlinedTextField](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt;l=133?q=OutlinedTextFieldLayout&ss=androidx%2Fplatform%2Fframeworks%2Fsupport:compose%2F). + +#### Recommended configuration + +* Set `functionThreshold` to a higher value +* Additionally, can set `ignoreDefaultParameters = true` + +### MagicNumber for Compose + +See [MagicNumber](/docs/rules/style#magicnumber). + +Class/companion object/top-level properties that declare objects such as `Color(0xFFEA6D7E)` may be considered violations if they don't specify the named parameter (i.e. `Color(color = 0xFFEA6D7E)`). + +``` kotlin +val color1 = Color(0xFFEA6D7E) // Violation + +class Foo { + val color2 = Color(0xFFEA6D7E) // Violation + + companion object { + val color3 = Color(0xFFEA6D7E) // No violation if ignoreCompanionObjectPropertyDeclaration = true by default + } +} +``` + +#### Recommended configuration + +* Set `ignorePropertyDeclaration = true`, `ignoreCompanionObjectPropertyDeclaration = true` (default) + +### UnusedPrivateMember for Compose + +See [UnusedPrivateMember](/docs/rules/style#unusedprivatemember). + +detekt may see composable preview functions, i.e. those marked with `@Preview`, as unused. + +``` kotlin +@Preview +@Composable +private fun FooLazyColumnPreview() { // Violation for FooLazyColumnPreview() + FooLazyColumn() +} +``` + +#### Recommended configuration + +* Set `ignoreAnnotated` to `['Preview']` diff --git a/website/versioned_docs/version-1.23.0/introduction/configurations.md b/website/versioned_docs/version-1.23.0/introduction/configurations.md new file mode 100644 index 000000000000..4dcad9f4189c --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/configurations.md @@ -0,0 +1,191 @@ +--- +id: configurations +title: "detekt Configuration File" +keywords: [config, configuration, yaml] +permalink: configurations.html +sidebar_position: 3 +--- + +_detekt_ uses a [YAML style configuration](https://yaml.org/spec/1.2/spec.html) file for various things: + +- rule set and rule properties +- build failure +- console reports +- output reports +- processors + +See the [default-detekt-config.yml](https://github.com/detekt/detekt/blob/main/detekt-core/src/main/resources/default-detekt-config.yml) +file for all defined configuration options and their default values. + +_Note:_ When using a custom config file, the default values are ignored unless you also set the `--build-upon-default-config` flag. + +## Config validation + +If config validation is enabled, _detekt_ will verify that your configuration file is structured correctly and all first party rule sets, rules and configuration options are valid and not marked as deprecated. + +```yaml +config: + validation: true + warningsAsErrors: false + excludes: '' +``` + +Invalid or deprecated rules and configuration options are by default printed as warnings unless `warningsAsErrors` is set to `true`. + +_Note:_ Custom rules sets are excluded from config validation by default. + +If you have extended _detekt_ and rely on a custom properties, you will need to exclude those from config validation by adding their paths to the `excludes` attribute. Multiple values are separated by comma and `.*` can be used as a wildcard (e.g. `propA,build>.*>propB`). + +## Rule sets and rules + +_detekt_ allows easily to just pick the rules you want and configure them the way you like. +For example if you want to allow up to 20 functions inside a Kotlin file instead of the default threshold, write: + +```yaml +complexity: + TooManyFunctions: + thresholdInFiles: 20 +``` + +To read about all supported rule sets and rules, use the side navigation `Rule Sets`. + +### Path Filters / Excludes / Includes + +Fine grained path filters can be defined for each rule or rule set through globbing patterns. +This gives the user more freedom in analyzing only specific files +and rule authors the ability to write *library only* rules. + +```yaml +complexity: + TooManyFunctions: + ... + excludes: ['**/internal/**'] + includes: ['**/internal/util/NeedsToBeChecked.kt'] +``` + +In case you want to apply the same filters for different rules, you can use +[YAML anchors and aliases](https://yaml.org/spec/1.2/spec.html#id2785586) to reapply previously defined paths. + +```yaml +naming: + ClassNaming: + ... + excludes: &testFolders + - '**/test/**' + - '**/androidTest/**' + ConstructorParameterNaming: + ... + excludes: *testFolders +``` + +## Build failure + +_Detekt_ supports the option to fail your build if a threshold of code smell issues is met. + +For this the following code must be inside the detekt config: + +```yaml +build: + maxIssues: 10 # break the build if more than ten weighted issues are found + weights: + complexity: 2 # every rule of the complexity rule set should count as if two issues were found... + LongParameterList: 1 # ...with the exception of the LongParameterList rule. + comments: 0 # comment rules are just a nice to know?! +``` + +Every rule and rule set can be attached with an integer value which is the weight of the finding. +For example: If you have 5 findings of the category _complexity_, then your failThreshold of 10 is reached as +5 x 2 = 10. + +Weights are respected in the following priority order: +- The specified weight for a rule +- The specified weight for a rule set +- By default, the weight is 1. + +## Console Reports + +Uncomment the reports you don't care about. + +```yaml +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + # - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' +``` + +**ProjectStatisticsReport** contains metrics and statistics concerning the analyzed project sorted by priority. + +**ComplexityReport** contains metrics concerning the analyzed code. +For instance the source lines of code and the McCabe complexity are calculated. + +**NotificationReport** contains notifications reported by the detekt analyzer similar to push notifications. +It's simply a way of alerting users to information that they have opted-in to. + +**FindingsReport** contains all rule violations in a list format grouped by ruleset. + +**FileBasedFindingsReport** is similar to the FindingsReport shown above. +The rule violations are grouped by file location. + +## Output Reports + +Uncomment the reports you don't care about. The detailed description can be found in [reporting](reporting.md). + +```yaml +output-reports: + active: true + exclude: + # - 'HtmlOutputReport' + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'SarifOutputReport' + # - 'MdOutputReport' +``` + + +## Processors + +Count processors are used to calculate project metrics. +For example, when all count processors are enabled, a detekt html report might look like this: + +![Processor metrics in html report](/img/tutorial/processor_metrics_in_html_report.png) + +The `'DetektProgressListener'` processor shows a progress indicator in stdout while a detekt process is running. + +Uncomment the processors you don't care about. + +```yaml +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' +``` + +## Config JSON Schema + +A JSON Schema for the config file is available on: [json.schemastore.org/detekt-1.22.0.json](https://json.schemastore.org/detekt-1.22.0.json). + +You can configure your IDE (e.g. IntelliJ or Android Studio have built-in support) +to use that schema to give you **autocompletion** capabilities on your config file. +More details on the IntelliJ support are available +[on this page](https://www.jetbrains.com/help/ruby/yaml.html#remote_json). + +![JSON Schema validator on IntelliJ](/img/tutorial/json_schema_validator_intellij.png) + +The JSON Schema is currently not automatically generated. It can be updated manually [on this repository](https://github.com/SchemaStore/schemastore). diff --git a/website/versioned_docs/version-1.23.0/introduction/extensions.md b/website/versioned_docs/version-1.23.0/introduction/extensions.md new file mode 100644 index 000000000000..be5a494a3f32 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/extensions.md @@ -0,0 +1,255 @@ +--- +id: extensions +title: "Extending detekt" +keywords: [extensions, rulesets] +sidebar_position: 9 +--- + +The following page describes how to extend detekt and how to customize it to your domain-specific needs. +The associated **code samples** to this guide can be found in the package [detekt/detekt-sample-extensions](https://github.com/detekt/detekt/tree/main/detekt-sample-extensions). + +#### Custom RuleSets + +_detekt_ uses the `ServiceLoader` pattern to collect all instances of `RuleSetProvider` interfaces. +So it is possible to define rules/rule sets and enhance _detekt_ with your own flavor. + +:::caution Attention + +You need a `resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider` file which +has as content the fully qualified name of your `RuleSetProvider` e.g. `io.gitlab.arturbosch.detekt.sample.extensions.SampleProvider`. + +::: + +You can use our [GitHub template](https://github.com/detekt/detekt-custom-rule-template) to have a basic scaffolding to +develop your own custom rules. Another option is to clone the provided [detekt/detekt-sample-extensions](https://github.com/detekt/detekt/tree/main/detekt-sample-extensions) project. + +Own rules have to extend the abstract _Rule_ class and override the `visitXXX()`-functions from the AST. +A `RuleSetProvider` must be implemented, which declares a `RuleSet` in the `instance()`-function. +To leverage the configuration mechanism of detekt you must pass the Config object from your rule set provider to your rule. +An `Issue` property defines what ID, severity and message should be printed on the console or on any other output format. + +Example of a custom rule: +```kotlin +class TooManyFunctions(config: Config) : Rule(config) { + + override val issue = Issue(javaClass.simpleName, + Severity.CodeSmell, + "This rule reports a file with an excessive function count.", + Debt.TWENTY_MINS) + + private val threshold = 10 + private var amount: Int = 0 + + override fun visitKtFile(file: KtFile) { + super.visitKtFile(file) + if (amount > threshold) { + report(CodeSmell(issue, Entity.from(file), + "Too many functions can make the maintainability of a file costlier") + } + amount = 0 + } + + override fun visitNamedFunction(function: KtNamedFunction) { + super.visitNamedFunction(function) + amount++ + } +} +``` + +Example of a much preciser rule in terms of more specific CodeSmell constructor and Rule attributes: +```kotlin +class TooManyFunctions2(config: Config) : Rule(config) { + + override val issue = Issue( + javaClass.simpleName, + Severity.CodeSmell, + "This rule reports a file with an excessive function count.", + Debt.TWENTY_MINS + ) + + private val threshold: Int by config(defaultValue = 10) + private var amount: Int = 0 + + override fun visitKtFile(file: KtFile) { + super.visitKtFile(file) + if (amount > threshold) { + report(ThresholdedCodeSmell(issue, + entity = Entity.from(file), + metric = Metric(type = "SIZE", value = amount, threshold = threshold), + message = "The file ${file.name} has $amount function declarations. " + + "Threshold is specified with $threshold.", + references = emptyList()) + ) + } + amount = 0 + } + + override fun visitNamedFunction(function: KtNamedFunction) { + super.visitNamedFunction(function) + amount++ + } +} +``` + +If you want your rule to be configurable, write down your properties inside the detekt.yml file. +Please note that this will only take effect, if the `Config` object is passed on by the `RuleSetProvider` +to the rule itself. + +```yaml +MyRuleSet: + TooManyFunctions2: + active: true + threshold: 5 + OtherRule: + active: false +``` + +By specifying the rule set and rule ids, _detekt_ will use the sub configuration of `TooManyFunctions2`: + +```val threshold = valueOrDefault("threshold", THRESHOLD)``` + +:::note + +As of version 1.2.0 detekt now verifies if all configured properties actually exist in a configuration created by `--generate-config`. +This means that by default detekt does not know about your new properties. +Therefore we need to mention them in the configuration under `config>excludes`. + +::: + +```yaml +config: + validation: true + # 1. exclude rule set 'sample' and all its nested members + # 2. exclude every property in every rule under the rule set 'sample' + excludes: "sample.*,sample>.*>.*" +``` + +##### Testing your rules + +To test your rules, add the dependency on `detekt-test` to your project: `testCompile "io.gitlab.arturbosch.detekt:detekt-test:$version"`. + +The easiest way to detect issues with your newly created rule is to use the `lint` extension function: +- `Rule.lint(StringContent/Path/KtFile): List` + +If you need to reuse the Kotlin file for performance reasons within similar test cases, please use one of these functions: +- `compileContentForTest(content: String): KtFile` +- `compileForTest(path: Path): KtFile` + +#### Custom Processors + +Custom processors can be used for example to implement additional project metrics. + +When for whatever reason you want to count all loop statements inside your code base, you could write something like: + +```kotlin +class NumberOfLoopsProcessor : FileProcessListener { + + override fun onProcess(file: KtFile) { + val visitor = LoopVisitor() + file.accept(visitor) + file.putUserData(numberOfLoopsKey, visitor.numberOfLoops) + } + + companion object { + val numberOfLoopsKey = Key("number of loops") + } + + class LoopVisitor : DetektVisitor() { + + internal var numberOfLoops = 0 + override fun visitLoopExpression(loopExpression: KtLoopExpression) { + super.visitLoopExpression(loopExpression) + numberOfLoops++ + } + } +} +``` + +To let detekt know about the new processor, we specify a `resources/META-INF/services/io.gitlab.arturbosch.detekt.api.FileProcessListener` file +with the full qualify name of our processor as the content: `io.gitlab.arturbosch.detekt.sample.extensions.processors.NumberOfLoopsProcessor`. + + +To test the code we use the `detekt-test` module and write a JUnit 5 testcase. + +```kotlin +class NumberOfLoopsProcessorTest { + + @Test + fun `should expect two loops`() { + val code = """ + fun main() { + for (i in 0..10) { + while (i < 5) { + println(i) + } + } + } + """ + + val ktFile = compileContentForTest(code) + NumberOfLoopsProcessor().onProcess(ktFile) + + assertThat(ktFile.getUserData(NumberOfLoopsProcessor.numberOfLoopsKey)).isEqualTo(2) + } +} +``` + +#### Custom Reports + +_detekt_ allows you to extend the console output and to create custom output formats. +If you want to customize the output, take a look at the `ConsoleReport` and `OutputReport` classes. + +All they need are an implementation of the `render()`-function which takes an object with all findings and returns a string to be printed out. + +```kotlin +abstract fun render(detektion: Detektion): String? +``` + +#### Let detekt know about your extensions + +So you have implemented your own rules or other extensions and want to integrate them +into your `detekt` run? Great, make sure to have a `jar` with all your needed dependencies +minus the ones `detekt` brings itself. + +Take a look at our [sample project](https://github.com/detekt/detekt/tree/main/detekt-sample-extensions) on how to achieve this with gradle. + +##### Integrate your extension with the detekt CLI + +Mention your `jar` with the `--plugins` flag when calling the cli fatjar: +```sh +detekt --input ... --plugins /path/to/my/jar +``` + +##### Integrate your extension with the Detekt Gradle Plugin + +For example `detekt` itself provides a wrapper over [ktlint](https://github.com/pinterest/ktlint) as a +custom `formatting` rule set. +To enable it, we add the published dependency to `detekt` via the `detektPlugins` configuration: + +###### Gradle (Kotlin/Groovy DSL) + +```kotlin +dependencies { + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:[detekt_version]") +} +``` + +##### Pitfalls + +- All rules are disabled by default and have to be explicitly enabled in the `detekt` yaml configuration file. +- If you do not pass the `Config` object from the `RuleSetProvider` to the rule, the rule is active, but you will not be able to use +any configuration options or disable the rule via config file. +- If your extension is part of your project and you integrate it like `detektPlugins project(":my-rules")` make sure that this +subproject is build before `gradle detekt` is run. +In the `kotlin-dsl` you could add something like `tasks.withType { dependsOn(":my-rules:assemble") }` to explicitly run `detekt` only +after your extension sub project is built. +- If you use detekt for your Android project, and if you want to integrate all your custom rules in a new module, please make sure that +you created a pure kotlin module which has no Android dependencies. `apply plugin: "kotlin"` is enough to make it work. +- Sometimes when you run detekt task, you may not see the violations detected by your custom rules. In this case open a terminal and run +`./gradlew --stop` to stop gradle daemons and run the task again. + +#### autoCorrect property + +In detekt you can write custom rules which can manipulate your code base. +For this a cli flag `--auto-correct` and the gradle plugin property `autoCorrect` exists. +Only write auto correcting code within the `Rule#withAutoCorrect()`-function. diff --git a/website/versioned_docs/version-1.23.0/introduction/reporting.md b/website/versioned_docs/version-1.23.0/introduction/reporting.md new file mode 100644 index 000000000000..81f6bffc9b5a --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/reporting.md @@ -0,0 +1,171 @@ +--- +id: reporting +title: "Reporting" +keywords: [reporting] +summary: This page describes each reporting format and explains how to leverage them. +sidebar_position: 4 +--- + +## Formats + +In addition to the CLI output, detekt supports 4 different types of output reporting formats. +You can refer to [CLI](/docs/gettingstarted/cli) or [Gradle](/docs/gettingstarted/gradle) to find +out how to configure these report formats. + +### TXT +Similar to the console output, each line of the txt output represents a finding and contains +finding signature to help edit [baseline files](/docs/gettingstarted/gradle). + +``` +EmptyFunctionBlock - [This empty block of code can be removed.] at /user/home/detekt/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt:14:42 - Signature=DetektPlugin.kt$DetektPlugin${ } +NoUnusedImports - [Unused import] at /user/home/detekt/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt:9:1 - Signature=io.gitlab.arturbosch.detekt.DetektPlugin.kt:9 +NoUnusedImports - [Unused import] at /user/home/detekt/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt:10:1 - Signature=io.gitlab.arturbosch.detekt.DetektPlugin.kt:10 +NoConsecutiveBlankLines - [Needless blank line(s)] at /user/home/detekt/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt:86:1 - Signature=io.gitlab.arturbosch.detekt.DetektPlugin.kt:86 +UnusedPrivateMember - [Private function registerDetektJvmTasks is unused.] at /user/home/detekt/detekt-gradle-plugin/src/main/kotlin/io/gitlab/arturbosch/detekt/DetektPlugin.kt:17:5 - Signature=DetektPlugin.kt$DetektPlugin$private fun Project.registerDetektJvmTasks(extension: DetektExtension) +``` + +### HTML +HTML is a human-readable format that can be open through browser. It includes different metrics +and complexity reports of this run, in addition to the findings with detailed descriptions and +report. Check out the example: ![HTML report](/img/tutorial/html.png) + +### XML +XML is a machine-readable format that can be integrated with CI tools. It is compatible with +[Checkstyle](https://checkstyle.sourceforge.io/) output. + +### SARIF +[SARIF](https://sarifweb.azurewebsites.net/) is a standard format for the output of +static analysis tools. It is a JSON format with a defined +[schema](https://docs.oasis-open.org/sarif/sarif/v2.0/csprd02/schemas/). It is currently supported +by GitHub Code Scanning, and we expect more consuming tools will adopt this format in the future. + +### MD +Markdown is a lightweight markup language for creating formatted text using a plain-text editor. +The output structure looks similar to HTML format. +About [markdown](https://github.github.com/gfm/#what-is-markdown-) on GitHub. + +## Severity +For machine-readable format, it is possible to configure the severity of each finding to fit +your CI policy with respects to errors. You may specify the severity level in the config file +for rule, or ruleSets: + +```yaml +empty-blocks: + active: true + severity: error + EmptyCatchBlock: + active: true + severity: info +``` + +The severity will be computed in the priority order: +- Severity of the rule if exists +- Severity of the parent ruleset if exists +- Default severity: warning + +## Relative path +In a shared codebase, it is often required to use relative path so that all developers and tooling +have a consistent view. This can be enabled by CLI option `--base-path` or Gradle as the following: + +```groovy +detekt { + basePath = projectDir +} +``` + +Note that this option only affects file paths in those formats for machine consumers, +namely XML and SARIF. + +## Merging reports + +The machine-readable report formats support report merging. +Detekt Gradle Plugin is not opinionated in how merging is set up and respects each project's build logic, especially +the merging makes most sense in a multi-module project. In this spirit, only Gradle tasks are provided. + +At the moment, merging XML and SARIF are supported. You can refer to the sample build script below and +run `./gradlew detekt reportMerge --continue` to execute detekt tasks and merge the corresponding reports. + +### Groovy DSL +```groovy +tasks.register("reportMerge", io.gitlab.arturbosch.detekt.report.ReportMergeTask) { + output = project.layout.buildDirectory.file("reports/detekt/merge.xml") // or "reports/detekt/merge.sarif" +} + +subprojects { + detekt { + reports.xml.required.set(true) + // reports.sarif.required.set(true) + } + + tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach { + finalizedBy(reportMerge) + } + + reportMerge.configure { + input.from(tasks.withType(io.gitlab.arturbosch.detekt.Detekt).collect { it.xmlReportFile }) // or sarifReportFile + } +} +``` + +### Kotlin DSL + +```kotlin +val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { + output.set(rootProject.layout.buildDirectory.file("reports/detekt/merge.xml")) // or "reports/detekt/merge.sarif" +} + +subprojects { + detekt { + reports.xml.required.set(true) + // reports.sarif.required.set(true) + } + + tasks.withType().configureEach { + finalizedBy(reportMerge) + } + + reportMerge { + input.from(tasks.withType().map { it.xmlReportFile }) // or .sarifReportFile + } +} +``` + +## Integration with GitHub Code Scanning +If your repository is hosted on GitHub, you can enable SARIF output in your repository. +You can follow to the [official documentation](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/uploading-a-sarif-file-to-github). + +To change the severity level to fail your GitHub Action build configure it in [GitHub Settings](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#defining-the-severities-causing-pull-request-check-failure). + +You can follow the example below as a quick start: +```yaml +jobs: + without-type-resolution: + runs-on: ubuntu-latest + env: + GRADLE_OPTS: -Dorg.gradle.daemon=false + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + java-version: 11 + + - name: Run detekt + run: ./gradlew detekt + + # Make sure we always run this upload task, + # because the previous step may fail if there are findings. + - name: Upload SARIF to GitHub using the upload-sarif action + uses: github/codeql-action/upload-sarif@v2 + if: success() || failure() + with: + sarif_file: build/reports/detekt/detekt.sarif +``` + +Note: you'll have to set `Detekt.basePath` on each Detekt Gradle task, +so that GitHub knows where the repository is to place annotations correctly. +```gradle +basePath = rootProject.projectDir.absolutePath +``` diff --git a/website/versioned_docs/version-1.23.0/introduction/snapshots.md b/website/versioned_docs/version-1.23.0/introduction/snapshots.md new file mode 100644 index 000000000000..640b8305f0e4 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/snapshots.md @@ -0,0 +1,82 @@ +--- +id: snapshots +title: "Using Snapshots" +keywords: [snapshot, releases] +summary: This page explains how you can setup snapshots for your detekt build to test the latest unreleased features. +sidebar_position: 10 +--- + +This page explains how you can use our **latest snapshots** to test upcoming unreleased features. + +## Where to download snapshots + +You can find the latest snapshot on [sonatype](https://oss.sonatype.org/#view-repositories;snapshots~browsestorage~io/gitlab/arturbosch/detekt). A new snapshot is published after every merge to `main` from the [Deploy Snapshot](https://github.com/detekt/detekt/actions?query=workflow%3A%22Deploy+Snapshot%22) GitHub Action workflow. + +## Gradle setup with Buildscript + +If you're using Gradle with the `buildscript` block, you should update your top level `build.gradle` file with: + +```groovy +buildscript { + repositories { + maven { + url "https://oss.sonatype.org/content/repositories/snapshots/" + } + } + dependencies { + classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:main-SNAPSHOT" + } +} + +apply plugin: "io.gitlab.arturbosch.detekt" + +allprojects { + repositories { + maven { + url "https://oss.sonatype.org/content/repositories/snapshots/" + } + } +} +``` + +Make sure that you're adding the sonatype maven repository to both the `repositories{}` block **inside** the `buildscript{}` block and outside it. + +## Gradle setup with Plugin block + +If you're using the `plugins{}` block to apply detekt, you should update your `build.gradle` file to: + +```groovy +plugins { + id("io.gitlab.arturbosch.detekt") version "main-SNAPSHOT" +} + +allprojects { + repositories { + maven { + url "https://oss.sonatype.org/content/repositories/snapshots/" + } + } +} +``` + +Plus you need to update the `settings.gradle` file as follows: + +```groovy +pluginManagement { + resolutionStrategy { + eachPlugin { + if (requested.id.id == "io.gitlab.arturbosch.detekt") { + useModule("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${requested.version}") + } + } + } + repositories { + // Your other repos here. + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + } + } +} +``` + +Please note that the extra `resolutionStrategy{}` block is needed as we don't publish a Gradle Plugin marker for our snapshots. diff --git a/website/versioned_docs/version-1.23.0/introduction/suppressing-rules.md b/website/versioned_docs/version-1.23.0/introduction/suppressing-rules.md new file mode 100644 index 000000000000..73bcf57d3610 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/suppressing-rules.md @@ -0,0 +1,51 @@ +--- +id: suppressing-rules +title: "Suppressing Issues" +keywords: [suppressing, issues] +sidebar_position: 6 +--- + +_detekt_ supports the Java (`@SuppressWarnings`) and Kotlin (`@Suppress`) style suppression. +If both annotations are present, Kotlin's annotation is favored! + +To suppress an issue, the id of the rule must be written inside the values field of the annotation (e.g. `@Suppress("LongMethod")`). + +If a `LargeClass` is reported, but that is totally fine for you codebase, then just annotate it: + +```kotlin +@Suppress("LargeClass") // or use complexity.LargeClass +object Constants { + ... +} +``` + +It is also possible to prefix the rule id with `detekt` and/or the ruleset id such as `@Suppress("detekt:LongMethod")` or `@Suppress("complexity:LongParameterList")`. + +The following table shows the various supported suppression formats. + +| Example Suppression | Description | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------| +| `all`, `detekt:all`, `detekt.all` | Suppresses all detekt findings. | +| `style`, `detekt:style`, `detekt.style` | Suppresses all findings from rules in the style rule set. | +| `MagicNumber`, `style:MagicNumber`, `style.MagicNumber`, `detekt:style:MagicNumber`, `detekt.style.MagicNumber` | Suppresses all MagicNumber rule findings. | + +Some rules like `TooManyFunctions` can only be suppressed by using a file level annotation `@file:Suppress("TooManyFunctions")`. + +**Formatting rules suppression** + +Please note that rules inside the [`formatting`](/docs/rules/formatting) ruleset can only be suppressed at **the file level**. + +Rules inside this ruleset are wrappers around KtLint rules, and we don't have the same reporting capabilities that we offer for first party rules. For example, you can suppress the [MaximumLineLength](/docs/rules/formatting#maximumlinelength) rule only in your entire file with: + +```kotlin +@file:Suppress("MaximumLineLength") +package com.example + +object AClassWithLongLines { + //... +} +``` + +Several rules in the [`formatting`](/docs/rules/formatting) ruleset also have a "first party" counterpart. For instance you can use the [`MaxLineLength`](/docs/rules/style#maxlinelength) rule instead from the [`style`](/docs/rules/style) ruleset. + +For those rules, you can suppress the inspection also locally (on top of an expression, function, class, etc.). diff --git a/website/versioned_docs/version-1.23.0/introduction/suppressors.md b/website/versioned_docs/version-1.23.0/introduction/suppressors.md new file mode 100644 index 000000000000..656ebab226d4 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/introduction/suppressors.md @@ -0,0 +1,41 @@ +--- +id: suppressors +title: "Suppressors" +keywords: [suppressing, issues, smells] +sidebar_position: 8 +--- + +The `Suppressor`s are a tool that you can use to customize the reports of detekt. They allow you to (surprise) suppress some issues detected by some rules, and they can be applied to any rule. + +An example is the **annotation** suppressor. It works like this. First, you need to configure the tag `ignoreAnnotated` with a list of annotations, you want the suppressor to consider. Example: + +```yaml +UnusedPrivateMember: + active: true + ignoreAnnotated: + - 'Preview' +``` + +Now, if an issue is found under a code that is annotated with `@Preview` that issue will be suppressed. This example is really handy if you use [Jetpack Compose](/docs/introduction/compose), for example. + +## Available `Suppressor`s + +### Annotation Suppressor + +Suppress all the issues that are raised under a code that is annotated with the annotations defined at `ignoreAnnotated`. + +##### Config tag + +`ignoreAnnotated: List`: The annotations can be defined just by its name or with its fully qualified name. If you don't run detekt with type solving the fully qualified name does not work. + +### Function Suppressor + +Suppress any issue raised under a function definition that matches the signatures defined at `ignoreFunction`. + +*Note*: this Suppressor doesn't suppress issues found when you call these functions. It just suppresses the ones in the function **definition**. + +##### Config tag: + +`ignoreFunction: List`: The signature of the function. You can ignore all the overloads of a function defining just its name like `java.time.LocalDate.now` or you can specify the parameters to only suppress one: `java.time.LocalDate(java.time.Clock)`. + +*Note:* you need to write all the types with fully qualified names e.g. `org.example.foo(kotlin.String)`. It is important to add `kotlin.String`. Just adding `String` will not work. diff --git a/website/versioned_docs/version-1.23.0/rules/_category_.json b/website/versioned_docs/version-1.23.0/rules/_category_.json new file mode 100644 index 000000000000..017ff3449cf9 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Rules Documentation", + "position": 4 +} diff --git a/website/versioned_docs/version-1.23.0/rules/comments.md b/website/versioned_docs/version-1.23.0/rules/comments.md new file mode 100644 index 000000000000..e15e367b6195 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/comments.md @@ -0,0 +1,277 @@ +--- +title: Comments Rule Set +sidebar: home_sidebar +keywords: [rules, comments] +permalink: comments.html +toc: true +folder: documentation +--- +This rule set provides rules that address issues in comments and documentation +of the code. + +### AbsentOrWrongFileLicense + +This rule will report every Kotlin source file which doesn't have the required license header. +The rule validates each Kotlin source and operates in two modes: if `licenseTemplateIsRegex = false` (or missing) +the rule checks whether the input file header starts with the read text from the passed file in the +`licenseTemplateFile` configuration option. If `licenseTemplateIsRegex = true` the rule matches the header with +a regular expression produced from the passed template license file (defined via `licenseTemplateFile` configuration +option). + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``licenseTemplateFile`` (default: ``'license.template'``) + + path to file with license header template resolved relatively to config file + +* ``licenseTemplateIsRegex`` (default: ``false``) + + whether or not the license header template is a regex template + +### CommentOverPrivateFunction + +This rule reports comments and documentation that has been added to private functions. These comments get reported +because they probably explain the functionality of the private function. However, private functions should be small +enough and have an understandable name so that they are self-explanatory and do not need this comment in the first +place. + +Instead of simply removing this comment to solve this issue prefer to split up the function into smaller functions +with better names if necessary. Giving the function a better, more descriptive name can also help in +solving this issue. + +**Active by default**: No + +**Debt**: 20min + +### CommentOverPrivateProperty + +This rule reports comments and documentation above private properties. This can indicate that the property has a +confusing name or is not in a small enough context to be understood. +Private properties should be named in a self-explanatory way and readers of the code should be able to understand +why the property exists and what purpose it solves without the comment. + +Instead of simply removing the comment to solve this issue, prefer renaming the property to a more self-explanatory +name. If this property is inside a bigger class, it makes sense to refactor and split up the class. This can +increase readability and make the documentation obsolete. + +**Active by default**: No + +**Debt**: 20min + +### DeprecatedBlockTag + +This rule reports use of the `@deprecated` block tag in KDoc comments. Deprecation must be specified using a +`@Deprecated` annotation as adding a `@deprecated` block tag in KDoc comments +[has no effect and is not supported](https://kotlinlang.org/docs/kotlin-doc.html#suppress). The `@Deprecated` +annotation constructor has dedicated fields for a message and a type (warning, error, etc.). You can also use the +`@ReplaceWith` annotation to specify how to solve the deprecation automatically via the IDE. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +/** +* This function prints a message followed by a new line. +* +* @deprecated Useless, the Kotlin standard library can already do this. Replace with println. +*/ +fun printThenNewline(what: String) { + // ... +} +``` + +#### Compliant Code: + +```kotlin +/** +* This function prints a message followed by a new line. +*/ +@Deprecated("Useless, the Kotlin standard library can already do this.") +@ReplaceWith("println(what)") +fun printThenNewline(what: String) { + // ... +} +``` + +### EndOfSentenceFormat + +This rule validates the end of the first sentence of a KDoc comment. +It should end with proper punctuation or with a correct URL. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``endOfSentenceFormat`` (default: ``'([.?!][ \t\n\r\f<])|([.?!:]$)'``) + + regular expression which should match the end of the first sentence in the KDoc + +### KDocReferencesNonPublicProperty + +This rule will report any KDoc comments that refer to non-public properties of a class. +Clients do not need to know the implementation details. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +/** +* Comment +* [prop1] - non-public property +* [prop2] - public property +*/ +class Test { + private val prop1 = 0 + val prop2 = 0 +} +``` + +#### Compliant Code: + +```kotlin +/** +* Comment +* [prop2] - public property +*/ +class Test { + private val prop1 = 0 + val prop2 = 0 +} +``` + +### OutdatedDocumentation + +This rule will report any class, function or constructor with KDoc that does not match the declaration signature. +If KDoc is not present or does not contain any @param or @property tags, rule violation will not be reported. +By default, both type and value parameters need to be matched and declarations orders must be preserved. You can +turn off these features using configuration options. + +**Active by default**: No + +**Debt**: 10min + +#### Configuration options: + +* ``matchTypeParameters`` (default: ``true``) + + if type parameters should be matched + +* ``matchDeclarationsOrder`` (default: ``true``) + + if the order of declarations should be preserved + +* ``allowParamOnConstructorProperties`` (default: ``false``) + + if we allow constructor parameters to be marked as @param instead of @property + +#### Noncompliant Code: + +```kotlin +/** +* @param someParam +* @property someProp +*/ +class MyClass(otherParam: String, val otherProp: String) + +/** +* @param T +* @param someParam +*/ +fun myFun(someParam: String) +``` + +#### Compliant Code: + +```kotlin +/** +* @param someParam +* @property someProp +*/ +class MyClass(someParam: String, val someProp: String) + +/** +* @param T +* @param S +* @param someParam +*/ +fun myFun(someParam: String) +``` + +### UndocumentedPublicClass + +This rule reports public classes, objects and interfaces which do not have the required documentation. +Enable this rule if the codebase should have documentation on every public class, interface and object. + +By default, this rule also searches for nested and inner classes and objects. This default behavior can be changed +with the configuration options of this rule. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``searchInNestedClass`` (default: ``true``) + + if nested classes should be searched + +* ``searchInInnerClass`` (default: ``true``) + + if inner classes should be searched + +* ``searchInInnerObject`` (default: ``true``) + + if inner objects should be searched + +* ``searchInInnerInterface`` (default: ``true``) + + if inner interfaces should be searched + +* ``searchInProtectedClass`` (default: ``false``) + + if protected classes should be searched + +### UndocumentedPublicFunction + +This rule will report any public function which does not have the required documentation. +If the codebase should have documentation on all public functions enable this rule to enforce this. +Overridden functions are excluded by this rule. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``searchProtectedFunction`` (default: ``false``) + + if protected functions should be searched + +### UndocumentedPublicProperty + +This rule will report any public property which does not have the required documentation. +This also includes public properties defined in a primary constructor. +If the codebase should have documentation on all public properties enable this rule to enforce this. +Overridden properties are excluded by this rule. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``searchProtectedProperty`` (default: ``false``) + + if protected functions should be searched diff --git a/website/versioned_docs/version-1.23.0/rules/complexity.md b/website/versioned_docs/version-1.23.0/rules/complexity.md new file mode 100644 index 000000000000..df85cba3c0c4 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/complexity.md @@ -0,0 +1,529 @@ +--- +title: Complexity Rule Set +sidebar: home_sidebar +keywords: [rules, complexity] +permalink: complexity.html +toc: true +folder: documentation +--- +This rule set contains rules that report complex code. + +### CognitiveComplexMethod + +Complex methods are hard to understand and read. It might not be obvious what side-effects a complex method has. +Prefer splitting up complex methods into smaller methods that are in turn easier to understand. +Smaller methods can also be named much clearer which leads to improved readability of the code. + +This rule measures and restricts the complexity of the method through the [Cognitive Complexity metric of Sonasource](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). +Which improves McCabe's Cyclomatic Complexity (see [CyclomaticComplexMethod](/docs/rules/complexity#cyclomaticcomplexmethod)) considering the programmer's mental model. + +Similar to cyclomatic complexity, it is a mathematical model that increases +1 complexity for flow control statements, +but increases additional complexity when the statements are deeply nested. + +The statements that increase the complexity or the nesting level are as follows. +- __Complexity Increments__ - `if`, `when`, `for`, `while`, `do while`, `catch`, `labeled break`, `labeled continue`, `labeled return`, `recursion call`, `&&`, `||` +- __Nesting Level Increments__ - `if`, `when`, `for`, `while`, `do while`, `catch`, `nested function` +- __Additional Complexity Increments by Nesting Level__ - `if`, `when`, `for`, `while`, `do while`, `catch` + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``15``) + + Cognitive Complexity number for a method. + +### ComplexCondition + +Complex conditions make it hard to understand which cases lead to the condition being true or false. To improve +readability and understanding of complex conditions consider extracting them into well-named functions or variables +and call those instead. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``4``) + + the number of conditions which will trigger the rule + +#### Noncompliant Code: + +```kotlin +val str = "foo" +val isFoo = if (str.startsWith("foo") && !str.endsWith("foo") && !str.endsWith("bar") && !str.endsWith("_")) { + // ... +} +``` + +#### Compliant Code: + +```kotlin +val str = "foo" +val isFoo = if (str.startsWith("foo") && hasCorrectEnding()) { + // ... +} + +fun hasCorrectEnding() = return !str.endsWith("foo") && !str.endsWith("bar") && !str.endsWith("_") +``` + +### ComplexInterface + +Complex interfaces which contain too many functions and/or properties indicate that this interface is handling too +many things at once. Interfaces should follow the single-responsibility principle to also encourage implementations +of this interface to not handle too many things at once. + +Large interfaces should be split into smaller interfaces which have a clear responsibility and are easier +to understand and implement. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``10``) + + the amount of definitions in an interface to trigger the rule + +* ``includeStaticDeclarations`` (default: ``false``) + + whether static declarations should be included + +* ``includePrivateDeclarations`` (default: ``false``) + + whether private declarations should be included + +* ``ignoreOverloaded`` (default: ``false``) + + ignore overloaded methods - only count once + +### CyclomaticComplexMethod + +Complex methods are hard to understand and read. It might not be obvious what side-effects a complex method has. +Prefer splitting up complex methods into smaller methods that are in turn easier to understand. +Smaller methods can also be named much clearer which leads to improved readability of the code. + +This rule uses McCabe's Cyclomatic Complexity (MCC) metric to measure the number of +linearly independent paths through a function's source code (https://www.ndepend.com/docs/code-metrics#CC). +The higher the number of independent paths, the more complex a method is. +Complex methods use too many of the following statements. +Each one of them adds one to the complexity count. + +- __Conditional statements__ - `if`, `else if`, `when` +- __Jump statements__ - `continue`, `break` +- __Loops__ - `for`, `while`, `do-while`, `forEach` +- __Operators__ `&&`, `||`, `?:` +- __Exceptions__ - `catch`, `use` +- __Scope Functions__ - `let`, `run`, `with`, `apply`, and `also` -> +[Reference](https://kotlinlang.org/docs/scope-functions.html) + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +**Aliases**: ComplexMethod + +#### Configuration options: + +* ``threshold`` (default: ``15``) + + McCabe's Cyclomatic Complexity (MCC) number for a method. + +* ``ignoreSingleWhenExpression`` (default: ``false``) + + Ignores a complex method if it only contains a single when expression. + +* ``ignoreSimpleWhenEntries`` (default: ``false``) + + Whether to ignore simple (braceless) when entries. + +* ``ignoreNestingFunctions`` (default: ``false``) + + Whether to ignore functions which are often used instead of an `if` or `for` statement. + +* ``nestingFunctions`` (default: ``['also', 'apply', 'forEach', 'isNotNull', 'ifNull', 'let', 'run', 'use', 'with']``) + + Comma separated list of function names which add complexity. + +### LabeledExpression + +This rule reports labeled expressions. Expressions with labels generally increase complexity and worsen the +maintainability of the code. Refactor the violating code to not use labels instead. +Labeled expressions referencing an outer class with a label from an inner class are allowed, because there is no +way to get the instance of an outer class from an inner class in Kotlin. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``ignoredLabels`` (default: ``[]``) + + allows to provide a list of label names which should be ignored by this rule + +#### Noncompliant Code: + +```kotlin +val range = listOf("foo", "bar") +loop@ for (r in range) { + if (r == "bar") break@loop + println(r) +} + +class Outer { + inner class Inner { + fun f() { + val i = this@Inner // referencing itself, use `this instead + } + } +} +``` + +#### Compliant Code: + +```kotlin +val range = listOf("foo", "bar") +for (r in range) { + if (r == "bar") break + println(r) +} + +class Outer { + inner class Inner { + fun f() { + val outer = this@Outer + } + fun Int.extend() { + val inner = this@Inner // this would reference Int and not Inner + } + } +} +``` + +### LargeClass + +This rule reports large classes. Classes should generally have one responsibility. Large classes can indicate that +the class does instead handle multiple responsibilities. Instead of doing many things at once prefer to +split up large classes into smaller classes. These smaller classes are then easier to understand and handle less +things. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``600``) + + the size of class required to trigger the rule + +### LongMethod + +Methods should have one responsibility. Long methods can indicate that a method handles too many cases at once. +Prefer smaller methods with clear names that describe their functionality clearly. + +Extract parts of the functionality of long methods into separate, smaller methods. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``60``) + + number of lines in a method to trigger the rule + +### LongParameterList + +Reports functions and constructors which have more parameters than a certain threshold. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ~~``threshold``~~ (default: ``6``) + + **Deprecated**: Use `functionThreshold` and `constructorThreshold` instead + + number of parameters required to trigger the rule + +* ``functionThreshold`` (default: ``6``) + + number of function parameters required to trigger the rule + +* ``constructorThreshold`` (default: ``7``) + + number of constructor parameters required to trigger the rule + +* ``ignoreDefaultParameters`` (default: ``false``) + + ignore parameters that have a default value + +* ``ignoreDataClasses`` (default: ``true``) + + ignore long constructor parameters list for data classes + +* ``ignoreAnnotatedParameter`` (default: ``[]``) + + ignore the annotated parameters for the count (e.g. `fun foo(@Value bar: Int)` would not be counted + +### MethodOverloading + +This rule reports methods which are overloaded often. +Method overloading tightly couples these methods together which might make the code harder to understand. + +Refactor these methods and try to use optional parameters instead to prevent some of the overloading. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``6``) + + number of overloads which will trigger the rule + +### NamedArguments + +Reports function invocations which have more arguments than a certain threshold and are all not named. Calls with +too many arguments are more difficult to understand so a named arguments help. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``threshold`` (default: ``3``) + + number of arguments that triggers this inspection + +* ``ignoreArgumentsMatchingNames`` (default: ``false``) + + ignores when argument values are the same as the parameter names + +#### Noncompliant Code: + +```kotlin +fun sum(a: Int, b: Int, c: Int, d: Int) { +} +sum(1, 2, 3, 4) +``` + +#### Compliant Code: + +```kotlin +fun sum(a: Int, b: Int, c: Int, d: Int) { +} +sum(a = 1, b = 2, c = 3, d = 4) +``` + +### NestedBlockDepth + +This rule reports excessive nesting depth in functions. Excessively nested code becomes harder to read and increases +its hidden complexity. It might become harder to understand edge-cases of the function. + +Prefer extracting the nested code into well-named functions to make it easier to understand. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``threshold`` (default: ``4``) + + the nested depth required to trigger rule + +### NestedScopeFunctions + +Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease +your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: +it's easy to get confused about the current context object and the value of this or it. + +[Reference](https://kotlinlang.org/docs/scope-functions.html) + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``threshold`` (default: ``1``) + + Number of nested scope functions allowed. + +* ``functions`` (default: ``['kotlin.apply', 'kotlin.run', 'kotlin.with', 'kotlin.let', 'kotlin.also']``) + + Set of scope function names which add complexity. Function names have to be fully qualified. For example 'kotlin.apply'. + +#### Noncompliant Code: + +```kotlin +// Try to figure out, what changed, without knowing the details +first.apply { + second.apply { + b = a + c = b + } +} +``` + +#### Compliant Code: + +```kotlin +// 'a' is a property of current class +// 'b' is a property of class 'first' +// 'c' is a property of class 'second' +first.b = this.a +second.c = first.b +``` + +### ReplaceSafeCallChainWithRun + +Chains of safe calls on non-nullable types are redundant and can be removed by enclosing the redundant safe calls in +a `run {}` block. This improves code coverage and reduces cyclomatic complexity as redundant null checks are removed. + +This rule only checks from the end of a chain and works backwards, so it won't recommend inserting run blocks in the +middle of a safe call chain as that is likely to make the code more difficult to understand. + +The rule will check for every opportunity to replace a safe call when it sits at the end of a chain, even if there's +only one, as that will still improve code coverage and reduce cyclomatic complexity. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +val x = System.getenv() + ?.getValue("HOME") + ?.toLowerCase() + ?.split("/") ?: emptyList() +``` + +#### Compliant Code: + +```kotlin +val x = getenv()?.run { + getValue("HOME") + .toLowerCase() + .split("/") +} ?: emptyList() +``` + +### StringLiteralDuplication + +This rule detects and reports duplicated String literals. Repeatedly typing out the same String literal across the +codebase makes it harder to change and maintain. + +Instead, prefer extracting the String literal into a property or constant. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``threshold`` (default: ``3``) + + amount of duplications to trigger rule + +* ``ignoreAnnotation`` (default: ``true``) + + if values in Annotations should be ignored + +* ``excludeStringsWithLessThan5Characters`` (default: ``true``) + + if short strings should be excluded + +* ``ignoreStringsRegex`` (default: ``'$^'``) + + RegEx of Strings that should be ignored + +#### Noncompliant Code: + +```kotlin +class Foo { + + val s1 = "lorem" + fun bar(s: String = "lorem") { + s1.equals("lorem") + } +} +``` + +#### Compliant Code: + +```kotlin +class Foo { + val lorem = "lorem" + val s1 = lorem + fun bar(s: String = lorem) { + s1.equals(lorem) + } +} +``` + +### TooManyFunctions + +This rule reports files, classes, interfaces, objects and enums which contain too many functions. +Each element can be configured with different thresholds. + +Too many functions indicate a violation of the single responsibility principle. Prefer extracting functionality +which clearly belongs together in separate parts of the code. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``thresholdInFiles`` (default: ``11``) + + threshold in files + +* ``thresholdInClasses`` (default: ``11``) + + threshold in classes + +* ``thresholdInInterfaces`` (default: ``11``) + + threshold in interfaces + +* ``thresholdInObjects`` (default: ``11``) + + threshold in objects + +* ``thresholdInEnums`` (default: ``11``) + + threshold in enums + +* ``ignoreDeprecated`` (default: ``false``) + + ignore deprecated functions + +* ``ignorePrivate`` (default: ``false``) + + ignore private functions + +* ``ignoreOverridden`` (default: ``false``) + + ignore overridden functions diff --git a/website/versioned_docs/version-1.23.0/rules/coroutines.md b/website/versioned_docs/version-1.23.0/rules/coroutines.md new file mode 100644 index 000000000000..995eef148e75 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/coroutines.md @@ -0,0 +1,310 @@ +--- +title: Coroutines Rule Set +sidebar: home_sidebar +keywords: [rules, coroutines] +permalink: coroutines.html +toc: true +folder: documentation +--- +The coroutines rule set analyzes code for potential coroutines problems. + +### GlobalCoroutineUsage + +Report usages of `GlobalScope.launch` and `GlobalScope.async`. It is highly discouraged by the Kotlin documentation: + +> Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are +> not cancelled prematurely. + +> Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance +> of GlobalScope is highly discouraged. + +See https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/ + +**Active by default**: No + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +fun foo() { + GlobalScope.launch { delay(1_000L) } +} +``` + +#### Compliant Code: + +```kotlin +val scope = CoroutineScope(Dispatchers.Default) + +fun foo() { + scope.launch { delay(1_000L) } +} + +fun onDestroy() { + scope.cancel() +} +``` + +### InjectDispatcher + +Always use dependency injection to inject dispatchers for easier testing. +This rule is based on the recommendation +https://developer.android.com/kotlin/coroutines/coroutines-best-practices#inject-dispatchers + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``dispatcherNames`` (default: ``['IO', 'Default', 'Unconfined']``) + + The names of dispatchers to detect by this rule + +#### Noncompliant Code: + +```kotlin +fun myFunc() { +coroutineScope(Dispatchers.IO) +} +``` + +#### Compliant Code: + +```kotlin +fun myFunc(dispatcher: CoroutineDispatcher = Dispatchers.IO) { +coroutineScope(dispatcher) +} + +class MyRepository(dispatchers: CoroutineDispatcher = Dispatchers.IO) +``` + +### RedundantSuspendModifier + +`suspend` modifier should only be used where needed, otherwise the function can only be used from other suspending +functions. This needlessly restricts use of the function and should be avoided by removing the `suspend` modifier +where it's not needed. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +suspend fun normalFunction() { + println("string") +} +``` + +#### Compliant Code: + +```kotlin +fun normalFunction() { + println("string") +} +``` + +### SleepInsteadOfDelay + +Report usages of `Thread.sleep` in suspending functions and coroutine blocks. A thread can +contain multiple coroutines at one time due to coroutines' lightweight nature, so if one +coroutine invokes `Thread.sleep`, it could potentially halt the execution of unrelated coroutines +and cause unpredictable behavior. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +suspend fun foo() { + Thread.sleep(1_000L) +} +``` + +#### Compliant Code: + +```kotlin +suspend fun foo() { + delay(1_000L) +} +``` + +### SuspendFunSwallowedCancellation + +`suspend` functions should not be called inside `runCatching`'s lambda block, because `runCatching` catches all the +`Exception`s. For Coroutines to work in all cases, developers should make sure to propagate `CancellationException` +exceptions. This means `CancellationException` should never be: +* caught and swallowed (even if logged) +* caught and propagated to external systems +* caught and shown to the user + +they must always be rethrown in the same context. + +Using `runCatching` increases this risk of mis-handling cancellation. If you catch and don't rethrow all the +`CancellationException`, your coroutines are not cancelled even if you cancel their `CoroutineScope`. + +This can very easily lead to: +* unexpected crashes +* extremely hard to diagnose bugs +* memory leaks +* performance issues +* battery drain + +See reference, [Kotlin doc](https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative). + +If your project wants to use `runCatching` and `Result` objects, it is recommended to write a `coRunCatching` +utility function which immediately re-throws `CancellationException`; and forbid `runCatching` and `suspend` +combinations by activating this rule. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +@Throws(IllegalStateException::class) +suspend fun bar(delay: Long) { + check(delay <= 1_000L) + delay(delay) +} + +suspend fun foo() { + runCatching { + bar(1_000L) + } +} +``` + +#### Compliant Code: + +```kotlin +@Throws(IllegalStateException::class) +suspend fun bar(delay: Long) { + check(delay <= 1_000L) + delay(delay) +} + +suspend fun foo() { + try { + bar(1_000L) + } catch (e: IllegalStateException) { + // handle error + } +} + +// Alternate +@Throws(IllegalStateException::class) +suspend fun foo() { + bar(1_000L) +} +``` + +### SuspendFunWithCoroutineScopeReceiver + +Suspend functions that use `CoroutineScope` as receiver should not be marked as `suspend`. +A `CoroutineScope` provides structured concurrency via its `coroutineContext`. A `suspend` +function also has its own `coroutineContext`, which is now ambiguous and mixed with the +receiver`s. + +See https://kotlinlang.org/docs/coroutines-basics.html#scope-builder-and-concurrency + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +**Aliases**: SuspendFunctionOnCoroutineScope + +#### Noncompliant Code: + +```kotlin +suspend fun CoroutineScope.foo() { + launch { + delay(1.seconds) + } +} +``` + +#### Compliant Code: + +```kotlin +fun CoroutineScope.foo() { + launch { + delay(1.seconds) + } +} + +// Alternative +suspend fun foo() = coroutineScope { + launch { + delay(1.seconds) + } +} +``` + +### SuspendFunWithFlowReturnType + +Functions that return `Flow` from `kotlinx.coroutines.flow` should not be marked as `suspend`. +`Flows` are intended to be cold observable streams. The act of simply invoking a function that +returns a `Flow`, should not have any side effects. Only once collection begins against the +returned `Flow`, should work actually be done. + +See https://kotlinlang.org/docs/flow.html#flows-are-cold + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +suspend fun observeSignals(): Flow { + val pollingInterval = getPollingInterval() // Done outside of the flow builder block. + return flow { + while (true) { + delay(pollingInterval) + emit(Unit) + } + } +} + +private suspend fun getPollingInterval(): Long { + // Return the polling interval from some repository + // in a suspending manner. +} +``` + +#### Compliant Code: + +```kotlin +fun observeSignals(): Flow { + return flow { + val pollingInterval = getPollingInterval() // Moved into the flow builder block. + while (true) { + delay(pollingInterval) + emit(Unit) + } + } +} + +private suspend fun getPollingInterval(): Long { + // Return the polling interval from some repository + // in a suspending manner. +} +``` diff --git a/website/versioned_docs/version-1.23.0/rules/empty-blocks.md b/website/versioned_docs/version-1.23.0/rules/empty-blocks.md new file mode 100644 index 000000000000..66caf1ae453a --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/empty-blocks.md @@ -0,0 +1,155 @@ +--- +title: Empty-blocks Rule Set +sidebar: home_sidebar +keywords: [rules, empty-blocks] +permalink: empty-blocks.html +toc: true +folder: documentation +--- +The empty-blocks ruleset contains rules that will report empty blocks of code +which should be avoided. + +### EmptyCatchBlock + +Reports empty `catch` blocks. Empty catch blocks indicate that an exception is ignored and not handled. +In case exceptions are ignored intentionally, this should be made explicit +by using the specified names in the `allowedExceptionNameRegex`. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``allowedExceptionNameRegex`` (default: ``'_|(ignore|expected).*'``) + + ignores exception types which match this regex + +### EmptyClassBlock + +Reports empty classes. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyDefaultConstructor + +Reports empty default constructors. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyDoWhileBlock + +Reports empty `do`/`while` loops. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyElseBlock + +Reports empty `else` blocks. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyFinallyBlock + +Reports empty `finally` blocks. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyForBlock + +Reports empty `for` loops. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyFunctionBlock + +Reports empty functions. Empty blocks of code serve no purpose and should be removed. +This rule will not report functions with the override modifier that have a comment as their only body contents +(e.g., a `// no-op` comment in an unused listener function). + +Set the `ignoreOverridden` parameter to `true` to exclude all functions which are overriding other +functions from the superclass or from an interface (i.e., functions declared with the override modifier). + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ~~``ignoreOverriddenFunctions``~~ (default: ``false``) + + **Deprecated**: Use `ignoreOverridden` instead + + Excludes all the overridden functions + +* ``ignoreOverridden`` (default: ``false``) + + Excludes all the overridden functions + +### EmptyIfBlock + +Reports empty `if` blocks. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyInitBlock + +Reports empty `init` expressions. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyKtFile + +Reports empty Kotlin (.kt) files. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptySecondaryConstructor + +Reports empty secondary constructors. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyTryBlock + +Reports empty `try` blocks. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.6.0 + +**Debt**: 5min + +### EmptyWhenBlock + +Reports empty `when` expressions. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### EmptyWhileBlock + +Reports empty `while` expressions. Empty blocks of code serve no purpose and should be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min diff --git a/website/versioned_docs/version-1.23.0/rules/exceptions.md b/website/versioned_docs/version-1.23.0/rules/exceptions.md new file mode 100644 index 000000000000..6b8745d09521 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/exceptions.md @@ -0,0 +1,507 @@ +--- +title: Exceptions Rule Set +sidebar: home_sidebar +keywords: [rules, exceptions] +permalink: exceptions.html +toc: true +folder: documentation +--- +Rules in this rule set report issues related to how code throws and handles Exceptions. + +### ExceptionRaisedInUnexpectedLocation + +This rule reports functions which should never throw an exception. If a function exists that does throw +an exception it will be reported. By default, this rule checks `toString`, `hashCode`, `equals` and +`finalize`. This rule is configurable via the `methodNames` configuration to change the list of functions which +should not throw any exceptions. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 20min + +#### Configuration options: + +* ``methodNames`` (default: ``['equals', 'finalize', 'hashCode', 'toString']``) + + methods which should not throw exceptions + +#### Noncompliant Code: + +```kotlin +class Foo { + + override fun toString(): String { + throw IllegalStateException() // exception should not be thrown here + } +} +``` + +### InstanceOfCheckForException + +This rule reports `catch` blocks which check for the type of exception via `is` checks or casts. +Instead of catching generic exception types and then checking for specific exception types the code should +use multiple catch blocks. These catch blocks should then catch the specific exceptions. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... do some I/O + } catch(e: IOException) { + if (e is MyException || (e as MyException) != null) { } + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { + try { + // ... do some I/O + } catch(e: MyException) { + } catch(e: IOException) { + } +} +``` + +### NotImplementedDeclaration + +This rule reports all exceptions of the type `NotImplementedError` that are thrown. It also reports all `TODO(..)` +functions. +These indicate that functionality is still under development and will not work properly. Both of these should only +serve as temporary declarations and should not be put into production environments. + +**Active by default**: No + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun foo() { + throw NotImplementedError() +} + +fun todo() { + TODO("") +} +``` + +### ObjectExtendsThrowable + +This rule reports all `objects` including `companion objects` that extend any type of +`Throwable`. Throwable instances are not intended for reuse as they are stateful and contain +mutable information about a specific exception or error. Hence, global singleton `Throwables` +should be avoided. + +See https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview +See https://kotlinlang.org/docs/object-declarations.html#companion-objects + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +object InvalidCredentialsException : Throwable() + +object BanException : Exception() + +object AuthException : RuntimeException() +``` + +#### Compliant Code: + +```kotlin +class InvalidCredentialsException : Throwable() + +class BanException : Exception() + +class AuthException : RuntimeException() +``` + +### PrintStackTrace + +This rule reports code that tries to print the stacktrace of an exception. Instead of simply printing a stacktrace +a better logging solution should be used. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun foo() { + Thread.dumpStack() +} + +fun bar() { + try { + // ... + } catch (e: IOException) { + e.printStackTrace() + } +} +``` + +#### Compliant Code: + +```kotlin +val LOGGER = Logger.getLogger() + +fun bar() { + try { + // ... + } catch (e: IOException) { + LOGGER.info(e) + } +} +``` + +### RethrowCaughtException + +This rule reports all exceptions that are caught and then later re-thrown without modification. +It ignores cases: +1. When caught exceptions that are rethrown if there is work done before that. +2. When there are more than one catch in try block and at least one of them has some work. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch (e: IOException) { + throw e + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch (e: IOException) { + throw MyException(e) + } + try { + // ... + } catch (e: IOException) { + print(e) + throw e + } + try { + // ... + } catch (e: IOException) { + print(e.message) + throw e + } + + try { + // ... + } catch (e: IOException) { + throw e + } catch (e: Exception) { + print(e.message) + } +} +``` + +### ReturnFromFinally + +Reports all `return` statements in `finally` blocks. +Using `return` statements in `finally` blocks can discard and hide exceptions that are thrown in the `try` block. +Furthermore, this rule reports values from `finally` blocks, if the corresponding `try` is used as an expression. + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 20min + +#### Configuration options: + +* ``ignoreLabeled`` (default: ``false``) + + ignores labeled return statements + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + throw MyException() + } finally { + return // prevents MyException from being propagated + } +} + +val a: String = try { "s" } catch (e: Exception) { "e" } finally { "f" } +``` + +### SwallowedException + +Exceptions should not be swallowed. This rule reports all instances where exceptions are `caught` and not correctly +passed (e.g. as a cause) into a newly thrown exception. + +The exception types configured in `ignoredExceptionTypes` indicate nonexceptional outcomes. +These by default configured exception types are part of Java. +Therefore, Kotlin developers have to handle them by using the catch clause. +For that reason, this rule ignores that these configured exception types are caught. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 20min + +#### Configuration options: + +* ``ignoredExceptionTypes`` (default: ``['InterruptedException', 'MalformedURLException', 'NumberFormatException', 'ParseException']``) + + exception types which should be ignored (both in the catch clause and body) + +* ``allowedExceptionNameRegex`` (default: ``'_|(ignore|expected).*'``) + + ignores too generic exception types which match this regex + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch(e: IOException) { + throw MyException(e.message) // e is swallowed + } + try { + // ... + } catch(e: IOException) { + throw MyException() // e is swallowed + } + try { + // ... + } catch(e: IOException) { + bar() // exception is unused + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch(e: IOException) { + throw MyException(e) + } + try { + // ... + } catch(e: IOException) { + println(e) // logging is ok here + } +} +``` + +### ThrowingExceptionFromFinally + +This rule reports all cases where exceptions are thrown from a `finally` block. Throwing exceptions from a `finally` +block should be avoided as it can lead to confusion and discarded exceptions. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... + } finally { + throw IOException() + } +} +``` + +### ThrowingExceptionInMain + +This rule reports all exceptions that are thrown in a `main` method. +An exception should only be thrown if it can be handled by a "higher" function. + +**Active by default**: No + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun main(args: Array) { + // ... + throw IOException() // exception should not be thrown here +} +``` + +### ThrowingExceptionsWithoutMessageOrCause + +This rule reports all exceptions which are thrown without arguments or further description. +Exceptions should always call one of the constructor overloads to provide a message or a cause. +Exceptions should be meaningful and contain as much detail about the error case as possible. This will help to track +down an underlying issue in a better way. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Configuration options: + +* ``exceptions`` (default: ``['ArrayIndexOutOfBoundsException', 'Exception', 'IllegalArgumentException', 'IllegalMonitorStateException', 'IllegalStateException', 'IndexOutOfBoundsException', 'NullPointerException', 'RuntimeException', 'Throwable']``) + + exceptions which should not be thrown without message or cause + +#### Noncompliant Code: + +```kotlin +fun foo(bar: Int) { + if (bar < 1) { + throw IllegalArgumentException() + } + // ... +} +``` + +#### Compliant Code: + +```kotlin +fun foo(bar: Int) { + if (bar < 1) { + throw IllegalArgumentException("bar must be greater than zero") + } + // ... +} +``` + +### ThrowingNewInstanceOfSameException + +Exceptions should not be wrapped inside the same exception type and then rethrown. Prefer wrapping exceptions in more +meaningful exception types. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch (e: IllegalStateException) { + throw IllegalStateException(e) // rethrows the same exception + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { + try { + // ... + } catch (e: IllegalStateException) { + throw MyException(e) + } +} +``` + +### TooGenericExceptionCaught + +This rule reports `catch` blocks for exceptions that have a type that is too generic. +It should be preferred to catch specific exceptions to the case that is currently handled. If the scope of the caught +exception is too broad it can lead to unintended exceptions being caught. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``exceptionNames`` (default: ``['ArrayIndexOutOfBoundsException', 'Error', 'Exception', 'IllegalMonitorStateException', 'IndexOutOfBoundsException', 'NullPointerException', 'RuntimeException', 'Throwable']``) + + exceptions which are too generic and should not be caught + +* ``allowedExceptionNameRegex`` (default: ``'_|(ignore|expected).*'``) + + ignores too generic exception types which match this regex + +#### Noncompliant Code: + +```kotlin +fun foo() { + try { + // ... do some I/O + } catch(e: Exception) { } // too generic exception caught here +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { + try { + // ... do some I/O + } catch(e: IOException) { } +} +``` + +### TooGenericExceptionThrown + +This rule reports thrown exceptions that have a type that is too generic. It should be preferred to throw specific +exceptions to the case that has currently occurred. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Configuration options: + +* ``exceptionNames`` (default: ``['Error', 'Exception', 'RuntimeException', 'Throwable']``) + + exceptions which are too generic and should not be thrown + +#### Noncompliant Code: + +```kotlin +fun foo(bar: Int) { + if (bar < 1) { + throw Exception() // too generic exception thrown here + } + // ... +} +``` + +#### Compliant Code: + +```kotlin +fun foo(bar: Int) { + if (bar < 1) { + throw IllegalArgumentException("bar must be greater than zero") + } + // ... +} +``` diff --git a/website/versioned_docs/version-1.23.0/rules/formatting.md b/website/versioned_docs/version-1.23.0/rules/formatting.md new file mode 100644 index 000000000000..26cf7d5d141e --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/formatting.md @@ -0,0 +1,758 @@ +--- +title: Formatting Rule Set +sidebar: home_sidebar +keywords: [rules, formatting] +permalink: formatting.html +toc: true +folder: documentation +--- +This rule set provides wrappers for rules implemented by ktlint - https://ktlint.github.io/. + +**Note: The `formatting` rule set is not included in the detekt-cli or Gradle plugin.** + +To enable this rule set, add `detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$version"` +to your gradle `dependencies` or reference the `detekt-formatting`-jar with the `--plugins` option +in the command line interface. + +Note: Issues reported by this rule set can only be suppressed on file level (`@file:Suppress("detekt.rule")`). + +### AnnotationOnSeparateLine + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#annotation-formatting) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### AnnotationSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#annotation-spacing) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### ArgumentListWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#argument-list-wrapping) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +### BlockCommentInitialStarAlignment + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#block-comment-initial-star-alignment) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +### ChainWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#chain-wrapping) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### ClassName + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#classobject-naming) for +documentation. + +**Active by default**: No + +### CommentSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#comment-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### CommentWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#comment-wrapping) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### ContextReceiverMapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#content-receiver-wrapping) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +* ``indentSize`` (default: ``4``) + + indentation size + +### DiscouragedCommentLocation + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#discouraged-comment-location) for +documentation. + +**Active by default**: No + +### EnumEntryNameCase + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#enum-entry) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### EnumWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#enum-wrapping) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### Filename + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#file-name) for documentation. + +This rules overlaps with [naming>MatchingDeclarationName](https://detekt.dev/naming.html#matchingdeclarationname) +from the standard rules, make sure to enable just one. + +**Active by default**: Yes - Since v1.0.0 + +### FinalNewline + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#final-newline) for documentation. + +This rules overlaps with [style>NewLineAtEndOfFile](https://detekt.dev/style.html#newlineatendoffile) +from the standard rules, make sure to enable just one. The pro of this rule is that it can auto-correct the issue. + +**Active by default**: Yes - Since v1.0.0 + +#### Configuration options: + +* ``insertFinalNewLine`` (default: ``true``) + + report absence or presence of a newline + +### FunKeywordSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#fun-keyword-spacing) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +### FunctionName + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#function-naming) for +documentation. + +**Active by default**: No + +### FunctionReturnTypeSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#function-return-type-spacing) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +#### Configuration options: + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +### FunctionSignature + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#function-signature) for +documentation. + +**Active by default**: No + +#### Configuration options: + +* ``forceMultilineWhenParameterCountGreaterOrEqualThan`` (default: ``2147483647``) + + parameter count means multiline threshold + +* ``functionBodyExpressionWrapping`` (default: ``'default'``) + + indentation size + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +* ``indentSize`` (default: ``4``) + + indentation size + +### FunctionStartOfBodySpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#function-start-of-body-spacing) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +### FunctionTypeReferenceSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#function-type-reference-spacing) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +### IfElseBracing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#if-else-bracing) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### IfElseWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#if-else-wrapping) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### ImportOrdering + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#import-ordering) for documentation. + +For defining import layout patterns see the [KtLint Source Code](https://github.com/pinterest/ktlint/blob/a6ca5b2edf95cc70a138a9470cfb6c4fd5d9d3ce/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ImportOrderingRule.kt) + +**Active by default**: Yes - Since v1.19.0 + +#### Configuration options: + +* ``layout`` (default: ``'*,java.**,javax.**,kotlin.**,^'``) (android default: ``'*'``) + + the import ordering layout + +### Indentation + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#indentation) for documentation. + +**Active by default**: Yes - Since v1.19.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ~~``continuationIndentSize``~~ (default: ``4``) + + **Deprecated**: `continuationIndentSize` is ignored by KtLint and will have no effect + + continuation indentation size + +### KdocWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#kdoc-wrapping) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### MaximumLineLength + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#max-line-length) for documentation. + +This rules overlaps with [style>MaxLineLength](https://detekt.dev/style.html#maxlinelength) +from the standard rules, make sure to enable just one or keep them aligned. The pro of this rule is that it can +auto-correct the issue. + +**Active by default**: Yes - Since v1.0.0 + +#### Configuration options: + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +* ``ignoreBackTickedIdentifier`` (default: ``false``) + + ignore back ticked identifier + +### ModifierListSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#modifier-list-spacing) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +### ModifierOrdering + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#modifier-order) for documentation. + +This rules overlaps with [style>ModifierOrder](https://detekt.dev/style.html#modifierorder) +from the standard rules, make sure to enable just one. The pro of this rule is that it can auto-correct the issue. + +**Active by default**: Yes - Since v1.0.0 + +### MultiLineIfElse + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#multiline-if-else) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### MultilineExpressionWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#multiline-expression-wrapping) for +documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### NoBlankLineBeforeRbrace + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-blank-lines-before) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoBlankLineInList + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#no-blank-lines-in-list) for documentation. + +**Active by default**: No + +### NoBlankLinesInChainedMethodCalls + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-blank-lines-in-chained-method-calls) for +documentation. + +**Active by default**: Yes - Since v1.22.0 + +### NoConsecutiveBlankLines + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-consecutive-blank-lines) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoConsecutiveComments + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#disallow-consecutive-comments) for documentation. + +**Active by default**: No + +### NoEmptyClassBody + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-empty-class-bodies) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoEmptyFirstLineInClassBody + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#disallow-empty-lines-at-start-of-class-body) +for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### NoEmptyFirstLineInMethodBlock + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-leading-empty-lines-in-method-blocks) for +documentation. + +**Active by default**: Yes - Since v1.22.0 + +### NoLineBreakAfterElse + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-line-break-after-else) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoLineBreakBeforeAssignment + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-line-break-before-assignment) for +documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoMultipleSpaces + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-multi-spaces) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoSemicolons + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-semicolons) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoSingleLineBlockComment + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#no-single-line-block-comments) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### NoTrailingSpaces + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-trailing-whitespaces) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoUnitReturn + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-unit-as-return-type) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoUnusedImports + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-unused-imports) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### NoWildcardImports + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-wildcard-imports) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +#### Configuration options: + +* ``packagesToUseImportOnDemandProperty`` (default: ``'java.util.*,kotlinx.android.synthetic.**'``) + + Defines allowed wildcard imports + +### NullableTypeSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#nullable-type-spacing) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +### PackageName + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#no-underscores-in-package-names) for +documentation. + +**Active by default**: Yes - Since v1.22.0 + +### ParameterListSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#parameter-list-spacing) for +documentation. + +**Active by default**: No + +### ParameterListWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#parameter-list-wrapping) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +#### Configuration options: + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +* ``indentSize`` (default: ``4``) + + indentation size + +### ParameterWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#parameter-wrapping) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +### PropertyName + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#property-naming) for +documentation. + +**Active by default**: No + +### PropertyWrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#property-wrapping) for documentation. + +**Active by default**: Yes - Since v1.23.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length + +### SpacingAroundAngleBrackets + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#angle-bracket-spacing) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### SpacingAroundColon + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#colon-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundComma + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#comma-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundCurly + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#curly-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundDot + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#dot-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundDoubleColon + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#double-colon-spacing) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### SpacingAroundKeyword + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#keyword-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundOperators + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#operator-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundParens + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#parenthesis-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundRangeOperator + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#range-spacing) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### SpacingAroundUnaryOperator + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#unary-operator-spacing) for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### SpacingBetweenDeclarationsWithAnnotations + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#blank-line-between-declarations-with-annotations) +for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### SpacingBetweenDeclarationsWithComments + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#blank-line-between-declaration-with-comments) +for documentation. + +**Active by default**: Yes - Since v1.22.0 + +### SpacingBetweenFunctionNameAndOpeningParenthesis + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#spacing-between-function-name-and-opening-parenthesis) for +documentation. + +**Active by default**: Yes - Since v1.23.0 + +### StringTemplate + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#string-template) for documentation. + +**Active by default**: Yes - Since v1.0.0 + +### StringTemplateIndent + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#string-template-indent) for documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### TrailingCommaOnCallSite + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/) for documentation. + +The default config comes from ktlint and follows these conventions: +- [Kotlin coding convention](https://kotlinlang.org/docs/coding-conventions.html#trailing-commas) recommends +trailing comma encourage the use of trailing commas at the declaration site and +leaves it at your discretion for the call site. +- [Android Kotlin style guide](https://developer.android.com/kotlin/style-guide) does not include +trailing comma usage yet. + +**Active by default**: No + +#### Configuration options: + +* ``useTrailingCommaOnCallSite`` (default: ``true``) (android default: ``false``) + + Defines whether trailing commas are required (true) or forbidden (false) at call sites + +### TrailingCommaOnDeclarationSite + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/) for documentation. + +The default config comes from ktlint and follows these conventions: +- [Kotlin coding convention](https://kotlinlang.org/docs/coding-conventions.html#trailing-commas) recommends +trailing comma encourage the use of trailing commas at the declaration site and +leaves it at your discretion for the call site. +- [Android Kotlin style guide](https://developer.android.com/kotlin/style-guide) does not include +trailing comma usage yet. + +**Active by default**: No + +#### Configuration options: + +* ``useTrailingCommaOnDeclarationSite`` (default: ``true``) (android default: ``false``) + + Defines whether trailing commas are required (true) or forbidden (false) at declaration sites + +### TryCatchFinallySpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#try-catch-finally-spacing) for +documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### TypeArgumentListSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#type-argument-list-spacing) for +documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### TypeParameterListSpacing + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#type-parameter-list-spacing) for +documentation. + +**Active by default**: No + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +### UnnecessaryParenthesesBeforeTrailingLambda + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/experimental/#unnecessary-parenthesis-before-trailing-lambda) +for documentation. + +**Active by default**: Yes - Since v1.23.0 + +### Wrapping + +See [ktlint docs](https://pinterest.github.io/ktlint/rules/standard/#wrapping) for documentation. + +**Active by default**: Yes - Since v1.20.0 + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ``maxLineLength`` (default: ``120``) (android default: ``100``) + + maximum line length diff --git a/website/versioned_docs/version-1.23.0/rules/libraries.md b/website/versioned_docs/version-1.23.0/rules/libraries.md new file mode 100644 index 000000000000..2d922b2698f6 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/libraries.md @@ -0,0 +1,109 @@ +--- +title: Libraries Rule Set +sidebar: home_sidebar +keywords: [rules, libraries] +permalink: libraries.html +toc: true +folder: documentation +--- +Rules in this rule set report issues related to libraries API exposure. + +**Note: The `libraries` rule set is not included in the detekt-cli or Gradle plugin.** + +To enable this rule set, add `detektPlugins "io.gitlab.arturbosch.detekt:detekt-rules-libraries:$version"` +to your Gradle `dependencies` or reference the `detekt-rules-libraries`-jar with the `--plugins` option +in the command line interface. + +### ForbiddenPublicDataClass + +Data classes are bad for binary compatibility in public APIs. Avoid using them. + +This rule is aimed at library maintainers. If you are developing a final application you can ignore this issue. + +More info: [Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/) + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 20min + +#### Configuration options: + +* ``ignorePackages`` (default: ``['*.internal', '*.internal.*']``) + + ignores classes in the specified packages. + +#### Noncompliant Code: + +```kotlin +data class C(val a: String) // violation: public data class +``` + +#### Compliant Code: + +```kotlin +internal data class C(val a: String) +``` + +### LibraryCodeMustSpecifyReturnType + +Functions/properties exposed as public APIs of a library should have an explicit return type. +Inferred return type can easily be changed by mistake which may lead to breaking changes. + +See also: [Kotlin 1.4 Explicit API](https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors) + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``allowOmitUnit`` (default: ``false``) + + if functions with `Unit` return type should be allowed without return type declaration + +#### Noncompliant Code: + +```kotlin +// code from a library +val strs = listOf("foo, bar") +fun bar() = 5 +class Parser { + fun parse() = ... +} +``` + +#### Compliant Code: + +```kotlin +// code from a library +val strs: List = listOf("foo, bar") +fun bar(): Int = 5 + +class Parser { + fun parse(): ParsingResult = ... +} +``` + +### LibraryEntitiesShouldNotBePublic + +Library typealias and classes should be internal or private. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +// code from a library +class A +``` + +#### Compliant Code: + +```kotlin +// code from a library +internal class A +``` diff --git a/website/versioned_docs/version-1.23.0/rules/naming.md b/website/versioned_docs/version-1.23.0/rules/naming.md new file mode 100644 index 000000000000..25364335c9f4 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/naming.md @@ -0,0 +1,507 @@ +--- +title: Naming Rule Set +sidebar: home_sidebar +keywords: [rules, naming] +permalink: naming.html +toc: true +folder: documentation +--- +The naming ruleset contains rules which assert the naming of different parts of the codebase. + +### BooleanPropertyNaming + +Reports when a boolean property doesn't match a pattern + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``allowedPattern`` (default: ``'^(is|has|are)'``) + + naming pattern + +* ~~``ignoreOverridden``~~ (default: ``true``) + + **Deprecated**: This configuration is ignored and will be removed in the future + + ignores properties that have the override modifier + +#### Noncompliant Code: + +```kotlin +val progressBar: Boolean = true +``` + +#### Compliant Code: + +```kotlin +val hasProgressBar: Boolean = true +``` + +### ClassNaming + +Reports class or object names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +**Aliases**: ClassName + +#### Configuration options: + +* ``classPattern`` (default: ``'[A-Z][a-zA-Z0-9]*'``) + + naming pattern + +### ConstructorParameterNaming + +Reports constructor parameter names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``parameterPattern`` (default: ``'[a-z][A-Za-z0-9]*'``) + + naming pattern + +* ``privateParameterPattern`` (default: ``'[a-z][A-Za-z0-9]*'``) + + naming pattern + +* ``excludeClassPattern`` (default: ``'$^'``) + + ignores variables in classes which match this regex + +* ~~``ignoreOverridden``~~ (default: ``true``) + + **Deprecated**: This configuration is ignored and will be removed in the future + + ignores constructor properties that have the override modifier + +### EnumNaming + +Reports enum names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``enumEntryPattern`` (default: ``'[A-Z][_a-zA-Z0-9]*'``) + + naming pattern + +### ForbiddenClassName + +Reports class names which are forbidden per configuration. By default, this rule does not report any classes. +Examples for forbidden names might be too generic class names like `...Manager`. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``forbiddenName`` (default: ``[]``) + + forbidden class names + +### FunctionMaxLength + +Reports when very long function names are used. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``maximumFunctionNameLength`` (default: ``30``) + + maximum name length + +### FunctionMinLength + +Reports when very short function names are used. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``minimumFunctionNameLength`` (default: ``3``) + + minimum name length + +### FunctionNaming + +Reports function names that do not follow the specified naming convention. +One exception are factory functions used to create instances of classes. +These factory functions can have the same name as the class being created. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +**Aliases**: FunctionName + +#### Configuration options: + +* ``functionPattern`` (default: ``'[a-z][a-zA-Z0-9]*'``) + + naming pattern + +* ``excludeClassPattern`` (default: ``'$^'``) + + ignores functions in classes which match this regex + +* ~~``ignoreOverridden``~~ (default: ``true``) + + **Deprecated**: This configuration is ignored and will be removed in the future + + ignores functions that have the override modifier + +### FunctionParameterNaming + +Reports function parameter names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``parameterPattern`` (default: ``'[a-z][A-Za-z0-9]*'``) + + naming pattern + +* ``excludeClassPattern`` (default: ``'$^'``) + + ignores variables in classes which match this regex + +* ~~``ignoreOverriddenFunctions``~~ (default: ``true``) + + **Deprecated**: Use `ignoreOverridden` instead + + ignores overridden functions with parameters not matching the pattern + +* ~~``ignoreOverridden``~~ (default: ``true``) + + **Deprecated**: This configuration is ignored and will be removed in the future + + ignores overridden functions with parameters not matching the pattern + +### InvalidPackageDeclaration + +Reports when the file location does not match the declared package. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +**Aliases**: PackageDirectoryMismatch + +#### Configuration options: + +* ``rootPackage`` (default: ``''``) + + if specified this part of the package structure is ignored + +* ``requireRootInDeclaration`` (default: ``false``) + + requires the declaration to start with the specified rootPackage + +### LambdaParameterNaming + +Reports lambda parameter names that do not follow the specified naming convention. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``parameterPattern`` (default: ``'[a-z][A-Za-z0-9]*|_'``) + + naming pattern + +### MatchingDeclarationName + +"If a Kotlin file contains a single non-private class (potentially with related top-level declarations), +its name should be the same as the name of the class, with the .kt extension appended. +If a file contains multiple classes, or only top-level declarations, +choose a name describing what the file contains, and name the file accordingly. +Use camel humps with an uppercase first letter (e.g. ProcessDeclarations.kt). + +The name of the file should describe what the code in the file does. +Therefore, you should avoid using meaningless words such as "Util" in file names." - Official Kotlin Style Guide + +More information at: https://kotlinlang.org/docs/coding-conventions.html + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``mustBeFirst`` (default: ``true``) + + name should only be checked if the file starts with a class or object + +#### Noncompliant Code: + +```kotlin +class Foo // FooUtils.kt + +fun Bar.toFoo(): Foo = ... +fun Foo.toBar(): Bar = ... +``` + +#### Compliant Code: + +```kotlin +class Foo { // Foo.kt + fun stuff() = 42 +} + +fun Bar.toFoo(): Foo = ... +``` + +### MemberNameEqualsClassName + +This rule reports a member that has the same as the containing class or object. +This might result in confusion. +The member should either be renamed or changed to a constructor. +Factory functions that create an instance of the class are exempt from this rule. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +#### Configuration options: + +* ~~``ignoreOverriddenFunction``~~ (default: ``true``) + + **Deprecated**: Use `ignoreOverridden` instead + + if overridden functions and properties should be ignored + +* ``ignoreOverridden`` (default: ``true``) + + if overridden functions and properties should be ignored + +#### Noncompliant Code: + +```kotlin +class MethodNameEqualsClassName { + + fun methodNameEqualsClassName() { } +} + +class PropertyNameEqualsClassName { + + val propertyEqualsClassName = 0 +} +``` + +#### Compliant Code: + +```kotlin +class Manager { + + companion object { + // factory functions can have the same name as the class + fun manager(): Manager { + return Manager() + } + } +} +``` + +### NoNameShadowing + +Disallows shadowing variable declarations. +Shadowing makes it impossible to access a variable with the same name in the scope. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun test(i: Int, j: Int, k: Int) { + val i = 1 + val (j, _) = 1 to 2 + listOf(1).map { k -> println(k) } + listOf(1).forEach { + listOf(2).forEach { + } + } +} +``` + +#### Compliant Code: + +```kotlin +fun test(i: Int, j: Int, k: Int) { + val x = 1 + val (y, _) = 1 to 2 + listOf(1).map { z -> println(z) } + listOf(1).forEach { + listOf(2).forEach { x -> + } + } +} +``` + +### NonBooleanPropertyPrefixedWithIs + +Reports when property with 'is' prefix doesn't have a boolean type. +Please check the [chapter 8.3.2 at Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.3.2) + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val isEnabled: Int = 500 +``` + +#### Compliant Code: + +```kotlin +val isEnabled: Boolean = false +``` + +### ObjectPropertyNaming + +Reports property names inside objects that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``constantPattern`` (default: ``'[A-Za-z][_A-Za-z0-9]*'``) + + naming pattern + +* ``propertyPattern`` (default: ``'[A-Za-z][_A-Za-z0-9]*'``) + + naming pattern + +* ``privatePropertyPattern`` (default: ``'(_)?[A-Za-z][_A-Za-z0-9]*'``) + + naming pattern + +### PackageNaming + +Reports package names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +**Aliases**: PackageName, PackageDirectoryMismatch + +#### Configuration options: + +* ``packagePattern`` (default: ``'[a-z]+(\.[a-z][A-Za-z0-9]*)*'``) + + naming pattern + +### TopLevelPropertyNaming + +Reports top level constant that which do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``constantPattern`` (default: ``'[A-Z][_A-Z0-9]*'``) + + naming pattern + +* ``propertyPattern`` (default: ``'[A-Za-z][_A-Za-z0-9]*'``) + + naming pattern + +* ``privatePropertyPattern`` (default: ``'_?[A-Za-z][_A-Za-z0-9]*'``) + + naming pattern + +### VariableMaxLength + +Reports when very long variable names are used. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``maximumVariableNameLength`` (default: ``64``) + + maximum name length + +### VariableMinLength + +Reports when very short variable names are used. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``minimumVariableNameLength`` (default: ``1``) + + minimum name length + +### VariableNaming + +Reports variable names that do not follow the specified naming convention. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``variablePattern`` (default: ``'[a-z][A-Za-z0-9]*'``) + + naming pattern + +* ``privateVariablePattern`` (default: ``'(_)?[a-z][A-Za-z0-9]*'``) + + naming pattern + +* ``excludeClassPattern`` (default: ``'$^'``) + + ignores variables in classes which match this regex + +* ~~``ignoreOverridden``~~ (default: ``true``) + + **Deprecated**: This configuration is ignored and will be removed in the future + + ignores member properties that have the override modifier diff --git a/website/versioned_docs/version-1.23.0/rules/performance.md b/website/versioned_docs/version-1.23.0/rules/performance.md new file mode 100644 index 000000000000..84e7fa0956e7 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/performance.md @@ -0,0 +1,194 @@ +--- +title: Performance Rule Set +sidebar: home_sidebar +keywords: [rules, performance] +permalink: performance.html +toc: true +folder: documentation +--- +The performance rule set analyzes code for potential performance problems. + +### ArrayPrimitive + +Using `Array` leads to implicit boxing and performance hit. Prefer using Kotlin specialized Array +Instances. + +As stated in the Kotlin [documentation](https://kotlinlang.org/docs/basic-types.html#arrays) Kotlin has +specialized arrays to represent primitive types without boxing overhead, such as `IntArray`, `ByteArray` and so on. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun function(array: Array) { } + +fun returningFunction(): Array { } +``` + +#### Compliant Code: + +```kotlin +fun function(array: IntArray) { } + +fun returningFunction(): DoubleArray { } +``` + +### CouldBeSequence + +Long chains of collection operations will have a performance penalty due to a new list being created for each call. Consider using sequences instead. Read more about this in the [documentation](https://kotlinlang.org/docs/sequences.html) + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``threshold`` (default: ``3``) + + the number of chained collection operations required to trigger rule + +#### Noncompliant Code: + +```kotlin +listOf(1, 2, 3, 4).map { it*2 }.filter { it < 4 }.map { it*it } +``` + +#### Compliant Code: + +```kotlin +listOf(1, 2, 3, 4).asSequence().map { it*2 }.filter { it < 4 }.map { it*it }.toList() + +listOf(1, 2, 3, 4).map { it*2 } +``` + +### ForEachOnRange + +Using the forEach method on ranges has a heavy performance cost. Prefer using simple for loops. + +Benchmarks have shown that using forEach on a range can have a huge performance cost in comparison to +simple for loops. Hence, in most contexts, a simple for loop should be used instead. +See more details here: https://sites.google.com/a/athaydes.com/renato-athaydes/posts/kotlinshiddencosts-benchmarks +To solve this CodeSmell, the forEach usage should be replaced by a for loop. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +(1..10).forEach { + println(it) +} +(1 until 10).forEach { + println(it) +} +(10 downTo 1).forEach { + println(it) +} +``` + +#### Compliant Code: + +```kotlin +for (i in 1..10) { + println(i) +} +``` + +### SpreadOperator + +In most cases using a spread operator causes a full copy of the array to be created before calling a method. +This has a very high performance penalty. Benchmarks showing this performance penalty can be seen here: +https://sites.google.com/a/athaydes.com/renato-athaydes/posts/kotlinshiddencosts-benchmarks + +The Kotlin compiler since v1.1.60 has an optimization that skips the array copy when an array constructor +function is used to create the arguments that are passed to the vararg parameter. When type resolution is enabled in +detekt this case will not be flagged by the rule since it doesn't suffer the performance penalty of an array copy. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +val strs = arrayOf("value one", "value two") +val foo = bar(*strs) + +fun bar(vararg strs: String) { + strs.forEach { println(it) } +} +``` + +#### Compliant Code: + +```kotlin +// array copy skipped in this case since Kotlin 1.1.60 +val foo = bar(*arrayOf("value one", "value two")) + +// array not passed so no array copy is required +val foo2 = bar("value one", "value two") + +fun bar(vararg strs: String) { + strs.forEach { println(it) } +} +``` + +### UnnecessaryPartOfBinaryExpression + +Unnecessary binary expression add complexity to the code and accomplish nothing. They should be removed. +The rule works with all binary expression included if and when condition. The rule also works with all predicates. +The rule verify binary expression only in case when the expression use only one type of the following +operators || or &&. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val foo = true +val bar = true + +if (foo || bar || foo) { +} +``` + +#### Compliant Code: + +```kotlin +val foo = true +if (foo) { +} +``` + +### UnnecessaryTemporaryInstantiation + +Avoid temporary objects when converting primitive types to String. This has a performance penalty when compared +to using primitive types directly. +To solve this issue, remove the wrapping type. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val i = Integer(1).toString() // temporary Integer instantiation just for the conversion +``` + +#### Compliant Code: + +```kotlin +val i = Integer.toString(1) +``` diff --git a/website/versioned_docs/version-1.23.0/rules/potential-bugs.md b/website/versioned_docs/version-1.23.0/rules/potential-bugs.md new file mode 100644 index 000000000000..8eaa68897f22 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/potential-bugs.md @@ -0,0 +1,1272 @@ +--- +title: Potential-bugs Rule Set +sidebar: home_sidebar +keywords: [rules, potential-bugs] +permalink: potential-bugs.html +toc: true +folder: documentation +--- +The potential-bugs rule set provides rules that detect potential bugs. + +### AvoidReferentialEquality + +Kotlin supports two types of equality: structural equality and referential equality. While there are +use cases for both, checking for referential equality for some types (such as `String` or `List`) is +likely not intentional and may cause unexpected results. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``forbiddenTypePatterns`` (default: ``['kotlin.String']``) + + Specifies those types for which referential equality checks are considered a rule violation. The types are defined by a list of simple glob patterns (supporting `*` and `?` wildcards) that match the fully qualified type name. + +#### Noncompliant Code: + +```kotlin + val areEqual = "aString" === otherString + val areNotEqual = "aString" !== otherString +``` + +#### Compliant Code: + +```kotlin + val areEqual = "aString" == otherString + val areNotEqual = "aString" != otherString +``` + +### CastNullableToNonNullableType + +Reports cast of nullable variable to non-null type. Cast like this can hide `null` +problems in your code. The compliant code would be that which will correctly check +for two things (nullability and type) and not just one (cast). + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(bar: Any?) { + val x = bar as String +} +``` + +#### Compliant Code: + +```kotlin +fun foo(bar: Any?) { + val x = checkNotNull(bar) as String +} + +// Alternative +fun foo(bar: Any?) { + val x = (bar ?: error("null assertion message")) as String +} +``` + +### CastToNullableType + +Reports unsafe cast to nullable types. +`as String?` is unsafed and may be misused as safe cast (`as? String`). + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(a: Any?) { + val x: String? = a as String? // If 'a' is not String, ClassCastException will be thrown. +} +``` + +#### Compliant Code: + +```kotlin +fun foo(a: Any?) { + val x: String? = a as? String +} +``` + +### Deprecation + +Deprecated elements are expected to be removed in the future. Alternatives should be found if possible. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 20min + +**Aliases**: DEPRECATION + +### DontDowncastCollectionTypes + +Down-casting immutable types from kotlin.collections should be discouraged. +The result of the downcast is platform specific and can lead to unexpected crashes. +Prefer to use instead the `toMutable()` functions. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +val list : List = getAList() +if (list is MutableList) { + list.add(42) +} + +(list as MutableList).add(42) +``` + +#### Compliant Code: + +```kotlin +val list : List = getAList() +list.toMutableList().add(42) +``` + +### DoubleMutabilityForCollection + +Using `var` when declaring a mutable collection or value holder leads to double mutability. +Consider instead declaring your variable with `val` or switching your declaration to use an +immutable type. + +By default, the rule triggers on standard mutable collections, however it can be configured +to trigger on other types of mutable value types, such as `MutableState` from Jetpack +Compose. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +**Aliases**: DoubleMutability + +#### Configuration options: + +* ``mutableTypes`` (default: ``['kotlin.collections.MutableList', 'kotlin.collections.MutableMap', 'kotlin.collections.MutableSet', 'java.util.ArrayList', 'java.util.LinkedHashSet', 'java.util.HashSet', 'java.util.LinkedHashMap', 'java.util.HashMap']``) + + Define a list of mutable types to trigger on when defined with `var`. + +#### Noncompliant Code: + +```kotlin +var myList = mutableListOf(1,2,3) +var mySet = mutableSetOf(1,2,3) +var myMap = mutableMapOf("answer" to 42) +``` + +#### Compliant Code: + +```kotlin +// Use val +val myList = mutableListOf(1,2,3) +val mySet = mutableSetOf(1,2,3) +val myMap = mutableMapOf("answer" to 42) + +// Use immutable types +var myList = listOf(1,2,3) +var mySet = setOf(1,2,3) +var myMap = mapOf("answer" to 42) +``` + +### ~~DuplicateCaseInWhenExpression~~ + +Rule deprecated as compiler performs this check by default + +Flags duplicate `case` statements in `when` expressions. + +If a `when` expression contains the same `case` statement multiple times they should be merged. Otherwise, it might be +easy to miss one of the cases when reading the code, leading to unwanted side effects. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +when (i) { + 1 -> println("one") + 1 -> println("one") + else -> println("else") +} +``` + +#### Compliant Code: + +```kotlin +when (i) { + 1 -> println("one") + else -> println("else") +} +``` + +### ElseCaseInsteadOfExhaustiveWhen + +This rule reports `when` expressions that contain an `else` case even though they have an exhaustive set of cases. + +This occurs when the subject of the `when` expression is either an enum class, sealed class or of type boolean. +Using `else` cases for these expressions can lead to unintended behavior when adding new enum types, sealed subtypes +or changing the nullability of a boolean, since this will be implicitly handled by the `else` case. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``ignoredSubjectTypes`` (default: ``[]``) + + List of fully qualified types which should be ignored for when expressions with a subject. Example `kotlinx.serialization.json.JsonObject` + +#### Noncompliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +when(c) { + Color.RED -> {} + Color.GREEN -> {} + else -> {} +} +``` + +#### Compliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +when(c) { + Color.RED -> {} + Color.GREEN -> {} + Color.BLUE -> {} +} +``` + +### EqualsAlwaysReturnsTrueOrFalse + +Reports `equals()` methods which will always return true or false. + +Equals methods should always report if some other object is equal to the current object. +See the Kotlin documentation for Any.equals(other: Any?): +https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/equals.html + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +override fun equals(other: Any?): Boolean { + return true +} +``` + +#### Compliant Code: + +```kotlin +override fun equals(other: Any?): Boolean { + return this === other +} +``` + +### EqualsWithHashCodeExist + +When a class overrides the equals() method it should also override the hashCode() method. + +All hash-based collections depend on objects meeting the equals-contract. Two equal objects must produce the +same hashcode. When inheriting equals or hashcode, override the inherited and call the super method for +clarification. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class Foo { + + override fun equals(other: Any?): Boolean { + return super.equals(other) + } +} +``` + +#### Compliant Code: + +```kotlin +class Foo { + + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + override fun hashCode(): Int { + return super.hashCode() + } +} +``` + +### ExitOutsideMain + +Reports the usage of `System.exit()`, `Runtime.exit()`, `Runtime.halt()` and Kotlin's `exitProcess()` +when used outside the `main` function. +This makes code more difficult to test, causes unexpected behaviour on Android, and is a poor way to signal a +failure in the program. In almost all cases it is more appropriate to throw an exception. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +fun randomFunction() { + val result = doWork() + if (result == FAILURE) { + exitProcess(2) + } else { + exitProcess(0) + } +} +``` + +#### Compliant Code: + +```kotlin +fun main() { + val result = doWork() + if (result == FAILURE) { + exitProcess(2) + } else { + exitProcess(0) + } +} +``` + +### ExplicitGarbageCollectionCall + +Reports all calls to explicitly trigger the Garbage Collector. +Code should work independently of the garbage collector and should not require the GC to be triggered in certain +points in time. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +System.gc() +Runtime.getRuntime().gc() +System.runFinalization() +``` + +### HasPlatformType + +Platform types must be declared explicitly in public APIs to prevent unexpected errors. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class Person { + fun apiCall() = System.getProperty("propertyName") +} +``` + +#### Compliant Code: + +```kotlin +class Person { + fun apiCall(): String = System.getProperty("propertyName") +} +``` + +### IgnoredReturnValue + +This rule warns on instances where a function, annotated with either `@CheckReturnValue` or `@CheckResult`, +returns a value but that value is not used in any way. The Kotlin compiler gives no warning for this scenario +normally so that's the rationale behind this rule. + +fun returnsValue() = 42 +fun returnsNoValue() {} + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 20min + +#### Configuration options: + +* ~~``restrictToAnnotatedMethods``~~ (default: ``true``) + + **Deprecated**: Use `restrictToConfig` instead + + if the rule should check only annotated methods + +* ``restrictToConfig`` (default: ``true``) + + If the rule should check only methods matching to configuration, or all methods + +* ``returnValueAnnotations`` (default: ``['CheckResult', '*.CheckResult', 'CheckReturnValue', '*.CheckReturnValue']``) + + List of glob patterns to be used as inspection annotation + +* ``ignoreReturnValueAnnotations`` (default: ``['CanIgnoreReturnValue', '*.CanIgnoreReturnValue']``) + + Annotations to skip this inspection + +* ``returnValueTypes`` (default: ``['kotlin.sequences.Sequence', 'kotlinx.coroutines.flow.*Flow', 'java.util.stream.*Stream']``) + + List of return types that should not be ignored + +* ``ignoreFunctionCall`` (default: ``[]``) + + List of function signatures which should be ignored by this rule. Specifying fully-qualified function signature with name only (i.e. `java.time.LocalDate.now`) will ignore all function calls matching the name. Specifying fully-qualified function signature with parameters (i.e. `java.time.LocalDate.now(java.time.Clock)`) will ignore only function calls matching the name and parameters exactly. + +#### Noncompliant Code: + +```kotlin +returnsValue() +``` + +#### Compliant Code: + +```kotlin +if (42 == returnsValue()) {} +val x = returnsValue() +``` + +### ImplicitDefaultLocale + +Prefer passing [java.util.Locale] explicitly than using implicit default value when formatting +strings or performing a case conversion. + +The default locale is almost always inappropriate for machine-readable text like HTTP headers. +For example, if locale with tag `ar-SA-u-nu-arab` is a current default then `%d` placeholders +will be evaluated to a number consisting of Eastern-Arabic (non-ASCII) digits. +[java.util.Locale.US] is recommended for machine-readable output. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +String.format("Timestamp: %d", System.currentTimeMillis()) + +val str: String = getString() +str.toUpperCase() +str.toLowerCase() +``` + +#### Compliant Code: + +```kotlin +String.format(Locale.US, "Timestamp: %d", System.currentTimeMillis()) + +val str: String = getString() +str.toUpperCase(Locale.US) +str.toLowerCase(Locale.US) +``` + +### ImplicitUnitReturnType + +Functions using expression statements have an implicit return type. +Changing the type of the expression accidentally, changes the functions return type. +This may lead to backward incompatibility. +Use a block statement to make clear this function will never return a value. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``allowExplicitReturnType`` (default: ``true``) + + if functions with explicit `Unit` return type should be allowed + +#### Noncompliant Code: + +```kotlin +fun errorProneUnit() = println("Hello Unit") +fun errorProneUnitWithParam(param: String) = param.run { println(this) } +fun String.errorProneUnitWithReceiver() = run { println(this) } +``` + +#### Compliant Code: + +```kotlin +fun blockStatementUnit() { + // code +} + +// explicit Unit is compliant by default; can be configured to enforce block statement +fun safeUnitReturn(): Unit = println("Hello Unit") +``` + +### InvalidRange + +Reports ranges which are empty. +This might be a bug if it is used for instance as a loop condition. This loop will never be triggered then. +This might be due to invalid ranges like (10..9) which will cause the loop to never be entered. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +for (i in 2..1) {} +for (i in 1 downTo 2) {} + +val range1 = 2 until 1 +val range2 = 2 until 2 +``` + +#### Compliant Code: + +```kotlin +for (i in 2..2) {} +for (i in 2 downTo 2) {} + +val range = 2 until 3 +``` + +### IteratorHasNextCallsNextMethod + +Verifies implementations of the Iterator interface. +The hasNext() method of an Iterator implementation should not have any side effects. +This rule reports implementations that call the next() method of the Iterator inside the hasNext() method. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +class MyIterator : Iterator { + + override fun hasNext(): Boolean { + return next() != null + } +} +``` + +### IteratorNotThrowingNoSuchElementException + +Reports implementations of the `Iterator` interface which do not throw a NoSuchElementException in the +implementation of the next() method. When there are no more elements to return an Iterator should throw a +NoSuchElementException. + +See: https://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html#next() + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +class MyIterator : Iterator { + + override fun next(): String { + return "" + } +} +``` + +#### Compliant Code: + +```kotlin +class MyIterator : Iterator { + + override fun next(): String { + if (!this.hasNext()) { + throw NoSuchElementException() + } + // ... + } +} +``` + +### LateinitUsage + +Reports usages of the lateinit modifier. + +Using lateinit for property initialization can be error-prone and the actual initialization is not +guaranteed. Try using constructor injection or delegation to initialize properties. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ~~``excludeAnnotatedProperties``~~ (default: ``[]``) + + **Deprecated**: Use `ignoreAnnotated` instead + + Allows you to provide a list of annotations that disable this check. + +* ``ignoreOnClassesPattern`` (default: ``''``) + + Allows you to disable the rule for a list of classes + +#### Noncompliant Code: + +```kotlin +class Foo { + private lateinit var i1: Int + lateinit var i2: Int +} +``` + +### MapGetWithNotNullAssertionOperator + +Reports calls of the map access methods `map[]` or `map.get()` with a not-null assertion operator `!!`. +This may result in a NullPointerException. +Preferred access methods are `map[]` without `!!`, `map.getValue()`, `map.getOrDefault()` or `map.getOrElse()`. + +Based on an IntelliJ IDEA inspection MapGetWithNotNullAssertionOperatorInspection. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val map = emptyMap() +map["key"]!! + +val map = emptyMap() +map.get("key")!! +``` + +#### Compliant Code: + +```kotlin +val map = emptyMap() +map["key"] + +val map = emptyMap() +map.getValue("key") + +val map = emptyMap() +map.getOrDefault("key", "") + +val map = emptyMap() +map.getOrElse("key", { "" }) +``` + +### MissingPackageDeclaration + +Reports when the package declaration is missing. + +**Active by default**: No + +**Debt**: 5min + +### ~~MissingWhenCase~~ + +Rule deprecated as compiler performs this check by default + +Turn on this rule to flag `when` expressions that do not check that all cases are covered when the subject is an enum +or sealed class and the `when` expression is used as a statement. + +When this happens it's unclear what was intended when an unhandled case is reached. It is better to be explicit and +either handle all cases or use a default `else` statement to cover the unhandled cases. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 20min + +#### Configuration options: + +* ``allowElseExpression`` (default: ``true``) + + whether `else` can be treated as a valid case for enums and sealed classes + +#### Noncompliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +fun whenOnEnumFail(c: Color) { + when(c) { + Color.BLUE -> {} + Color.GREEN -> {} + } +} +``` + +#### Compliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +fun whenOnEnumCompliant(c: Color) { + when(c) { + Color.BLUE -> {} + Color.GREEN -> {} + Color.RED -> {} + } +} + +fun whenOnEnumCompliant2(c: Color) { + when(c) { + Color.BLUE -> {} + else -> {} + } +} +``` + +### NullCheckOnMutableProperty + +Reports null-checks on mutable properties, as these properties' value can be +changed - and thus make the null-check invalid - after the execution of the +if-statement. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +class A(private var a: Int?) { +fun foo() { + if (a != null) { + println(2 + a!!) + } +} +} +``` + +#### Compliant Code: + +```kotlin +class A(private val a: Int?) { +fun foo() { + if (a != null) { + println(2 + a) + } +} +} +``` + +### NullableToStringCall + +Reports `toString()` calls with a nullable receiver that may return the string "null". + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(a: Any?): String { + return a.toString() +} + +fun bar(a: Any?): String { + return "$a" +} +``` + +#### Compliant Code: + +```kotlin +fun foo(a: Any?): String { + return a?.toString() ?: "-" +} + +fun bar(a: Any?): String { + return "${a ?: "-"}" +} +``` + +### PropertyUsedBeforeDeclaration + +Reports properties that are used before declaration. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class C { + private val number + get() = if (isValid) 1 else 0 + + val list = listOf(number) + + private val isValid = true +} + +fun main() { + println(C().list) // [0] +} +``` + +#### Compliant Code: + +```kotlin +class C { + private val isValid = true + + private val number + get() = if (isValid) 1 else 0 + + val list = listOf(number) +} + +fun main() { + println(C().list) // [1] +} +``` + +### ~~RedundantElseInWhen~~ + +Rule deprecated as compiler performs this check by default + +Reports `when` expressions that contain a redundant `else` case. This occurs when it can be +verified that all cases are already covered when checking cases on an enum or sealed class. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +fun whenOnEnumFail(c: Color) { + when(c) { + Color.BLUE -> {} + Color.GREEN -> {} + Color.RED -> {} + else -> {} + } +} +``` + +#### Compliant Code: + +```kotlin +enum class Color { + RED, + GREEN, + BLUE +} + +fun whenOnEnumCompliant(c: Color) { + when(c) { + Color.BLUE -> {} + Color.GREEN -> {} + else -> {} + } +} + +fun whenOnEnumCompliant2(c: Color) { + when(c) { + Color.BLUE -> {} + Color.GREEN -> {} + Color.RED -> {} + } +} +``` + +### UnconditionalJumpStatementInLoop + +Reports loops which contain jump statements that jump regardless of any conditions. +This implies that the loop is only executed once and thus could be rewritten without a +loop altogether. + +**Active by default**: No + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +for (i in 1..2) break +``` + +#### Compliant Code: + +```kotlin +for (i in 1..2) { + if (i == 1) break +} +``` + +### UnnecessaryNotNullCheck + +Reports unnecessary not-null checks with `requireNotNull` or `checkNotNull` that can be removed by the user. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +var string = "foo" +println(requireNotNull(string)) +``` + +#### Compliant Code: + +```kotlin +var string : String? = "foo" +println(requireNotNull(string)) +``` + +### UnnecessaryNotNullOperator + +Reports unnecessary not-null operator usage (!!) that can be removed by the user. + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val a = 1 +val b = a!! +``` + +#### Compliant Code: + +```kotlin +val a = 1 +val b = a +``` + +### UnnecessarySafeCall + +Reports unnecessary safe call operators (`?.`) that can be removed by the user. + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val a: String = "" +val b = a?.length +``` + +#### Compliant Code: + +```kotlin +val a: String? = null +val b = a?.length +``` + +### UnreachableCatchBlock + +Reports unreachable catch blocks. +Catch blocks can be unreachable if the exception has already been caught in the block above. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun test() { + try { + foo() + } catch (t: Throwable) { + bar() + } catch (e: Exception) { + // Unreachable + baz() + } +} +``` + +#### Compliant Code: + +```kotlin +fun test() { + try { + foo() + } catch (e: Exception) { + baz() + } catch (t: Throwable) { + bar() + } +} +``` + +### UnreachableCode + +Reports unreachable code. +Code can be unreachable because it is behind return, throw, continue or break expressions. +This unreachable code should be removed as it serves no purpose. + +**Active by default**: Yes - Since v1.0.0 + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +for (i in 1..2) { + break + println() // unreachable +} + +throw IllegalArgumentException() +println() // unreachable + +fun f() { + return + println() // unreachable +} +``` + +### UnsafeCallOnNullableType + +Reports unsafe calls on nullable types. These calls will throw a NullPointerException in case +the nullable value is null. Kotlin provides many ways to work with nullable types to increase +null safety. Guard the code appropriately to prevent NullPointerExceptions. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +fun foo(str: String?) { + println(str!!.length) +} +``` + +#### Compliant Code: + +```kotlin +fun foo(str: String?) { + println(str?.length) +} +``` + +### UnsafeCast + +Reports casts that will never succeed. + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 20min + +**Aliases**: UNCHECKED_CAST + +#### Noncompliant Code: + +```kotlin +fun foo(s: String) { + println(s as Int) +} + +fun bar(s: String) { + println(s as? Int) +} +``` + +#### Compliant Code: + +```kotlin +fun foo(s: Any) { + println(s as Int) +} +``` + +### UnusedUnaryOperator + +Detects unused unary operators. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val x = 1 + 2 + + 3 + 4 +println(x) // 3 +``` + +#### Compliant Code: + +```kotlin +val x = 1 + 2 + 3 + 4 +println(x) // 10 +``` + +### UselessPostfixExpression + +Reports postfix expressions (++, --) which are unused and thus unnecessary. +This leads to confusion as a reader of the code might think the value will be incremented/decremented. +However, the value is replaced with the original value which might lead to bugs. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +var i = 0 +i = i-- +i = 1 + i++ +i = i++ + 1 + +fun foo(): Int { + var i = 0 + // ... + return i++ +} +``` + +#### Compliant Code: + +```kotlin +var i = 0 +i-- +i = i + 2 +i = i + 2 + +fun foo(): Int { + var i = 0 + // ... + i++ + return i +} +``` + +### WrongEqualsTypeParameter + +Reports equals() methods which take in a wrongly typed parameter. +Correct implementations of the equals() method should only take in a parameter of type Any? +See: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/equals.html + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +class Foo { + + fun equals(other: String): Boolean { + return super.equals(other) + } +} +``` + +#### Compliant Code: + +```kotlin +class Foo { + + fun equals(other: Any?): Boolean { + return super.equals(other) + } +} +``` diff --git a/website/versioned_docs/version-1.23.0/rules/ruleauthors.md b/website/versioned_docs/version-1.23.0/rules/ruleauthors.md new file mode 100644 index 000000000000..8a888e48851d --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/ruleauthors.md @@ -0,0 +1,35 @@ +--- +title: Ruleauthors Rule Set +sidebar: home_sidebar +keywords: [rules, ruleauthors] +permalink: ruleauthors.html +toc: true +folder: documentation +--- +The rule authors ruleset provides rules that ensures good practices when writing detekt rules. + +**Note: The `ruleauthors` rule set is not included in the detekt-cli or Gradle plugin.** + +To enable this rule set, add `detektPlugins "io.gitlab.arturbosch.detekt:detekt-rules-ruleauthors:$version"` +to your Gradle `dependencies` or reference the `detekt-rules-ruleauthors`-jar with the `--plugins` option +in the command line interface. + +### UseEntityAtName + +If a rule [report]s issues using [Entity.from] with [KtNamedDeclaration.getNameIdentifier], +then it can be replaced with [Entity.atName] for more semantic code and better baseline support. + +**Active by default**: Yes - Since v1.22.0 + +**Debt**: 5min + +### ViolatesTypeResolutionRequirements + +If a rule uses the property [BaseRule.bindingContext] should be annotated with `@RequiresTypeResolution`. +And if the rule doesn't use that property it shouldn't be annotated with it. + +**Active by default**: Yes - Since v1.22.0 + +**Requires Type Resolution** + +**Debt**: 5min diff --git a/website/versioned_docs/version-1.23.0/rules/style.md b/website/versioned_docs/version-1.23.0/rules/style.md new file mode 100644 index 000000000000..6af8aeeeff38 --- /dev/null +++ b/website/versioned_docs/version-1.23.0/rules/style.md @@ -0,0 +1,3162 @@ +--- +title: Style Rule Set +sidebar: home_sidebar +keywords: [rules, style] +permalink: style.html +toc: true +folder: documentation +--- +The Style ruleset provides rules that assert the style of the code. +This will help keep code in line with the given +code style guidelines. + +### AlsoCouldBeApply + +Detects when an `also` block contains only `it`-started expressions. + +By refactoring the `also` block to an `apply` block makes it so that all `it`s can be removed +thus making the block more concise and easier to read. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +Buzz().also { +it.init() +it.block() +} +``` + +#### Compliant Code: + +```kotlin +Buzz().apply { +init() +block() +} + +// Also compliant +fun foo(a: Int): Int { +return a.also { println(it) } +} +``` + +### BracesOnIfStatements + +This rule detects `if` statements which do not comply with the specified rules. +Keeping braces consistent will improve readability and avoid possible errors. + +The available options are: +* `always`: forces braces on all `if` and `else` branches in the whole codebase. +* `consistent`: ensures that braces are consistent within each `if`-`else if`-`else` chain. + If there's a brace on one of the branches, all branches should have it. +* `necessary`: forces no braces on any `if` and `else` branches in the whole codebase + except where necessary for multi-statement branches. +* `never`: forces no braces on any `if` and `else` branches in the whole codebase. + +Single-line if-statement has no line break (\n): +```kotlin +if (a) b else c +``` +Multi-line if-statement has at least one line break (\n): +```kotlin +if (a) b +else c +``` + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``singleLine`` (default: ``'never'``) + + single-line braces policy + +* ``multiLine`` (default: ``'always'``) + + multi-line braces policy + +#### Noncompliant Code: + +```kotlin +// singleLine = 'never' +if (a) { b } else { c } + +if (a) { b } else c + +if (a) b else { c; d } + +// multiLine = 'never' +if (a) { + b +} else { + c +} + +// singleLine = 'always' +if (a) b else c + +if (a) { b } else c + +// multiLine = 'always' +if (a) { + b +} else + c + +// singleLine = 'consistent' +if (a) b else { c } +if (a) b else if (c) d else { e } + +// multiLine = 'consistent' +if (a) + b +else { + c +} + +// singleLine = 'necessary' +if (a) { b } else { c; d } + +// multiLine = 'necessary' +if (a) { + b + c +} else if (d) { + e +} else { + f +} +``` + +#### Compliant Code: + +```kotlin +// singleLine = 'never' +if (a) b else c + +// multiLine = 'never' +if (a) + b +else + c + +// singleLine = 'always' +if (a) { b } else { c } + +if (a) { b } else if (c) { d } + +// multiLine = 'always' +if (a) { + b +} else { + c +} + +if (a) { + b +} else if (c) { + d +} + +// singleLine = 'consistent' +if (a) b else c + +if (a) { b } else { c } + +if (a) { b } else { c; d } + +// multiLine = 'consistent' +if (a) { + b +} else { + c +} + +if (a) b +else c + +// singleLine = 'necessary' +if (a) b else { c; d } + +// multiLine = 'necessary' +if (a) { + b + c +} else if (d) + e +else + f +``` + +### BracesOnWhenStatements + +This rule detects `when` statements which do not comply with the specified policy. +Keeping braces consistent will improve readability and avoid possible errors. + +Single-line `when` statement is: +a `when` where each of the branches are single-line (has no line breaks `\n`). + +Multi-line `when` statement is: +a `when` where at least one of the branches is multi-line (has a break line `\n`). + +Available options are: +* `never`: forces no braces on any branch. +_Tip_: this is very strict, it will force a simple expression, like a single function call / expression. +Extracting a function for "complex" logic is one way to adhere to this policy. +* `necessary`: forces no braces on any branch except where necessary for multi-statement branches. +* `consistent`: ensures that braces are consistent within `when` statement. + If there are braces on one of the branches, all branches should have it. +* `always`: forces braces on all branches. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``singleLine`` (default: ``'necessary'``) + + single-line braces policy + +* ``multiLine`` (default: ``'consistent'``) + + multi-line braces policy + +#### Noncompliant Code: + +```kotlin +// singleLine = 'never' +when (a) { + 1 -> { f1() } // Not allowed. + 2 -> f2() +} +// multiLine = 'never' +when (a) { + 1 -> { // Not allowed. + f1() + } + 2 -> f2() +} +// singleLine = 'necessary' +when (a) { + 1 -> { f1() } // Unnecessary braces. + 2 -> f2() +} +// multiLine = 'necessary' +when (a) { + 1 -> { // Unnecessary braces. + f1() + } + 2 -> f2() +} + +// singleLine = 'consistent' +when (a) { + 1 -> { f1() } + 2 -> f2() +} +// multiLine = 'consistent' +when (a) { + 1 -> + f1() // Missing braces. + 2 -> { + f2() + f3() + } +} + +// singleLine = 'always' +when (a) { + 1 -> { f1() } + 2 -> f2() // Missing braces. +} +// multiLine = 'always' +when (a) { + 1 -> + f1() // Missing braces. + 2 -> { + f2() + f3() + } +} +``` + +#### Compliant Code: + +```kotlin +// singleLine = 'never' +when (a) { + 1 -> f1() + 2 -> f2() +} +// multiLine = 'never' +when (a) { + 1 -> + f1() + 2 -> f2() +} +// singleLine = 'necessary' +when (a) { + 1 -> f1() + 2 -> { f2(); f3() } // Necessary braces because of multiple statements. +} +// multiLine = 'necessary' +when (a) { + 1 -> + f1() + 2 -> { // Necessary braces because of multiple statements. + f2() + f3() + } +} + +// singleLine = 'consistent' +when (a) { + 1 -> { f1() } + 2 -> { f2() } +} +when (a) { + 1 -> f1() + 2 -> f2() +} +// multiLine = 'consistent' +when (a) { + 1 -> { + f1() + } + 2 -> { + f2() + f3() + } +} + +// singleLine = 'always' +when (a) { + 1 -> { f1() } + 2 -> { f2() } +} +// multiLine = 'always' +when (a) { + 1 -> { + f1() + } + 2 -> { + f2() + f3() + } +} +``` + +### CanBeNonNullable + +This rule inspects variables marked as nullable and reports which could be +declared as non-nullable instead. + +It's preferred to not have functions that do "nothing". +A function that does nothing when the value is null hides the logic, +so it should not allow null values in the first place. +It is better to move the null checks up around the calls, +instead of having it inside the function. + +This could lead to less nullability overall in the codebase. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Noncompliant Code: + +```kotlin +class A { + var a: Int? = 5 + + fun foo() { + a = 6 + } +} + +class A { + val a: Int? + get() = 5 +} + +fun foo(a: Int?) { + val b = a!! + 2 +} + +fun foo(a: Int?) { + if (a != null) { + println(a) + } +} + +fun foo(a: Int?) { + if (a == null) return + println(a) +} +``` + +#### Compliant Code: + +```kotlin +class A { + var a: Int = 5 + + fun foo() { + a = 6 + } +} + +class A { + val a: Int + get() = 5 +} + +fun foo(a: Int) { + val b = a + 2 +} + +fun foo(a: Int) { + println(a) +} +``` + +### CascadingCallWrapping + +Requires that all chained calls are placed on a new line if a preceding one is. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``includeElvis`` (default: ``true``) + + require trailing elvis expressions to be wrapped on a new line + +#### Noncompliant Code: + +```kotlin +foo() +.bar().baz() +``` + +#### Compliant Code: + +```kotlin +foo().bar().baz() + +foo() +.bar() +.baz() +``` + +### ClassOrdering + +This rule ensures class contents are ordered as follows as recommended by the Kotlin +[Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html#class-layout): +- Property declarations and initializer blocks +- Secondary constructors +- Method declarations +- Companion object + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class OutOfOrder { + companion object { + const val IMPORTANT_VALUE = 3 + } + + fun returnX(): Int { + return x + } + + private val x = 2 +} +``` + +#### Compliant Code: + +```kotlin +class InOrder { + private val x = 2 + + fun returnX(): Int { + return x + } + + companion object { + const val IMPORTANT_VALUE = 3 + } +} +``` + +### CollapsibleIfStatements + +This rule detects `if` statements which can be collapsed. This can reduce nesting and help improve readability. + +However, carefully consider whether merging the if statements actually improves readability, as collapsing the +statements may hide some edge cases from the reader. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val i = 1 +if (i > 0) { + if (i < 5) { + println(i) + } +} +``` + +#### Compliant Code: + +```kotlin +val i = 1 +if (i > 0 && i < 5) { + println(i) +} +``` + +### DataClassContainsFunctions + +This rule reports functions inside data classes which have not been marked as a conversion function. + +Data classes should mainly be used to store data. This rule assumes that they should not contain any extra functions +aside functions that help with converting objects from/to one another. +Data classes will automatically have a generated `equals`, `toString` and `hashCode` function by the compiler. + +**Active by default**: No + +**Debt**: 20min + +#### Configuration options: + +* ``conversionFunctionPrefix`` (default: ``['to']``) + + allowed conversion function names + +* ``allowOperators`` (default: ``false``) + + allows overloading an operator + +#### Noncompliant Code: + +```kotlin +data class DataClassWithFunctions(val i: Int) { + fun foo() { } +} +``` + +### DataClassShouldBeImmutable + +This rule reports mutable properties inside data classes. + +Data classes should mainly be used to store immutable data. This rule assumes that they should not contain any +mutable properties. + +**Active by default**: No + +**Debt**: 20min + +#### Noncompliant Code: + +```kotlin +data class MutableDataClass(var i: Int) { + var s: String? = null +} +``` + +#### Compliant Code: + +```kotlin +data class ImmutableDataClass( + val i: Int, + val s: String? +) +``` + +### DestructuringDeclarationWithTooManyEntries + +Destructuring declarations with too many entries are hard to read and understand. +To increase readability they should be refactored to reduce the number of entries or avoid using a destructuring +declaration. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 10min + +#### Configuration options: + +* ``maxDestructuringEntries`` (default: ``3``) + + maximum allowed elements in a destructuring declaration + +#### Noncompliant Code: + +```kotlin +data class TooManyElements(val a: Int, val b: Int, val c: Int, val d: Int) +val (a, b, c, d) = TooManyElements(1, 2, 3, 4) +``` + +#### Compliant Code: + +```kotlin +data class FewerElements(val a: Int, val b: Int, val c: Int) +val (a, b, c) = TooManyElements(1, 2, 3) +``` + +### DoubleNegativeLambda + +Detects negation in lambda blocks where the function name is also in the negative (like `takeUnless`). +A double negative is harder to read than a positive. In particular, if there are multiple conditions with `&&` etc. inside +the lambda, then the reader may need to unpack these using DeMorgan's laws. Consider rewriting the lambda to use a positive version +of the function (like `takeIf`). + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``negativeFunctions`` (default: ``['takeUnless', 'none']``) + + Function names expressed in the negative that can form double negatives with their lambda blocks. These are grouped together with a recommendation to use a positive counterpart, or `null` if this is unknown. + +* ``negativeFunctionNameParts`` (default: ``['not', 'non']``) + + Function name parts to look for in the lambda block when deciding if the lambda contains a negative. + +#### Noncompliant Code: + +```kotlin +fun Int.evenOrNull() = takeUnless { it % 2 != 0 } +``` + +#### Compliant Code: + +```kotlin +fun Int.evenOrNull() = takeIf { it % 2 == 0 } +``` + +### EqualsNullCall + +To compare an object with `null` prefer using `==`. This rule detects and reports instances in the code where the +`equals()` method is used to compare a value with `null`. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun isNull(str: String) = str.equals(null) +``` + +#### Compliant Code: + +```kotlin +fun isNull(str: String) = str == null +``` + +### EqualsOnSignatureLine + +Requires that the equals sign, when used for an expression style function, is on the same line as the +rest of the function signature. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun stuff(): Int + = 5 + +fun foo(): Int where V : Int + = 5 +``` + +#### Compliant Code: + +```kotlin +fun stuff() = 5 + +fun stuff() = + foo.bar() + +fun foo(): Int where V : Int = 5 +``` + +### ExplicitCollectionElementAccessMethod + +In Kotlin functions `get` or `set` can be replaced with the shorter operator — `[]`, +see [Indexed access operator](https://kotlinlang.org/docs/operator-overloading.html#indexed-access-operator). +Prefer the usage of the indexed access operator `[]` for map or list element access or insert methods. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val map = mutableMapOf() +map.put("key", "value") +val value = map.get("key") +``` + +#### Compliant Code: + +```kotlin +val map = mutableMapOf() +map["key"] = "value" +val value = map["key"] +``` + +### ExplicitItLambdaParameter + +Lambda expressions are one of the core features of the language. They often include very small chunks of +code using only one parameter. In this cases Kotlin can supply the implicit `it` parameter +to make code more concise. It fits most use cases, but when faced larger or nested chunks of code, +you might want to add an explicit name for the parameter. Naming it just `it` is meaningless and only +makes your code misleading, especially when dealing with nested functions. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +a?.let { it -> it.plus(1) } +foo.flatMapObservable { it -> Observable.fromIterable(it) } +listOfPairs.map(::second).forEach { it -> + it.execute() +} +collection.zipWithNext { it, next -> Pair(it, next) } +``` + +#### Compliant Code: + +```kotlin +a?.let { it.plus(1) } // Much better to use implicit it +foo.flatMapObservable(Observable::fromIterable) // Here we can have a method reference + +// For multiline blocks it is usually better come up with a clear and more meaningful name +listOfPairs.map(::second).forEach { apiRequest -> + apiRequest.execute() +} + +// Lambdas with multiple parameter should be named clearly, using it for one of them can be confusing +collection.zipWithNext { prev, next -> + Pair(prev, next) +} +``` + +### ExpressionBodySyntax + +Functions which only contain a `return` statement can be collapsed to an expression body. This shortens and +cleans up the code. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``includeLineWrapping`` (default: ``false``) + + include return statements with line wraps in it + +#### Noncompliant Code: + +```kotlin +fun stuff(): Int { + return 5 +} +``` + +#### Compliant Code: + +```kotlin +fun stuff() = 5 + +fun stuff() { + return + moreStuff() + .getStuff() + .stuffStuff() +} +``` + +### ForbiddenAnnotation + +This rule allows to set a list of forbidden annotations. This can be used to discourage the use +of language annotations which do not require explicit import. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``annotations`` (default: ``['java.lang.SuppressWarnings', 'java.lang.Deprecated', 'java.lang.annotation.Documented', 'java.lang.annotation.Target', 'java.lang.annotation.Retention', 'java.lang.annotation.Repeatable', 'java.lang.annotation.Inherited']``) + + List of fully qualified annotation classes which are forbidden. + +#### Noncompliant Code: + +```kotlin +@SuppressWarnings("unused") +class SomeClass() +``` + +#### Compliant Code: + +```kotlin +@Suppress("unused") +class SomeClass() +``` + +### ForbiddenComment + +This rule allows to set a list of comments which are forbidden in the codebase and should only be used during +development. Offending code comments will then be reported. + +The regular expressions in `comments` list will have the following behaviors while matching the comments: +* **Each comment will be handled individually.** + * single line comments are always separate, consecutive lines are not merged. + * multi line comments are not split up, the regex will be applied to the whole comment. + * KDoc comments are not split up, the regex will be applied to the whole comment. +* **The following comment delimiters (and indentation before them) are removed** before applying the regex: + `//`, `// `, `/​*`, `/​* `, `/​**`, `*` aligners, `*​/`, ` *​/` +* **The regex is applied as a multiline regex**, + see [Anchors](https://www.regular-expressions.info/anchors.html) for more info. + To match the start and end of each line, use `^` and `$`. + To match the start and end of the whole comment, use `\A` and `\Z`. + To turn off multiline, use `(?-m)` at the start of your regex. +* **The regex is applied with dotall semantics**, meaning `.` will match any character including newlines, + this is to ensure that freeform line-wrapping doesn't mess with simple regexes. + To turn off this behavior, use `(?-s)` at the start of your regex, or use `[^\r\n]*` instead of `.*`. +* **The regex will be searched using "contains" semantics** not "matches", + so partial comment matches will flag forbidden comments. + In practice this means there's no need to start and end the regex with `.*`. + +The rule can be configured to add extra comments to the list of forbidden comments, here are some examples: +```yaml +ForbiddenComment: + comments: + # Repeat the default configuration if it's still needed. + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + # Add additional patterns to the list. + + - reason: 'Authors are not recorded in KDoc.' + value: '@author' + + - reason: 'REVIEW markers are not allowed in production code, only use before PR is merged.' + value: '^\s*(?i)REVIEW\b' + # Non-compliant: // REVIEW this code before merging. + # Compliant: // Preview will show up here. + + - reason: 'Use @androidx.annotation.VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) instead.' + value: '^private$' + # Non-compliant: /*private*/fun f() { } + + - reason: 'KDoc tag should have a value.' + value: '^\s*@(?!suppress|hide)\w+\s*$' + # Non-compliant: /** ... @see */ + # Compliant: /** ... @throws IOException when there's a network problem */ + + - reason: 'include an issue link at the beginning preceded by a space' + value: 'BUG:(?! https://github\.com/company/repo/issues/\d+).*' +``` + +By default the commonly used todo markers are forbidden: `TODO:`, `FIXME:` and `STOPSHIP:`. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 10min + +#### Configuration options: + +* ~~``values``~~ (default: ``[]``) + + **Deprecated**: Use `comments` instead, make sure you escape your text for Regular Expressions. + + forbidden comment strings + +* ``comments`` (default: ``['FIXME:', 'STOPSHIP:', 'TODO:']``) + + forbidden comment string patterns + +* ``allowedPatterns`` (default: ``''``) + + ignores comments which match the specified regular expression. For example `Ticket|Task`. + +* ~~``customMessage``~~ (default: ``''``) + + **Deprecated**: Use `comments` and provide `reason` against each `value`. + + error message which overrides the default one + +#### Noncompliant Code: + +```kotlin +val a = "" // TODO: remove please +/** +* FIXME: this is a hack +*/ +fun foo() { } +/* STOPSHIP: */ +``` + +### ForbiddenImport + +Reports all imports that are forbidden. + +This rule allows to set a list of forbidden [imports]. +This can be used to discourage the use of unstable, experimental or deprecated APIs. + +**Active by default**: No + +**Debt**: 10min + +#### Configuration options: + +* ``imports`` (default: ``[]``) + + imports which should not be used + +* ``forbiddenPatterns`` (default: ``''``) + + reports imports which match the specified regular expression. For example `net.*R`. + +#### Noncompliant Code: + +```kotlin +import kotlin.jvm.JvmField +import kotlin.SinceKotlin +``` + +### ForbiddenMethodCall + +Reports all method or constructor invocations that are forbidden. + +This rule allows to set a list of forbidden [methods] or constructors. This can be used to discourage the use +of unstable, experimental or deprecated methods, especially for methods imported from external libraries. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 10min + +#### Configuration options: + +* ``methods`` (default: ``['kotlin.io.print', 'kotlin.io.println']``) + + List of fully qualified method signatures which are forbidden. Methods can be defined without full signature (i.e. `java.time.LocalDate.now`) which will report calls of all methods with this name or with full signature (i.e. `java.time.LocalDate(java.time.Clock)`) which would report only call with this concrete signature. If you want to forbid an extension function like `fun String.hello(a: Int)` you should add the receiver parameter as the first parameter like this: `hello(kotlin.String, kotlin.Int)`. To forbid constructor calls you need to define them with ``, for example `java.util.Date.`. To forbid calls involving type parameters, omit them, for example `fun hello(args: Array)` is referred to as simply `hello(kotlin.Array)` (also the signature for vararg parameters). To forbid methods from the companion object reference the Companion class, for example as `TestClass.Companion.hello()` (even if it is marked `@JvmStatic`). + +#### Noncompliant Code: + +```kotlin +import java.lang.System +fun main() { + System.gc() + System::gc +} +``` + +### ForbiddenSuppress + +Report suppressions of all forbidden rules. + +This rule allows to set a list of [rules] whose suppression is forbidden. +This can be used to discourage the abuse of the `Suppress` and `SuppressWarnings` annotations. + +This rule is not capable of reporting suppression of itself, as that's a language feature with precedence. + +**Active by default**: No + +**Debt**: 10min + +#### Configuration options: + +* ``rules`` (default: ``[]``) + + Rules whose suppression is forbidden. + +#### Noncompliant Code: + +```kotlin +package foo + +// When the rule "MaximumLineLength" is forbidden +@Suppress("MaximumLineLength", "UNCHECKED_CAST") +class Bar +``` + +#### Compliant Code: + +```kotlin +package foo + +// When the rule "MaximumLineLength" is forbidden +@Suppress("UNCHECKED_CAST") +class Bar +``` + +### ForbiddenVoid + +This rule detects usages of `Void` and reports them as forbidden. +The Kotlin type `Unit` should be used instead. This type corresponds to the `Void` class in Java +and has only one value - the `Unit` object. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``ignoreOverridden`` (default: ``false``) + + ignores void types in signatures of overridden functions + +* ``ignoreUsageInGenerics`` (default: ``false``) + + ignore void types as generic arguments + +#### Noncompliant Code: + +```kotlin +runnable: () -> Void +var aVoid: Void? = null +``` + +#### Compliant Code: + +```kotlin +runnable: () -> Unit +Void::class +``` + +### FunctionOnlyReturningConstant + +A function that only returns a single constant can be misleading. Instead, prefer declaring the constant +as a `const val`. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Configuration options: + +* ``ignoreOverridableFunction`` (default: ``true``) + + if overriden functions should be ignored + +* ``ignoreActualFunction`` (default: ``true``) + + if actual functions should be ignored + +* ``excludedFunctions`` (default: ``[]``) + + excluded functions + +* ~~``excludeAnnotatedFunction``~~ (default: ``[]``) + + **Deprecated**: Use `ignoreAnnotated` instead + + allows to provide a list of annotations that disable this check + +#### Noncompliant Code: + +```kotlin +fun functionReturningConstantString() = "1" +``` + +#### Compliant Code: + +```kotlin +const val constantString = "1" +``` + +### LoopWithTooManyJumpStatements + +Loops which contain multiple `break` or `continue` statements are hard to read and understand. +To increase readability they should be refactored into simpler loops. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 10min + +#### Configuration options: + +* ``maxJumpCount`` (default: ``1``) + + maximum allowed jumps in a loop + +#### Noncompliant Code: + +```kotlin +val strs = listOf("foo, bar") +for (str in strs) { + if (str == "bar") { + break + } else { + continue + } +} +``` + +### MagicNumber + +This rule detects and reports usages of magic numbers in the code. Prefer defining constants with clear names +describing what the magic number means. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 10min + +#### Configuration options: + +* ``ignoreNumbers`` (default: ``['-1', '0', '1', '2']``) + + numbers which do not count as magic numbers + +* ``ignoreHashCodeFunction`` (default: ``true``) + + whether magic numbers in hashCode functions should be ignored + +* ``ignorePropertyDeclaration`` (default: ``false``) + + whether magic numbers in property declarations should be ignored + +* ``ignoreLocalVariableDeclaration`` (default: ``false``) + + whether magic numbers in local variable declarations should be ignored + +* ``ignoreConstantDeclaration`` (default: ``true``) + + whether magic numbers in constant declarations should be ignored + +* ``ignoreCompanionObjectPropertyDeclaration`` (default: ``true``) + + whether magic numbers in companion object declarations should be ignored + +* ``ignoreAnnotation`` (default: ``false``) + + whether magic numbers in annotations should be ignored + +* ``ignoreNamedArgument`` (default: ``true``) + + whether magic numbers in named arguments should be ignored + +* ``ignoreEnums`` (default: ``false``) + + whether magic numbers in enums should be ignored + +* ``ignoreRanges`` (default: ``false``) + + whether magic numbers in ranges should be ignored + +* ``ignoreExtensionFunctions`` (default: ``true``) + + whether magic numbers as subject of an extension function should be ignored + +#### Noncompliant Code: + +```kotlin +class User { + + fun checkName(name: String) { + if (name.length > 42) { + throw IllegalArgumentException("username is too long") + } + // ... + } +} +``` + +#### Compliant Code: + +```kotlin +class User { + + fun checkName(name: String) { + if (name.length > MAX_USERNAME_SIZE) { + throw IllegalArgumentException("username is too long") + } + // ... + } + + companion object { + private const val MAX_USERNAME_SIZE = 42 + } +} +``` + +### MandatoryBracesLoops + +This rule detects multi-line `for` and `while` loops which do not have braces. +Adding braces would improve readability and avoid possible errors. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +for (i in 0..10) + println(i) + +while (true) + println("Hello, world") + +do + println("Hello, world") +while (true) +``` + +#### Compliant Code: + +```kotlin +for (i in 0..10) { + println(i) +} + +for (i in 0..10) println(i) + +while (true) { + println("Hello, world") +} + +while (true) println("Hello, world") + +do { + println("Hello, world") +} while (true) + +do println("Hello, world") while (true) +``` + +### MaxChainedCallsOnSameLine + +Limits the number of chained calls which can be placed on a single line. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ``maxChainedCalls`` (default: ``5``) + + maximum chained calls allowed on a single line + +#### Noncompliant Code: + +```kotlin +a().b().c().d().e().f() +``` + +#### Compliant Code: + +```kotlin +a().b().c() +.d().e().f() +``` + +### MaxLineLength + +This rule reports lines of code which exceed a defined maximum line length. + +Long lines might be hard to read on smaller screens or printouts. Additionally, having a maximum line length +in the codebase will help make the code more uniform. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``maxLineLength`` (default: ``120``) + + maximum line length + +* ``excludePackageStatements`` (default: ``true``) + + if package statements should be ignored + +* ``excludeImportStatements`` (default: ``true``) + + if import statements should be ignored + +* ``excludeCommentStatements`` (default: ``false``) + + if comment statements should be ignored + +* ``excludeRawStrings`` (default: ``true``) + + if raw strings should be ignored + +### MayBeConst + +This rule identifies and reports properties (`val`) that may be `const val` instead. +Using `const val` can lead to better performance of the resulting bytecode as well as better interoperability with +Java. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +**Aliases**: MayBeConstant + +#### Noncompliant Code: + +```kotlin +val myConstant = "abc" +``` + +#### Compliant Code: + +```kotlin +const val MY_CONSTANT = "abc" +``` + +### ModifierOrder + +This rule reports cases in the code where modifiers are not in the correct order. The default modifier order is +taken from: [Modifiers order](https://kotlinlang.org/docs/coding-conventions.html#modifiers-order) + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +lateinit internal val str: String +``` + +#### Compliant Code: + +```kotlin +internal lateinit val str: String +``` + +### MultilineLambdaItParameter + +Lambda expressions are very useful in a lot of cases, and they often include very small chunks of +code using only one parameter. In this cases Kotlin can supply the implicit `it` parameter +to make code more concise. However, when you are dealing with lambdas that contain multiple statements, +you might end up with code that is hard to read if you don't specify a readable, descriptive parameter name +explicitly. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val digits = 1234.let { + println(it) + listOf(it) +} + +val digits = 1234.let { it -> + println(it) + listOf(it) +} + +val flat = listOf(listOf(1), listOf(2)).mapIndexed { index, it -> + println(it) + it + index +} +``` + +#### Compliant Code: + +```kotlin +val digits = 1234.let { explicitParameterName -> + println(explicitParameterName) + listOf(explicitParameterName) +} + +val lambda = { item: Int, that: String -> + println(item) + item.toString() + that +} + +val digits = 1234.let { listOf(it) } +val digits = 1234.let { + listOf(it) +} +val digits = 1234.let { it -> listOf(it) } +val digits = 1234.let { it -> + listOf(it) +} +val digits = 1234.let { explicit -> listOf(explicit) } +val digits = 1234.let { explicit -> + listOf(explicit) +} +``` + +### MultilineRawStringIndentation + +This rule ensures that raw strings have a consistent indentation. + +The content of a multi line raw string should have the same indentation as the enclosing expression plus the +configured indentSize. The closing triple-quotes (`"""`) must have the same indentation as the enclosing expression. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``indentSize`` (default: ``4``) + + indentation size + +* ``trimmingMethods`` (default: ``['trimIndent', 'trimMargin']``) + + allows to provide a list of multiline string trimming methods + +#### Noncompliant Code: + +```kotlin +val a = """ +Hello World! +How are you? +""".trimMargin() + +val a = """ + Hello World! + How are you? + """.trimMargin() +``` + +#### Compliant Code: + +```kotlin +val a = """ + Hello World! + How are you? +""".trimMargin() + +val a = """ + Hello World! + How are you? +""".trimMargin() +``` + +### NestedClassesVisibility + +Nested classes inherit their visibility from the parent class +and are often used to implement functionality local to the class it is nested in. +These nested classes can't have a higher visibility than their parent. +However, the visibility can be further restricted by using a private modifier for instance. +In internal classes the _explicit_ public modifier for nested classes is misleading and thus unnecessary, +because the nested class still has an internal visibility. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +internal class Outer { + // explicit public modifier still results in an internal nested class + public class Nested +} +``` + +#### Compliant Code: + +```kotlin +internal class Outer { + class Nested1 + internal class Nested2 +} +``` + +### NewLineAtEndOfFile + +This rule reports files which do not end with a line separator. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +### NoTabs + +This rule reports if tabs are used in Kotlin files. +According to +[Google's Kotlin style guide](https://android.github.io/kotlin-guides/style.html#whitespace-characters) +the only whitespace chars that are allowed in a source file are the line terminator sequence +and the ASCII horizontal space character (0x20). Strings containing tabs are allowed. + +**Active by default**: No + +**Debt**: 5min + +### NullableBooleanCheck + +Detects nullable boolean checks which use an elvis expression `?:` rather than equals `==`. + +Per the [Kotlin coding conventions](https://kotlinlang.org/docs/coding-conventions.html#nullable-boolean-values-in-conditions) +converting a nullable boolean property to non-null should be done via `!= false` or `== true` +rather than `?: true` or `?: false` (respectively). + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +value ?: true +value ?: false +``` + +#### Compliant Code: + +```kotlin +value != false +value == true +``` + +### ObjectLiteralToLambda + +An anonymous object that does nothing other than the implementation of a single method +can be used as a lambda. + +See [SAM conversions](https://kotlinlang.org/docs/java-interop.html#sam-conversions), +[Functional (SAM) interfaces](https://kotlinlang.org/docs/fun-interfaces.html) + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +object : Foo { + override fun bar() { + } +} +``` + +#### Compliant Code: + +```kotlin +Foo { +} +``` + +### OptionalAbstractKeyword + +This rule reports `abstract` modifiers which are unnecessary and can be removed. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +abstract interface Foo { // abstract keyword not needed + + abstract fun x() // abstract keyword not needed + abstract var y: Int // abstract keyword not needed +} +``` + +#### Compliant Code: + +```kotlin +interface Foo { + + fun x() + var y: Int +} +``` + +### OptionalUnit + +It is not necessary to define a return type of `Unit` on functions or to specify a lone Unit statement. +This rule detects and reports instances where the `Unit` return type is specified on functions and the occurrences +of a lone Unit statement. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(): Unit { + return Unit +} +fun foo() = Unit + +fun doesNothing() { + Unit +} +``` + +#### Compliant Code: + +```kotlin +fun foo() { } + +// overridden no-op functions are allowed +override fun foo() = Unit +``` + +### OptionalWhenBraces + +This rule reports unnecessary braces in when expressions. These optional braces should be removed. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val i = 1 +when (i) { + 1 -> { println("one") } // unnecessary curly braces since there is only one statement + else -> println("else") +} +``` + +#### Compliant Code: + +```kotlin +val i = 1 +when (i) { + 1 -> println("one") + else -> println("else") +} +``` + +### PreferToOverPairSyntax + +This rule detects the usage of the Pair constructor to create pairs of values. + +Using `` to `` is preferred. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val pair = Pair(1, 2) +``` + +#### Compliant Code: + +```kotlin +val pair = 1 to 2 +``` + +### ProtectedMemberInFinalClass + +Kotlin classes are `final` by default. Thus classes which are not marked as `open` should not contain any `protected` +members. Consider using `private` or `internal` modifiers instead. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class ProtectedMemberInFinalClass { + protected var i = 0 +} +``` + +#### Compliant Code: + +```kotlin +class ProtectedMemberInFinalClass { + private var i = 0 +} +``` + +### RedundantExplicitType + +Local properties do not need their type to be explicitly provided when the inferred type matches the explicit type. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun function() { + val x: String = "string" +} +``` + +#### Compliant Code: + +```kotlin +fun function() { + val x = "string" +} +``` + +### RedundantHigherOrderMapUsage + +Redundant maps add complexity to the code and accomplish nothing. They should be removed or replaced with the proper +operator. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(list: List): List { + return list + .filter { it > 5 } + .map { it } +} + +fun bar(list: List): List { + return list + .filter { it > 5 } + .map { + doSomething(it) + it + } +} + +fun baz(set: Set): List { + return set.map { it } +} +``` + +#### Compliant Code: + +```kotlin +fun foo(list: List): List { + return list + .filter { it > 5 } +} + +fun bar(list: List): List { + return list + .filter { it > 5 } + .onEach { + doSomething(it) + } +} + +fun baz(set: Set): List { + return set.toList() +} +``` + +### RedundantVisibilityModifierRule + +This rule checks for redundant visibility modifiers. +One exemption is the +[explicit API mode](https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors) +In this mode, the visibility modifier should be defined explicitly even if it is public. +Hence, the rule ignores the visibility modifiers in explicit API mode. + +**Active by default**: No + +**Debt**: 5min + +**Aliases**: RedundantVisibilityModifier + +#### Noncompliant Code: + +```kotlin +public interface Foo { // public per default + + public fun bar() // public per default +} +``` + +#### Compliant Code: + +```kotlin +interface Foo { + + fun bar() +} +``` + +### ReturnCount + +Restrict the number of return methods allowed in methods. + +Having many exit points in a function can be confusing and impacts readability of the +code. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 10min + +#### Configuration options: + +* ``max`` (default: ``2``) + + define the maximum number of return statements allowed per function + +* ``excludedFunctions`` (default: ``['equals']``) + + define a list of function names to be ignored by this check + +* ``excludeLabeled`` (default: ``false``) + + if labeled return statements should be ignored + +* ``excludeReturnFromLambda`` (default: ``true``) + + if labeled return from a lambda should be ignored + +* ``excludeGuardClauses`` (default: ``false``) + + if true guard clauses at the beginning of a method should be ignored + +#### Noncompliant Code: + +```kotlin +fun foo(i: Int): String { + when (i) { + 1 -> return "one" + 2 -> return "two" + else -> return "other" + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo(i: Int): String { + return when (i) { + 1 -> "one" + 2 -> "two" + else -> "other" + } +} +``` + +### SafeCast + +This rule inspects casts and reports casts which could be replaced with safe casts instead. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun numberMagic(number: Number) { + val i = if (number is Int) number else null + // ... +} +``` + +#### Compliant Code: + +```kotlin +fun numberMagic(number: Number) { + val i = number as? Int + // ... +} +``` + +### SerialVersionUIDInSerializableClass + +Classes which implement the `Serializable` interface should also correctly declare a `serialVersionUID`. +This rule verifies that a `serialVersionUID` was correctly defined and declared as `private`. + +[More about `SerialVersionUID`](https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html) + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class IncorrectSerializable : Serializable { + + companion object { + val serialVersionUID = 1 // wrong declaration for UID + } +} +``` + +#### Compliant Code: + +```kotlin +class CorrectSerializable : Serializable { + + companion object { + const val serialVersionUID = 1L + } +} +``` + +### SpacingBetweenPackageAndImports + +This rule verifies spacing between package and import statements as well as between import statements and class +declarations. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +package foo +import a.b +class Bar { } +``` + +#### Compliant Code: + +```kotlin +package foo + +import a.b + +class Bar { } +``` + +### StringShouldBeRawString + +This rule reports when the string can be converted to Kotlin raw string. +Usage of a raw string is preferred as that avoids the need for escaping strings escape characters like \n, \t, ". +Raw string also allows us to represent multiline string without the need of \n. +Also, see [Kotlin coding convention](https://kotlinlang.org/docs/coding-conventions.html#strings) for +recommendation on using multiline strings + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``maxEscapedCharacterCount`` (default: ``2``) + + maximum escape characters allowed + +* ``ignoredCharacters`` (default: ``[]``) + + list of characters to ignore + +#### Noncompliant Code: + +```kotlin +val windowJson = "{\n" + + " \"window\": {\n" + + " \"title\": \"Sample Quantum With AI and ML Widget\",\n" + + " \"name\": \"main_window\",\n" + + " \"width\": 500,\n" + + " \"height\": 500\n" + + " }\n" + + "}" + +val patRegex = "/^(\\/[^\\/]+){0,2}\\/?\$/gm\n" +``` + +#### Compliant Code: + +```kotlin +val windowJson = """ + { + "window": { + "title": "Sample Quantum With AI and ML Widget", + "name": "main_window", + "width": 500, + "height": 500 + } + } +""".trimIndent() + +val patRegex = """/^(\/[^\/]+){0,2}\/?$/gm""" +``` + +### ThrowsCount + +Functions should have clear `throw` statements. Functions with many `throw` statements can be harder to read and lead +to confusion. Instead, prefer limiting the number of `throw` statements in a function. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 10min + +#### Configuration options: + +* ``max`` (default: ``2``) + + maximum amount of throw statements in a method + +* ``excludeGuardClauses`` (default: ``false``) + + if set to true, guard clauses do not count towards the allowed throws count + +#### Noncompliant Code: + +```kotlin +fun foo(i: Int) { + when (i) { + 1 -> throw IllegalArgumentException() + 2 -> throw IllegalArgumentException() + 3 -> throw IllegalArgumentException() + } +} +``` + +#### Compliant Code: + +```kotlin +fun foo(i: Int) { + when (i) { + 1,2,3 -> throw IllegalArgumentException() + } +} +``` + +### TrailingWhitespace + +This rule reports lines that end with a whitespace. + +**Active by default**: No + +**Debt**: 5min + +### TrimMultilineRawString + +All the Raw strings that have more than one line should be followed by `trimMargin()` or `trimIndent()`. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``trimmingMethods`` (default: ``['trimIndent', 'trimMargin']``) + + allows to provide a list of multiline string trimming methods + +#### Noncompliant Code: + +```kotlin +""" +Hello World! +How are you? +""" +``` + +#### Compliant Code: + +```kotlin +""" +| Hello World! +| How are you? +""".trimMargin() + +""" +Hello World! +How are you? +""".trimIndent() + +"""Hello World! How are you?""" +``` + +### UnderscoresInNumericLiterals + +This rule detects and reports long base 10 numbers which should be separated with underscores +for readability. For `Serializable` classes or objects, the field `serialVersionUID` is +explicitly ignored. For floats and doubles, anything to the right of the decimal point is ignored. + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ~~``acceptableDecimalLength``~~ (default: ``5``) + + **Deprecated**: Use `acceptableLength` instead + + Length under which base 10 numbers are not required to have underscores + +* ``acceptableLength`` (default: ``4``) + + Maximum number of consecutive digits that a numeric literal can have without using an underscore + +* ``allowNonStandardGrouping`` (default: ``false``) + + If set to false, groups of exactly three digits must be used. If set to true, 100_00 is allowed. + +#### Noncompliant Code: + +```kotlin +const val DEFAULT_AMOUNT = 1000000 +``` + +#### Compliant Code: + +```kotlin +const val DEFAULT_AMOUNT = 1_000_000 +``` + +### UnnecessaryAbstractClass + +This rule inspects `abstract` classes. In case an `abstract class` does not have any concrete members it should be +refactored into an interface. Abstract classes which do not define any `abstract` members should instead be +refactored into concrete classes. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Configuration options: + +* ~~``excludeAnnotatedClasses``~~ (default: ``[]``) + + **Deprecated**: Use `ignoreAnnotated` instead + + Allows you to provide a list of annotations that disable this check. + +#### Noncompliant Code: + +```kotlin +abstract class OnlyAbstractMembersInAbstractClass { // violation: no concrete members + + abstract val i: Int + abstract fun f() +} + +abstract class OnlyConcreteMembersInAbstractClass { // violation: no abstract members + + val i: Int = 0 + fun f() { } +} +``` + +#### Compliant Code: + +```kotlin +interface OnlyAbstractMembersInInterface { + val i: Int + fun f() +} + +class OnlyConcreteMembersInClass { + val i: Int = 0 + fun f() { } +} +``` + +### UnnecessaryAnnotationUseSiteTarget + +This rule inspects the use of the Annotation use-site Target. In case that the use-site Target is not needed it can +be removed. For more information check the kotlin documentation: +[Annotation use-site targets](https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets) + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +@property:Inject private val foo: String = "bar" // violation: unnecessary @property: + +class Module(@param:Inject private val foo: String) // violation: unnecessary @param: +``` + +#### Compliant Code: + +```kotlin +class Module(@Inject private val foo: String) +``` + +### UnnecessaryApply + +`apply` expressions are used frequently, but sometimes their usage should be replaced with +an ordinary method/extension function call to reduce visual complexity + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +config.apply { version = "1.2" } // can be replaced with `config.version = "1.2"` +config?.apply { environment = "test" } // can be replaced with `config?.environment = "test"` +config?.apply { println(version) } // `apply` can be replaced by `let` +``` + +#### Compliant Code: + +```kotlin +config.apply { + version = "1.2" + environment = "test" +} +``` + +### UnnecessaryBackticks + +This rule reports unnecessary backticks. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class `HelloWorld` +``` + +#### Compliant Code: + +```kotlin +class HelloWorld +``` + +### UnnecessaryBracesAroundTrailingLambda + +In Kotlin functions the last lambda parameter of a function is a function then a lambda expression passed as the +corresponding argument can be placed outside the parentheses. +see [Passing trailing lambdas](https://kotlinlang.org/docs/lambdas.html#passing-trailing-lambdas). +Prefer the usage of trailing lambda. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun test() { + repeat(10, { + println(it) + }) +} +``` + +#### Compliant Code: + +```kotlin +fun test() { + repeat(10) { + println(it) + } +} +``` + +### UnnecessaryFilter + +Unnecessary filters add complexity to the code and accomplish nothing. They should be removed. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val x = listOf(1, 2, 3) + .filter { it > 1 } + .count() + +val x = listOf(1, 2, 3) + .filter { it > 1 } + .isEmpty() +``` + +#### Compliant Code: + +```kotlin +val x = listOf(1, 2, 3) + .count { it > 2 } +} + +val x = listOf(1, 2, 3) + .none { it > 1 } +``` + +### UnnecessaryInheritance + +This rule reports unnecessary super types. Inheriting from `Any` or `Object` is unnecessary and should simply be +removed. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class A : Any() +class B : Object() +``` + +### UnnecessaryInnerClass + +This rule reports unnecessary inner classes. Nested classes that do not access members from the outer class do +not require the `inner` qualifier. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class A { + val foo = "BAR" + + inner class B { + val fizz = "BUZZ" + + fun printFizz() { + println(fizz) + } + } +} +``` + +### UnnecessaryLet + +`let` expressions are used extensively in our code for null-checking and chaining functions, +but sometimes their usage should be replaced with an ordinary method/extension function call +to reduce visual complexity. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +a.let { print(it) } // can be replaced with `print(a)` +a.let { it.plus(1) } // can be replaced with `a.plus(1)` +a?.let { it.plus(1) } // can be replaced with `a?.plus(1)` +a?.let { that -> that.plus(1) }?.let { it.plus(1) } // can be replaced with `a?.plus(1)?.plus(1)` +a.let { 1.plus(1) } // can be replaced with `1.plus(1)` +a?.let { 1.plus(1) } // can be replaced with `if (a != null) 1.plus(1)` +``` + +#### Compliant Code: + +```kotlin +a?.let { print(it) } +a?.let { 1.plus(it) } ?.let { msg -> print(msg) } +a?.let { it.plus(it) } +val b = a?.let { 1.plus(1) } +``` + +### UnnecessaryParentheses + +This rule reports unnecessary parentheses around expressions. +These unnecessary parentheses can safely be removed. + +Added in v1.0.0.RC4 + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``allowForUnclearPrecedence`` (default: ``false``) + + allow parentheses when not strictly required but precedence may be unclear, such as `(a && b) || c` + +#### Noncompliant Code: + +```kotlin +val local = (5 + 3) + +if ((local == 8)) { } + +fun foo() { + function({ input -> println(input) }) +} +``` + +#### Compliant Code: + +```kotlin +val local = 5 + 3 + +if (local == 8) { } + +fun foo() { + function { input -> println(input) } +} +``` + +### UntilInsteadOfRangeTo + +Reports calls to '..' operator instead of calls to 'until'. +'until' is applicable in cases where the upper range value is described as +some value subtracted by 1. 'until' helps to prevent off-by-one errors. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +for (i in 0 .. 10 - 1) {} +val range = 0 .. 10 - 1 +``` + +#### Compliant Code: + +```kotlin +for (i in 0 until 10) {} +val range = 0 until 10 +``` + +### UnusedImports + +This rule reports unused imports. Unused imports are dead code and should be removed. +Exempt from this rule are imports resulting from references to elements within KDoc and +from destructuring declarations (componentN imports). + +**Active by default**: No + +**Debt**: 5min + +### UnusedParameter + +An unused parameter can be removed to simplify the signature of the function. + +**Active by default**: Yes - Since v1.23.0 + +**Debt**: 5min + +**Aliases**: UNUSED_VARIABLE, UNUSED_PARAMETER, unused, UnusedPrivateMember + +#### Configuration options: + +* ``allowedNames`` (default: ``'ignored|expected'``) + + unused parameter names matching this regex are ignored + +#### Noncompliant Code: + +```kotlin +fun foo(unused: String) { +println() +} +``` + +#### Compliant Code: + +```kotlin +fun foo(used: String) { +println(used) +} +``` + +### UnusedPrivateClass + +Reports unused private classes. If private classes are unused they should be removed. Otherwise, this dead code +can lead to confusion and potential bugs. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +**Aliases**: unused + +### UnusedPrivateMember + +Reports unused private functions. + +If these private functions are unused they should be removed. Otherwise, this dead code +can lead to confusion and potential bugs. + +**Active by default**: Yes - Since v1.16.0 + +**Debt**: 5min + +**Aliases**: UNUSED_VARIABLE, UNUSED_PARAMETER, unused + +#### Configuration options: + +* ``allowedNames`` (default: ``''``) + + unused private function names matching this regex are ignored + +### UnusedPrivateProperty + +An unused private property can be removed to simplify the source file. + +This rule also detects unused constructor parameters since these can become +properties of the class when they are declared with `val` or `var`. + +**Active by default**: Yes - Since v1.23.0 + +**Debt**: 5min + +**Aliases**: UNUSED_VARIABLE, UNUSED_PARAMETER, unused, UnusedPrivateMember + +#### Configuration options: + +* ``allowedNames`` (default: ``'_|ignored|expected|serialVersionUID'``) + + unused property names matching this regex are ignored + +#### Noncompliant Code: + +```kotlin +class Foo { +private val unused = "unused" +} +``` + +#### Compliant Code: + +```kotlin +class Foo { +private val used = "used" + +fun greet() { + println(used) +} +} +``` + +### UseAnyOrNoneInsteadOfFind + +Turn on this rule to flag `find` calls for null check that can be replaced with a `any` or `none` call. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +listOf(1, 2, 3).find { it == 4 } != null +listOf(1, 2, 3).find { it == 4 } == null +``` + +#### Compliant Code: + +```kotlin +listOf(1, 2, 3).any { it == 4 } +listOf(1, 2, 3).none { it == 4 } +``` + +### UseArrayLiteralsInAnnotations + +This rule detects annotations which use the arrayOf(...) syntax instead of the array literal [...] syntax. +The latter should be preferred as it is more readable. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +@PositiveCase(arrayOf("...")) +``` + +#### Compliant Code: + +```kotlin +@NegativeCase(["..."]) +``` + +### UseCheckNotNull + +Turn on this rule to flag `check` calls for not-null check that can be replaced with a `checkNotNull` call. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +check(x != null) +``` + +#### Compliant Code: + +```kotlin +checkNotNull(x) +``` + +### UseCheckOrError + +Kotlin provides a concise way to check invariants as well as pre- and post-conditions. +Prefer them instead of manually throwing an IllegalStateException. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +if (value == null) throw IllegalStateException("value should not be null") +if (value < 0) throw IllegalStateException("value is $value but should be at least 0") +when(a) { + 1 -> doSomething() + else -> throw IllegalStateException("Unexpected value") +} +``` + +#### Compliant Code: + +```kotlin +checkNotNull(value) { "value should not be null" } +check(value >= 0) { "value is $value but should be at least 0" } +when(a) { + 1 -> doSomething() + else -> error("Unexpected value") +} +``` + +### UseDataClass + +Classes that simply hold data should be refactored into a `data class`. Data classes are specialized to hold data +and generate `hashCode`, `equals` and `toString` implementations as well. + +Read more about [data classes](https://kotlinlang.org/docs/data-classes.html) + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ~~``excludeAnnotatedClasses``~~ (default: ``[]``) + + **Deprecated**: Use `ignoreAnnotated` instead + + allows to provide a list of annotations that disable this check + +* ``allowVars`` (default: ``false``) + + allows to relax this rule in order to exclude classes that contains one (or more) vars + +#### Noncompliant Code: + +```kotlin +class DataClassCandidate(val i: Int) { + val i2: Int = 0 +} +``` + +#### Compliant Code: + +```kotlin +data class DataClass(val i: Int, val i2: Int) + +// classes with delegating interfaces are compliant +interface I +class B() : I +class A(val b: B) : I by b +``` + +### UseEmptyCounterpart + +Instantiation of an object's "empty" state should use the object's "empty" initializer for clarity purposes. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +arrayOf() +listOf() // or listOfNotNull() +mapOf() +sequenceOf() +setOf() +``` + +#### Compliant Code: + +```kotlin +emptyArray() +emptyList() +emptyMap() +emptySequence() +emptySet() +``` + +### UseIfEmptyOrIfBlank + +This rule detects `isEmpty` or `isBlank` calls to assign a default value. They can be replaced with `ifEmpty` or +`ifBlank` calls. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun test(list: List, s: String) { + val a = if (list.isEmpty()) listOf(1) else list + val b = if (list.isNotEmpty()) list else listOf(2) + val c = if (s.isBlank()) "foo" else s + val d = if (s.isNotBlank()) s else "bar" +} +``` + +#### Compliant Code: + +```kotlin +fun test(list: List, s: String) { + val a = list.ifEmpty { listOf(1) } + val b = list.ifEmpty { listOf(2) } + val c = s.ifBlank { "foo" } + val d = s.ifBlank { "bar" } +} +``` + +### UseIfInsteadOfWhen + +Binary expressions are better expressed using an `if` expression than a `when` expression. + +See [if versus when](https://kotlinlang.org/docs/coding-conventions.html#if-versus-when) + +**Active by default**: No + +**Debt**: 5min + +#### Configuration options: + +* ``ignoreWhenContainingVariableDeclaration`` (default: ``false``) + + ignores when statements with a variable declaration used in the subject + +#### Noncompliant Code: + +```kotlin +when (x) { + null -> true + else -> false +} +``` + +#### Compliant Code: + +```kotlin +if (x == null) true else false +``` + +### UseIsNullOrEmpty + +This rule detects null or empty checks that can be replaced with `isNullOrEmpty()` call. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun foo(x: List?) { + if (x == null || x.isEmpty()) return +} +fun bar(x: List?) { + if (x == null || x.count() == 0) return +} +fun baz(x: List?) { + if (x == null || x.size == 0) return +} +``` + +#### Compliant Code: + +```kotlin +if (x.isNullOrEmpty()) return +``` + +### UseLet + +`if` expressions that either check for not-null and return `null` in the false case or check for `null` and returns +`null` in the truthy case are better represented as `?.let {}` blocks. + +**Active by default**: No + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +if (x != null) { x.transform() } else null +if (x == null) null else y +``` + +#### Compliant Code: + +```kotlin +x?.let { it.transform() } +x?.let { y } +``` + +### UseOrEmpty + +This rule detects `?: emptyList()` that can be replaced with `orEmpty()` call. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +fun test(x: List?, s: String?) { + val a = x ?: emptyList() + val b = s ?: "" +} +``` + +#### Compliant Code: + +```kotlin +fun test(x: List?, s: String?) { + val a = x.orEmpty() + val b = s.orEmpty() +} +``` + +### UseRequire + +Kotlin provides a much more concise way to check preconditions than to manually throw an +IllegalArgumentException. + +**Active by default**: Yes - Since v1.21.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +if (value == null) throw IllegalArgumentException("value should not be null") +if (value < 0) throw IllegalArgumentException("value is $value but should be at least 0") +``` + +#### Compliant Code: + +```kotlin +requireNotNull(value) { "value should not be null" } +require(value >= 0) { "value is $value but should be at least 0" } +``` + +### UseRequireNotNull + +Turn on this rule to flag `require` calls for not-null check that can be replaced with a `requireNotNull` call. + +**Active by default**: Yes - Since v1.21.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +require(x != null) +``` + +#### Compliant Code: + +```kotlin +requireNotNull(x) +``` + +### UseSumOfInsteadOfFlatMapSize + +Turn on this rule to flag `flatMap` and `size/count` calls that can be replaced with a `sumOf` call. + +**Active by default**: No + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class Foo(val foo: List) +list.flatMap { it.foo }.size +list.flatMap { it.foo }.count() +list.flatMap { it.foo }.count { it > 2 } +listOf(listOf(1), listOf(2, 3)).flatten().size +``` + +#### Compliant Code: + +```kotlin +list.sumOf { it.foo.size } +list.sumOf { it.foo.count() } +list.sumOf { it.foo.count { foo -> foo > 2 } } +listOf(listOf(1), listOf(2, 3)).sumOf { it.size } +``` + +### UselessCallOnNotNull + +The Kotlin stdlib provides some functions that are designed to operate on references that may be null. These +functions can also be called on non-nullable references or on collections or sequences that are known to be empty - +the calls are redundant in this case and can be removed or should be changed to a call that does not check whether +the value is null or not. + +**Active by default**: Yes - Since v1.2.0 + +**Requires Type Resolution** + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +val testList = listOf("string").orEmpty() +val testList2 = listOf("string").orEmpty().map { _ } +val testList3 = listOfNotNull("string") +val testString = ""?.isNullOrBlank() +``` + +#### Compliant Code: + +```kotlin +val testList = listOf("string") +val testList2 = listOf("string").map { } +val testList3 = listOf("string") +val testString = ""?.isBlank() +``` + +### UtilityClassWithPublicConstructor + +A class which only contains utility variables and functions with no concrete implementation can be refactored +into an `object` or a class with a non-public constructor. +Furthermore, this rule reports utility classes which are not final. + +**Active by default**: Yes - Since v1.2.0 + +**Debt**: 5min + +#### Noncompliant Code: + +```kotlin +class UtilityClassViolation { + + // public constructor here + constructor() { + // ... + } + + companion object { + val i = 0 + } +} + +open class UtilityClassViolation private constructor() { + + // ... +} +``` + +#### Compliant Code: + +```kotlin +class UtilityClass { + + private constructor() { + // ... + } + + companion object { + val i = 0 + } +} +object UtilityClass { + + val i = 0 +} +``` + +### VarCouldBeVal + +Reports var declarations (both local variables and private class properties) that could be val, +as they are not re-assigned. Val declarations are assign-once (read-only), which makes understanding +the current state easier. + +**Active by default**: Yes - Since v1.16.0 + +**Requires Type Resolution** + +**Debt**: 5min + +**Aliases**: CanBeVal + +#### Configuration options: + +* ``ignoreLateinitVar`` (default: ``false``) + + Whether to ignore uninitialized lateinit vars + +#### Noncompliant Code: + +```kotlin +fun example() { + var i = 1 // violation: this variable is never re-assigned + val j = i + 1 +} +``` + +#### Compliant Code: + +```kotlin +fun example() { + val i = 1 + val j = i + 1 +} +``` + +### WildcardImport + +Wildcard imports should be replaced with imports using fully qualified class names. This helps increase clarity of +which classes are imported and helps prevent naming conflicts. + +Library updates can introduce naming clashes with your own classes which might result in compilation errors. + +**NOTE**: This rule has a twin implementation NoWildcardImports in the formatting rule set (a wrapped KtLint rule). +When suppressing an issue of WildcardImport in the baseline file, make sure to suppress the corresponding NoWildcardImports issue. + +**Active by default**: Yes - Since v1.0.0 + +**Debt**: 5min + +#### Configuration options: + +* ``excludeImports`` (default: ``['java.util.*']``) + + Define a list of package names that should be allowed to be imported with wildcard imports. + +#### Noncompliant Code: + +```kotlin +import io.gitlab.arturbosch.detekt.* + +class DetektElements { + val element1 = DetektElement1() + val element2 = DetektElement2() +} +``` + +#### Compliant Code: + +```kotlin +import io.gitlab.arturbosch.detekt.DetektElement1 +import io.gitlab.arturbosch.detekt.DetektElement2 + +class DetektElements { + val element1 = DetektElement1() + val element2 = DetektElement2() +} +``` diff --git a/website/versioned_sidebars/version-1.23.0-sidebars.json b/website/versioned_sidebars/version-1.23.0-sidebars.json new file mode 100644 index 000000000000..771aeb7601cb --- /dev/null +++ b/website/versioned_sidebars/version-1.23.0-sidebars.json @@ -0,0 +1,24 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Changelogs", + "items": [ + { + "type": "link", + "label": "1.x Changelog", + "href": "/changelog" + }, + { + "type": "link", + "label": "0.x Changelog", + "href": "/changelog-pre-stable" + } + ] + } + ] +} diff --git a/website/versions.json b/website/versions.json index 510ea32345f0..f520786b60a5 100644 --- a/website/versions.json +++ b/website/versions.json @@ -1,4 +1,5 @@ [ + "1.23.0", "1.22.0", "1.21.0" ]