Save 37% off PRO during our Black Friday Sale! »

Detekt - State of the Union

Detekt - State of the Union

Do you know detekt? We are The static analyzer for Kotlin, trusted by millions of developers around the globe, and completely built by the open-source community.

Our mission: spotting bugs, anti-patterns, and potential errors in your Kotlin code.

Did you know that you can extend detekt with your custom rules?
Did you know that we recently launched support for Kotlin Multiplatform?
Did you know that we're always looking for new contributors?

Whether you're new to detekt or you're a veteran, this talk is for you!
We will walk through the tool, and give you updates on the latest features we shipped and what comes next for the future.

3dc29e8cfc6ef333e2b41a1b0e826b57?s=128

Nicola Corti

June 09, 2021
Tweet

Transcript

  1. State of the Union Nicola Corti @cortinico

  2. Nicola Corti Kotlin GDE Detekt Maintainer twitter.com/cortinico github.com/cortinico ncorti.com The

    Developers’ Bakery Podcast thebakery.dev
  3. What is ?

  4. What is ?

  5. What is ?

  6. 220 Rules

  7. Rulesets

  8. Rulesets • comments AbsentOrWrongFileLicense CommentOverPrivateFunction CommentOverPrivateProperty DeprecatedBlockTag EndOfSentenceFormat UndocumentedPublicClass UndocumentedPublicFunction

    UndocumentedPublicProperty
  9. Rulesets • comments • complexity ComplexCondition ComplexInterface ComplexMethod LabeledExpression LargeClass

    LongMethod LongParameterList MethodOverloading NamedArguments NestedBlockDepth ReplaceSafeCallChainWithRun StringLiteralDuplication TooManyFunctions
  10. Rulesets • comments • complexity • coroutines GlobalCoroutineUsage RedundantSuspendModifier SleepInsteadOfDelay

    SuspendFunWith"FlowReturnType
  11. Rulesets • comments • complexity • coroutines • empty-blocks EmptyCatchBlock

    EmptyClassBlock EmptyDefaultConstructor EmptyDoWhileBlock EmptyElseBlock EmptyFinallyBlock EmptyForBlock EmptyFunctionBlock EmptyIfBlock EmptyInitBlock EmptyKtFile EmptySecondaryConstructor EmptyTryBlock EmptyWhenBlock EmptyWhileBlock
  12. Rulesets • comments • complexity • coroutines • empty-blocks •

    exceptions ExceptionRaisedInUnexpectedLocation InstanceOfCheckForException NotImplementedDeclaration ObjectExtendsThrowable PrintStackTrace RethrowCaughtException ReturnFromFinally SwallowedException ThrowingExceptionFromFinally ThrowingExceptionInMain ThrowingExceptionsWithoutMessage ThrowingNewInstanceOfSameException TooGenericExceptionCaught TooGenericExceptionThrown
  13. Rulesets • comments • complexity • coroutines • empty-blocks •

    exceptions • naming ClassNaming ConstructorParameterNaming EnumNaming ForbiddenClassName FunctionMaxLength FunctionMinLength FunctionNaming FunctionParameterNaming InvalidPackageDeclaration MatchingDeclarationName MemberNameEqualsClassName NoNameShadowing NonBooleanPropertyPrefixedWithIs ObjectPropertyNaming PackageNaming TopLevelPropertyNaming VariableMaxLength VariableMinLength VariableNaming
  14. Rulesets • comments • complexity • coroutines • empty-blocks •

    exceptions • naming • performance ArrayPrimitive ForEachOnRange SpreadOperator UnnecessaryTemporaryInstantiation
  15. Rulesets • comments • complexity • coroutines • empty-blocks •

    exceptions • naming • performance • potential-bugs CastToNullableType Deprecation DontDowncastCollectionTypes DoubleMutabilityForCollection DuplicateCaseInWhenExpression EqualsAlwaysReturnsTrueOrFalse EqualsWithHashCodeExist ExitOutsideMain ExplicitGarbageCollectionCall HasPlatformType IgnoredReturnValue ImplicitDefaultLocale ImplicitUnitReturnType InvalidRange IteratorHasNextCallsNextMethod IteratorNotThrowingNoSuchElementException LateinitUsage MapGetWithNotNullAssertionOperator MissingWhenCase NullableToStringCall RedundantElseInWhen UnconditionalJumpStatementInLoop UnnecessaryNotNullOperator UnnecessarySafeCall UnreachableCatchBlock UnreachableCode UnsafeCallOnNullableType UnsafeCast UnusedUnaryOperator UselessPostfixExpression WrongEqualsTypeParameter
  16. Rulesets • comments • complexity • coroutines • empty-blocks •

    exceptions • naming • performance • potential-bugs • style ClassOrdering CollapsibleIfStatements DataClassContainsFunctions DataClassShouldBeImmutable DestructuringDeclarationWithTooManyEntries EqualsNullCall EqualsOnSignatureLine ExplicitCollectionElementAccessMethod ExplicitItLambdaParameter ExpressionBodySyntax ForbiddenComment ForbiddenImport ForbiddenMethodCall ForbiddenPublicDataClass ForbiddenVoid FunctionOnlyReturningConstant LibraryCodeMustSpecifyReturnType LibraryEntitiesShouldNotBePublic LoopWithTooManyJumpStatements MagicNumber MandatoryBracesIfStatements MandatoryBracesLoops MaxLineLength MayBeConst ModifierOrder MultilineLambdaItParameter NestedClassesVisibility NewLineAtEndOfFile NoTabs ObjectLiteralToLambda OptionalAbstractKeyword OptionalUnit OptionalWhenBraces PreferToOverPairSyntax ProtectedMemberInFinalClass RedundantExplicitType RedundantHigherOrderMapUsage RedundantVisibilityModifierRule ReturnCount SafeCast SerialVersionUIDInSerializableClass SpacingBetweenPackageAndImports ThrowsCount TrailingWhitespace UnderscoresInNumericLiterals UnnecessaryAbstractClass UnnecessaryAnnotationUseSiteTarget UnnecessaryApply UnnecessaryFilter UnnecessaryInheritance UnnecessaryLet UnnecessaryParentheses UntilInsteadOfRangeTo UnusedImports UnusedPrivateClass UnusedPrivateMember UseArrayLiteralsInAnnotations UseCheckNotNull UseCheckOrError UseDataClass UseEmptyCounterpart UseIfEmptyOrIfBlank UseIfInsteadOfWhen UseIsNullOrEmpty UseOrEmpty UseRequire UseRequireNotNull UselessCallOnNotNull UtilityClassWithPublicConstructor VarCouldBeVal WildcardImport
  17. UnusedPrivateMember

  18. UnusedPrivateMember class Sample { private val aMember = "^^" private

    val anotherMember = "U.U" }
  19. UnusedPrivateMember class Sample { private val aMember = "^^" private

    val anotherMember = "U.U" fun main() { println(aMember) } } Private property `anotherMember` is unused.
  20. MagicNumber

  21. MagicNumber fun main() { }

  22. MagicNumber fun main() { if (Random.nextInt() > 42) { println("🐋")

    } } This expression contains a magic number. Consider defining it to a well named constant.
  23. Documentation

  24. Documentation /** * This rule detects and reports usages of

    magic numbers in the code. * Prefer defining constants with clear names * describing what the magic number means. *
  25. Documentation /** * This rule detects and reports usages of

    magic numbers in the code. * Prefer defining constants with clear names * describing what the magic number means. * * <noncompliant> * class User { * * fun checkName(name: String) { * if (name.length > 42) { * throw IllegalArgumentException("username is too long") * } * // ... * } * } * </noncompliant>
  26. Documentation /** * This rule detects and reports usages of

    magic numbers in the code. * Prefer defining constants with clear names * describing what the magic number means. * * <noncompliant> * class User { * * fun checkName(name: String) { * if (name.length > 42) { * throw IllegalArgumentException("username is too long") * } * // ... * } * } * </noncompliant>
  27. * if (name.length > 42) { * throw IllegalArgumentException("username is

    too long") * } * // ... * } * } * </noncompliant> * * <compliant> * * 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 * } * } * </compliant> */
  28. * } * } * </noncompliant> * * <compliant> *

    * 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 * } * } * </compliant> */ @Suppress("TooManyFunctions") @ActiveByDefault(since = "1.0.0") class MagicNumber(config: Config = Config.empty) : Rule(config) {
  29. * } * } * </noncompliant> * * <compliant> *

    * 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 * } * } * </compliant> */ @Suppress("TooManyFunctions") @ActiveByDefault(since = "1.0.0") class MagicNumber(config: Config = Config.empty) : Rule(config) {
  30. Documentation

  31. Documentation

  32. Documentation

  33. Documentation

  34. detekt.yml

  35. build: maxIssues: 0 excludeCorrectable: false weights: # complexity: 2 #

    LongParameterList: 1 # style: 1 # comments: 1 config: validation: true warningsAsErrors: false # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,."*>."*>[my_property]' excludes: '' processors: active: true exclude: - 'DetektProgressListener' # - 'KtFileCountProcessor' # - 'PackageCountProcessor' # - 'ClassCountProcessor' # - 'FunctionCountProcessor' # - 'PropertyCountProcessor' detekt.yml
  36. excludes: ['"**'] LibraryEntitiesShouldNotBePublic: active: true excludes: ['"**'] LoopWithTooManyJumpStatements: active: true

    maxJumpCount: 1 MagicNumber: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/ iosTest/**'] ignoreNumbers: ['-1', '0', '1', '2'] ignoreHashCodeFunction: true ignorePropertyDeclaration: false ignoreLocalVariableDeclaration: false ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false ignoreNamedArgument: true ignoreEnums: false ignoreRanges: false ignoreExtensionFunctions: true MandatoryBracesIfStatements: active: false MandatoryBracesLoops: active: false detekt.yml
  37. excludes: ['"**'] LibraryEntitiesShouldNotBePublic: active: true excludes: ['"**'] LoopWithTooManyJumpStatements: active: true

    maxJumpCount: 1 MagicNumber: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/ iosTest/**'] ignoreNumbers: ['-1', '0', '1', '2'] ignoreHashCodeFunction: true ignorePropertyDeclaration: false ignoreLocalVariableDeclaration: false ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false ignoreNamedArgument: true ignoreEnums: false ignoreRanges: false ignoreExtensionFunctions: true MandatoryBracesIfStatements: active: false MandatoryBracesLoops: active: false detekt.yml
  38. UnnecessaryNotNullOperator

  39. UnnecessaryNotNullOperator val zero = 0 val answer = zero.plus(42)

  40. UnnecessaryNotNullOperator val zero = 0 val answer = zero""!!.plus(42)

  41. UnnecessaryNotNullOperator val zero = 0 val answer = zero""!!.plus(42) :

    Int
  42. UnnecessaryNotNullOperator val zero = 0 val answer = zero""!!.plus(42) `zero"!!`

    contains an unnecessary not-null ("!!) operators
  43. DoubleMutabilityForCollection

  44. DoubleMutabilityForCollection fun main() { var hobbits = mutableListOf("Frodo", "Sam", "Merry",

    "Pippin") } Variable `hobbits` is declared as `var` with a mutable type `kotlin.collections.MutableList`. Consider using `val` or an immutable collection type
  45. ForbiddenMethodCall

  46. ForbiddenMethodCall style: ForbiddenMethodCall: active: false methods: ['kotlin.io.println', 'kotlin.io.print']

  47. ForbiddenMethodCall style: ForbiddenMethodCall: active: true methods: ['kotlin.io.println', 'kotlin.io.print']

  48. ForbiddenMethodCall class Sample { private val aMember = "^^" private

    val anotherMember = "U.U" } style: ForbiddenMethodCall: active: true methods: ['kotlin.io.println', 'kotlin.io.print']
  49. ForbiddenMethodCall class Sample { private val aMember = "^^" private

    val anotherMember = "U.U" fun main() { println(aMember) } } style: ForbiddenMethodCall: active: true methods: ['kotlin.io.println', 'kotlin.io.print'] The method kotlin.io.println(String) has been forbidden in the Detekt config.
  50. Suppressing

  51. Suppressing fun main() { if (Random.nextInt() > 42) { println("🐋")

    } }
  52. Suppressing fun main() { @Suppress("MagicNumber") if (Random.nextInt() > 42) {

    println("🐋") } }
  53. Suppressing @Suppress("MagicNumber") fun main() { if (Random.nextInt() > 42) {

    println("🐋") } }
  54. Suppressing @file:Suppress(“MagicNumber”) fun main() { if (Random.nextInt() > 42) {

    println("🐋") } }
  55. Detekt CLI

  56. Custom Rules

  57. • Ktlint Wrapper • Offered as Custom rules (formatting) •

    Only rules offering autoCorrection formatting: android: false autoCorrect: true AnnotationOnSeparateLine: AnnotationSpacing: ArgumentListWrapping: ChainWrapping: CommentSpacing: EnumEntryNameCase: Filename: FinalNewline: ImportOrdering: Indentation: MaximumLineLength: ModifierOrdering: MultiLineIfElse: NoBlankLineBeforeRbrace: NoConsecutiveBlankLines: NoEmptyClassBody: NoEmptyFirstLineInMethodBlock: NoLineBreakAfterElse: NoLineBreakBeforeAssignment: NoMultipleSpaces: NoSemicolons: NoTrailingSpaces: NoUnitReturn: NoUnusedImports: NoWildcardImports: PackageName: ParameterListWrapping: SpacingAroundAngleBrackets: SpacingAroundColon: SpacingAroundComma: SpacingAroundCurly: SpacingAroundDot: SpacingAroundDoubleColon: SpacingAroundKeyword: SpacingAroundOperators: SpacingAroundParens: SpacingAroundRangeOperator: SpacingAroundUnaryOperator: SpacingBetweenDeclarationsWithAnnotations: SpacingBetweenDeclarationsWithComments: StringTemplate:
  58. • Ktlint Wrapper • Offered as Custom rules (formatting) •

    Only rules offering autoCorrection formatting: android: false autoCorrect: true AnnotationOnSeparateLine: AnnotationSpacing: ArgumentListWrapping: ChainWrapping: CommentSpacing: EnumEntryNameCase: Filename: FinalNewline: ImportOrdering:
  59. formatting: active: true NoWildcardImports: active: true NoUnusedImports: active: true style:

    WildcardImport: active: true excludeImports: [‘java.util.*'] UnusedImports: active: false
  60. formatting: active: true NoWildcardImports: active: true NoUnusedImports: active: true style:

    WildcardImport: active: true excludeImports: [‘java.util.*'] UnusedImports: active: false
  61. formatting: active: true NoWildcardImports: active: true NoUnusedImports: active: true autoCorrect:

    true style: WildcardImport: active: true excludeImports: [‘java.util.*'] UnusedImports: active: false
  62. Custom Rules

  63. Custom Rules class MyCustomRuleSetProvider : RuleSetProvider { override val ruleSetId

    = "my-rules" override fun instance(config: Config) = RuleSet( ruleSetId, listOf(MyCustomRule(config)) ) } com.example.MyCustomRuleSetProvider class MyCustomRule( config: Config = Config.empty ) : Rule(config) { override val issue = Issue( "//… ) }
  64. Custom Rules

  65. Integrations

  66. None
  67. None
  68. SARIF - Static Analysis Result Interchange Format https://sarifweb.azurewebsites.net/

  69. None
  70. None
  71. None
  72. None
  73. None
  74. Type Resolution

  75. Type Resolution fun main() { if (Random.nextInt() > 42) {

    println("🐋") } } val zero = 0 val answer = zero""!!.plus(42) UnnecessaryNotNullOperator MagicNumber
  76. Type Resolution fun main() { if (Random.nextInt() > 42) {

    println("🐋") } } val zero = getANumber() val answer = zero""!!.plus(42) UnnecessaryNotNullOperator MagicNumber
  77. Type Resolution fun main() { if (Random.nextInt() > 42) {

    println("🐋") } } val zero = getANumber() val answer = zero""!!.plus(42) UnnecessaryNotNullOperator MagicNumber : Int? : Int UnnecessaryNotNullOperator needs to know the return type of getANumber()
  78. Rules w/ Type Resolution

  79. Rules w/ Type Resolution /** * Reports unnecessary not-null operator

    usage ("!!) that can be removed by the user. * * <noncompliant> * val a = 1 * val b = a"!! * "</noncompliant> * * <compliant> * val a = 1 * val b = a * "</compliant> "*/ @RequiresTypeResolution @ActiveByDefault(since = "1.16.0") class UnnecessaryNotNullOperator(config: Config = Config.empty) : Rule(config) {
  80. Rules w/ Type Resolution @ActiveByDefault(since = "1.16.0") class UnnecessaryNotNullOperator(config: Config

    = Config.empty) : Rule(config) { override val issue: Issue = Issue( "UnnecessaryNotNullOperator", Severity.Defect, "Unnecessary not-null unary operator ("!!) detected.", Debt.FIVE_MINS ) override fun visitUnaryExpression(expression: KtUnaryExpression) { super.visitUnaryExpression(expression) if (bindingContext "== BindingContext.EMPTY) return "// Type Resolution is enabled here } }
  81. Rules w/ Type Resolution @ActiveByDefault(since = "1.16.0") class UnnecessaryNotNullOperator(config: Config

    = Config.empty) : Rule(config) { override val issue: Issue = Issue( "UnnecessaryNotNullOperator", Severity.Defect, "Unnecessary not-null unary operator ("!!) detected.", Debt.FIVE_MINS ) override fun visitUnaryExpression(expression: KtUnaryExpression) { super.visitUnaryExpression(expression) if (bindingContext "== BindingContext.EMPTY) return "// Type Resolution is enabled here } }
  82. Rules w/ Type Resolution

  83. Using Type Resolution

  84. Using Type Resolution

  85. Using Type Resolution

  86. Gradle

  87. None
  88. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" }

  89. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

  90. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

    detekt { config = files(“config/detekt/detekt.yml”) }
  91. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

    detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConfig
  92. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

    detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConfig
  93. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

    detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConfig
  94. plugins { id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() }

    detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConfig
  95. plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral()

    } detekt { config = files(“config/detekt/detekt.yml”) }
  96. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConfig detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  97. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConfig detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  98. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConfig detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories { mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  99. None
  100. plugins { kotlin(“android") id(“com.android.application”) id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  101. plugins { kotlin(“android") id(“com.android.application”) id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt app:detekt app:detektBaseline app:detektBaselineDebug app:detektBaselineDebugAndroidTest app:detektBaselineDebugUnitTest app:detektBaselineMain app:detektBaselineRelease app:detektBaselineReleaseUnitTest app:detektBaselineTest app:detektDebug app:detektDebugAndroidTest app:detektDebugUnitTest app:detektGenerateConfig app:detektMain app:detektRelease app:detektReleaseUnitTest app:detektTest
  102. plugins { kotlin(“android") id(“com.android.application”) id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt app:detekt app:detektBaseline app:detektBaselineDebug app:detektBaselineDebugAndroidTest app:detektBaselineDebugUnitTest app:detektBaselineMain app:detektBaselineRelease app:detektBaselineReleaseUnitTest app:detektBaselineTest app:detektDebug app:detektDebugAndroidTest app:detektDebugUnitTest app:detektGenerateConfig app:detektMain app:detektRelease app:detektReleaseUnitTest app:detektTest
  103. plugins { kotlin(“android") id(“com.android.application”) id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt app:detekt app:detektBaseline app:detektBaselineDebug app:detektBaselineDebugAndroidTest app:detektBaselineDebugUnitTest app:detektBaselineMain app:detektBaselineRelease app:detektBaselineReleaseUnitTest app:detektBaselineTest app:detektDebug app:detektDebugAndroidTest app:detektDebugUnitTest app:detektGenerateConfig app:detektMain app:detektRelease app:detektReleaseUnitTest app:detektTest
  104. plugins { kotlin(“android") id(“com.android.application”) id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) } $ ./gradlew tasks | grep detekt app:detekt app:detektBaseline app:detektBaselineDebug app:detektBaselineDebugAndroidTest app:detektBaselineDebugUnitTest app:detektBaselineMain app:detektBaselineRelease app:detektBaselineReleaseUnitTest app:detektBaselineTest app:detektDebug app:detektDebugAndroidTest app:detektDebugUnitTest app:detektGenerateConfig app:detektMain app:detektRelease app:detektReleaseUnitTest app:detektTest
  105. Multiplatform

  106. Multiplatform plugins { kotlin(“multiplatform") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  107. Multiplatform plugins { kotlin(“multiplatform") id("io.gitlab.arturbosch.detekt") version "1.17.1" } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) } kotlin { jvm() android() ios() js() }
  108. Multiplatform $ ./gradlew tasks | grep detekt detekt detektAndroidDebug detektAndroidDebugAndroidTest

    detektAndroidDebugUnitTest detektAndroidRelease detektAndroidReleaseUnitTest detektBaseline detektBaselineAndroidDebug detektBaselineAndroidDebugAndroidTest detektBaselineAndroidDebugUnitTest detektBaselineAndroidRelease detektBaselineAndroidReleaseUnitTest detektBaselineIosArm64Main detektBaselineIosArm64Test detektBaselineIosX64Main detektBaselineIosX64Test detektBaselineJsMain detektBaselineJsTest detektBaselineJvmMain detektBaselineJvmTest detektBaselineMetadataCommonMain detektBaselineMetadataIosMain detektBaselineMetadataMain detektGenerateConfig detektIosArm64Main detektIosArm64Test detektIosX64Main detektIosX64Test detektJsMain detektJsTest detektJvmMain detektJvmTest detektMetadataCommonMain detektMetadataIosMain detektMetadataMain
  109. The Future

  110. ./gradlew check detekt (no Type Resolution) compileKotlin

  111. detektMain (w/ Type Resolution) compileKotlin Runs another compilation Internally to

    resolve types ./gradlew check • Running two compilations is bad for performances • Misaligned compiler flags • Harder to get the correct class path every time (ViewBinding, etc.)
  112. detekt-compiler-plugin

  113. detekt-compiler-plugin plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version “…” } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  114. detekt-compiler-plugin plugins { kotlin("jvm") id("io.github.detekt.gradle.compiler-plugin") version “…” } repositories {

    mavenCentral() } detekt { config = files(“config/detekt/detekt.yml”) }
  115. compileKotlin Runs detekt during the compilation ./gradlew check • No

    more double compilation • Types Resolution close to the production code • No more custom tasks for every source set
  116. detekt-compiler-plugin

  117. detekt-compiler-plugin $ ./gradlew assemble

  118. detekt-compiler-plugin $ ./gradlew assemble > Task :compileKotlin FAILED w: Build

    failed with 1 weighted issues. e: warnings found and -Werror specified w: /Users/ncorti/oss/detekt-compiler-plugin/src/main/java/Sample.kt: (3, 21): MagicNumber: This expression contains a magic number. Consider defining it to a well named constant. FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':compileKotlin'. > Compilation error. See log for more details BUILD FAILED in 1s 1 actionable task: 1 executed
  119. detekt-compiler-plugin $ ./gradlew assemble > Task :compileKotlin FAILED w: Build

    failed with 1 weighted issues. e: warnings found and -Werror specified w: /Users/ncorti/oss/detekt-compiler-plugin/src/main/java/Sample.kt: (3, 21): MagicNumber: This expression contains a magic number. Consider defining it to a well named constant. FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':compileKotlin'. > Compilation error. See log for more details BUILD FAILED in 1s 1 actionable task: 1 executed
  120. The Community ❤

  121. Where is ? • github.com/detekt/detekt • #detekt on KotlinLang •

    detekt.github.io/ • Discussions & Github Issues
  122. • Search for • Update documentation • Add more tests

    • Submit new rules ideas • Fix false positives/false negatives • Help with integrations • Test new versions (-SNAPSHOT) • Help with branding How to ?
  123. Who is ?

  124. None
  125. Thank you & Contribs are Welcome 🙏 Nicola Corti @cortinico