Closer/scripts/painter-xml-scan.sh

61 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# CloserApp — painterResource(non-vector XML) crash scanner.
#
# WHY THIS EXISTS: Compose's `painterResource(id)` picks its loader by file extension — any `.xml`
# drawable is routed through the VectorDrawable loader, which throws at runtime:
# java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported
# the instant such a composable is rendered, if the XML's root is NOT <vector> (e.g. <bitmap>,
# <layer-list>, <adaptive-icon>, <shape>, <selector>, <animated-vector>, ...). It compiles fine and is
# invisible to unit tests — it only blows up on screen. This shipped once as O-ONBOARD-001: the app-icon
# redesign (commit 334cb07) turned ic_launcher_foreground.xml from <vector> into a <bitmap>, crashing
# every fresh install on the final onboarding slide + the login screen (both used
# painterResource(R.drawable.ic_launcher_foreground)). Recurring QA missed it (logged-in emulators skip
# onboarding/auth). Fix: point painterResource at the underlying raster, or use ImageVector/ImageBitmap.
#
# This scan is a cheap deterministic gate against the whole class. Exit 1 on any finding.
#
# Usage: ./scripts/painter-xml-scan.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="$ROOT/app/src/main/java"
RES_GLOB="$ROOT/app/src/main/res/drawable"*
fail=0
found=0
# Collect every painterResource(R.drawable.NAME) reference in Kotlin source.
while IFS= read -r line; do
file="${line%%:*}"
rest="${line#*:}"
lineno="${rest%%:*}"
name="$(printf '%s\n' "$rest" | grep -oE 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' | head -1 | sed -E 's/.*R\.drawable\.//')"
[ -z "$name" ] && continue
# Is there an XML drawable resource of that name in any drawable* bucket?
xml=""
for d in $RES_GLOB; do
[ -f "$d/$name.xml" ] && xml="$d/$name.xml" && break
done
[ -z "$xml" ] && continue # not an XML drawable (raster) → painterResource-safe
# Root element of the XML drawable.
root="$(grep -m1 -oE '<(vector|bitmap|layer-list|shape|selector|adaptive-icon|animated-vector|ripple|inset|clip|rotate|scale|transition|level-list|nine-patch|drawable)' "$xml" | head -1 | tr -d '<')"
if [ "$root" != "vector" ]; then
found=$((found+1)); fail=1
rel="${file#"$ROOT"/}"
echo "🔴 CRASH RISK $rel:$lineno painterResource(R.drawable.$name) → <$root> ($name.xml)"
echo " painterResource only loads <vector> XML or raster files; a <$root> root throws at render."
echo " Fix: reference the underlying raster, or use ImageVector.vectorResource / ImageBitmap.imageResource."
fi
done < <(grep -rREn 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' "$SRC" 2>/dev/null || true)
if [ "$fail" -eq 0 ]; then
echo "✅ painter-xml-scan: 0 painterResource() calls target a non-<vector> XML drawable."
else
echo ""
echo "❌ painter-xml-scan: $found painterResource(non-vector XML) call(s) — each is a guaranteed runtime crash on render."
fi
exit $fail