feat(voyage): sync browser-side anchor-parser regex with Node-side allowlist
Step 16 of v4.3 playground plan. Mirror lib/parsers/anchor-parser.mjs
ANCHOR_LINE_RE + ATTR_RE + ID_RE constants verbatim into voyage-playground.html
inline-script (file COLON COLON scheme compat — no ES-module). parseAnchor(line)
validates id matches ANN-NNNN, target non-empty, line positive integer,
snippet ≤80c, intent in {fix,change,question,block}.
Trace: SC6, research/02 Sec T4, plan-critic blocker B4 + scope-guardian DEP-3.
Cross-file regex sync verified by static-grep test.
This commit is contained in:
parent
946eb7ab0f
commit
3973be2a90
2 changed files with 54 additions and 0 deletions
|
|
@ -28,6 +28,40 @@
|
|||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Voyage anchor parser — mirrored verbatim from lib-parsers anchor-parser.mjs.
|
||||
// Pure I/O-free parser for v4.2 voyage:anchor markdown comments.
|
||||
// Format reference: voyage anchor id ANN-NNNN target heading-slug line N snippet ≤80c intent fix change question block.
|
||||
// Constants mirror Node-side ANCHOR_LINE_RE + ATTR_RE + ID_RE (anchor-parser.mjs:20-25). Inline keeps file COLON COLON scheme compat (no ES-module).
|
||||
var VOYAGE_ANCHOR_RE = /^(\s*)<!--\s*voyage:anchor\s+([^>]+?)\s*-->\s*$/;
|
||||
var VOYAGE_ANCHOR_ATTR_RE = /(\w+)="([^"]*)"/g;
|
||||
var VOYAGE_ANCHOR_ID_RE = /^ANN-\d{4}$/;
|
||||
var VOYAGE_ANCHOR_INTENTS = ['fix', 'change', 'question', 'block'];
|
||||
|
||||
function parseAnchor(line) {
|
||||
if (typeof line !== 'string') return null;
|
||||
var m = line.match(VOYAGE_ANCHOR_RE);
|
||||
if (!m) return null;
|
||||
var attrs = {};
|
||||
VOYAGE_ANCHOR_ATTR_RE.lastIndex = 0;
|
||||
var a;
|
||||
while ((a = VOYAGE_ANCHOR_ATTR_RE.exec(m[2])) !== null) attrs[a[1]] = a[2];
|
||||
if (!attrs.id || !VOYAGE_ANCHOR_ID_RE.test(attrs.id)) return null;
|
||||
if (typeof attrs.target !== 'string' || attrs.target.length === 0) return null;
|
||||
var lineNum = null;
|
||||
if (attrs.line !== undefined) {
|
||||
var n = parseInt(attrs.line, 10);
|
||||
if (!Number.isInteger(n) || n <= 0) return null;
|
||||
lineNum = n;
|
||||
}
|
||||
var snippet = attrs.snippet || null;
|
||||
if (snippet !== null && snippet.length > 80) return null;
|
||||
var intent = attrs.intent || null;
|
||||
if (intent !== null && VOYAGE_ANCHOR_INTENTS.indexOf(intent) === -1) return null;
|
||||
return { id: attrs.id, target: attrs.target, line: lineNum, snippet: snippet, intent: intent };
|
||||
}
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="vendor/playground-design-system/fonts.css">
|
||||
<link rel="stylesheet" href="vendor/playground-design-system/tokens.css">
|
||||
<link rel="stylesheet" href="vendor/playground-design-system/base.css">
|
||||
|
|
|
|||
|
|
@ -200,3 +200,23 @@ test('voyage-playground.html declares popstate handler (v4.3 Step 15 back/forwar
|
|||
const text = readFileSync(HTML, 'utf-8');
|
||||
assert.match(text, /'popstate'/, 'popstate listener required for browser back/forward');
|
||||
});
|
||||
|
||||
test('voyage-playground.html declares VOYAGE_ANCHOR_RE constant (v4.3 Step 16 anchor allowlist)', () => {
|
||||
const text = readFileSync(HTML, 'utf-8');
|
||||
assert.match(text, /VOYAGE_ANCHOR_RE\s*=\s*\/\^/, 'VOYAGE_ANCHOR_RE regex constant required');
|
||||
assert.match(text, /VOYAGE_ANCHOR_ATTR_RE\s*=\s*\//, 'VOYAGE_ANCHOR_ATTR_RE constant required');
|
||||
assert.match(text, /VOYAGE_ANCHOR_ID_RE\s*=\s*\/\^ANN-/, 'VOYAGE_ANCHOR_ID_RE constant required');
|
||||
});
|
||||
|
||||
test('voyage-playground.html anchor regex matches Node-side allowlist (v4.3 Step 16 cross-file sync)', () => {
|
||||
const html = readFileSync(HTML, 'utf-8');
|
||||
const node = readFileSync(join(ROOT, 'lib', 'parsers', 'anchor-parser.mjs'), 'utf-8');
|
||||
const htmlMatch = html.match(/voyage:anchor[^/]+/)?.[0];
|
||||
const nodeMatch = node.match(/voyage:anchor[^/]+/)?.[0];
|
||||
assert.equal(htmlMatch, nodeMatch, 'first voyage:anchor token in HTML must mirror Node-side parser exactly');
|
||||
});
|
||||
|
||||
test('voyage-playground.html declares parseAnchor validator (v4.3 Step 16)', () => {
|
||||
const text = readFileSync(HTML, 'utf-8');
|
||||
assert.match(text, /function\s+parseAnchor\s*\(\s*line\s*\)/, 'parseAnchor(line) function required');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue