forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanalyze_ratchet.sh
More file actions
executable file
·167 lines (152 loc) · 6.17 KB
/
Copy pathanalyze_ratchet.sh
File metadata and controls
executable file
·167 lines (152 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env bash
# Dart analyzer ratchet for app/.
#
# Contract:
# - Any ERROR severity diagnostic always fails the build and is never
# baselined (undefined methods, wrong argument types, broken imports,
# etc. must never be silently accepted). Verified empirically: dart
# analyze reports these as COMPILE_TIME_ERROR/ERROR, distinct from the
# WARNING-severity diagnostics below.
# - WARNING and INFO level diagnostics are ratcheted together: the script
# counts occurrences PER RULE across the whole app/ tree (not per file,
# by design — this tracks total occurrences of a rule so moving/renaming
# files doesn't trip false positives) and compares against the committed
# baseline in analysis_baseline.json. A rule may not regress above its
# baseline count; rules with fewer occurrences than baseline are reported
# as an improvement opportunity but do not fail the build.
# - Fail closed on analyzer crash: if dart analyze exits non-zero without
# producing a single machine-format diagnostic line, the invocation
# itself failed (crash, bad SDK, unparseable output) and the script
# exits 1 instead of treating the empty output as "zero issues". The
# check is structural (nonzero exit + zero parseable diagnostics), not
# tied to specific exit codes, which vary across Dart versions. A clean
# run (exit 0, no diagnostics) still passes.
#
# Deviation from the original design note ("ERROR or WARNING always
# fails"): dart analyze on this codebase currently reports 138 pre-existing
# WARNING-severity diagnostics (unused_import, dead_null_aware_expression,
# unnecessary_non_null_assertion, etc. — style/dead-code issues, not broken
# builds), not zero as assumed. Hard-failing on WARNING unconditionally
# would make this gate permanently red on unrelated PRs and require fixing
# pre-existing lint debt out of scope. Genuine broken-code diagnostics
# (undefined methods, wrong argument types, broken imports) are ERROR
# severity, not WARNING — so ERROR-only hard-fail preserves the intended
# safety property while WARNING rides the same ratchet as INFO.
#
# Usage:
# app/scripts/analyze_ratchet.sh # check against baseline
# app/scripts/analyze_ratchet.sh --update-baseline # regenerate baseline
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$ROOT_DIR"
BASELINE_FILE="analysis_baseline.json"
UPDATE_BASELINE=false
for arg in "$@"; do
case "$arg" in
--update-baseline)
UPDATE_BASELINE=true
;;
*)
echo "Unknown argument: $arg" >&2
exit 1
;;
esac
done
required_files=(
"lib/env/dev_env.g.dart"
"lib/firebase_options_dev.dart"
)
missing_files=()
for file in "${required_files[@]}"; do
if [[ ! -f "$file" ]]; then
missing_files+=("$file")
fi
done
if [[ ${#missing_files[@]} -gt 0 ]]; then
echo "Generated files missing — run \`bash app/test.sh\` once (or the setup in app/test.sh) before analyzing." >&2
echo "Missing: ${missing_files[*]}" >&2
exit 1
fi
echo "Running: dart analyze --format=machine ."
# dart analyze exits non-zero whenever it finds any issue (info included),
# so capture output without letting set -e kill the script on that line.
set +e
ANALYZE_OUTPUT=$(dart analyze --format=machine . 2>&1)
ANALYZE_EXIT=$?
set -e
# Machine format is pipe-delimited:
# SEVERITY|TYPE|RULE|FILE|LINE|COLUMN|LENGTH|MESSAGE
# Fail closed on analyzer crash: a nonzero exit with zero parseable
# machine-format lines means the analyzer invocation itself failed, not that
# the tree is clean. Don't check specific exit codes — they vary across Dart
# versions; the structural check is the contract. Count with awk rather than
# grep -q: under pipefail, grep -q exits at the first match and printf takes
# a SIGPIPE on large outputs, turning a healthy run into a false failure.
PARSEABLE_COUNT=$(printf '%s\n' "$ANALYZE_OUTPUT" | awk '/^(ERROR|WARNING|INFO)\|/ { n++ } END { print n + 0 }')
if [[ $ANALYZE_EXIT -ne 0 && $PARSEABLE_COUNT -eq 0 ]]; then
echo "" >&2
echo "dart analyze did not produce parseable results — failing closed." >&2
echo "Exit code: $ANALYZE_EXIT" >&2
echo "Raw output:" >&2
printf '%s\n' "$ANALYZE_OUTPUT" >&2
exit 1
fi
SEVERE_LINES=$(printf '%s\n' "$ANALYZE_OUTPUT" | awk -F'|' '$1 == "ERROR"')
if [[ -n "$SEVERE_LINES" ]]; then
echo ""
echo "Error-severity diagnostics found (never baselined):"
printf '%s\n' "$SEVERE_LINES"
exit 1
fi
# WARNING and INFO level diagnostics: exclude generated files (build_runner
# outputs and lib/l10n/), then count remaining occurrences per rule.
CURRENT_JSON=$(printf '%s\n' "$ANALYZE_OUTPUT" | awk -F'|' '
$1 == "WARNING" || $1 == "INFO" {
file = $4
if (file ~ /\.g\.dart$/ || file ~ /\.gen\.dart$/ || file ~ /\.freezed\.dart$/ || file ~ /\/lib\/l10n\//) next
rule = tolower($3)
counts[rule]++
}
END {
printf "{"
first = 1
for (r in counts) {
if (!first) printf ","
printf "\"%s\":%d", r, counts[r]
first = 0
}
printf "}"
}
')
CURRENT_JSON=$(echo "$CURRENT_JSON" | jq -S '.')
if $UPDATE_BASELINE; then
echo "$CURRENT_JSON" | jq -S '.' > "$BASELINE_FILE"
echo "Updated $BASELINE_FILE from the current analyzer run."
exit 0
fi
if [[ ! -f "$BASELINE_FILE" ]]; then
echo "$BASELINE_FILE not found. Run with --update-baseline to create it." >&2
exit 1
fi
FAILED=false
ALL_RULES=$( { jq -r 'keys[]' <<<"$CURRENT_JSON"; jq -r 'keys[]' "$BASELINE_FILE"; } | sort -u)
while IFS= read -r rule; do
[[ -z "$rule" ]] && continue
current=$(jq -r --arg r "$rule" '.[$r] // 0' <<<"$CURRENT_JSON")
baseline=$(jq -r --arg r "$rule" '.[$r] // 0' "$BASELINE_FILE")
if (( current > baseline )); then
echo "$rule: $current found, baseline $baseline — fix the new occurrences"
FAILED=true
elif (( current < baseline )); then
echo "$rule: improved ($current found, baseline $baseline) — consider running --update-baseline to lock in"
fi
done <<<"$ALL_RULES"
if $FAILED; then
echo ""
echo "Analyzer ratchet failed. Deliberate acceptances require running:"
echo " app/scripts/analyze_ratchet.sh --update-baseline"
echo "in the same PR."
exit 1
fi
echo "Analyzer ratchet passed."