feat: add :featured-lint-rules Android Lint module with HardcodedFlagValue detector#141
feat: add :featured-lint-rules Android Lint module with HardcodedFlagValue detector#141kirich1409 merged 20 commits intomainfrom
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scope UncheckedFlagAccess to same-file only (consistent with all existing rules, avoids FQN/lifecycle ambiguity without type resolution) - Specify two-pass accumulator algorithm for ordering safety - Add companion object exclusion corner case and test coverage - Document Debt constants, koverVerify note, FeaturedRuleSetProvider KDoc update Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KMP project requires cross-file analysis. UncheckedFlagAccess now uses BindingContext via detektWithTypeResolution task. InvalidFlagReference remains PSI-only. Documents BindingContext.EMPTY guard and Gradle setup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion pseudocode, minApi, stub bound, code-sharing rationale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dule Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…figValues test expectation
…nt, constructor-free tests, stronger unrelated-type test, suppress in ConfigValues
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Android Lint rules artifact (:featured-lint-rules) to complement the existing Detekt rules by detecting direct ConfigParam.defaultValue reads using UAST + type resolution, and wires in suppression for the known-safe fallback path in ConfigValues.
Changes:
- Introduces
:featured-lint-rules(published asfeatured-lint-rules) withHardcodedFlagValueDetector+FeaturedIssueRegistry. - Adds Lint dependencies/version catalog entries and registers the new module in Gradle settings.
- Documents the design/plan for the lint pilot (plus additional specs/plans added in this PR) and suppresses the rule at the
ConfigValuesdefault fallback.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Includes the new :featured-lint-rules module. |
| gradle/libs.versions.toml | Adds lint version + lint-api/lint-tests dependencies for the new module. |
| featured-lint-rules/build.gradle.kts | Configures the JVM lint-rules module, publishing, and Lint-Registry-v2 manifest attribute. |
| featured-lint-rules/src/main/kotlin/dev/androidbroadcast/featured/lint/HardcodedFlagValueDetector.kt | Implements the UAST-based detector for ConfigParam.defaultValue access. |
| featured-lint-rules/src/main/kotlin/dev/androidbroadcast/featured/lint/FeaturedIssueRegistry.kt | Registers the lint issues and vendor metadata. |
| featured-lint-rules/src/test/kotlin/dev/androidbroadcast/featured/lint/HardcodedFlagValueDetectorTest.kt | Adds lint-tests coverage for positive/negative scenarios. |
| featured-lint-rules/api/featured-lint-rules.api | Adds BCV API surface snapshot for the new published module. |
| docs/superpowers/specs/2026-03-23-featured-lint-rules-design.md | Design spec for the lint-rules pilot and detector behavior. |
| docs/superpowers/specs/2026-03-23-behind-flag-annotation-design.md | Adds a draft design spec for future flag-guard annotations/rules. |
| docs/superpowers/specs/2026-03-22-agp9-migration-design.md | Adds an AGP9 migration design document (context/planning). |
| docs/superpowers/plans/2026-03-23-featured-lint-rules.md | Implementation plan for :featured-lint-rules. |
| docs/superpowers/plans/2026-03-23-behind-flag-annotation.md | Implementation plan for @BehindFlag/@AssumesFlag + detekt rules. |
| docs/superpowers/plans/2026-03-22-agp9-migration.md | Implementation plan for the AGP9 migration workstream. |
| core/src/commonMain/kotlin/dev/androidbroadcast/featured/ConfigValues.kt | Suppresses HardcodedFlagValue at the intentional default fallback. |
| override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) { | ||
| // Only care about references named "defaultValue" | ||
| if (node.identifier != DEFAULT_VALUE_PROPERTY) return | ||
|
|
||
| // Must be the selector of a qualified expression: receiver.defaultValue | ||
| val parent = node.uastParent as? UQualifiedReferenceExpression ?: return | ||
|
|
||
| // Resolve the receiver's type | ||
| val receiverType = parent.receiver.getExpressionType() as? PsiClassType ?: return | ||
| val receiverClass = receiverType.resolve() ?: return | ||
|
|
There was a problem hiding this comment.
visitSimpleNameReferenceExpression assumes any defaultValue identifier inside a UQualifiedReferenceExpression is the selector (i.e., receiver.defaultValue). But the same AST shape occurs when a variable/property named defaultValue is used as the receiver (e.g., defaultValue.key). In that case this detector will incorrectly report a violation if defaultValue happens to be a ConfigParam. Please guard against this by verifying the defaultValue node is the qualified expression’s selector (or switch to visiting UQualifiedReferenceExpression and checking its selector name) before resolving the receiver type.
| @Test | ||
| fun `does not report defaultValue on String receiver`() { | ||
| lint() | ||
| .files( | ||
| configParamStub, | ||
| kotlin( | ||
| """ | ||
| fun check(s: String) { | ||
| println(s.defaultValue) | ||
| } | ||
| """, | ||
| ).indented(), | ||
| ) | ||
| .run() | ||
| .expectClean() | ||
| } |
There was a problem hiding this comment.
Consider adding a regression test for the false-positive case where a ConfigParam-typed variable/property is named defaultValue and used as a receiver (e.g., val defaultValue: ConfigParam<Boolean> = …; println(defaultValue.key)). The current suite doesn’t cover this, and the detector logic can easily regress here because it visits all USimpleNameReferenceExpressions named defaultValue.
…ession test Guard against false positives when a ConfigParam-typed variable is named "defaultValue" and used as a receiver (e.g. defaultValue.key). The detector now verifies the "defaultValue" node is the selector of the qualified expression (not the receiver) before resolving the receiver type. Adds a regression test covering this case so the selector-position guard cannot silently regress. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
:featured-lint-rulesGradle module published to Maven Central asfeatured-lint-rulesHardcodedFlagValueDetector— fires when.defaultValueis accessed directly on aConfigParam, using full UAST type resolution (zero false positives vs. the name-based heuristic infeatured-detekt-rules)@Suppress("HardcodedFlagValue")toConfigValues.ktinternal fallback path (legitimate access that must be silenced)Why Lint alongside Detekt
Detekt's
HardcodedFlagValueRulefires on anyx.defaultValuewherexis a simple name — it cannot resolve types. The Lint version usesJavaEvaluator.extendsClassagainst the compiled classpath, so it only fires when the receiver is truly aConfigParam.Test Plan
./gradlew :featured-lint-rules:checkpasses (7 tests, BCV, spotless)unzip -p featured-lint-rules/build/libs/featured-lint-rules.jar META-INF/MANIFEST.MFcontainsLint-Registry-v2: dev.androidbroadcast.featured.lint.FeaturedIssueRegistry@Suppress("HardcodedFlagValue")onConfigValues.kt:94suppresses the rule correctly in an Android project consuming the artifact🤖 Generated with Claude Code