Skip to content

Instantly share code, notes, and snippets.

@princeparadoxes
Created September 11, 2023 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save princeparadoxes/f6e79fb7af6ee7987934bbb25e5435f9 to your computer and use it in GitHub Desktop.
Save princeparadoxes/f6e79fb7af6ee7987934bbb25e5435f9 to your computer and use it in GitHub Desktop.
DaggerUnusedValidator
package ru.cian.validate.dagger.deps.unused
import com.google.auto.service.AutoService
import dagger.Component
import dagger.model.BindingGraph
import dagger.spi.BindingGraphPlugin
import dagger.spi.DiagnosticReporter
import javax.lang.model.element.AnnotationValue
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
import javax.lang.model.type.DeclaredType
import javax.tools.Diagnostic
@AutoService(BindingGraphPlugin::class)
class DaggerUnusedValidator : BindingGraphPlugin {
override fun visitGraph(
bindingGraph: BindingGraph,
diagnosticReporter: DiagnosticReporter
) {
val currentComponent = bindingGraph.rootComponentNode()
if (!currentComponent.isRealComponent || isSuppress(currentComponent.componentPath().currentComponent())) {
return
}
val dependencies = getDependencies(currentComponent)
val bindings = bindingGraph.bindings().map { contextBinding -> contextBinding.key().type().toString() }
dependencies.forEach { (dependency, dependencyMethods) ->
val unusedMethods = dependencyMethods.subtract(bindings)
if (unusedMethods.size == dependencyMethods.size) {
diagnosticReporter.reportComponent(
Diagnostic.Kind.ERROR,
currentComponent,
"Dependency ${dependency} is unused"
)
}
}
}
private fun getDependencies(
componentNode: BindingGraph.ComponentNode
): Map<String, List<String>> {
val dependenciesTypes = getComponentDependencies(componentNode)
val dependenciesMap = dependenciesTypes
.associateWith { getMethodsReturnTypes(it) }
.mapKeys { it.key.toString() }
return dependenciesMap
}
private fun getMethodsReturnTypes(declaredType: DeclaredType): List<String> {
val methods = declaredType.asElement().enclosedElements
.filter { it.kind == ElementKind.METHOD }
.map { it as ExecutableElement }
val returnTypes = methods
.map { it.returnType.toString() }
return returnTypes
}
@Suppress("UNCHECKED_CAST")
private fun getComponentDependencies(componentNode: BindingGraph.ComponentNode): List<DeclaredType> {
val dependenciesMethod = getDependenciesMethod(componentNode)
return if (dependenciesMethod != null) {
(dependenciesMethod.value as List<AnnotationValue>)
.map { it.value }
.map { it as DeclaredType }
.filter { !isSuppress(it.asElement()) }
} else {
emptyList()
}
}
private fun getDependenciesMethod(componentNode: BindingGraph.ComponentNode): AnnotationValue? {
val annotationMirrors = componentNode.componentPath().currentComponent().annotationMirrors
val componentAnnotationMirror = annotationMirrors
.first { it.annotationType.toString() == Component::class.java.name }
val dependenciesMethod = componentAnnotationMirror.elementValues.entries
.firstOrNull { it.key.simpleName.toString() == Component::dependencies.name }
return dependenciesMethod?.value
}
private fun isSuppress(element: Element): Boolean {
val annotationMirrors = element.annotationMirrors
val suppressAnnotationMirror = annotationMirrors
.firstOrNull { it.annotationType.toString() == SuppressDaggerUnusedDeps::class.java.name }
return suppressAnnotationMirror != null
}
}
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class SuppressDaggerUnusedDeps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment