fix(site-builder): make formatHtml quote- and raw-text-aware
Code review found two Important bugs from only <pre> being exempted from the naive </> tag-boundary scan: a > inside a quoted attribute value split tags and broke idempotency, and <script>/<style> (declared BLOCK_TAGS but never given raw-text treatment) let JS/CSS < and > desync sibling nesting. Adds a quote-aware tag-end scanner, generalizes verbatim handling to <script>/<style>, and makes close-tag stack popping verify the tag name before popping instead of blindly popping by position. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,3 +39,45 @@ describe('formatHtml', () => {
|
||||
expect(formatHtml('</div><p>x</p>')).toBe('</div>\n<p>x</p>');
|
||||
});
|
||||
});
|
||||
|
||||
// Regression coverage from code review: only <pre> was originally exempted
|
||||
// from the naive '<'/'>' tag-boundary scan, which let a '>' inside a quoted
|
||||
// attribute value corrupt output, and let '<'/'>' inside <script>/<style>
|
||||
// content be misparsed as tag boundaries.
|
||||
describe('formatHtml - raw content and quoted attributes', () => {
|
||||
test('a ">" inside a quoted attribute value does not split the tag', () => {
|
||||
const src = '<div title="a>b"><p>hi</p></div><footer>bye</footer>';
|
||||
expect(formatHtml(src)).toBe(
|
||||
'<div title="a>b">\n <p>hi</p>\n</div>\n<footer>bye</footer>',
|
||||
);
|
||||
});
|
||||
|
||||
test('"<" and ">" inside <script> content do not desync sibling nesting', () => {
|
||||
const src = '<div><script>a < b;</script></div><div><script>c < d;</script></div>';
|
||||
expect(formatHtml(src)).toBe(
|
||||
'<div>\n <script>a < b;</script>\n</div>\n<div>\n <script>c < d;</script>\n</div>',
|
||||
);
|
||||
});
|
||||
|
||||
test('a CSS child combinator inside <style> content is not treated as markup', () => {
|
||||
const src = '<div><style>div > p { color: red; }</style></div>';
|
||||
expect(formatHtml(src)).toBe(
|
||||
'<div>\n <style>div > p { color: red; }</style>\n</div>',
|
||||
);
|
||||
});
|
||||
|
||||
test('is idempotent across a quoted ">" attribute and <script> content', () => {
|
||||
const quotedAttr = formatHtml('<div title="a>b"><p>hi</p></div><footer>bye</footer>');
|
||||
expect(formatHtml(quotedAttr)).toBe(quotedAttr);
|
||||
|
||||
const scriptSrc = '<div><script>a < b;</script></div><div><script>c < d;</script></div>';
|
||||
const scripted = formatHtml(scriptSrc);
|
||||
expect(formatHtml(scripted)).toBe(scripted);
|
||||
});
|
||||
|
||||
test('an unclosed <pre> is swallowed verbatim to the end of the document', () => {
|
||||
expect(formatHtml('<div><pre>no closing tag here')).toBe(
|
||||
'<div>\n <pre>no closing tag here',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user