summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/test/java/org/terst/listen/ReachabilityTest.kt39
1 files changed, 28 insertions, 11 deletions
diff --git a/app/src/test/java/org/terst/listen/ReachabilityTest.kt b/app/src/test/java/org/terst/listen/ReachabilityTest.kt
index ec603fa..e1e93b3 100644
--- a/app/src/test/java/org/terst/listen/ReachabilityTest.kt
+++ b/app/src/test/java/org/terst/listen/ReachabilityTest.kt
@@ -27,22 +27,31 @@ class ReachabilityTest {
"YouTubeAudio.ytDlpVersion" to
"Called from YouTubeAudioTest, which fails on a stale bundled yt-dlp. That guard " +
"is the whole point; it has no job in production.",
- "SkipSummary.describeKind" to
- "Only called from SkipSummary.breakLines, in the same file, since the episode " +
- "screen's break-list labels and its ad/furniture colour split (FIX 3) were " +
- "deliberately decoupled -- the colour split now uses isAdvertising, not this " +
- "display string. This heuristic only credits a cross-file call as a caller, so " +
- "it cannot see that breakLines is reached from EpisodeScreen.kt via " +
- "SkipSummary.episode(); the function is genuinely live, just one hop further " +
- "than the check looks.",
)
private val mainDir = File("src/main/java/org/terst/listen")
private val objectDecl = Regex("""^(?:internal )?object (\w+)""", RegexOption.MULTILINE)
- /** Top-level members of an object are indented exactly four spaces. */
- private val memberFun = Regex("""^ (?:suspend )?fun (\w+)""", RegexOption.MULTILINE)
+ /**
+ * Top-level members of an object are indented exactly four spaces.
+ *
+ * `internal` is included because it was the hole: every `internal fun` on an object was
+ * invisible to this check, which is most of what the UI layer exposes to its own module.
+ * `private` is left out -- the compiler and detekt already refuse an unused one.
+ */
+ private val memberFun = Regex(
+ """^ (?:internal )?(?:suspend )?fun (\w+)""",
+ RegexOption.MULTILINE,
+ )
+
+ /**
+ * A declaration, so a function is not credited as its own caller.
+ *
+ * The same trick the DAO check uses. Without it, scanning a file for `name(` always finds
+ * `fun name(` and nothing is ever dead.
+ */
+ private val anyDeclaration = Regex("""(?:internal |private )?(?:suspend )?fun (\w+)\(""")
/** Any declaration starting at column zero ends the preceding object's body. */
private val nextTopLevel = Regex(
@@ -74,9 +83,17 @@ class ReachabilityTest {
// method reference such as `map(LibraryTree::subscription)`, which has no parens at
// all and was reported as dead while being called.
.filterNot { qualified ->
+ val bare = qualified.substringAfter(".")
val reference = qualified.replace(".", "::")
bodies.any { (other, b) ->
- other != file && (b.contains("$qualified(") || b.contains(reference))
+ if (other != file) {
+ b.contains("$qualified(") || b.contains(reference)
+ } else {
+ // Same file: a helper called only by its own object's public entry
+ // point is live, and used to need an allowlist entry saying so.
+ // Declarations are stripped first or every function finds itself.
+ anyDeclaration.replace(b, "DECL(").contains("$bare(")
+ }
}
}
.toList()