Repository: autonomousapps/dependency-analysis-android-gradle-plugin
Issue: Incorrect Advice to replace dependence from `implementation` to `testImplementation`

gradle=6.5
plugin=0.50
agp=3.6.2 but in this module, it isn't used 
branch for reproducing: https://github.com/avito-tech/avito-android/tree/dependencies-analysis-0.50

**Describe the bug**
1. `./gradlew -p subprojects buildHealth`
2. Look at `subprojects/gradle/upload-cd-build-result/build/reports/dependency-analysis/advice-all-variants-pretty.json`
3. `{
      "dependency": {
        "identifier": ":gradle:git",
        "configurationName": "implementation"
      },
      "usedTransitiveDependencies": [],
      "fromConfiguration": "implementation",
      "toConfiguration": "testImplementation"
    }`
4. Do what advice says
5. `make publish_to_maven_local` from the project root dir
6. Get compile error
`e: /Users/sergey/projects/avito-android-infra/subprojects/gradle/upload-cd-build-result/src/main/kotlin/com/avito/cd/UploadCdBuildResultTask.kt: (4, 18): Unresolved reference: git
e: /Users/sergey/projects/avito-android-infra/subprojects/gradle/upload-cd-build-result/src/main/kotlin/com/avito/cd/UploadCdBuildResultTask.kt: (5, 18): Unresolved reference: git
e: /Users/sergey/projects/avito-android-infra/subprojects/gradle/upload-cd-build-result/src/main/kotlin/com/avito/cd/UploadCdBuildResultTask.kt: (57, 32): Unresolved reference: gitState
e: /Users/sergey/projects/avito-android-infra/subprojects/gradle/upload-cd-build-result/src/main/kotlin/com/avito/cd/UploadCdBuildResultTask.kt: (57, 65): Expression expected, but a package name found
e: /Users/sergey/projects/avito-android-infra/subprojects/gradle/upload-cd-build-result/src/main/kotlin/com/avito/cd/UploadCdBuildResultTask.kt: (79, 19): Unresolved reference: GitState
`

**Expected behavior**
Advice is correct and module compiles and publishes to maven local

Changed files:
src/main/kotlin/com/autonomousapps/internal/analyzer/JvmProjectAnalyzer.kt, src/main/kotlin/com/autonomousapps/DependencyAnalysisPlugin.kt, src/main/kotlin/com/autonomousapps/tasks/DependencyMisuseTask.kt, src/main/kotlin/com/autonomousapps/internal/analyzer/JvmSourceSet.kt

diff --git a/src/main/kotlin/com/autonomousapps/DependencyAnalysisPlugin.kt b/src/main/kotlin/com/autonomousapps/DependencyAnalysisPlugin.kt
index f25dfd0b..4d0fc3c5 100644
--- a/src/main/kotlin/com/autonomousapps/DependencyAnalysisPlugin.kt
+++ b/src/main/kotlin/com/autonomousapps/DependencyAnalysisPlugin.kt
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
 import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
 import java.util.concurrent.atomic.AtomicBoolean
 
+//region constants
 private const val ANDROID_APP_PLUGIN = "com.android.application"
 private const val ANDROID_LIBRARY_PLUGIN = "com.android.library"
 
@@ -39,6 +40,7 @@ private const val CONF_ADVICE_ALL_CONSUMER = "adviceAllConsumer"
 private const val CONF_ADVICE_ALL_PRODUCER = "adviceAllProducer"
 
 internal const val TASK_GROUP_DEP = "dependency-analysis"
+//endregion
 
 @Suppress("unused")
 class DependencyAnalysisPlugin : Plugin<Project> {
@@ -90,6 +92,8 @@ class DependencyAnalysisPlugin : Plugin<Project> {
       subExtension = extensions.create(EXTENSION_NAME, objects, getExtension(), path)
     }
 
+    // this is done _after_ creating the extension so that AS doesn't report configuration issues
+    // The goal is fundamentally to avoid configuring any tasks, but adding the extensions is ok
     if (isInAndroidStudio()) return@run
 
     pluginManager.withPlugin(ANDROID_APP_PLUGIN) {
@@ -318,19 +322,19 @@ class DependencyAnalysisPlugin : Plugin<Project> {
 
     afterEvaluate {
       val kotlin = the<KotlinProjectExtension>()
-      val testSource = kotlin.sourceSets.findByName("test")
       val mainSource = kotlin.sourceSets.findByName("main")
-      mainSource?.let { kotlinSourceSet ->
+      val testSourceSet = kotlin.sourceSets.findByName("test")
+      mainSource?.let { mainSourceSet ->
         try {
           val kotlinJvmModuleClassAnalyzer: KotlinJvmAnalyzer =
             if (pluginManager.hasPlugin(APPLICATION_PLUGIN)) {
-              KotlinJvmAppAnalyzer(this, kotlinSourceSet, testSource)
+              KotlinJvmAppAnalyzer(this, mainSourceSet, testSourceSet)
             } else {
-              KotlinJvmLibAnalyzer(this, kotlinSourceSet, testSource)
+              KotlinJvmLibAnalyzer(this, mainSourceSet, testSourceSet)
             }
           analyzeDependencies(kotlinJvmModuleClassAnalyzer)
         } catch (_: UnknownTaskException) {
-          logger.warn("Skipping tasks creation for sourceSet `${kotlinSourceSet.name}`")
+          logger.warn("Skipping tasks creation for sourceSet `${mainSourceSet.name}`")
         }
       } ?: logger.warn("No main source set. No analysis performed")
     }
@@ -346,6 +350,8 @@ class DependencyAnalysisPlugin : Plugin<Project> {
    * Root project. Configures lifecycle tasks that aggregates reports across all subprojects.
    */
   private fun Project.configureRootProject() {
+    // this is done _after_ creating the extension so that AS doesn't report configuration issues
+    // The goal is fundamentally to avoid configuring any tasks, but adding the extensions is ok
     if (isInAndroidStudio()) return
 
     val outputPaths = RootOutputPaths(this)
diff --git a/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmProjectAnalyzer.kt b/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmProjectAnalyzer.kt
index dc4ff286..c5b4f57f 100644
--- a/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmProjectAnalyzer.kt
+++ b/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmProjectAnalyzer.kt
@@ -25,12 +25,12 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet as JbKotlinSourceSet
 
 internal abstract class JvmAnalyzer(
   project: Project,
-  private val sourceSet: JvmSourceSet,
+  private val mainSourceSet: JvmSourceSet,
   private val testSourceSet: JvmSourceSet?
 ) : AbstractDependencyAnalyzer<JarAnalysisTask>(project) {
 
   final override val flavorName: String? = null
-  final override val variantName: String = sourceSet.name
+  final override val variantName: String = mainSourceSet.name
   final override val variantNameCapitalized = variantName.capitalizeSafely()
 
   // Yes, these two are the same for this case
@@ -53,11 +53,8 @@ internal abstract class JvmAnalyzer(
   protected val outputPaths = OutputPaths(project, variantName)
 
   private val variantFiles: Set<VariantFile> by lazy(mode = LazyThreadSafetyMode.NONE) {
-    val mainVariantFiles = project.files(sourceSet.sourceCode.sourceDirectories).asFileTree.files
-      .toVariantFiles("main")
-    val testVariantFiles = testSourceSet?.let {
-      project.files(it.sourceCode.sourceDirectories).asFileTree.files.toVariantFiles("test")
-    } ?: emptySet()
+    val mainVariantFiles = mainSourceSet.asFiles(project).toVariantFiles("main")
+    val testVariantFiles = testSourceSet?.asFiles(project)?.toVariantFiles("test") ?: emptySet()
 
     // return
     mainVariantFiles + testVariantFiles
@@ -102,7 +99,7 @@ internal abstract class JvmAnalyzer(
 
       output.set(outputPaths.allUsedClassesPath)
       outputPretty.set(outputPaths.allUsedClassesPrettyPath)
-      }
+    }
   }
 
   final override fun registerFindDeclaredProcsTask(
@@ -167,7 +164,7 @@ internal abstract class JvmAnalyzer(
     null
   }
 
-  protected fun getJarTask(): TaskProvider<Jar> = project.tasks.named(sourceSet.jarTaskName, Jar::class.java)
+  protected fun getJarTask(): TaskProvider<Jar> = project.tasks.named(mainSourceSet.jarTaskName, Jar::class.java)
 
   private fun getKotlinSources(): FileTree = getSourceDirectories().matching {
     include("**/*.kt")
@@ -180,7 +177,7 @@ internal abstract class JvmAnalyzer(
   }
 
   private fun getSourceDirectories(): FileTree {
-    val javaAndKotlinSource = sourceSet.sourceCode.sourceDirectories
+    val javaAndKotlinSource = mainSourceSet.sourceCode.sourceDirectories
     return project.files(javaAndKotlinSource).asFileTree
   }
 }
@@ -209,14 +206,13 @@ internal class JavaLibAnalyzer(project: Project, sourceSet: SourceSet, testSourc
 
 internal abstract class KotlinJvmAnalyzer(
   project: Project,
-  sourceSet: JbKotlinSourceSet,
+  mainSourceSet: JbKotlinSourceSet,
   testSourceSet: JbKotlinSourceSet?
 ) : JvmAnalyzer(
   project = project,
-  sourceSet = KotlinSourceSet(sourceSet),
+  mainSourceSet = KotlinSourceSet(mainSourceSet),
   testSourceSet = testSourceSet?.let { KotlinSourceSet(it) }
 ) {
-
   final override val javaSourceFiles: FileTree? = null
 }
 
@@ -226,17 +222,17 @@ internal class KotlinJvmAppAnalyzer(
   testSourceSet: JbKotlinSourceSet?
 ) : KotlinJvmAnalyzer(
   project = project,
-  sourceSet = sourceSet,
+  mainSourceSet = sourceSet,
   testSourceSet = testSourceSet
 )
 
 internal class KotlinJvmLibAnalyzer(
   project: Project,
-  sourceSet: JbKotlinSourceSet,
+  mainSourceSet: JbKotlinSourceSet,
   testSourceSet: JbKotlinSourceSet?
 ) : KotlinJvmAnalyzer(
   project = project,
-  sourceSet = sourceSet,
+  mainSourceSet = mainSourceSet,
   testSourceSet = testSourceSet
 ) {
 
diff --git a/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmSourceSet.kt b/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmSourceSet.kt
index 04d29279..27a92686 100644
--- a/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmSourceSet.kt
+++ b/src/main/kotlin/com/autonomousapps/internal/analyzer/JvmSourceSet.kt
@@ -1,14 +1,19 @@
 package com.autonomousapps.internal.analyzer
 
 import com.android.builder.model.SourceProvider
+import org.gradle.api.Project
 import org.gradle.api.file.SourceDirectorySet
 import org.gradle.api.tasks.SourceSet
+import java.io.File
 import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet as JbKotlinSourceSet
 
 internal interface JvmSourceSet {
   val name: String
   val jarTaskName: String
   val sourceCode: SourceDirectorySet
+
+  fun asFiles(project: Project): Set<File> =
+    project.files(sourceCode.sourceDirectories).asFileTree.files
 }
 
 internal class JavaSourceSet(sourceSet: SourceSet) : JvmSourceSet {
diff --git a/src/main/kotlin/com/autonomousapps/tasks/DependencyMisuseTask.kt b/src/main/kotlin/com/autonomousapps/tasks/DependencyMisuseTask.kt
index b1cb5908..f1b87ba7 100644
--- a/src/main/kotlin/com/autonomousapps/tasks/DependencyMisuseTask.kt
+++ b/src/main/kotlin/com/autonomousapps/tasks/DependencyMisuseTask.kt
@@ -135,7 +135,7 @@ internal class MisusedDependencyDetector(
     val unusedDeps = mutableListOf<Dependency>()
     val usedTransitiveComponents = mutableSetOf<TransitiveComponent>()
     val usedDirectClasses = mutableSetOf<String>()
-    val usedDependencies = mutableSetOf<VariantDependency>()
+    val usedDependencies = mutableMapOf<Dependency, MutableSet<String>>()
 
     declaredComponents
       // Exclude dependencies with zero class files (such as androidx.legacy:legacy-support-v4)
@@ -156,7 +156,9 @@ internal class MisusedDependencyDetector(
             } else {
               // Used class
               usedDirectClasses.add(declClass)
-              usedDependencies.add(VariantDependency(component.dependency, variantClass.variants))
+              usedDependencies.merge(component.dependency, variantClass.variants.toMutableSet()) { oldSet, newSet ->
+                oldSet.apply { addAll(newSet) }
+              }
             }
           }
 
@@ -226,10 +228,18 @@ internal class MisusedDependencyDetector(
       allComponentsWithTransitives = allComponentsWithTransitives,
       unusedComponentsWithTransitives = unusedDepsWithTransitives,
       usedTransitives = usedTransitiveComponents,
-      usedDependencies = usedDependencies
+      usedDependencies = usedDependencies.toVariantDependencies()
     )
   }
 
+  private fun Map<Dependency, Set<String>>.toVariantDependencies(): Set<VariantDependency> {
+    val set = mutableSetOf<VariantDependency>()
+    forEach { (dep, variants) ->
+      set.add(VariantDependency(dep, variants))
+    }
+    return set
+  }
+
   private fun Component.hasNoInlineUsages(): Boolean {
     return usedInlineDependencies.none { it == dependency }
   }