Skip to content

feat: add :featured-lint-rules Android Lint module with HardcodedFlagValue detector#141

Merged
kirich1409 merged 20 commits intomainfrom
feat/featured-lint-rules
Mar 23, 2026
Merged

feat: add :featured-lint-rules Android Lint module with HardcodedFlagValue detector#141
kirich1409 merged 20 commits intomainfrom
feat/featured-lint-rules

Conversation

@kirich1409
Copy link
Copy Markdown
Contributor

Summary

  • Adds new :featured-lint-rules Gradle module published to Maven Central as featured-lint-rules
  • Implements HardcodedFlagValueDetector — fires when .defaultValue is accessed directly on a ConfigParam, using full UAST type resolution (zero false positives vs. the name-based heuristic in featured-detekt-rules)
  • Adds @Suppress("HardcodedFlagValue") to ConfigValues.kt internal fallback path (legitimate access that must be silenced)

Why Lint alongside Detekt

Detekt's HardcodedFlagValueRule fires on any x.defaultValue where x is a simple name — it cannot resolve types. The Lint version uses JavaEvaluator.extendsClass against the compiled classpath, so it only fires when the receiver is truly a ConfigParam.

Test Plan

  • ./gradlew :featured-lint-rules:check passes (7 tests, BCV, spotless)
  • unzip -p featured-lint-rules/build/libs/featured-lint-rules.jar META-INF/MANIFEST.MF contains Lint-Registry-v2: dev.androidbroadcast.featured.lint.FeaturedIssueRegistry
  • Verify @Suppress("HardcodedFlagValue") on ConfigValues.kt:94 suppresses the rule correctly in an Android project consuming the artifact

🤖 Generated with Claude Code

kirich1409 and others added 19 commits March 22, 2026 16:33
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>
Also add testImplementation(lint.api) to fix test classpath resolution,
and @RunWith(JUnit4::class) to make @test methods visible to Gradle's
JUnit runner (LintDetectorTest extends JUnit3 TestCase).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt, constructor-free tests, stronger unrelated-type test, suppress in ConfigValues
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 23, 2026 12:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 as featured-lint-rules) with HardcodedFlagValueDetector + 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 ConfigValues default 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.

Comment on lines +45 to +55
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

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +104
@Test
fun `does not report defaultValue on String receiver`() {
lint()
.files(
configParamStub,
kotlin(
"""
fun check(s: String) {
println(s.defaultValue)
}
""",
).indented(),
)
.run()
.expectClean()
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
…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>
@kirich1409 kirich1409 merged commit 6884dae into main Mar 23, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants