diff --git a/.github/workflows/android_build.yml b/.github/workflows/android_build.yml index e06d3a1..9842594 100644 --- a/.github/workflows/android_build.yml +++ b/.github/workflows/android_build.yml @@ -18,4 +18,4 @@ jobs: java-version: 17 - name: Clean build android app - run: ./gradlew clean androidApp:build + run: ./gradlew clean detekt androidApp:build diff --git a/build.gradle.kts b/build.gradle.kts index e2fd775..addff5a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import io.gitlab.arturbosch.detekt.Detekt +import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask + plugins { // this is necessary to avoid the plugins to be loaded multiple times in each subproject's classloader alias(libs.plugins.android.application).apply(false) @@ -5,9 +8,43 @@ plugins { alias(libs.plugins.jetbrains.compose).apply(false) alias(libs.plugins.jetbrains.kotlin.multiplatform).apply(false) alias(libs.plugins.jetbrains.kotlin.plugin.serialization).apply(false) + alias(libs.plugins.detekt) id("dependency-version-management") } +detekt { + // Documentation: https://detekt.dev/docs/gettingstarted/gradle/#kotlin-dsl-3 + + source.setFrom(".") // The directories where detekt looks for source files. + + buildUponDefaultConfig = true // Applies the config files on top of detekt's default config file. `false` by default. + config.setFrom("${rootProject.rootDir}/detekt.yml") + + parallel = true // Builds the AST in parallel. + + baseline = file("${rootProject.rootDir}/detekt-baseline.xml") // TODO get rid of baseline as fast as possible + + basePath = projectDir.absolutePath + + allRules = true // Turns on all the rules. Default: false +} + +tasks.withType().configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/build/**") // but exclude our legacy internal package +} + +tasks.withType().configureEach { + // include("**/special/package/**") // only analyze a sub package inside src/main/kotlin + exclude("**/build/**") // but exclude our legacy internal package +} + +dependencies { + detektPlugins(libs.detekt.rules.compose.kode) + detektPlugins(libs.detekt.rules.compose.nlopez) + detektPlugins(libs.detekt.rules.formatting.ktlint) +} + tasks.register("clean", Delete::class) { delete(rootProject.buildDir) delete(rootProject.projectDir.resolve("buildSrc/build")) diff --git a/detekt-baseline.xml b/detekt-baseline.xml new file mode 100644 index 0000000..ce90900 --- /dev/null +++ b/detekt-baseline.xml @@ -0,0 +1,23 @@ + + + + + ComposableFunctionName:main.android.kt$@Composable fun MainView() + Filename:main.android.kt$.main.android.kt + Filename:main.ios.kt$.main.ios.kt + FinalNewline:App.kt$.App.kt + FinalNewline:MainActivity.kt$com.myapplication.MainActivity.kt + FinalNewline:main.ios.kt$.main.ios.kt + FunctionNaming:main.ios.kt$fun MainViewController() + MissingPackageDeclaration:App.kt$.App.kt + MissingPackageDeclaration:main.android.kt$.main.android.kt + MissingPackageDeclaration:main.ios.kt$.main.ios.kt + ModifierMissing:App.kt$App + NewLineAtEndOfFile:App.kt$.App.kt + NewLineAtEndOfFile:MainActivity.kt$com.myapplication.MainActivity.kt + NewLineAtEndOfFile:main.ios.kt$.main.ios.kt + UnusedPrivateProperty:build.gradle.kts$val androidMain by getting { dependencies { api(libs.androidx.activity.compose) api(libs.androidx.appcompat) api(libs.androidx.core.ktx) api(libs.ktor.client.android) } } + UnusedPrivateProperty:build.gradle.kts$val androidMain by getting { dependencies { implementation(project(":shared")) } } + UnusedPrivateProperty:build.gradle.kts$val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) iosArm64Main.dependsOn(this) iosSimulatorArm64Main.dependsOn(this) dependencies { implementation(libs.ktor.client.ios) } } + + diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000..f800e7b --- /dev/null +++ b/detekt.yml @@ -0,0 +1,1123 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: true + checkExhaustiveness: 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' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'FindingsReport' + - 'LiteFindingsReport' + - 'NotificationReport' +# - 'ProjectStatisticsReport' +# - 'ComplexityReport' +# - 'FileBasedFindingsReport' + +output-reports: + active: true + exclude: + - 'TxtOutputReport' + - 'XmlOutputReport' + - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: true + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + OutdatedDocumentation: + active: true + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedProperty: false + +complexity: + active: true + CognitiveComplexMethod: + active: true + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: true + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: true + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: true + threshold: 6 + NamedArguments: + active: true + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: true + threshold: 2 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: true + StringLiteralDuplication: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: true + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + NotImplementedDeclaration: + active: true + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: true + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_A-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [ ] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + ignoreAnnotated: [ 'Composable' ] + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: true + threshold: 3 + ForEachOnRange: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + SpreadOperator: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnnecessaryPartOfBinaryExpression: + active: true + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: false + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [ ] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [ ] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: true + excludes: [ '**/*.kts' ] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: true + BracesOnIfStatements: + active: true + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: true + singleLine: 'necessary' + multiLine: 'necessary' + CanBeNonNullable: + active: true + CascadingCallWrapping: + active: true + includeElvis: true + ClassOrdering: + active: true + CollapsibleIfStatements: + active: true + DataClassContainsFunctions: + active: true + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: true + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 2 + DoubleNegativeLambda: + active: true + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: true + ExplicitCollectionElementAccessMethod: + active: true + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: true + includeLineWrapping: false + ForbiddenAnnotation: + active: false + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + 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:' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [ ] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: true + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [ ] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [ ] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 0 + 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 + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: true + MaxChainedCallsOnSameLine: + active: true + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 140 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: true + MultilineRawStringIndentation: + active: true + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: true + NullableBooleanCheck: + active: true + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: true + PreferToOverPairSyntax: + active: true + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: true + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: true + StringShouldBeRawString: + active: true + maxEscapedCharacterCount: 2 + ignoredCharacters: [ ] + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: true + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: true + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: true + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: true + UnnecessaryBracesAroundTrailingLambda: + active: true + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: true + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: true + allowForUnclearPrecedence: true + UntilInsteadOfRangeTo: + active: true + UnusedImports: + active: true + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + ignoreAnnotated: [ 'Preview' ] + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: true + allowVars: false + UseEmptyCounterpart: + active: true + UseIfEmptyOrIfBlank: + active: true + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludeImports: + - '' + +# Rules from https://github.com/appKODE/detekt-rules-compose +compose: + active: true + ModifierHeightWithText: + active: true + ReusedModifierInstance: + active: true + PublicComposablePreview: + active: true + ModifierParameterPosition: + active: true + ComposableEventParameterNaming: + active: true + UnnecessaryEventHandlerParameter: + active: true + ComposableParametersOrdering: + active: true + ModifierDefaultValue: + active: true + MissingModifierDefaultValue: + active: true + TopLevelComposableFunctions: + active: true + ComposableFunctionName: + active: true + ConditionCouldBeLifted: + active: true + ignoreCallsWithArgumentNames: [ 'modifier' ] + + # Rules from https://mrmans0n.github.io/compose-rules/ +Compose: + CompositionLocalAllowlist: + active: true + ContentEmitterReturningValues: + active: true + DefaultsVisibility: + active: true + ModifierComposable: + active: true + ModifierMissing: + active: true + ModifierNaming: + active: true + ModifierNotUsedAtRoot: + active: true + ModifierReused: + active: true + ModifierWithoutDefault: + active: true + MultipleEmitters: + active: true + MutableParams: + active: true + ComposableNaming: + active: true + ComposableParamOrder: + active: true + PreviewNaming: + active: true + PreviewPublic: + active: true + RememberMissing: + active: true + UnstableCollections: + active: true + ViewModelForwarding: + active: true + ViewModelInjection: + active: true + +formatting: + active: true + android: false + autoCorrect: true + AnnotationOnSeparateLine: + active: true + autoCorrect: true + indentSize: 4 + AnnotationSpacing: + active: true + autoCorrect: true + ArgumentListWrapping: + active: true + autoCorrect: true + indentSize: 4 + maxLineLength: 140 + BlockCommentInitialStarAlignment: + active: true + autoCorrect: true + ChainWrapping: + active: true + autoCorrect: true + indentSize: 4 + ClassName: + active: false + CommentSpacing: + active: true + autoCorrect: true + CommentWrapping: + active: true + autoCorrect: true + indentSize: 4 + ContextReceiverMapping: + active: false + autoCorrect: true + maxLineLength: 140 + indentSize: 4 + DiscouragedCommentLocation: + active: false + autoCorrect: true + EnumEntryNameCase: + active: true + autoCorrect: true + EnumWrapping: + active: false + autoCorrect: true + indentSize: 4 + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + insertFinalNewLine: true + FunKeywordSpacing: + active: true + autoCorrect: true + FunctionName: + active: false + FunctionReturnTypeSpacing: + active: true + autoCorrect: true + maxLineLength: 140 + FunctionSignature: + active: false + autoCorrect: true + forceMultilineWhenParameterCountGreaterOrEqualThan: 2147483647 + functionBodyExpressionWrapping: 'default' + maxLineLength: 140 + indentSize: 4 + FunctionStartOfBodySpacing: + active: true + autoCorrect: true + FunctionTypeReferenceSpacing: + active: true + autoCorrect: true + IfElseBracing: + active: false + autoCorrect: true + indentSize: 4 + IfElseWrapping: + active: false + autoCorrect: true + indentSize: 4 + ImportOrdering: + active: true + autoCorrect: true + layout: '*' + Indentation: + active: true + autoCorrect: true + indentSize: 4 + KdocWrapping: + active: true + autoCorrect: true + indentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 140 + ignoreBackTickedIdentifier: false + ModifierListSpacing: + active: true + autoCorrect: true + ModifierOrdering: + active: true + autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true + indentSize: 4 + MultilineExpressionWrapping: + active: false + autoCorrect: true + indentSize: 4 + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoBlankLineInList: + active: false + autoCorrect: true + NoBlankLinesInChainedMethodCalls: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoConsecutiveComments: + active: false + NoEmptyClassBody: + active: true + autoCorrect: true + NoEmptyFirstLineInClassBody: + active: false + autoCorrect: true + indentSize: 4 + NoEmptyFirstLineInMethodBlock: + active: true + autoCorrect: true + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoSingleLineBlockComment: + active: false + autoCorrect: true + indentSize: 4 + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: true + packagesToUseImportOnDemandProperty: '' + NullableTypeSpacing: + active: true + autoCorrect: true + PackageName: + active: true + autoCorrect: true + ParameterListSpacing: + active: false + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + maxLineLength: 140 + indentSize: 4 + ParameterWrapping: + active: true + autoCorrect: true + indentSize: 4 + maxLineLength: 140 + PropertyName: + active: false + PropertyWrapping: + active: true + autoCorrect: true + indentSize: 4 + maxLineLength: 140 + SpacingAroundAngleBrackets: + active: true + autoCorrect: true + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true + SpacingAroundDoubleColon: + active: true + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundParens: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + SpacingAroundUnaryOperator: + active: true + autoCorrect: true + SpacingBetweenDeclarationsWithAnnotations: + active: true + autoCorrect: true + SpacingBetweenDeclarationsWithComments: + active: true + autoCorrect: true + SpacingBetweenFunctionNameAndOpeningParenthesis: + active: true + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + StringTemplateIndent: + active: false + autoCorrect: true + indentSize: 4 + TrailingCommaOnCallSite: + active: false + autoCorrect: true + useTrailingCommaOnCallSite: true + TrailingCommaOnDeclarationSite: + active: false + autoCorrect: true + useTrailingCommaOnDeclarationSite: true + TryCatchFinallySpacing: + active: false + autoCorrect: true + indentSize: 4 + TypeArgumentListSpacing: + active: false + autoCorrect: true + indentSize: 4 + TypeParameterListSpacing: + active: false + autoCorrect: true + indentSize: 4 + UnnecessaryParenthesesBeforeTrailingLambda: + active: true + autoCorrect: true + Wrapping: + active: true + autoCorrect: true + indentSize: 4 + maxLineLength: 140 diff --git a/docs/tooling/linting.md b/docs/tooling/linting.md index 51ff894..efbbf04 100644 --- a/docs/tooling/linting.md +++ b/docs/tooling/linting.md @@ -5,5 +5,42 @@ description: Linting with detekt and ktlint # Linting -:construction: Plan is to set up a [detekt](https://detekt.dev/) and [ktlint](https://github.com/JLLeitschuh/ktlint-gradle) integration with -gradle. +Adding linting to a code project, with tools like Detekt for Kotlin, is crucial for ensuring consistent coding styles, improving code +quality, and catching potential bugs early in the development process. Linting promotes collaboration within development teams by enforcing +uniform coding conventions, enhancing code readability, and automating the identification of common issues during code reviews. Detekt, +being Kotlin-specific, allows for customizable rules tailored to a project's needs, ensuring adherence to best practices and Kotlin-specific +coding conventions. This leads to increased productivity, seamless integration into CI/CD pipelines, and ultimately results in more +maintainable and reliable Kotlin projects. + +:construction: Plan is to set up a [detekt](https://detekt.dev/) (done) and [ktlint](https://github.com/JLLeitschuh/ktlint-gradle) (todo) +integration with gradle. + +## Detekt + +Linting is currently set up with [detekt](https://detekt.github.io/detekt/). The configuration is located in `detekt.yml`. + +Current issue is the set up of a convention plugin that can be applied to all modules. The current solution is to apply the plugin to the +root project and run it on all files in the project. This is not ideal as it will run on all files in the project, including the build +files. Therefor an exclusion for the `build` folder was added. Also detekt doesn't run with type resolution, which means that it can't find +deeper issues related to the codebase. Have another go at it when detekt 2.0 is released. This should then have official support for type +resolution. + +### Updating detekt + +1. Update detekt in `libs.versions.toml` +2. Delete `detekt.yml` in the root project +3. Run `./gradlew detektGenerateConfig` in the root project +4. Check the git diff for changes of the default detekt config + +More information about updating detekt can be found in the blog post: [Recipe: diff your detekt config with the default one][detektDiff]. + + +[detektDiff]: https://detekt.dev/blog/2020/09/27/additional-diff-config-task/ + +### Plugins + +Detekt has a plugin system to extend detekt with additional rulesets. The following plugins are currently used: + +- +- +- diff --git a/gradle/dependency-locks/MyApplication.gradle.lockfile b/gradle/dependency-locks/MyApplication.gradle.lockfile index 455625d..49e4813 100644 --- a/gradle/dependency-locks/MyApplication.gradle.lockfile +++ b/gradle/dependency-locks/MyApplication.gradle.lockfile @@ -1,13 +1,75 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.10=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.9.10=kotlinKlibCommonizerClasspath +com.beust:jcommander:1.82=detekt +com.pinterest.ktlint:ktlint-cli-ruleset-core:0.50.0=detektPlugins +com.pinterest.ktlint:ktlint-logger:0.50.0=detektPlugins +com.pinterest.ktlint:ktlint-rule-engine-core:0.50.0=detektPlugins +com.pinterest.ktlint:ktlint-ruleset-standard:0.50.0=detektPlugins +io.github.davidburstrom.contester:contester-breakpoint:0.2.0=detekt,detektPlugins +io.github.detekt.sarif4k:sarif4k-jvm:0.4.0=detekt,detektPlugins +io.github.detekt.sarif4k:sarif4k:0.4.0=detekt,detektPlugins +io.github.microutils:kotlin-logging-jvm:3.0.5=detektPlugins +io.gitlab.arturbosch.detekt:detekt-api:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-api:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-cli:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-core:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-core:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-formatting:1.23.3=detektPlugins +io.gitlab.arturbosch.detekt:detekt-metrics:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-metrics:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-parser:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-parser:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-psi-utils:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-psi-utils:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-report-html:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-report-html:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-report-md:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-report-md:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-report-sarif:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-report-sarif:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-report-txt:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-report-txt:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-report-xml:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-report-xml:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-complexity:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-coroutines:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-documentation:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-empty:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-errorprone:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-exceptions:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-naming:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-performance:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules-style:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-rules:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-tooling:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-tooling:1.23.3=detekt +io.gitlab.arturbosch.detekt:detekt-utils:1.23.1=detektPlugins +io.gitlab.arturbosch.detekt:detekt-utils:1.23.3=detekt +io.nlopez.compose.rules:common:0.3.0=detektPlugins +io.nlopez.compose.rules:core-common:0.3.0=detektPlugins +io.nlopez.compose.rules:core-detekt:0.3.0=detektPlugins +io.nlopez.compose.rules:detekt:0.3.0=detektPlugins +org.ec4j.core:ec4j-core:0.3.0=detektPlugins +org.jetbrains.intellij.deps:trove4j:1.0.20200330=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.10=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.9.10=detekt,detektPlugins,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.9.10=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-script-runtime:1.9.10=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.9.10=kotlinKlibCommonizerClasspath -org.jetbrains:annotations:13.0=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.9.0=detektPlugins +org.jetbrains.kotlin:kotlin-reflect:1.9.10=detekt +org.jetbrains.kotlin:kotlin-script-runtime:1.9.10=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=detekt,detektPlugins +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=detekt,detektPlugins +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.1=detekt,detektPlugins +org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.4.1=detekt,detektPlugins +org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.1=detekt,detektPlugins +org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.1=detekt,detektPlugins +org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1=detekt,detektPlugins +org.jetbrains:annotations:13.0=detekt,detektPlugins,kotlinKlibCommonizerClasspath +org.slf4j:slf4j-api:2.0.3=detektPlugins +org.snakeyaml:snakeyaml-engine:2.6=detekt,detektPlugins +ru.kode:detekt-rules-compose:1.3.0=detektPlugins empty= diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eaeb986..1f9f5f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ android-sdk-compile = "34" android-sdk-min = "24" android-sdk-target = "34" arrow = "1.2.1" +detekt = "1.23.3" java-jvm = "11" kotlin = "1.9.10" ktor = "2.3.5" @@ -16,6 +17,9 @@ arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-core-serialization = { module = "io.arrow-kt:arrow-core-serialization", version.ref = "arrow" } arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } coil-compose = "io.coil-kt:coil-compose:2.4.0" +detekt-rules-compose-kode = "ru.kode:detekt-rules-compose:1.3.0" +detekt-rules-compose-nlopez = "io.nlopez.compose.rules:detekt:0.3.0" +detekt-rules-formatting-ktlint = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } jetbrains-kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } @@ -28,6 +32,7 @@ plugin-version-management-version-catalog = "nl.littlerobots.vcu:plugin:0.8.1" [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } jetbrains-compose = "org.jetbrains.compose:1.5.3" jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }