Detekt

コード品質リンターKotlinDevOps静的解析AndroidJVMマルチプラットフォーム

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