swc-project/swc · warning · swc_html_parser::error::Error
EndTagViolatesNestingRules
EndTagViolatesNestingRules
Error message
End tag "{tag_name}" violates nesting rules What it means
Step 6 of the adoption agency algorithm (crates/swc_html_parser/src/parser/mod.rs:7343): when a formatting end tag is processed and the current node (top of the open elements stack) is not the formatting element itself, the end tag 'violates nesting rules'. The parser reports the error and then performs the full adoption agency run — reconstructing active formatting elements and re-parenting nodes — which is exactly the famous `<b><p>x</b>` transformation.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:7343
if formatting_element_stack_index.is_some()
&& !self
.open_elements_stack
.has_node_in_scope(&formatting_element.1)
{
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::NoElementToCloseButEndTagSeen(subject),
));
return Ok(());
}
let formatting_element_stack_index = formatting_element_stack_index.unwrap();
// 6.
if let Some(node) = self.open_elements_stack.items.last() {
if !is_same_node(node, &formatting_element.1) {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::EndTagViolatesNestingRules(subject.clone()),
));
}
}
// 7.
let furthest_block = self
.open_elements_stack
.items
.iter()
.enumerate()
.skip(formatting_element_stack_index)
.find(|&(_, open_element)| self.is_special_element(open_element))
.map(|(i, h)| (i, h.clone()));
// 8.
if furthest_block.is_none() {View on GitHub (pinned to 5176682b65)
Solutions
- Close the formatting element before opening the block element: <b>1</b><p>2</p>
- Move the formatting inside the block: <p><b>2</b></p>
- Use CSS (font-weight, color) instead of wrapping block content in formatting tags
- Expect re-parented output from recovery — snapshot-test normalized HTML for such inputs
Example fix
<!-- before --> <b>1<p>2</b>3 <!-- after --> <b>1</b><p><b>2</b></p>3
Defensive patterns
Strategy: validation
Validate before calling
// Reject inline formatting elements containing block-level children
const BLOCK = /^(div|p|ul|ol|li|table|tr|td|th|h[1-6]|section|article|blockquote|form|pre|hr)$/i;
function assertNoInlineAroundBlocks(html: string): void {
for (const m of html.matchAll(/<(b|i|em|strong|u|s|a|span|font)\b[^>]*>([\s\S]*?)<\/\1>/gi)) {
if (BLOCK.test((m[2].match(/^\s*<([a-zA-Z0-9]+)/) || [])[1] || '')) {
throw new Error(`<${m[1]}> wraps a block element`);
}
}
} Try / catch
for err in parser.take_errors() {
if let ErrorKind::EndTagViolatesNestingRules(name) = &err.kind {
// adoption agency will re-parent nodes; snapshot-test the output
}
} Prevention
- Close formatting tags before opening block elements (the classic <b><p></b> fix)
- Prefer CSS styling over wrapping blocks in formatting tags
- Configure WYSIWYG editors to enforce inline-content models
When it happens
Trigger: Parsing `<b>1<p>2</b>3` or `<a href><div>click</a>` — a formatting end tag whose element is in scope but not the current node, forcing the parser to run the re-parenting machinery instead of a simple pop.
Common situations: Classic WYSIWYG output (`<b>` wrapped around block elements); anchor tags opened around divs/li; sanitizers stripping block elements inside inline formatting; HTML emails where styling spans wrap paragraphs.
Related errors
- NoElementToCloseButEndTagSeen
- Start tag "{tag_name}" seen but an element of the same type
- No cell to close
- "select" start tag where end tag expected
- "{tag_name}" start tag with "select" open
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/fcc231bc9cae4d4f.
Report an issue: GitHub.