mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
fix(agents): validate rendered HTML navigation
This commit is contained in:
1 parent
bdf7017a97
commit
244b1e43ce
3 files changed
+56
-5
No files matched your search
@@ -42,6 +42,8 @@ separately so encoded filename delimiters stay in the filename. Fenced and inden
|
||||
do not count as root guidance imports or satisfy the required Claude import.
|
||||
Heading anchors decode HTML character references in text. Explicit HTML anchors
|
||||
use `parse5`, excluding comments, scripts, styles and template contents.
|
||||
Rendered HTML anchor hrefs and image sources use the same local-reference checks
|
||||
as Markdown links, including decoded attributes and fragment validation.
|
||||
The Nx test hash includes `marked` and `parse5` so dependency changes invalidate parser coverage.
|
||||
It cannot prove semantic equivalence; review changed contracts as well.
|
||||
|
||||
|
||||
@@ -49,8 +49,9 @@ function decodeEntities(text) {
|
||||
);
|
||||
}
|
||||
|
||||
function htmlAnchors(html) {
|
||||
const found = [];
|
||||
function htmlNavigation(html) {
|
||||
const anchors = [];
|
||||
const references = [];
|
||||
function visit(node) {
|
||||
if (['script', 'style', 'template'].includes(node.tagName)) return;
|
||||
for (const attribute of node.attrs ?? []) {
|
||||
@@ -58,12 +59,17 @@ function htmlAnchors(html) {
|
||||
attribute.name === 'id' ||
|
||||
(node.tagName === 'a' && attribute.name === 'name')
|
||||
)
|
||||
found.push(attribute.value);
|
||||
anchors.push(attribute.value);
|
||||
if (
|
||||
(node.tagName === 'a' && attribute.name === 'href') ||
|
||||
(node.tagName === 'img' && attribute.name === 'src')
|
||||
)
|
||||
references.push(attribute.value);
|
||||
}
|
||||
for (const child of node.childNodes ?? []) visit(child);
|
||||
}
|
||||
visit(parseFragment(html));
|
||||
return found;
|
||||
return { anchors, references };
|
||||
}
|
||||
|
||||
export function guidanceProse(markdown) {
|
||||
@@ -92,7 +98,7 @@ export function guidanceAnchors(markdown) {
|
||||
}
|
||||
if (token.type === 'html') html.push(token.raw);
|
||||
});
|
||||
return new Set([...found, ...htmlAnchors(html.join('\n'))]);
|
||||
return new Set([...found, ...htmlNavigation(html.join('\n')).anchors]);
|
||||
}
|
||||
|
||||
function isLiteralRepositoryPath(token) {
|
||||
@@ -124,6 +130,7 @@ function isLiteralRepositoryPath(token) {
|
||||
export function guidanceReferences(markdown, includeLiterals) {
|
||||
const result = [];
|
||||
const destinations = new Set();
|
||||
const html = [];
|
||||
function add(target, literal = false) {
|
||||
const key = `${literal}:${target}`;
|
||||
if (!destinations.has(key)) {
|
||||
@@ -133,6 +140,7 @@ export function guidanceReferences(markdown, includeLiterals) {
|
||||
}
|
||||
markdownLexer.walkTokens(markdownLexer.lexer(markdown), (token) => {
|
||||
if (['link', 'image', 'def'].includes(token.type)) add(token.href);
|
||||
if (token.type === 'html') html.push(token.raw);
|
||||
if (token.type === 'unresolved-reference')
|
||||
result.push({ unresolvedReference: token.label });
|
||||
if (
|
||||
@@ -142,5 +150,7 @@ export function guidanceReferences(markdown, includeLiterals) {
|
||||
)
|
||||
add(token.text, true);
|
||||
});
|
||||
for (const target of htmlNavigation(html.join('\n')).references)
|
||||
add(target);
|
||||
return result;
|
||||
}
|
||||
@@ -498,3 +498,42 @@ for (const [heading, anchor] of [
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const html of [
|
||||
'<a href="docs/missing.md#section">Guide</a>',
|
||||
'<img src="docs/missing.png">',
|
||||
]) {
|
||||
test(`validates rendered HTML navigation: ${html}`, async (t) => {
|
||||
assert.match(
|
||||
(await diagnostics(t, { 'AGENTS.md': html })).join('\n'),
|
||||
/does not exist/
|
||||
);
|
||||
});
|
||||
}
|
||||
test('HTML navigation supports valid anchors and decoded attributes', async (t) => {
|
||||
assert.deepEqual(
|
||||
await diagnostics(t, {
|
||||
'AGENTS.md':
|
||||
'<a href="docs/example.md#hello-world">Guide</a> <img src="docs/a&b.png">',
|
||||
'docs/a&b.png': '',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
assert.match(
|
||||
(
|
||||
await diagnostics(t, {
|
||||
'AGENTS.md': '<a href="docs/example.md#missing">Guide</a>',
|
||||
})
|
||||
).join('\n'),
|
||||
/missing anchor/
|
||||
);
|
||||
});
|
||||
test('non-rendered HTML navigation is ignored', async (t) => {
|
||||
assert.deepEqual(
|
||||
await diagnostics(t, {
|
||||
'AGENTS.md':
|
||||
'<!-- <a href="missing.md"> -->\n<script>const a = \'<img src="missing.png">\';</script>\n\n<template><a href="missing.md">Hidden</a></template>',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user