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