Skip to content

Commit

Permalink
Make Anvil detectors configurable for anvil scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
WhosNickDoglio committed Jul 8, 2024
1 parent 93a54d5 commit a83b078
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.jetbrains.uast.kotlin.isKotlin
* `@ContributesBinding` or `@ContributesMultibinding` annotations for classes that use Dagger and
* implement an interface or abstract class.
*/
// TODO configurable
internal class MissingContributesBindingDetector :
Detector(),
SourceCodeScanner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.StringOption
import com.android.tools.lint.detector.api.TextFormat
import dev.whosnickdoglio.anvil.CONTRIBUTES_TO
import dev.whosnickdoglio.lint.shared.MODULE
Expand All @@ -22,11 +23,12 @@ import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.kotlin.isKotlin

// TODO make this configurable for Anvil scopes in quick fix
internal class MissingContributesToDetector :
Detector(),
SourceCodeScanner {
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UAnnotation::class.java)

override fun getApplicableUastTypes(): List<Class<out UElement>> =
listOf(UAnnotation::class.java)

override fun createUastHandler(context: JavaContext): UElementHandler? {
// Anvil is Kotlin only
Expand All @@ -37,16 +39,47 @@ internal class MissingContributesToDetector :
val element = node.uastParent as? UClass ?: return

if (!element.hasAnnotation(CONTRIBUTES_TO)) {
val anvilScopes =
customAnvilScopes.getValue(context).orEmpty().split(",").filter {
it.isNotEmpty()
}

context.report(
Incident(context, ISSUE)
.location(context.getNameLocation(element))
.message(ISSUE.getExplanation(TextFormat.RAW))
.fix(
fix()
.name("Add @ContributesTo annotation")
.annotate(CONTRIBUTES_TO, context, element)
.autoFix(robot = true, independent = true)
.build(),
if (anvilScopes.isEmpty()) {
fix()
.name("Add @ContributesTo annotation")
.annotate(CONTRIBUTES_TO, context, element)
.autoFix(robot = true, independent = true)
.build()
} else {
fix()
.alternatives()
.apply {
anvilScopes.forEach { scope ->
add(
fix()
.name(
"Contribute to ${scope.substringAfterLast(".")} ",
)
.annotate(
"$CONTRIBUTES_TO($scope::class)",
context,
element,
)
.autoFix(
robot = true,
independent = true,
)
.build(),
)
}
}
.build()
},
),
)
}
Expand All @@ -59,6 +92,18 @@ internal class MissingContributesToDetector :
private val implementation =
Implementation(MissingContributesToDetector::class.java, Scope.JAVA_FILE_SCOPE)

internal const val CUSTOM_ANVIL_SCOPE_OPTION_KEY = "anvilScopes"

private val customAnvilScopes =
StringOption(
name = CUSTOM_ANVIL_SCOPE_OPTION_KEY,
description = "A comma separated list of fully qualified custom Hilt components",
explanation =
"Hilt provides you the ability to define custom Components if the " +
"preexisting ones don't work for your use case, If you have any custom Hilt components " +
"defined they can be added to the quickfix suggestions with this option. ",
)

internal val ISSUE =
Issue.create(
id = "MissingContributesToAnnotation",
Expand All @@ -71,6 +116,6 @@ internal class MissingContributesToDetector :
priority = 5,
severity = Severity.ERROR,
implementation = implementation,
)
).setOptions(listOf(customAnvilScopes))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,55 @@ class MissingContributesToDetectorTest {
}

@Test
fun `java provides module without @ContributesTo annotation shows an error`() {
fun `kotlin @Module without @ContributesTo with lint option set shows error with expected quickfix`() {
TestLintTask.lint()
.files(
daggerAnnotations,
TestFiles.kotlin(
"""
import dagger.Module
import dagger.Provides
@Module
class MyModule {
@Provides fun provideMyThing(): String = "Hello World"
@Provides fun provideAnotherThing(): Int = 1
}
""",
)
.indented(),
)
.issues(MissingContributesToDetector.ISSUE)
.configureOption(
MissingContributesToDetector.CUSTOM_ANVIL_SCOPE_OPTION_KEY,
"dev.whosnickdoglio.anvil.AppScope",
)
.run()
.expect(
"""
src/MyModule.kt:5: Error: This Dagger module is missing a @ContributesTo annotation for Anvil to pick it up. See https://whosnickdoglio.dev/dagger-rules/rules/#a-class-annotated-with-module-should-also-be-annotated-with-contributesto for more information. [MissingContributesToAnnotation]
class MyModule {
~~~~~~~~
1 errors, 0 warnings
"""
.trimIndent(),
)
.expectErrorCount(1)
.expectFixDiffs(
"""
Autofix for src/MyModule.kt line 5: Contribute to AppScope :
@@ -4 +4
+ @com.squareup.anvil.annotations.ContributesTo(dev.whosnickdoglio.anvil.AppScope::class)
"""
.trimIndent(),
)
}

@Test
fun `java provides module without @ContributesTo annotation shows no error`() {
TestLintTask.lint()
.files(
daggerAnnotations,
Expand Down

0 comments on commit a83b078

Please sign in to comment.