Skip to content

Commit 4abe876

Browse files
RBusarowkodiakhq[bot]
authored andcommitted
count layout files and @+id/__ declarations as part of a module's declarations
fixes #498
1 parent 92bdb17 commit 4abe876

File tree

6 files changed

+354
-9
lines changed

6 files changed

+354
-9
lines changed

modulecheck-api/src/main/kotlin/modulecheck/api/context/AndroidResourceDeclaredNames.kt

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import modulecheck.parsing.gradle.AndroidPlatformPlugin.AndroidLibraryPlugin
2020
import modulecheck.parsing.gradle.SourceSetName
2121
import modulecheck.parsing.gradle.asSourceSetName
2222
import modulecheck.parsing.source.AndroidResourceDeclaredName
23+
import modulecheck.parsing.source.UnqualifiedAndroidResourceDeclaredName
2324
import modulecheck.project.McProject
2425
import modulecheck.project.ProjectContext
2526
import modulecheck.project.isAndroid
@@ -70,6 +71,18 @@ data class AndroidResourceDeclaredNames(
7071
simpleNames + simpleNames.map { it.toNamespacedDeclaredName(rName) }
7172
}
7273
}
74+
.plus(
75+
project.layoutFilesForSourceSetName(sourceSetName)
76+
.map { layoutFile ->
77+
78+
dataSource {
79+
layoutFile.idDeclarations
80+
.plus(UnqualifiedAndroidResourceDeclaredName.fromFile(layoutFile.file))
81+
.filterNotNull()
82+
.toSet()
83+
}
84+
}
85+
)
7386

7487
lazySet(declarations)
7588
}

modulecheck-core/src/test/kotlin/modulecheck/core/DisableAndroidResourcesTest.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ class DisableAndroidResourcesTest : RunnerTest() {
8484
}
8585
addResourceFile(
8686
"values/strings.xml",
87-
"""<resources>
88-
| <string name="app_name" translatable="false">MyApp</string>
89-
|</resources>
90-
""".trimMargin()
87+
"""
88+
<resources>
89+
<string name="app_name" translatable="false">MyApp</string>
90+
</resources>
91+
"""
9192
)
9293
}
9394

modulecheck-core/src/test/kotlin/modulecheck/core/UnusedDependenciesTest.kt

+252
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,258 @@ class UnusedDependenciesTest : RunnerTest() {
10361036
logger.parsedReport() shouldBe listOf()
10371037
}
10381038

1039+
@Test
1040+
fun `module contributing a used layout with non-transitive R should not be unused`() {
1041+
1042+
settings.deleteUnused = false
1043+
1044+
val lib1 = androidLibrary(":lib1", "com.modulecheck.lib1") {
1045+
platformPlugin.viewBindingEnabled = true
1046+
1047+
addLayoutFile(
1048+
"fragment_lib1.xml",
1049+
"""<?xml version="1.0" encoding="utf-8"?>
1050+
<layout/>
1051+
"""
1052+
)
1053+
}
1054+
1055+
val lib2 = androidLibrary(":lib2", "com.modulecheck.lib2") {
1056+
addDependency(ConfigurationName.api, lib1)
1057+
1058+
buildFile {
1059+
"""
1060+
plugins {
1061+
id("com.android.library")
1062+
kotlin("android")
1063+
}
1064+
1065+
dependencies {
1066+
api(project(path = ":lib1"))
1067+
}
1068+
"""
1069+
}
1070+
platformPlugin.viewBindingEnabled = false
1071+
1072+
addKotlinSource(
1073+
"""
1074+
package com.modulecheck.lib2
1075+
1076+
import com.modulecheck.lib1.R
1077+
1078+
val layout = R.layout.fragment_lib1
1079+
"""
1080+
)
1081+
}
1082+
1083+
run().isSuccess shouldBe true
1084+
1085+
lib2.buildFile shouldHaveText """
1086+
plugins {
1087+
id("com.android.library")
1088+
kotlin("android")
1089+
}
1090+
1091+
dependencies {
1092+
api(project(path = ":lib1"))
1093+
}
1094+
"""
1095+
1096+
logger.parsedReport() shouldBe listOf()
1097+
}
1098+
1099+
@Test
1100+
fun `module contributing a used layout with local R should not be unused`() {
1101+
1102+
settings.deleteUnused = false
1103+
1104+
val lib1 = androidLibrary(":lib1", "com.modulecheck.lib1") {
1105+
platformPlugin.viewBindingEnabled = true
1106+
1107+
addLayoutFile(
1108+
"fragment_lib1.xml",
1109+
"""<?xml version="1.0" encoding="utf-8"?>
1110+
<layout/>
1111+
"""
1112+
)
1113+
}
1114+
1115+
val lib2 = androidLibrary(":lib2", "com.modulecheck.lib2") {
1116+
addDependency(ConfigurationName.api, lib1)
1117+
1118+
buildFile {
1119+
"""
1120+
plugins {
1121+
id("com.android.library")
1122+
kotlin("android")
1123+
}
1124+
1125+
dependencies {
1126+
api(project(path = ":lib1"))
1127+
}
1128+
"""
1129+
}
1130+
platformPlugin.viewBindingEnabled = false
1131+
1132+
addKotlinSource(
1133+
"""
1134+
package com.modulecheck.lib2
1135+
1136+
val layout = R.layout.fragment_lib1
1137+
"""
1138+
)
1139+
}
1140+
1141+
run().isSuccess shouldBe true
1142+
1143+
lib2.buildFile shouldHaveText """
1144+
plugins {
1145+
id("com.android.library")
1146+
kotlin("android")
1147+
}
1148+
1149+
dependencies {
1150+
api(project(path = ":lib1"))
1151+
}
1152+
"""
1153+
1154+
logger.parsedReport() shouldBe listOf()
1155+
}
1156+
1157+
@Test
1158+
fun `module contributing a used id from a layout with non-transitive R should not be unused`() {
1159+
1160+
settings.deleteUnused = false
1161+
1162+
val lib1 = androidLibrary(":lib1", "com.modulecheck.lib1") {
1163+
platformPlugin.viewBindingEnabled = true
1164+
1165+
addLayoutFile(
1166+
"fragment_lib1.xml",
1167+
"""
1168+
<LinearLayout
1169+
xmlns:android="http://schemas.android.com/apk/res/android"
1170+
xmlns:tools="http://schemas.android.com/tools"
1171+
android:id="@+id/fragment_container"
1172+
android:layout_width="match_parent"
1173+
android:layout_height="wrap_content"
1174+
android:orientation="vertical"
1175+
tools:ignore="UnusedResources"
1176+
/>
1177+
"""
1178+
)
1179+
}
1180+
1181+
val lib2 = androidLibrary(":lib2", "com.modulecheck.lib2") {
1182+
addDependency(ConfigurationName.api, lib1)
1183+
1184+
buildFile {
1185+
"""
1186+
plugins {
1187+
id("com.android.library")
1188+
kotlin("android")
1189+
}
1190+
1191+
dependencies {
1192+
api(project(path = ":lib1"))
1193+
}
1194+
"""
1195+
}
1196+
platformPlugin.viewBindingEnabled = false
1197+
1198+
addKotlinSource(
1199+
"""
1200+
package com.modulecheck.lib2
1201+
1202+
import com.modulecheck.lib1.R
1203+
1204+
val id = R.id.fragment_container
1205+
"""
1206+
)
1207+
}
1208+
1209+
run().isSuccess shouldBe true
1210+
1211+
lib2.buildFile shouldHaveText """
1212+
plugins {
1213+
id("com.android.library")
1214+
kotlin("android")
1215+
}
1216+
1217+
dependencies {
1218+
api(project(path = ":lib1"))
1219+
}
1220+
"""
1221+
1222+
logger.parsedReport() shouldBe listOf()
1223+
}
1224+
1225+
@Test
1226+
fun `module contributing a used id from a layout with local R should not be unused`() {
1227+
1228+
settings.deleteUnused = false
1229+
1230+
val lib1 = androidLibrary(":lib1", "com.modulecheck.lib1") {
1231+
platformPlugin.viewBindingEnabled = true
1232+
1233+
addLayoutFile(
1234+
"fragment_lib1.xml",
1235+
"""
1236+
<LinearLayout
1237+
xmlns:android="http://schemas.android.com/apk/res/android"
1238+
xmlns:tools="http://schemas.android.com/tools"
1239+
android:id="@+id/fragment_container"
1240+
android:layout_width="match_parent"
1241+
android:layout_height="wrap_content"
1242+
android:orientation="vertical"
1243+
tools:ignore="UnusedResources"
1244+
/>
1245+
"""
1246+
)
1247+
}
1248+
1249+
val lib2 = androidLibrary(":lib2", "com.modulecheck.lib2") {
1250+
addDependency(ConfigurationName.api, lib1)
1251+
1252+
buildFile {
1253+
"""
1254+
plugins {
1255+
id("com.android.library")
1256+
kotlin("android")
1257+
}
1258+
1259+
dependencies {
1260+
api(project(path = ":lib1"))
1261+
}
1262+
"""
1263+
}
1264+
platformPlugin.viewBindingEnabled = false
1265+
1266+
addKotlinSource(
1267+
"""
1268+
package com.modulecheck.lib2
1269+
1270+
val id = R.id.fragment_container
1271+
"""
1272+
)
1273+
}
1274+
1275+
run().isSuccess shouldBe true
1276+
1277+
lib2.buildFile shouldHaveText """
1278+
plugins {
1279+
id("com.android.library")
1280+
kotlin("android")
1281+
}
1282+
1283+
dependencies {
1284+
api(project(path = ":lib1"))
1285+
}
1286+
"""
1287+
1288+
logger.parsedReport() shouldBe listOf()
1289+
}
1290+
10391291
@Test
10401292
fun `declaration used via a wildcard import should not be unused`() {
10411293

modulecheck-parsing/android/src/main/kotlin/modulecheck/parsing/android/XmlFile.kt

+20-4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,25 @@ interface XmlFile : HasReferences {
4141
AndroidLayoutParser().parseViews(file).mapToSet { ExplicitXmlReference(it) }
4242
}
4343

44-
private val rawResources: Set<String> by lazy {
44+
private val attributes by lazy {
4545
AndroidLayoutParser().parseResources(file)
46-
.filter { attribute -> PREFIXES.any { attribute.startsWith(it) } }
46+
}
47+
48+
val idDeclarations: Set<UnqualifiedAndroidResourceDeclaredName> by lazy {
49+
attributes.filter { attribute ->
50+
attribute.startsWith("@+id/")
51+
}
52+
.onEach { println(it) }
53+
.mapNotNull { UnqualifiedAndroidResourceDeclaredName.fromString(it) }
54+
.toSet()
55+
}
56+
57+
private val rawResources: Set<String> by lazy {
58+
attributes
59+
.filter { attribute ->
60+
REFERENCE_PREFIXES
61+
.any { attribute.startsWith(it) }
62+
}
4763
.toSet()
4864
}
4965

@@ -74,7 +90,7 @@ interface XmlFile : HasReferences {
7490

7591
private val rawResources: Set<String> by lazy {
7692
AndroidManifestParser().parseResources(file)
77-
.filter { attribute -> PREFIXES.any { attribute.startsWith(it) } }
93+
.filter { attribute -> REFERENCE_PREFIXES.any { attribute.startsWith(it) } }
7894
.toSet()
7995
}
8096

@@ -95,7 +111,7 @@ interface XmlFile : HasReferences {
95111
}
96112

97113
companion object {
98-
val PREFIXES = listOf(
114+
val REFERENCE_PREFIXES = listOf(
99115
"@anim/",
100116
"@animator/",
101117
"@arrays/",

0 commit comments

Comments
 (0)