diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ReminderNotification.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ReminderNotification.kt index c9ada48..e904209 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ReminderNotification.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ReminderNotification.kt @@ -78,6 +78,12 @@ internal object ReminderNotification { } } + /** Take a reminder off the shade, once it has been acted on. */ + fun dismiss( + context: Context, + noteId: String, + ) = NotificationManagerCompat.from(context).cancel(noteId.hashCode()) + private fun openIntent( context: Context, note: Note, diff --git a/android/tools/check-symbols.py b/android/tools/check-symbols.py index f0862dd..861fea6 100755 --- a/android/tools/check-symbols.py +++ b/android/tools/check-symbols.py @@ -12,6 +12,16 @@ one; this is a cheap pre-push filter for the single mistake that survives every other local gate. It errs toward false positives — anything it cannot account for is reported rather than assumed fine. +It also checks MEMBERS of this package's own `object` declarations — `Foo.bar()` +where `Foo` is an object declared here. That case was added after moving a +function between two objects and forgetting to paste it into the second: the +call site read `Other.thing()`, resolved fine as far as the leading token, and +failed in CI (785ebdb). + +Still NOT caught, so a clean run is not over-read: members of anything declared +outside this package, members reached through a variable rather than a type +name, and every question about types. Those are what `compileDebugKotlin` is for. + python3 android/tools/check-symbols.py [source-root] Exits non-zero when something is unaccounted for. @@ -69,6 +79,44 @@ def strip(src: str) -> str: return src +def object_members(src: str) -> dict: + """Map each `object Foo` declared here to the names declared directly in it. + + Brace-counted rather than regex-matched: an object body contains nested + braces (lambdas, apply blocks, companions) and no regex closes correctly over + them. Only top-level members count — anything nested deeper is not reachable + as `Foo.member` anyway. + """ + members = {} + for match in re.finditer(r"^(?:internal |private )?object (\w+)\s*\{", src, re.M): + name = match.group(1) + depth = 0 + body_start = match.end() - 1 + for i in range(body_start, len(src)): + if src[i] == "{": + depth += 1 + elif src[i] == "}": + depth -= 1 + if depth == 0: + break + body = src[body_start + 1 : i] + own = set() + depth = 0 + for line in body.splitlines(): + if depth == 0: + decl = re.match( + r"\s*(?:@\w+\s+)*(?:public |private |internal |protected )?" + r"(?:const |lateinit |inline |suspend )*(?:fun|val|var)\s+" + r"(?:<[^>]*>\s*)?(\w+)", + line, + ) + if decl: + own.add(decl.group(1)) + depth += line.count("{") - line.count("}") + members[name] = own + return members + + def main() -> int: root = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_ROOT files = [ @@ -79,6 +127,7 @@ def main() -> int: ] declared = collections.defaultdict(set) + objects = {} parsed = {} for path in files: with open(path, encoding="utf-8") as fh: @@ -86,6 +135,7 @@ def main() -> int: package = re.search(r"^package\s+([\w.]+)", raw, re.M).group(1) src = strip(raw) parsed[path] = (package, src, raw) + objects.update(object_members(src)) for match in DECL.finditer(src): declared[package].add(match.group(1)) # Enum entries are declarations too; DECL only sees the class itself. @@ -119,6 +169,18 @@ def main() -> int: print(f"{os.path.relpath(path, root)}:{line}: unresolved '{name}'") problems += 1 + # Members of objects declared in this package. + for match in re.finditer(r"(?