본문으로 건너뛰기
cccskills
무료GitHub에 공개

detekt-custom-rules

Register, activate, and test a custom detekt rule in :quality:detekt-rules. Use when adding a DI/privacy/suppression guardrail rule or debugging a false positive.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구GitLab
설치 방법 보기

포함된 파일(1)

  • SKILL.md7.8 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Custom Detekt Rules

Custom rule set diGuardrails in module :quality:detekt-rules. Enforces DI, Hilt, suppression, and resolver-log privacy conventions at compile time.

Existing Rules

scripts/ci/check_harness_manifests.py (validate_factual_ground_truth) mechanically checks that every rule registered in RipDpiRuleSetProvider.kt appears in this table -- omitting a rule here fails CI rather than silently drifting.

RuleWhat It CatchesWhy
InjectConstructorDefaultParameterDefault values on @Inject constructor paramsHilt ignores defaults, causing silent behavior mismatch
HiltViewModelApplicationContext@ApplicationContext in @HiltViewModel constructorsForces narrower abstractions, improves testability
DisallowNewSuppression@Suppress without an adjacent ROADMAP-architecture-refactor allowlist commentPrevents new lint debt from becoming invisible
NoResolverIpInLogsResolver or upstream IP identifiers interpolated into log callsKeeps user-visible network state out of logs

Module Structure

quality/detekt-rules/
  src/main/kotlin/com/poyka/ripdpi/quality/detekt/
    RipDpiRuleSetProvider.kt           -- Registers all rules (id: "diGuardrails")
    AnnotationMatchers.kt              -- Shared helpers: hasAnnotation(), findAnnotation()
    InjectConstructorDefaultParameter.kt
    HiltViewModelApplicationContext.kt
    DisallowNewSuppression.kt
    NoResolverIpInLogs.kt
  src/test/kotlin/com/poyka/ripdpi/quality/detekt/
    InjectConstructorDefaultParameterTest.kt
    HiltViewModelApplicationContextTest.kt
    DisallowNewSuppressionTest.kt
    NoResolverIpInLogsTest.kt

Creating a New Rule

Step 1: Write the Rule Class

Create src/main/kotlin/com/poyka/ripdpi/quality/detekt/<YourRuleName>.kt:

package com.poyka.ripdpi.quality.detekt

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtClass

class YourRuleName(config: Config) : Rule(config) {
    override val issue: Issue =
        Issue(
            id = "YourRuleName",
            severity = Severity.Defect,
            description = "One-line description of what this catches.",
            debt = Debt.FIVE_MINS,
        )

    override fun visitClass(klass: KtClass) {
        super.visitClass(klass)
        // Use hasAnnotation() from AnnotationMatchers.kt
        if (!klass.hasAnnotation("SomeAnnotation")) return

        // Check condition and report
        report(
            CodeSmell(
                issue = issue,
                entity = Entity.from(klass),
                message = "Actionable message: what to do instead.",
            ),
        )
    }
}

Step 2: Register in RuleSetProvider

Add to RipDpiRuleSetProvider.kt:

override fun instance(config: Config): RuleSet =
    RuleSet(
        ruleSetId,
        listOf(
            InjectConstructorDefaultParameter(config),
            HiltViewModelApplicationContext(config),
            DisallowNewSuppression(config),
            NoResolverIpInLogs(config),
            YourRuleName(config),  // <-- add here
        ),
    )

Step 3: Activate in detekt.yml

Add to config/detekt/detekt.yml:

diGuardrails:
  active: true
  InjectConstructorDefaultParameter:
    active: true
  HiltViewModelApplicationContext:
    active: true
  NoResolverIpInLogs:
    active: true
  YourRuleName:               # <-- add here
    active: true

DisallowNewSuppression is registered in RipDpiRuleSetProvider and inherits the active diGuardrails rule-set setting; it has no per-rule override in the current config.

Step 4: Write Tests

Create src/test/kotlin/com/poyka/ripdpi/quality/detekt/<YourRuleName>Test.kt:

package com.poyka.ripdpi.quality.detekt

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class YourRuleNameTest {
    private val rule = YourRuleName(Config.empty)

    @Test
    fun `reports violation on annotated class`() {
        val code = """
            @SomeAnnotation
            class Bad { /* violating pattern */ }
        """.trimIndent()
        val findings = rule.compileAndLint(code)
        assertEquals(1, findings.size)
    }

    @Test
    fun `allows non-annotated class`() {
        val code = """
            class Fine { /* same pattern but no annotation */ }
        """.trimIndent()
        val findings = rule.compileAndLint(code)
        assertEquals(0, findings.size)
    }
}

Cover: positive detection, negative (no false positive), edge cases (primary vs secondary constructors, nested classes).

Annotation Matching

Use the shared helpers from AnnotationMatchers.kt:

// Check if a KtAnnotated element has an annotation by simple name
klass.hasAnnotation("HiltViewModel")        // matches @HiltViewModel
constructor.hasAnnotation("Inject")          // matches @Inject

// Find the annotation entry (for Entity.from targeting)
parameter.findAnnotation("ApplicationContext")  // returns KtAnnotationEntry?

Important: These match by simple name, not fully-qualified name. hasAnnotation("Inject") matches both @javax.inject.Inject and @com.google.inject.Inject.

Common PSI Visitor Methods

MethodWhen To Use
visitClass(KtClass)Checking class-level patterns
visitPrimaryConstructor(KtPrimaryConstructor)Constructor parameter rules
visitSecondaryConstructor(KtSecondaryConstructor)Must handle alongside primary
visitNamedFunction(KtNamedFunction)Function-level rules
visitProperty(KtProperty)Property declaration rules

Use getStrictParentOfType<KtClassOrObject>() to navigate from a constructor to its owning class.

Convention Plugin

ripdpi.android.detekt.gradle.kts configures detekt for all modules:

  • Parallel: true
  • Config: config/detekt/detekt.yml
  • Reports: HTML + XML
  • JVM target: 17
  • Excludes: **/build/**, **/generated/**, **/jni/**, **/cpp/**
  • Baseline: Auto-detected from detekt-baseline.xml if present in module
  • Plugin deps: Custom rules (:quality:detekt-rules) + detekt-compose-rules

Run detekt:

./gradlew staticAnalysis          # detekt + ktlint + lint
./gradlew detektDebug             # detekt only (single variant)

Common Mistakes

MistakeFix
Forgetting to register in RipDpiRuleSetProviderRule exists but never runs. Add to the listOf(...) in instance().
Using FQN for annotation matchinghasAnnotation("javax.inject.Inject") won't match. Use simple name: "Inject".
Not activating in detekt.ymlRule is registered but inactive. Add YourRuleName: { active: true } under diGuardrails:.
Testing only primary constructorsMany rules apply to both primary and secondary. Override both visitor methods.
Reporting on wrong entityUse Entity.from(specificElement) not Entity.from(constructor) -- highlights the exact problem location.
Missing super.visitX() callAlways call super.visitPrimaryConstructor(constructor) etc. to continue traversal.

See Also

  • config/detekt/detekt.yml -- Full detekt configuration
  • build-logic/convention/src/main/kotlin/ripdpi.android.detekt.gradle.kts -- Convention plugin

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

po4yka/RIPDPI812026년 10월 20일 업데이트

po4yka의 스킬 모두 보기

이 스킬의 문제 신고