reviewOrder
The order to review changes in: what's new before what uses it, so each definition is read after what it
depends on; then tests; removed and renamed last. usesOf says which hashes a hash uses.
import type { ReviewItem } from "#codebase/tcmts/share"; export function reviewOrder(items: ReviewItem[], usesOf: (hash: string) => string[]): ReviewItem[] { const rank: Record<ReviewItem["change"], number> = { added: 0, edited: 1, reworded: 2, conflicted: 3, renamed: 4, removed: 5, }; const byHash = new Map(items.filter((i) => i.hash).map((i) => [i.hash!, i])); const before = new Map<ReviewItem, Set<ReviewItem>>(items.map((i) => [i, new Set()])); for (const i of items) { if (!i.hash || i.change === "removed") continue; for (const h of usesOf(i.hash)) { const dep = byHash.get(h); if (dep && dep !== i) before.get(i)!.add(dep); } } const key = (i: ReviewItem) => [i.test ? 1 : 0, rank[i.change], i.path] as const; const less = (a: ReviewItem, b: ReviewItem) => { const [x, y] = [key(a), key(b)]; return x[0] - y[0] || x[1] - y[1] || (x[2] < y[2] ? -1 : x[2] > y[2] ? 1 : 0); }; const out: ReviewItem[] = []; const done = new Set<ReviewItem>(); const left = [...items].sort(less); while (left.length) { // the first (by kind and name) whose dependencies are all read; in a cycle, simply the first const k = Math.max( 0, left.findIndex((i) => [...before.get(i)!].every((d) => done.has(d))), ); const [next] = left.splice(k, 1); out.push(next!); done.add(next!); } return out; }