Upgrade to Pro — share decks privately, control downloads, hide ads and more …

To Detekt 2.0, and beyond!

To Detekt 2.0, and beyond!

Do you know Detekt? We are a static analyzer for Kotlin.
Our mission: spot bugs, antipatterns, and potential errors in your Kotlin code.

Detekt is helping millions of Kotlin developers around the globe to spot bugs before they reach production. What took us here is a vibrant community of users & contributors that are helping us build this ecosystem of tools.

Today, you can easily extend Detekt with your own rules and integrate it with Gradle, Maven, Bazel, IntelliJ, Github, SonarQube, and much more.

Curious to hear about some of the future we've been working on? Join us in this session as we walk through the current status of Detekt, and give you updates on what's lined up for 2.0!

Nicola Corti

November 26, 2023
Tweet

More Decks by Nicola Corti

Other Decks in Programming

Transcript

  1. style: active: true MagicNumber: active: true excludes: [ '**/test/**', '**/androidTest/**',

    '**/commonTest/**', '**/ jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/ jsTest/**', '**/iosTest/**', '**/*.kts' ]
  2. style: active: true MagicNumber: active: true excludes: [ '**/test/**', '**/androidTest/**',

    '**/commonTest/**', '**/ jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/ jsTest/**', '**/iosTest/**', '**/*.kts' ] ignoreNumbers: - '-1' - '0' - '1' - '2'
  3. style: active: true MagicNumber: active: true excludes: [ '**/test/**', '**/androidTest/**',

    '**/commonTest/**', '**/ jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/ jsTest/**', '**/iosTest/**', '**/*.kts' ] ignoreNumbers: - '-1' - '0' - '1' - '2' ignoreHashCodeFunction: true ignorePropertyDeclaration: false ignoreLocalVariableDeclaration: false ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false
  4. fun main() { if (Random.nextInt() > 42) { println("🐋") }

    } MagicNumber This expression contains a magic number. Consider def i ning it to a well named constant
  5. UnnecessaryNotNullOperator val zero = 0 val answer = zero !

    ! . plus(42) `zero ! ! ` contains an unnecessary not - null ( ! ! ) operator
  6. val zero = 0 val answer = zero ! !

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

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

    . plus(42) UnnecessaryNotNullOperator MagicNumber fun main() { if (Random.nextInt() > 42) { println("🐋") } } : Int? : Int
  9. val zero = getANumber() val answer = zero ! !

    . plus(42) UnnecessaryNotNullOperator MagicNumber fun main() { if (Random.nextInt() > 42) { println("🐋") } } : Int? : Int Type Resolution UnnecessaryNotNullOperator needs to know the return type of getANumber()
  10. plugins { id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() }

    detekt { conf i g = f i les("conf i g/detekt/detekt.yml") } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConf i g
  11. plugins { id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() }

    detekt { conf i g = f i les("conf i g/detekt/detekt.yml") } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConf i g
  12. plugins { id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() }

    detekt { conf i g = f i les("conf i g/detekt/detekt.yml") } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConf i g
  13. plugins { id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() }

    detekt { conf i g = f i les("conf i g/detekt/detekt.yml") } $ ./gradlew tasks | grep detekt detekt detektBaseline detektGenerateConf i g
  14. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConf i g detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() } detekt { conf i g = f i les("conf i g/detekt/detekt.yml") }
  15. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConf i g detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() } detekt { conf i g = f i les("conf i g/detekt/detekt.yml") }
  16. $ ./gradlew tasks | grep detekt detekt detektBaseline detektBaselineMain detektBaselineTest

    detektGenerateConf i g detektMain detektTest plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories { mavenCentral() } detekt { conf i g = f i les("conf i g/detekt/detekt.yml") }
  17. Multiplatform plugins { kotlin("multiplatform") id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories {

    mavenCentral() } detekt { conf i g = f i les("conf i g/detekt/detekt.yml") }
  18. Multiplatform plugins { kotlin("multiplatform") id("io.gitlab.arturbosch.detekt") version "1.22.0" } repositories {

    mavenCentral() } detekt { conf i g = f i les("conf i g/detekt/detekt.yml") } kotlin { jvm() android() ios() js() }
  19. 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 detektGenerateConf i g detektIosArm64Main detektIosArm64Test detektIosX64Main detektIosX64Test detektJsMain detektJsTest detektJvmMain detektJvmTest detektMetadataCommonMain detektMetadataIosMain detektMetadataMain
  20. Much more… brew install detekt Danger JS Plugin github.com/AckeeCZ/danger-kotlin-detekt Maven

    Plugin github.com/Ozsie/detekt-maven-plugin Bazel Plugin github.com/buildfoundation/bazel_rules_detekt SARIF Support Sonarqube Plugin github.com/detekt/sonar-kotlin IntelliJ Plugin github.com/detekt/detekt-intellij-plugin Github Action Integration github.com/natiginfo/action-detekt-all
  21. class MyCustomRuleSetProvider : RuleSetProvider { override val ruleSetId = "my

    - rules" override fun instance(conf i g: Conf i g) = RuleSet( ruleSetId, listOf(MyCustomRule(conf i g)) ) } com.example.MyCustomRuleSetProvider class MyCustomRule( conf i g: Conf i g = Conf i g.empty ) : Rule(conf i g) { override val issue = Issue( / / … ) } github.com/detekt/detekt-custom-rule-template
  22. v1.0.0-RC15 v1.0.0-RC16 v1.0.0.M10 v1.0.0.M10.1 v1.0.0.M10.3 v1.0.0.M7 v1.0.0.M7.b1 v1.0.0.M8 v1.0.0.M8.1 v1.0.0.M9

    v1.0.1 v1.1.0 v1.1.1 v1.2.0 v1.2.1 v1.2.2 v1.3.0 v1.3.1 v1.4.0 v1.5.0 v1.5.1 v1.6.0 v1.7.0 v1.7.0-beta2 v1.7.1 v1.7.2 v1.7.3 v1.7.4 v1.8.0 v1.9.0 v1.9.1 v1.10.0 v1.10.0-RC1 v1.11.0 v1.11.0-RC1 v1.11.0-RC2 v1.11.1 v1.11.2 v1.12.0 v1.12.0-RC1 v1.13.0 v1.13.1 v1.14.0 v1.14.1 v1.14.2 v1.15.0 v1.15.0-RC1 v1.15.0-RC2 v1.16.0 v1.16.0-RC1 v1.16.0-RC2 v1.16.0-RC3 v1.17.0 v1.17.0-RC1 v1.17.0-RC2 v1.17.0-RC3 v1.17.1 v1.18.0 v1.18.0-RC1 v1.18.0-RC2 v1.18.0-RC3 v1.18.1 v1.19.0 v1.19.0-RC1 v1.19.0-RC2 v1.20.0 v1.20.0-RC1 v1.20.0-RC2 v1.21.0 v1.21.0-RC1 v1.21.0-RC2 v1.22.0 v1.22.0-RC1 v1.22.0-RC2 v1.22.0- RC3 v1.23.0-RC1
  23. detektMain (w/ Type Resolution) Runs another compilation Internally to resolve

    types ./gradlew build • Running two compilations is bad for performance • Misaligned compiler flags • Harder to get the correct classpath every time (ViewBinding, etc.) compileKotlin
  24. compileKotlin Runs detekt during the compilation ./gradlew build • No

    more double compilation • Type Resolution close to the production code • No more custom tasks for every source set Compiler Plugin
  25. $ ./gradlew assemble > Task :compileKotlin FAILED w: Build failed

    with 1 weighted issues. e: warnings found and -Werror specif i ed w: /Users/ncorti/oss/detekt - compiler - plugin/src/main/java/Sample.kt: (3, 21) : MagicNumber: This expression contains a magic number. Consider def i ning 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 Compiler Plugin
  26. $ ./gradlew assemble > Task :compileKotlin FAILED w: Build failed

    with 1 weighted issues. e: warnings found and -Werror specif i ed w: /Users/ncorti/oss/detekt - compiler - plugin/src/main/java/Sample.kt: (3, 21) : MagicNumber: This expression contains a magic number. Consider def i ning 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 Compiler Plugin
  27. 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 detektGenerateConf i g detektIosArm64Main detektIosArm64Test detektIosX64Main detektIosX64Test detektJsMain detektJsTest detektJvmMain detektJvmTest detektMetadataCommonMain detektMetadataIosMain detektMetadataMain
  28. 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 detektGenerateConf i g detektIosArm64Main detektIosArm64Test detektIosX64Main detektIosX64Test detektJsMain detektJsTest detektJvmMain detektJvmTest detektMetadataCommonMain detektMetadataIosMain detektMetadataMain
  29. New Rules • UnnecessaryInnerClass • CanBeNonNullable • NullCheckOnMutableProperty • SuspendFunWithCoroutineScopeReceiver

    • ElseCaseInsteadOfExhaustiveWhen • TrailingComma - From KtLint • UnnecessaryParenthesesBeforeTrailingLambda - From KtLint • BlockCommentInitialStarAlignment - From KtLint • CommentWrapping - From KtLint • DiscouragedCommentLocation - From KtLint • FunKeywordSpacing - From KtLint • FunctionTypeReferenceSpacing - From KtLint • KdocWrapping - From KtLint • Modif i erListSpacing - From KtLint • TypeArgumentListSpacing - From KtLint • Wrapping - From KtLint • BracesOnIfStatements • CastNullableToNonNullableType • DoubleNegativeLambda • ForbiddenAnnotation • StringShouldBeRawString • SuspendFunSwallowedCancellation • UnusedParameter • UnusedPrivateProperty
  30. New Rulesets libraries: active: true ForbiddenPublicDataClass: active: true ignorePackages: -

    '*.internal' - '*.internal.*' LibraryCodeMustSpecifyReturnType: active: true LibraryEntitiesShouldNotBePublic: active: true ruleauthors: active: true UseEntityAtName: active: true ViolatesTypeResolutionRequirements: active: true
  31. New Rulesets libraries: active: true ForbiddenPublicDataClass: active: true ignorePackages: -

    '*.internal' - '*.internal.*' LibraryCodeMustSpecifyReturnType: active: true LibraryEntitiesShouldNotBePublic: active: true ruleauthors: active: true UseEntityAtName: active: true ViolatesTypeResolutionRequirements: active: true
  32. main 1.23 2.x 1.x • 2.x • New Rules &

    New Features • Breaking Changes • 1.x • Tool Version Bumps • False positive/negative fixes
  33. dev.detekt.* plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") version "1.22.0" } dependencies {

    implementation("io.gitlab.arturbosch.detekt:detekt - api:1.22.0") }
  34. Spring Cleaning complexity>ComplexMethod=Rule is renamed to `CyclomaticComplexMethod` to distinguish between

    Cyclomatic Complexit complexity>LongParameterList>threshold=Use `functionThreshold` and `constructorThreshold` instead empty-blocks>EmptyFunctionBlock>ignoreOverriddenFunctions=Use `ignoreOverridden` instead formatting>Indentation>continuationIndentSize=`continuationIndentSize` is ignored by KtLint and will have no effe formatting>ParameterListWrapping>indentSize=`indentSize` is ignored by KtLint and will have no effect formatting>TrailingComma=Rule is split between `TrailingCommaOnCallSite` and `TrailingCommaOnDeclarationSite` now naming>BooleanPropertyNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>ConstructorParameterNaming>ignoreOverridden=This configuration is ignored and will be removed in the futur naming>FunctionNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>FunctionParameterNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>FunctionParameterNaming>ignoreOverriddenFunctions=Use `ignoreOverridden` instead naming>MemberNameEqualsClassName>ignoreOverriddenFunction=Use `ignoreOverridden` instead naming>VariableNaming>ignoreOverridden=This configuration is ignored and will be removed in the future potential-bugs>DuplicateCaseInWhenExpression=Rule deprecated as compiler performs this check by default potential-bugs>IgnoredReturnValue>restrictToAnnotatedMethods=Use `restrictToConfig` instead 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>ForbiddenPublicDataClass=Rule migrated to `libraries` ruleset plugin style>FunctionOnlyReturningConstant>excludeAnnotatedFunction=Use `ignoreAnnotated` instead style>LibraryCodeMustSpecifyReturnType=Rule migrated to `libraries` ruleset plugin style>LibraryEntitiesShouldNotBePublic=Rule migrated to `libraries` ruleset plugin
  35. Spring Cleaning complexity>ComplexMethod=Rule is renamed to `CyclomaticComplexMethod` to distinguish between

    Cyclomatic Complexit complexity>LongParameterList>threshold=Use `functionThreshold` and `constructorThreshold` instead empty-blocks>EmptyFunctionBlock>ignoreOverriddenFunctions=Use `ignoreOverridden` instead formatting>Indentation>continuationIndentSize=`continuationIndentSize` is ignored by KtLint and will have no effe formatting>ParameterListWrapping>indentSize=`indentSize` is ignored by KtLint and will have no effect formatting>TrailingComma=Rule is split between `TrailingCommaOnCallSite` and `TrailingCommaOnDeclarationSite` now naming>BooleanPropertyNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>ConstructorParameterNaming>ignoreOverridden=This configuration is ignored and will be removed in the futur naming>FunctionNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>FunctionParameterNaming>ignoreOverridden=This configuration is ignored and will be removed in the future naming>FunctionParameterNaming>ignoreOverriddenFunctions=Use `ignoreOverridden` instead naming>MemberNameEqualsClassName>ignoreOverriddenFunction=Use `ignoreOverridden` instead naming>VariableNaming>ignoreOverridden=This configuration is ignored and will be removed in the future potential-bugs>DuplicateCaseInWhenExpression=Rule deprecated as compiler performs this check by default potential-bugs>IgnoredReturnValue>restrictToAnnotatedMethods=Use `restrictToConfig` instead 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>ForbiddenPublicDataClass=Rule migrated to `libraries` ruleset plugin style>FunctionOnlyReturningConstant>excludeAnnotatedFunction=Use `ignoreAnnotated` instead style>LibraryCodeMustSpecifyReturnType=Rule migrated to `libraries` ruleset plugin style>LibraryEntitiesShouldNotBePublic=Rule migrated to `libraries` ruleset plugin
  36. Gradle API Revamp @CacheableTask abstract class Detekt : SourceTask() {

    @get:Input @get:Optional internal abstract val jvmTargetProp: Property<String> var jvmTarget: String @Internal get() = jvmTargetProp.get() set(value) = jvmTargetProp.set(value)
  37. Gradle API Revamp @CacheableTask abstract class Detekt : SourceTask() {

    @get:Input @get:Optional internal abstract val jvmTargetProp: Property<String> var jvmTarget: String @Internal get() = jvmTargetProp.get() set(value) = jvmTargetProp.set(value) Actually fixed in Gradle 8.1 😱
  38. 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:
  39. • Ktlint Wrapper • Offered as Custom rules (formatting) formatting:

    android: false autoCorrect: true AnnotationOnSeparateLine: AnnotationSpacing: ArgumentListWrapping: ChainWrapping: CommentSpacing: EnumEntryNameCase: Filename: FinalNewline: ImportOrdering: Indentation:
  40. formatting: active: true NoWildcardImports: active: true NoUnusedImports: active: true style:

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

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

    active: true NoWildcardImports: active: true NoUnusedImports: active: true
  43. formatting: active: true NoWildcardImports: active: true NoUnusedImports: active: true style:

    WildcardImport: active: true excludeImports: [‘java.util.*'] UnusedImports: active: false Should we remove it?
  44. Severity /** * Rules can classified into different severity grades.

    Maintainer can choose * a grade which is most harmful to their projects. * * Note: This will be replaced by [SeverityLevel] in future versions of detekt. */ enum class Severity {
  45. Severity /** * Rules can classified into different severity grades.

    Maintainer can choose * a grade which is most harmful to their projects. * * Note: This will be replaced by [SeverityLevel] in future versions of detekt. */ enum class Severity { /** * Represents clean coding violations which may lead to maintainability issues. */ CodeSmell, /** * Inspections in this category detect violations of code syntax styles. */ Style, /** * Corresponds to issues that do not prevent the code from working, * but may nevertheless represent coding inefficiencies. */ Warning, /** * Corresponds to coding mistakes which could lead to unwanted behavior. */ Defect,
  46. Severity /** * Rules can classified into different severity grades.

    Maintainer can choose * a grade which is most harmful to their projects. * * Note: This will be replaced by [SeverityLevel] in future versions of detekt. */ enum class Severity { /** * Represents clean coding violations which may lead to maintainability issues. */ CodeSmell, /** * Inspections in this category detect violations of code syntax styles. */ Style, /** * Corresponds to issues that do not prevent the code from working, * but may nevertheless represent coding inefficiencies. */ Warning, /** * Corresponds to coding mistakes which could lead to unwanted behavior. */ Defect,
  47. Severity /** * Rules can classified into different severity grades.

    Maintainer can choose * a grade which is most harmful to their projects. * * Note: This will be replaced by [SeverityLevel] in future versions of detekt. */ enum class Severity { /** * Represents clean coding violations which may lead to maintainability issues. */ CodeSmell, /** * Inspections in this category detect violations of code syntax styles. */ Style, /** * Corresponds to issues that do not prevent the code from working, * but may nevertheless represent coding inefficiencies. */ Warning, /** * Corresponds to coding mistakes which could lead to unwanted behavior. */ Defect,
  48. @Suppressing @f i le:Suppress("MagicNumber") val THE_ANSWER = 42 fun main()

    { if (Random.nextInt() > THE_ANSWER) { println("🐋") } } Not relevant anymore (and harmful)
  49. Multiple Config Instantiation naming: ClassNaming: active: true classPattern: '[A-Z][a-zA-Z0-9]*' testClassPattern:

    '[A-Z][a-zA-Z0-9]*(Test|Spec)' FunctionNaming: active: true functionPattern: '[a-z][a-zA-Z0-9]*' testFunctionPattern: '[A-Z][a-zA-Z0-9 _=+,!@#$^&()-]*' 🤔
  50. 3.0

  51. Help us migrate UseCheckOrError UseDataClass UseEmptyCounterpart UseIfEmptyOrIfBlank UseIfInsteadOfWhen UseIsNullOrEmpty UseOrEmpty

    UseRequire UseRequireNotNull UseSumOfInsteadOfFlatMapSize UselessCallOnNotNull UselessPostfixExpression UtilityClassWithPublicConstructor VarCouldBeVal VariableMaxLength VariableMinLength VariableNaming WildcardImport WrongEqualsTypeParameter
  52. BracesOnIfStatements if (a) { b } else c // *

    `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.
  53. BracesOnWhenStatements when (a) { 1 -> { f1() } 2

    -> f2() } Extra braces exist on this branch, remove them.
  54. fun foo(bar: Any?) { val x = bar as String

    } CastNullableToNonNullableType Use separate `null` assertion and type cast like ((bar ? : error("null assertion message")) as String) instead of 'bar as String'
  55. DoubleNegativeLambda fun Int.evenOrNull() = takeUnless { it % 2 !=

    0 } fun Int.evenOrNull() = takeIf { it % 2 == 0 } DON’T DO
  56. PropertyUsedBeforeDeclaration class Example { val number get() = if (isValid)

    true else false val list = listOf(number) val isValid = true init { println(list) / / [false] } } class Example { val isValid = true val number get() = if (isValid) true else false val list = listOf(number) init { println(list) / / [true] } } DON’T DO
  57. suspend fun foo() { runCatching { delay(500) } } SuspendFunSwallowedCancellation

    The `runCatching` has suspend call inside. You should either use specif i c `try - catch` only catching exception that you are expecting or rethrow the `CancellationException` if already caught
  58. UseLet if (x != null) { x.inc() } else null

    if (x == null) null else y x ?. let { it.inc() } x ?. let { y } DON’T DO