Commit 6f269a27f0

6f269a27f010e8299a326ba26779c7dac1aafa80

parent: e552c648bb

Unsigned

cmc <hello@cleberg.net> · 2026-07-26 17:43 UTC

fix: parse reference table by splitting on "|" instead of regex

SonarCloud kept flagging the row regex for super-linear backtracking — the
\s*(...)\s* trimming is inherently ambiguous since whitespace is also matched
by the cell-content class. Replace the regex with a split-on-"|" parse, which
is unambiguous and linear. A row counts only if it has the reference table's
shape (backtick-wrapped resource, known framework label, bare posture word),
which still excludes the per-section tables and glossary. Output unchanged:
41 mapping rows match between migrations/ and the doc.

Layout: unified · split

scripts/check-mappings.mjs +16 −15
@@ -42,26 +42,27 @@ function rowsFromMigrations() {
4242}
4343
4444// --- 2. Human-readable copy: parse the doc's reference table. ---
45// Matches only rows of the reference table, which are shaped:
46// | `resource` | status | SOC 2 | CC8.1 | positive | rationale |
47// The framework name in cell 3 and the bare posture word in cell 5 are what
48// distinguish these from the per-section tables (framework-first, prose posture)
49// and the glossary (bolded control in cell 1), so those are not matched.
45// Rows are parsed by splitting on "|" rather than a single big regex — that is
46// unambiguous and linear (no backtracking). A row counts only if it has the
47// reference table's shape: a backtick-wrapped resource, a known framework
48// label, and a bare posture word. That shape excludes the header/separator
49// rows, the per-section tables (framework-first, no backticked resource), and
50// the glossary (fewer columns) — so only reference-table data rows match.
5051const FRAMEWORK_LABELS = { "SOC 2": "soc2", "ISO 27001": "iso27001" };
51// Cell captures use [^|] rather than . so a capture cannot run past a column
52// boundary — keeps the match linear (no catastrophic backtracking) and is why
53// splitting on "|" is unnecessary.
54const ROW_RE =
55 /^\|\s*`([^`]+)`\s*\|\s*([^|]+?)\s*\|\s*(SOC 2|ISO 27001)\s*\|\s*([\w.]+)\s*\|\s*(positive|negative|informational)\s*\|/;
52const POSTURES = new Set(["positive", "negative", "informational"]);
53const BACKTICKED = /^`.+`$/;
5654
5755function rowsFromDoc() {
5856 const set = new Set();
5957 for (const line of readFileSync(docPath, "utf8").split("\n")) {
60 const m = ROW_RE.exec(line);
61 if (!m) continue;
62 const [, resource, statusCell, frameworkLabel, control, posture] = m;
63 const status = statusCell.replaceAll("`", "").trim(); // already "·" for NULL
64 set.add(key(resource, status, FRAMEWORK_LABELS[frameworkLabel], control, posture));
58 if (!line.startsWith("|")) continue;
59 // Leading "|" yields an empty cells[0]; data lives in cells[1..5].
60 const cells = line.split("|").map((c) => c.trim());
61 const [, resource, statusCell, frameworkLabel, control, posture] = cells;
62 const framework = FRAMEWORK_LABELS[frameworkLabel];
63 if (!framework || !POSTURES.has(posture) || !BACKTICKED.test(resource ?? "")) continue;
64 const status = statusCell.replaceAll("`", ""); // "·" for NULL
65 set.add(key(resource.replaceAll("`", ""), status, framework, control, posture));
6566 }
6667 return set;
6768}