reviewOrder

tcmts.share.reviewOrder · #76cumh7d · term · runs anywhere

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;
}

uses

used by