Commit e552c648bb

e552c648bbbe9f7a6665f2136e1bc4fa8d4056e5

parent: e87add187b

Unsigned

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

fix: address SonarCloud findings in mappings sync check

- Bound regex cell captures with [^|] instead of . to avoid backtracking
- Use replaceAll() for the global backtick strip
- Give Array#sort explicit comparators (the arrays are strings, so behavior
  is unchanged; this satisfies the quality gate and makes intent explicit)

Layout: unified · split

scripts/check-mappings.mjs +7 −4
@@ -48,8 +48,11 @@ function rowsFromMigrations() {
48// distinguish these from the per-section tables (framework-first, prose posture) 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. 49// and the glossary (bolded control in cell 1), so those are not matched.
50const FRAMEWORK_LABELS = { "SOC 2": "soc2", "ISO 27001": "iso27001" }; 50const 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.
51const ROW_RE = 54const ROW_RE =
52 /^\|\s*`([^`]+)`\s*\|\s*(.+?)\s*\|\s*(SOC 2|ISO 27001)\s*\|\s*([\w.]+)\s*\|\s*(positive|negative|informational)\s*\|/; 55 /^\|\s*`([^`]+)`\s*\|\s*([^|]+?)\s*\|\s*(SOC 2|ISO 27001)\s*\|\s*([\w.]+)\s*\|\s*(positive|negative|informational)\s*\|/;
53 56
54function rowsFromDoc() { 57function rowsFromDoc() {
55 const set = new Set(); 58 const set = new Set();
@@ -57,7 +60,7 @@ function rowsFromDoc() {
57 const m = ROW_RE.exec(line); 60 const m = ROW_RE.exec(line);
58 if (!m) continue; 61 if (!m) continue;
59 const [, resource, statusCell, frameworkLabel, control, posture] = m; 62 const [, resource, statusCell, frameworkLabel, control, posture] = m;
60 const status = statusCell.replace(/`/g, "").trim(); // already "·" for NULL 63 const status = statusCell.replaceAll("`", "").trim(); // already "·" for NULL
61 set.add(key(resource, status, FRAMEWORK_LABELS[frameworkLabel], control, posture)); 64 set.add(key(resource, status, FRAMEWORK_LABELS[frameworkLabel], control, posture));
62 } 65 }
63 return set; 66 return set;
@@ -67,8 +70,8 @@ function rowsFromDoc() {
67const db = rowsFromMigrations(); 70const db = rowsFromMigrations();
68const doc = rowsFromDoc(); 71const doc = rowsFromDoc();
69 72
70const onlyInDb = [...db].filter((k) => !doc.has(k)).sort(); 73const onlyInDb = [...db].filter((k) => !doc.has(k)).sort((a, b) => a.localeCompare(b));
71const onlyInDoc = [...doc].filter((k) => !db.has(k)).sort(); 74const onlyInDoc = [...doc].filter((k) => !db.has(k)).sort((a, b) => a.localeCompare(b));
72 75
73if (onlyInDb.length === 0 && onlyInDoc.length === 0) { 76if (onlyInDb.length === 0 && onlyInDoc.length === 0) {
74 console.log(`✓ mappings in sync: ${db.size} rows match between migrations/ and docs/framework-mapping.md`); 77 console.log(`✓ mappings in sync: ${db.size} rows match between migrations/ and docs/framework-mapping.md`);