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

  1. Close the formatting element before opening the block element: <b>1</b><p>2</p>
  2. Move the formatting inside the block: <p><b>2</b></p>
  3. Use CSS (font-weight, color) instead of wrapping block content in formatting tags
  4. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/fcc231bc9cae4d4f. Report an issue: GitHub.