Detekt
DevOpsツール
Detekt
概要
DetektはKotlinプログラミング言語専用の静的コード解析ツールで、Kotlinコードの品質向上とコードスメルの検出を目的としています。Kotlinコミュニティによって開発されたオープンソースツールで、Android、JVM、JS、Native、マルチプラットフォームプロジェクトをサポート。高度に設定可能なルールセット、複数のレポート形式、カスタムルール作成機能、IDE統合により、Kotlin開発における包括的なコード品質管理を実現します。
詳細
Detektは、Kotlinエコシステムにおける標準的な静的コード解析ツールとして、コミュニティ主導で開発されています。Kotlin言語の特性を深く理解し、Kotlinらしいコードの記述を促進することで、保守性の高いコードベースの構築を支援します。
主要な特徴
- 包括的なコード解析: コードスメル、アンチパターン、潜在的バグの検出
- 高度な設定機能: プロジェクト固有要件に対応するルールセットカスタマイズ
- 豊富なレポート形式: HTML、Markdown、SARIF、XML(Checkstyle)、カスタムレポート対応
- プラットフォーム横断サポート: Android、JVM、JS、Native、マルチプラットフォーム完全対応
- ベースライン機能: レガシープロジェクトでの段階的導入サポート
- カスタムルール: プロジェクト固有のルール作成と適用機能
- 複雑度レポート: 循環複雑度、コード行数、コードスメル数の測定
- IDE統合: IntelliJ IDEA、Android Studio等でのリアルタイム解析
- ktlint統合: コードスタイル強制とDetekt解析の統合実行
2025年版の特徴
- Kotlin 2.0対応: 最新Kotlin言語機能との完全互換性
- パフォーマンス向上: 大規模プロジェクトでの解析速度最適化
- enhanced baseline: より柔軟なベースライン管理機能
- improved reporting: より詳細で実用的なレポート生成
メリット・デメリット
メリット
- Kotlin専用設計による深い言語理解と最適化された解析
- オープンソースコミュニティによる継続的な改善と更新
- Android、JVM、マルチプラットフォーム開発での統一的品質管理
- 高度に設定可能なルールによるプロジェクト固有要件対応
- ベースライン機能によるレガシーコード段階的改善
- 複数レポート形式による柔軟な品質状況可視化
- IDE統合によるリアルタイム品質フィードバック
- カスタムルール作成による企業・プロジェクト固有標準対応
- ktlint統合によるスタイルと品質の一元管理
- CI/CD統合による自動品質ゲート実現
デメリット
- Kotlin専用のため他言語プロジェクトでは使用不可
- 初期設定の複雑さと学習コスト
- 大規模プロジェクトでの解析時間の長さ
- 過度な設定による開発速度低下のリスク
- レガシーコード適用時の大量警告対応負荷
- ルールセットの理解と適切な設定の難しさ
- カスタムルール開発の技術的要求
- 設定ファイルの複雑化によるメンテナンス負荷
- 一部ルールの誤検出(false positive)対応
- Kotlinバージョン依存による互換性管理
参考ページ
書き方の例
インストールと基本セットアップ
Gradle プラグインインストール
// build.gradle.kts (推奨)
plugins {
id("io.gitlab.arturbosch.detekt") version "1.23.6"
}
detekt {
toolVersion = "1.23.6"
config = files("config/detekt/detekt.yml")
buildUponDefaultConfig = true
allRules = false
baseline = file("config/detekt/baseline.xml")
parallel = true
}
dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.6")
}
// build.gradle (Groovy)
plugins {
id 'io.gitlab.arturbosch.detekt' version '1.23.6'
}
detekt {
toolVersion = '1.23.6'
config = files('config/detekt/detekt.yml')
buildUponDefaultConfig = true
allRules = false
baseline = file('config/detekt/baseline.xml')
parallel = true
}
Maven設定
<!-- pom.xml -->
<plugin>
<groupId>com.github.ozsie</groupId>
<artifactId>detekt-maven-plugin</artifactId>
<version>1.23.6</version>
<configuration>
<config>config/detekt/detekt.yml</config>
<baseline>config/detekt/baseline.xml</baseline>
<parallel>true</parallel>
<buildUponDefaultConfig>true</buildUponDefaultConfig>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
コマンドライン使用
# CLIダウンロード
curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.6/detekt-cli-1.23.6-all.jar
# 基本実行
java -jar detekt-cli-1.23.6-all.jar --input path/to/project
# 設定ファイル指定
java -jar detekt-cli-1.23.6-all.jar --input path/to/project --config config/detekt.yml
# レポート生成
java -jar detekt-cli-1.23.6-all.jar --input path/to/project --report html:report.html
# 複数レポート形式
java -jar detekt-cli-1.23.6-all.jar --input path/to/project --report html:report.html --report xml:report.xml
基本的な実行方法
Gradle タスク実行
# 基本解析
./gradlew detekt
# 設定ファイル生成
./gradlew detektGenerateConfig
# ベースライン生成
./gradlew detektBaseline
# 特定ルールセットのみ
./gradlew detekt -Ddetekt.config=config/custom-detekt.yml
# 詳細出力
./gradlew detekt --info
# 並列実行
./gradlew detekt --parallel
# 全ソースセット
./gradlew detektMain detektTest
設定ファイル(detekt.yml)
# config/detekt/detekt.yml
build:
maxIssues: 0
excludeCorrectable: false
weights:
complexity: 2
LongParameterList: 1
style: 1
comments: 1
config:
validation: true
warningsAsErrors: false
checkExhaustiveness: false
excludes: ""
processors:
active: true
exclude:
- 'DetektProgressListener'
parallel: true
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FindingsReport'
- 'FileBasedFindingsReport'
output-reports:
active: true
exclude:
- 'TxtOutputReport'
reports:
- type: 'html'
output: 'reports/detekt.html'
- type: 'xml'
output: 'reports/detekt.xml'
- type: 'md'
output: 'reports/detekt.md'
comments:
active: true
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
UndocumentedPublicClass:
active: false
UndocumentedPublicFunction:
active: false
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
CyclomaticComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotated: []
coroutines:
active: true
GlobalCoroutineUsage:
active: false
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: true
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: false
methodNames: 'toString,hashCode,equals,finalize'
InstanceOfCheckForException:
active: false
NotImplementedDeclaration:
active: false
PrintStackTrace:
active: false
RethrowCaughtException:
active: false
ReturnFromFinally:
active: false
SwallowedException:
active: false
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
ThrowingExceptionFromFinally:
active: false
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: false
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
TooGenericExceptionCaught:
active: true
excludes: ['src/test/**']
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
TooGenericExceptionThrown:
active: true
exceptionNames:
- Error
- Exception
- Throwable
- RuntimeException
formatting:
active: true
android: false
autoCorrect: true
AnnotationOnSeparateLine:
active: false
autoCorrect: true
AnnotationSpacing:
active: false
autoCorrect: true
ArgumentListWrapping:
active: false
autoCorrect: true
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
EnumEntryNameCase:
active: false
autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
ImportOrdering:
active: false
autoCorrect: true
Indentation:
active: false
autoCorrect: true
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
maxLineLength: 120
ModifierOrdering:
active: true
autoCorrect: true
MultiLineIfElse:
active: false
autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
NoConsecutiveBlankLines:
active: true
autoCorrect: true
NoEmptyClassBody:
active: true
autoCorrect: true
NoEmptyFirstLineInMethodBlock:
active: false
autoCorrect: true
NoLineBreakAfterElse:
active: false
autoCorrect: true
NoLineBreakBeforeAssignment:
active: false
autoCorrect: true
NoMultipleSpaces:
active: true
autoCorrect: true
NoSemicolons:
active: true
autoCorrect: true
NoTrailingSpaces:
active: true
autoCorrect: true
NoUnitReturn:
active: true
autoCorrect: true
NoUnusedImports:
active: true
autoCorrect: true
NoWildcardImports:
active: true
PackageName:
active: true
autoCorrect: true
ParameterListWrapping:
active: false
autoCorrect: true
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundComma:
active: true
autoCorrect: true
SpacingAroundCurly:
active: true
autoCorrect: true
SpacingAroundDot:
active: true
autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
SpacingAroundOperators:
active: true
autoCorrect: true
SpacingAroundParens:
active: true
autoCorrect: true
SpacingAroundRangeOperator:
active: true
autoCorrect: true
StringTemplate:
active: true
autoCorrect: true
naming:
active: true
ClassNaming:
active: true
excludes: ['src/test/**']
classPattern: '[A-Z$][a-zA-Z0-9$]*'
ConstructorParameterNaming:
active: true
excludes: ['src/test/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
excludes: ['src/test/**']
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
excludes: ['src/test/**']
forbiddenName: []
FunctionMaxLength:
active: false
excludes: ['src/test/**']
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
excludes: ['src/test/**']
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: ['src/test/**']
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
ignoreAnnotated: ['Composable']
FunctionParameterNaming:
active: true
excludes: ['src/test/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
ObjectNaming:
active: true
excludes: ['src/test/**']
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
excludes: ['src/test/**']
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
excludes: ['src/test/**']
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
excludes: ['src/test/**']
maximumVariableNameLength: 64
VariableMinLength:
active: false
excludes: ['src/test/**']
minimumVariableNameLength: 1
VariableNaming:
active: true
excludes: ['src/test/**']
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: false
CouldBeSequence:
active: false
threshold: 3
ForEachOnRange:
active: true
excludes: ['src/test/**']
SpreadOperator:
active: true
excludes: ['src/test/**']
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: false
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
active: false
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: false
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: false
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: false
IgnoredReturnValue:
active: false
restrictToAnnotatedMethods: true
returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult']
ImplicitDefaultLocale:
active: false
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: false
IteratorHasNextCallsNextMethod:
active: false
IteratorNotThrowingNoSuchElementException:
active: false
LateinitUsage:
active: false
excludes: ['src/test/**']
excludeAnnotatedProperties: []
ignoreOnClassesPattern: ""
MapGetWithNotNullAssertionOperator:
active: false
MissingWhenCase:
active: false
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullOperator:
active: false
UnnecessarySafeCall:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: false
UnsafeCast:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: false
style:
active: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunction:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: false
maxDestructuringEntries: 3
EqualsNullCall:
active: false
EqualsOnSignChangingDataType:
active: false
enabled: true
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values: ['TODO:', 'FIXME:', 'STOPSHIP:']
allowedPatterns: ""
ForbiddenImport:
active: false
imports: []
forbiddenPatterns: ""
ForbiddenMethodCall:
active: false
methods: []
ForbiddenPublicDataClass:
active: false
excludes: ['src/test/**']
ForbiddenVoid:
active: false
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: false
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
excludeAnnotatedFunction: ['dagger.Provides']
LibraryCodeMustSpecifyReturnType:
active: false
LibraryEntitiesShouldNotBePublic:
active: false
LoopWithTooManyJumpStatements:
active: false
maxJumpCount: 1
MagicNumber:
active: true
excludes: ['src/test/**']
ignoreNumbers: ['-1', '0', '1', '2']
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
MandatoryBracesIfStatements:
active: false
MandatoryBracesLoops:
active: false
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: false
ModifierOrder:
active: true
NestedClassesVisibility:
active: false
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: false
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: "equals"
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: false
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableDecimalLength: 5
UnnecessaryAbstractClass:
active: false
excludeAnnotatedClasses: ['dagger.Module']
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: false
UnnecessaryInheritance:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: false
UnusedPrivateMember:
active: false
allowedNames: "(_|ignored|expected|serialVersionUID)"
UseArrayLiteralsInAnnotations:
active: false
UseCheckOrError:
active: false
UseDataClass:
active: false
excludeAnnotatedClasses: []
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: false
UseOrEmpty:
active: false
UseRequire:
active: false
UseRequireNotNull:
active: false
UselessCallOnNotNull:
active: false
UtilityClassWithPublicConstructor:
active: false
VarCouldBeVal:
active: false
WildcardImport:
active: true
excludes: ['src/test/**']
excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*']
Android プロジェクト設定
Android用設定例
// app/build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("io.gitlab.arturbosch.detekt")
}
android {
compileSdk 34
defaultConfig {
minSdk 21
targetSdk 34
}
}
detekt {
toolVersion = "1.23.6"
config = files("$projectDir/config/detekt/detekt.yml")
buildUponDefaultConfig = true
allRules = false
baseline = file("$projectDir/config/detekt/baseline.xml")
parallel = true
source = files(
"src/main/java",
"src/main/kotlin",
"src/test/java",
"src/test/kotlin"
)
}
dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.6")
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
// Target version of the generated JVM bytecode
jvmTarget = "1.8"
}
tasks.withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>().configureEach {
jvmTarget = "1.8"
}
マルチプラットフォーム プロジェクト設定
Kotlin Multiplatform用設定
// build.gradle.kts (ルートプロジェクト)
plugins {
kotlin("multiplatform") version "1.9.10"
id("io.gitlab.arturbosch.detekt") version "1.23.6"
}
kotlin {
jvm()
js(IR) {
browser()
nodejs()
}
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting
val commonTest by getting
val jvmMain by getting
val jvmTest by getting
val jsMain by getting
val jsTest by getting
val iosMain by creating
val iosTest by creating
}
}
detekt {
source = files(
"src/commonMain/kotlin",
"src/commonTest/kotlin",
"src/jvmMain/kotlin",
"src/jvmTest/kotlin",
"src/jsMain/kotlin",
"src/jsTest/kotlin",
"src/iosMain/kotlin",
"src/iosTest/kotlin"
)
parallel = true
config = files("detekt-config.yml")
buildUponDefaultConfig = true
}
IDE統合設定
IntelliJ IDEA / Android Studio設定
# プラグインインストール
# File → Settings → Plugins → "Detekt" で検索してインストール
# 設定手順
# File → Settings → Tools → Detekt
# Configuration file: プロジェクトルートの detekt.yml を指定
# Enable Detekt: ✓
# Enable on-the-fly inspection: ✓
# External Tools設定(オプション)
# File → Settings → Tools → External Tools → Add
Name: Detekt
Description: Kotlin static analysis
Program: ./gradlew
Arguments: detekt
Working directory: $ProjectFileDir$
VS Code設定
// .vscode/settings.json
{
"kotlin.compiler.jvm.target": "1.8",
"kotlin.detekt.enabled": true,
"kotlin.detekt.configPath": "config/detekt/detekt.yml",
"[kotlin]": {
"editor.defaultFormatter": "fwcd.kotlin",
"editor.formatOnSave": true,
"editor.rulers": [120]
}
}
// tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Detekt Check",
"type": "shell",
"command": "./gradlew",
"args": ["detekt"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
},
{
"label": "Detekt Baseline",
"type": "shell",
"command": "./gradlew",
"args": ["detektBaseline"],
"group": "build"
}
]
}
CI/CD統合例
GitHub Actions
# .github/workflows/detekt.yml
name: Detekt
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
detekt:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run Detekt
run: ./gradlew detekt
- name: Upload Detekt reports
uses: actions/upload-artifact@v4
if: always()
with:
name: detekt-reports
path: |
build/reports/detekt/
*/build/reports/detekt/
- name: Annotate PR with Detekt results
uses: lcollins/detekt-action@v1
if: github.event_name == 'pull_request'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
detekt_config_path: config/detekt/detekt.yml
GitLab CI/CD
# .gitlab-ci.yml
stages:
- quality
detekt:
stage: quality
image: openjdk:11-jdk
before_script:
- chmod +x ./gradlew
script:
- ./gradlew detekt
- ./gradlew detekt --continue || true # Generate reports even if issues found
artifacts:
reports:
codequality: build/reports/detekt/detekt.sarif
paths:
- build/reports/detekt/
expire_in: 1 week
only:
- main
- develop
- merge_requests
カスタムルール作成
カスタムルール例
// custom-rules/src/main/kotlin/CustomRuleSet.kt
package com.example.detekt.rules
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
class CustomRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "custom-rules"
override fun instance(config: Config): RuleSet {
return RuleSet(
ruleSetId,
listOf(
NoHardcodedStringsRule(config),
ProperLogTagUsage(config),
AvoidSystemPrintRule(config)
)
)
}
}
// NoHardcodedStringsRule.kt
class NoHardcodedStringsRule(config: Config) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Warning,
"Hardcoded strings should be avoided, use string resources instead",
Debt.FIVE_MINS
)
override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) {
super.visitStringTemplateExpression(expression)
if (expression.entries.size == 1 && expression.entries[0] is KtLiteralStringTemplateEntry) {
val text = expression.entries[0].text
if (text.length > 10 && !isInTestFile(expression)) {
report(CodeSmell(issue, Entity.from(expression), "Hardcoded string found: $text"))
}
}
}
private fun isInTestFile(element: PsiElement): Boolean {
return element.containingFile?.virtualFile?.path?.contains("/test/") == true
}
}
カスタムルールの適用
# detekt.yml
custom-rules:
active: true
NoHardcodedStringsRule:
active: true
ProperLogTagUsage:
active: true
maxTagLength: 23
AvoidSystemPrintRule:
active: true
excludes: ['src/test/**']
ベースラインと段階的導入
ベースライン生成と管理
# 初回ベースライン生成
./gradlew detektBaseline
# ベースライン更新
./gradlew detektBaseline --update-baseline
# ベースラインを無視してチェック
./gradlew detekt --auto-correct
段階的導入戦略
// 段階的にルールを有効化するGradleタスク
tasks.register("detektGradual") {
group = "verification"
description = "Run detekt with gradually increasing rule strictness"
doLast {
val phases = listOf("phase1.yml", "phase2.yml", "phase3.yml")
phases.forEach { phase ->
exec {
commandLine("./gradlew", "detekt", "--config", "config/detekt/$phase")
}
}
}
}
トラブルシューティング
よくある問題と解決法
# メモリ不足の場合
export GRADLE_OPTS="-Xmx2g -XX:MaxMetaspaceSize=512m"
./gradlew detekt
# 並列実行でのアクセス競合
./gradlew detekt --no-parallel
# 設定ファイル検証
./gradlew detektGenerateConfig
diff detekt-default.yml config/detekt/detekt.yml
# キャッシュクリア
./gradlew clean
rm -rf ~/.gradle/caches/modules-2/files-2.1/io.gitlab.arturbosch.detekt/
# 依存関係確認
./gradlew dependencies --configuration detektPlugins
# デバッグ実行
./gradlew detekt --info --stacktrace