Repository: RBusarow/ModuleCheck (kotlin)

Issue:
Resource declarations are "unused" in intermediate module if consumed via that module's R class
#### Given:

`android.nonTransitiveRClass` is `false` in `gradle.properties`
Module `:lib1` declares string `R.string.string_from_lib1`
Module `:lib2` does not declare anything, but has `:lib1` as a dependency

`:lib3` depends upon both `:lib1` and `:lib2`, and has this source:
```kotlin
package com.modulecheck.lib3

import com.modulecheck.lib2.R

val string = R.string.string_from_lib1
```

#### Expected:

No issues should be found.

#### Actual:

ModuleCheck says that `:lib2` doesn't use `:lib1` since its only use is in the generated `com.modulecheck.lib2.R` class.
```
:lib2
      ​configuration​    dependency​    name            ​    source​    build file
   ✔  ​api          ​    :lib1     ​    unusedDependency​          ​    [...]/lib2/build.gradle.kts: (7, 3):

ModuleCheck found 1 issue in 0.89 seconds.
```

This is pretty similar to the problem posed by Anvil (#223).
- We can't just look at non-generated code to determine if the module is used.
  - This is what's causing this bug.
- We can't just assume that everything being generated is being used.
  - This is what's currently happening with Anvil -- again see #223.


In this case, `:lib2` is using `:lib1` in order to generate `com.modulecheck.lib2.R.string.string_from_lib1`.  If there are no references to that particular string from the `:lib2` namespace, then it should be "unused".

Pull request description:
fixes #484

Patch:
diff --git a/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidResourceDeclaredNames.kt b/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidResourceDeclaredNames.kt
index f8798bca05..405d9facff 100644
--- a/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidResourceDeclaredNames.kt
+++ b/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidResourceDeclaredNames.kt
@@ -15,20 +15,24 @@
 
 package modulecheck.api.context
 
-import modulecheck.parsing.android.AndroidResourceParser
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.flow.toSet
 import modulecheck.parsing.gradle.AndroidPlatformPlugin.AndroidLibraryPlugin
 import modulecheck.parsing.gradle.SourceSetName
 import modulecheck.parsing.gradle.asSourceSetName
 import modulecheck.parsing.source.AndroidResourceDeclaredName
-import modulecheck.parsing.source.UnqualifiedAndroidResourceDeclaredName
 import modulecheck.project.McProject
 import modulecheck.project.ProjectContext
 import modulecheck.project.isAndroid
 import modulecheck.utils.LazySet
 import modulecheck.utils.SafeCache
 import modulecheck.utils.dataSource
+import modulecheck.utils.dataSourceOf
 import modulecheck.utils.emptyLazySet
+import modulecheck.utils.flatMapToSet
 import modulecheck.utils.lazySet
+import modulecheck.utils.mapAsyncNotNull
 
 data class AndroidResourceDeclaredNames(
   private val delegate: SafeCache<SourceSetName, LazySet<AndroidResourceDeclaredName>>,
@@ -62,29 +66,46 @@ data class AndroidResourceDeclaredNames(
 
     return delegate.getOrPut(sourceSetName) {
 
-      val layoutsAndIds = project.layoutFilesForSourceSetName(sourceSetName)
-        .map { layoutFile ->
+      val allTransitiveUnqualified = project
+        .classpathDependencies()
+        .get(sourceSetName)
+        .mapAsyncNotNull { tpd ->
 
-          dataSource {
-            layoutFile.idDeclarations
-              .plus(UnqualifiedAndroidResourceDeclaredName.fromFile(layoutFile.file))
-              .filterNotNull()
-              .toSet()
-          }
+          val transitiveSourceSetName = tpd.source.declaringSourceSetName()
+
+          tpd.contributed.project
+            .androidUnqualifiedDeclarationNamesForSourceSetName(transitiveSourceSetName)
         }
 
-      val declarations = project.resourceFilesForSourceSetName(sourceSetName)
-        .map { file ->
-          dataSource {
-            val simpleNames = AndroidResourceParser().parseFile(file)
+      val localUnqualified = project
+        .androidUnqualifiedDeclarationNamesForSourceSetName(sourceSetName)
+
+      val qualified = dataSource {
+
+        val built = allTransitiveUnqualified
+          .toList(mutableListOf(localUnqualified))
+          .flatMapToSet { unqualifiedLazySet ->
 
-            simpleNames + simpleNames.map { it.toNamespacedDeclaredName(rName) }
+            unqualifiedLazySet
+              .map { unqualified ->
+                unqualified.toNamespacedDeclaredName(rName)
+              }
+              .toSet()
           }
-        }
-        .plus(layoutsAndIds)
-        .plus(project.androidDataBindingDeclarationsForSourceSetName(sourceSetName))
+        built
+      }
+
+      val dataBinding = project
+        .androidDataBindingDeclarationsForSourceSetName(sourceSetName)
+
+      val components = listOf(
+        localUnqualified,
+        dataSourceOf(rName),
+        dataBinding,
+        qualified
+      )
 
-      lazySet(declarations)
+      lazySet(components)
     }
   }
 
diff --git a/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidUnqualifiedDeclarationNames.kt b/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidUnqualifiedDeclarationNames.kt
new file mode 100644
index 0000000000..d8221eeafd
--- /dev/null
+++ b/modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidUnqualifiedDeclarationNames.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021-2022 Rick Busarow
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package modulecheck.api.context
+
+import modulecheck.parsing.android.AndroidResourceParser
+import modulecheck.parsing.gradle.SourceSetName
+import modulecheck.parsing.source.UnqualifiedAndroidResourceDeclaredName
+import modulecheck.project.McProject
+import modulecheck.project.ProjectContext
+import modulecheck.project.isAndroid
+import modulecheck.utils.LazySet
+import modulecheck.utils.SafeCache
+import modulecheck.utils.dataSource
+import modulecheck.utils.emptyLazySet
+import modulecheck.utils.lazySet
+import modulecheck.utils.toLazySet
+
+data class AndroidUnqualifiedDeclarationNames(
+  private val delegate: SafeCache<SourceSetName, LazySet<UnqualifiedAndroidResourceDeclaredName>>,
+  private val project: McProject
+) : ProjectContext.Element {
+
+  override val key: ProjectContext.Key<AndroidUnqualifiedDeclarationNames>
+    get() = Key
+
+  suspend fun get(sourceSetName: SourceSetName): LazySet<UnqualifiedAndroidResourceDeclaredName> {
+    if (!project.isAndroid()) return emptyLazySet()
+
+    return delegate.getOrPut(sourceSetName) {
+
+      sourceSetName
+        .withUpstream(project)
+        .map { sourceSetOrUpstream ->
+
+          val layoutsAndIds = project
+            .layoutFilesForSourceSetName(sourceSetOrUpstream)
+            .map { layoutFile ->
+
+              dataSource {
+                layoutFile.idDeclarations
+                  .plus(UnqualifiedAndroidResourceDeclaredName.fromFile(layoutFile.file))
+                  .filterNotNull()
+                  .toSet()
+              }
+            }
+
+          val declarations = project
+            .resourceFilesForSourceSetName(sourceSetOrUpstream)
+            .map { file ->
+              dataSource {
+
+                AndroidResourceParser().parseFile(file)
+              }
+            }
+            .plus(layoutsAndIds)
+
+          lazySet(declarations)
+        }.toLazySet()
+    }
+  }
+
+  companion object Key : ProjectContext.Key<AndroidUnqualifiedDeclarationNames> {
+    override suspend operator fun invoke(project: McProject): AndroidUnqualifiedDeclarationNames {
+
+      return AndroidUnqualifiedDeclarationNames(SafeCache(), project)
+    }
+  }
+}
+
+suspend fun ProjectContext.androidUnqualifiedDeclarationNames(): AndroidUnqualifiedDeclarationNames =
+  get(AndroidUnqualifiedDeclarationNames)
+
+suspend fun ProjectContext.androidUnqualifiedDeclarationNamesForSourceSetName(
+  sourceSetName: SourceSetName
+): LazySet<UnqualifiedAndroidResourceDeclaredName> =
+  androidUnqualifiedDeclarationNames().get(sourceSetName)
diff --git a/modulecheck-core/src/main/kotlin/modulecheck/core/internal/uses.kt b/modulecheck-core/src/main/kotlin/modulecheck/core/internal/uses.kt
index d1e1511038..9407f14f1f 100644
--- a/modulecheck-core/src/main/kotlin/modulecheck/core/internal/uses.kt
+++ b/modulecheck-core/src/main/kotlin/modulecheck/core/internal/uses.kt
@@ -15,14 +15,22 @@
 
 package modulecheck.core.internal
 
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.toSet
 import modulecheck.api.context.anvilGraph
 import modulecheck.api.context.anvilScopeContributionsForSourceSetName
 import modulecheck.api.context.declarations
+import modulecheck.api.context.dependents
 import modulecheck.api.context.referencesForSourceSetName
 import modulecheck.parsing.gradle.SourceSetName
+import modulecheck.parsing.source.Generated
 import modulecheck.project.ConfiguredProjectDependency
 import modulecheck.project.McProject
 import modulecheck.utils.any
+import modulecheck.utils.containsAny
+import modulecheck.utils.dataSource
+import modulecheck.utils.lazySet
 
 suspend fun McProject.uses(dependency: ConfiguredProjectDependency): Boolean {
 
@@ -38,6 +46,33 @@ suspend fun McProject.uses(dependency: ConfiguredProjectDependency): Boolean {
 
   if (usedInStaticSource) return true
 
+  // Any generated code from the receiver project which requires a declaration from the dependency
+  val generatedFromThisDependency = lazySet(
+    dataSource {
+
+      // TODO - probably make "all generated declarations" its own ProjectContext.Element,
+      //  specifically targeting generated declarations.  It shouldn't be needed in this specific
+      //  case, since `dependencyDeclarations` should already be fully cached by the time we get
+      //  here, and we have to iterate over the flow anyway in order to filter again.
+      declarations()
+        .get(dependency.declaringSourceSetName(), includeUpstream = true)
+        .filterIsInstance<Generated>()
+        .filter { dependencyDeclarations.containsAny(it.sources) }
+        .toSet()
+    }
+  )
+
+  val usedUpstream = dependents().any { downstreamDependency ->
+    val downstreamSourceSet = downstreamDependency.configuredProjectDependency
+      .declaringSourceSetName()
+
+    downstreamDependency.dependentProject
+      .referencesForSourceSetName(downstreamSourceSet)
+      .containsAny(generatedFromThisDependency)
+  }
+
+  if (usedUpstream) return true
+
   // If there are no references is manually/human written static code, then parse the Anvil graph.
   val anvilContributions = dependency.project
     .anvilScopeContributionsForSourceSetName(SourceSetName.MAIN)
diff --git a/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/AndroidResourceDeclaredName.kt b/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/AndroidResourceDeclaredName.kt
index 7549b159cd..f3a0e80001 100644
--- a/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/AndroidResourceDeclaredName.kt
+++ b/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/AndroidResourceDeclaredName.kt
@@ -22,9 +22,32 @@ import modulecheck.utils.unsafeLazy
 import java.io.File
 import kotlin.io.path.name
 
-sealed interface AndroidResourceDeclaredName : DeclaredName {
-  val prefix: String
-  val identifier: String
+sealed interface AndroidResourceDeclaredName : DeclaredName
+
+/** example: `com.example.app.R` */
+class AndroidRDeclaredName(override val name: String) :
+  AndroidResourceDeclaredName,
+  KotlinCompatibleDeclaredName,
+  JavaCompatibleDeclaredName,
+  XmlCompatibleDeclaredName {
+  init {
+    check(name.endsWith(".R")) {
+      "An ${this::class.java.simpleName} fqName must be the base package, appended with `.R`, " +
+        "such as `com.example.app.R`.  This instance's fqName is: `$name`."
+    }
+  }
+
+  override fun equals(other: Any?): Boolean {
+    return matches(
+      other,
+      ifReference = { name == it.name },
+      ifDeclaration = { name == it.safeAs<AndroidRDeclaredName>()?.name }
+    )
+  }
+
+  override fun hashCode(): Int = name.hashCode()
+
+  override fun toString(): String = "(${this::class.java.simpleName}) `$name`"
 }
 
 class GeneratedAndroidResourceDeclaredName(
@@ -36,13 +59,8 @@ class GeneratedAndroidResourceDeclaredName(
   XmlCompatibleDeclaredName,
   Generated {
 
-  override val identifier: String
-    get() = sourceResource.identifier
-  override val prefix: String
-    get() = sourceResource.prefix
-
   override val name: String by unsafeLazy {
-    "${sourceR.name}.$prefix.$identifier"
+    "${sourceR.name}.${sourceResource.prefix}.${sourceResource.identifier}"
   }
 
   override val sources: Set<Reference> = setOf(sourceR, sourceResource)
@@ -74,11 +92,6 @@ class AndroidDataBindingDeclaredName(
     sourceLayout = UnqualifiedAndroidResourceReference(sourceLayout.name)
   )
 
-  override val identifier: String
-    get() = sourceLayout.identifier
-  override val prefix: String
-    get() = sourceLayout.prefix
-
   override val sources: Set<Reference> = setOf(sourceLayout)
 
   override fun equals(other: Any?): Boolean {
@@ -95,9 +108,11 @@ class AndroidDataBindingDeclaredName(
 }
 
 sealed class UnqualifiedAndroidResourceDeclaredName(
-  override val prefix: String
+  val prefix: String
 ) : AndroidResourceDeclaredName {
 
+  abstract val identifier: String
+
   override val name: String by unsafeLazy { "R.$prefix.${this.identifier}" }
 
   class AndroidInteger(
diff --git a/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/DeclaredName.kt b/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/DeclaredName.kt
index b0b0f8ed76..a027022f66 100644
--- a/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/DeclaredName.kt
+++ b/modulecheck-parsing/source/src/main/kotlin/modulecheck/parsing/source/DeclaredName.kt
@@ -37,32 +37,6 @@ sealed interface JavaCompatibleDeclaredName : DeclaredName
 sealed interface KotlinCompatibleDeclaredName : DeclaredName
 sealed interface XmlCompatibleDeclaredName : JavaCompatibleDeclaredName, DeclaredName
 
-/** example: `com.example.app.R` */
-class AndroidRDeclaredName(override val name: String) :
-  DeclaredName,
-  KotlinCompatibleDeclaredName,
-  JavaCompatibleDeclaredName,
-  XmlCompatibleDeclaredName {
-  init {
-    check(name.endsWith(".R")) {
-      "An ${this::class.java.simpleName} fqName must be the base package, appended with `.R`, " +
-        "such as `com.example.app.R`.  This instance's fqName is: `$name`."
-    }
-  }
-
-  override fun equals(other: Any?): Boolean {
-    return matches(
-      other,
-      ifReference = { name == it.name },
-      ifDeclaration = { name == it.safeAs<AndroidRDeclaredName>()?.name }
-    )
-  }
-
-  override fun hashCode(): Int = name.hashCode()
-
-  override fun toString(): String = "(${this::class.java.simpleName}) `$name`"
-}
-
 /**
  * Represents names which can only be referenced from Kotlin.
  *
diff --git a/modulecheck-utils/src/main/kotlin/modulecheck/utils/LazySet.kt b/modulecheck-utils/src/main/kotlin/modulecheck/utils/LazySet.kt
index a6d175d36a..94023ea026 100644
--- a/modulecheck-utils/src/main/kotlin/modulecheck/utils/LazySet.kt
+++ b/modulecheck-utils/src/main/kotlin/modulecheck/utils/LazySet.kt
@@ -122,6 +122,21 @@ fun <E> lazySet(
   return lazySet(children.asList())
 }
 
+fun <E> lazySet(
+  priority: Priority = MEDIUM,
+  dataSource: suspend () -> Set<E>
+): LazySet<E> {
+  return lazySet(dataSource(priority, dataSource))
+}
+
+@JvmName("lazySetSingle")
+fun <E> lazySet(
+  priority: Priority = MEDIUM,
+  dataSource: suspend () -> E
+): LazySet<E> {
+  return lazySet(dataSource(priority) { setOf(dataSource()) })
+}
+
 fun <E> lazySet(
   children: Collection<LazySetComponent<E>>
 ): LazySet<E> {


Test patch:
diff --git a/modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt b/modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt
index 6ddb01ce0e..9f7b86e56a 100644
--- a/modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt
+++ b/modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt
@@ -1541,6 +1541,90 @@ class UnusedDependenciesTest : RunnerTest() {
     )
   }
 
+  @Test
+  fun `string resource declaration should not be unused if's used with a different R namespace`() {
+
+    val lib1 = androidLibrary(":lib1", "com.modulecheck.lib1") {
+      buildFile {
+        """
+        plugins {
+          id("com.android.library")
+          kotlin("android")
+        }
+        """
+      }
+      addResourceFile(
+        "values/strings.xml",
+        """<resources>
+            |  <string name="string_from_lib1">lib1</string>
+            |</resources>
+        """.trimMargin()
+      )
+    }
+
+    val lib2 = androidLibrary(":lib2", "com.modulecheck.lib2") {
+      addDependency(ConfigurationName.api, lib1)
+
+      buildFile {
+        """
+        plugins {
+          id("com.android.library")
+          kotlin("android")
+        }
+
+        dependencies {
+          api(project(":lib1"))
+        }
+        """
+      }
+    }
+
+    androidLibrary(":lib3", "com.modulecheck.lib3") {
+      addDependency(ConfigurationName.implementation, lib1)
+      addDependency(ConfigurationName.implementation, lib2)
+      platformPlugin.androidResourcesEnabled = false
+
+      buildFile {
+        """
+        plugins {
+          id("com.android.library")
+          kotlin("android")
+        }
+
+        dependencies {
+          implementation(project(":lib1"))
+          implementation(project(":lib2"))
+        }
+        """
+      }
+      addSource(
+        "com/modulecheck/lib3/internal/Source.kt",
+        """
+        package com.modulecheck.lib3
+
+        import com.modulecheck.lib2.R
+
+        val r = R.string.string_from_lib1
+        """
+      )
+    }
+
+    run().isSuccess shouldBe true
+
+    lib2.buildFile shouldHaveText """
+      plugins {
+        id("com.android.library")
+        kotlin("android")
+      }
+
+      dependencies {
+        api(project(":lib1"))
+      }
+    """
+
+    logger.parsedReport() shouldBe listOf()
+  }
+
   @Test
   fun `static member declaration used via wildcard import should not be unused`() {
 
diff --git a/modulecheck-parsing/java/src/test/kotlin/modulecheck/parsing/java/JavaFileTest.kt b/modulecheck-parsing/java/src/test/kotlin/modulecheck/parsing/java/JavaFileTest.kt
index b1adf801c4..b5672bc3c2 100644
--- a/modulecheck-parsing/java/src/test/kotlin/modulecheck/parsing/java/JavaFileTest.kt
+++ b/modulecheck-parsing/java/src/test/kotlin/modulecheck/parsing/java/JavaFileTest.kt
@@ -25,11 +25,12 @@ import modulecheck.api.context.jvmFiles
 import modulecheck.parsing.gradle.ConfigurationName
 import modulecheck.parsing.gradle.SourceSetName
 import modulecheck.parsing.source.AgnosticDeclaredName
+import modulecheck.parsing.source.AndroidDataBindingDeclaredName
 import modulecheck.parsing.source.AndroidDataBindingReference
 import modulecheck.parsing.source.AndroidRDeclaredName
 import modulecheck.parsing.source.AndroidRReference
-import modulecheck.parsing.source.AndroidResourceDeclaredName
 import modulecheck.parsing.source.DeclaredName
+import modulecheck.parsing.source.GeneratedAndroidResourceDeclaredName
 import modulecheck.parsing.source.JavaSpecificDeclaredName
 import modulecheck.parsing.source.JavaVersion
 import modulecheck.parsing.source.JavaVersion.VERSION_14
@@ -41,6 +42,7 @@ import modulecheck.parsing.source.Reference.ExplicitKotlinReference
 import modulecheck.parsing.source.Reference.ExplicitXmlReference
 import modulecheck.parsing.source.Reference.InterpretedJavaReference
 import modulecheck.parsing.source.Reference.InterpretedKotlinReference
+import modulecheck.parsing.source.UnqualifiedAndroidResourceDeclaredName
 import modulecheck.parsing.source.UnqualifiedAndroidResourceReference
 import modulecheck.parsing.source.asExplicitJavaReference
 import modulecheck.parsing.source.asInterpretedJavaReference
@@ -1065,7 +1067,9 @@ internal class JavaFileTest : ProjectTest() {
         is AndroidRDeclaredName -> "androidR"
         is JavaSpecificDeclaredName -> "java"
         is KotlinSpecificDeclaredName -> "kotlin"
-        is AndroidResourceDeclaredName -> declaration.prefix
+        is UnqualifiedAndroidResourceDeclaredName -> declaration.prefix
+        is GeneratedAndroidResourceDeclaredName -> "qualifiedAndroidResource"
+        is AndroidDataBindingDeclaredName -> "androidDataBinding"
       }
       names
         .sortedBy { it.name }
diff --git a/modulecheck-parsing/psi/src/test/kotlin/modulecheck/parsing/psi/KotlinFileTest.kt b/modulecheck-parsing/psi/src/test/kotlin/modulecheck/parsing/psi/KotlinFileTest.kt
index 452bde522f..645afae6f1 100644
--- a/modulecheck-parsing/psi/src/test/kotlin/modulecheck/parsing/psi/KotlinFileTest.kt
+++ b/modulecheck-parsing/psi/src/test/kotlin/modulecheck/parsing/psi/KotlinFileTest.kt
@@ -27,11 +27,12 @@ import modulecheck.parsing.gradle.ConfigurationName
 import modulecheck.parsing.gradle.SourceSetName
 import modulecheck.parsing.psi.internal.psiFileFactory
 import modulecheck.parsing.source.AgnosticDeclaredName
+import modulecheck.parsing.source.AndroidDataBindingDeclaredName
 import modulecheck.parsing.source.AndroidDataBindingReference
 import modulecheck.parsing.source.AndroidRDeclaredName
 import modulecheck.parsing.source.AndroidRReference
-import modulecheck.parsing.source.AndroidResourceDeclaredName
 import modulecheck.parsing.source.DeclaredName
+import modulecheck.parsing.source.GeneratedAndroidResourceDeclaredName
 import modulecheck.parsing.source.JavaSpecificDeclaredName
 import modulecheck.parsing.source.KotlinSpecificDeclaredName
 import modulecheck.parsing.source.QualifiedAndroidResourceReference
@@ -41,6 +42,7 @@ import modulecheck.parsing.source.Reference.ExplicitKotlinReference
 import modulecheck.parsing.source.Reference.ExplicitXmlReference
 import modulecheck.parsing.source.Reference.InterpretedJavaReference
 import modulecheck.parsing.source.Reference.InterpretedKotlinReference
+import modulecheck.parsing.source.UnqualifiedAndroidResourceDeclaredName
 import modulecheck.parsing.source.UnqualifiedAndroidResourceReference
 import modulecheck.parsing.source.asExplicitKotlinReference
 import modulecheck.parsing.source.asInterpretedKotlinReference
@@ -1258,7 +1260,9 @@ internal class KotlinFileTest : ProjectTest() {
         is AndroidRDeclaredName -> "androidR"
         is JavaSpecificDeclaredName -> "java"
         is KotlinSpecificDeclaredName -> "kotlin"
-        is AndroidResourceDeclaredName -> declaration.prefix
+        is UnqualifiedAndroidResourceDeclaredName -> declaration.prefix
+        is GeneratedAndroidResourceDeclaredName -> "qualifiedAndroidResource"
+        is AndroidDataBindingDeclaredName -> "androidDataBinding"
       }
       names
         .sortedBy { it.name }
diff --git a/modulecheck-parsing/source/src/test/kotlin/modulecheck/parsing/source/DeclaredNameTest.kt b/modulecheck-parsing/source/src/test/kotlin/modulecheck/parsing/source/DeclaredNameTest.kt
index 8d7988a6f6..571eb0c530 100644
--- a/modulecheck-parsing/source/src/test/kotlin/modulecheck/parsing/source/DeclaredNameTest.kt
+++ b/modulecheck-parsing/source/src/test/kotlin/modulecheck/parsing/source/DeclaredNameTest.kt
@@ -15,6 +15,11 @@
 
 package modulecheck.parsing.source
 
+import modulecheck.parsing.source.Reference.ExplicitJavaReference
+import modulecheck.parsing.source.Reference.ExplicitKotlinReference
+import modulecheck.parsing.source.Reference.ExplicitXmlReference
+import modulecheck.parsing.source.Reference.InterpretedJavaReference
+import modulecheck.parsing.source.Reference.InterpretedKotlinReference
 import modulecheck.testing.sealedSubclassInstances
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestFactory
@@ -25,19 +30,19 @@ class DeclaredNameTest : BaseNamedSymbolTest() {
   fun `agnostic declaration should match self and any reference type`() {
     AgnosticDeclaredName("com.modulecheck.subject").matchedClasses() shouldBe listOf(
       AgnosticDeclaredName::class,
-      Reference.ExplicitJavaReference::class,
-      Reference.ExplicitKotlinReference::class,
-      Reference.ExplicitXmlReference::class,
-      Reference.InterpretedJavaReference::class,
-      Reference.InterpretedKotlinReference::class
+      ExplicitJavaReference::class,
+      ExplicitKotlinReference::class,
+      ExplicitXmlReference::class,
+      InterpretedJavaReference::class,
+      InterpretedKotlinReference::class
     )
   }
 
   @Test
   fun `kotlin specific declaration should match self and any KotlinReference type`() {
     KotlinSpecificDeclaredName("com.modulecheck.subject").matchedClasses() shouldBe listOf(
-      Reference.ExplicitKotlinReference::class,
-      Reference.InterpretedKotlinReference::class,
+      ExplicitKotlinReference::class,
+      InterpretedKotlinReference::class,
       KotlinSpecificDeclaredName::class
     )
   }
@@ -45,9 +50,9 @@ class DeclaredNameTest : BaseNamedSymbolTest() {
   @Test
   fun `java specific declaration should match self and any JavaReference or XmlReference type`() {
     JavaSpecificDeclaredName("com.modulecheck.subject").matchedClasses() shouldBe listOf(
-      Reference.ExplicitJavaReference::class,
-      Reference.ExplicitXmlReference::class,
-      Reference.InterpretedJavaReference::class,
+      ExplicitJavaReference::class,
+      ExplicitXmlReference::class,
+      InterpretedJavaReference::class,
       JavaSpecificDeclaredName::class
     )
   }
@@ -55,13 +60,15 @@ class DeclaredNameTest : BaseNamedSymbolTest() {
   @Test
   fun `android r declaration should match self and any Reference type`() {
     AndroidRDeclaredName("com.modulecheck.R").matchedClasses() shouldBe listOf(
+      AndroidDataBindingReference::class,
       AndroidRDeclaredName::class,
       AndroidRReference::class,
-      Reference.ExplicitJavaReference::class,
-      Reference.ExplicitKotlinReference::class,
-      Reference.ExplicitXmlReference::class,
-      Reference.InterpretedJavaReference::class,
-      Reference.InterpretedKotlinReference::class
+      ExplicitJavaReference::class,
+      ExplicitKotlinReference::class,
+      ExplicitXmlReference::class,
+      InterpretedJavaReference::class,
+      InterpretedKotlinReference::class,
+      UnqualifiedAndroidResourceReference::class
     )
   }