swc-project/swc · warning · swc_html_parser::error::Error

Stray end tag "{tag_name}"

Error message

Stray end tag "{tag_name}"

What it means

The 'any other end tag' path of process_token_in_foreign_content (crates/swc_html_parser/src/parser/mod.rs:1040) walks the stack of open elements looking for a name matching the end tag. If the name differs from the current node and the walk would pass the stack bottom (stack_idx == 0, only the root html element), the parser records StrayEndTag and silently drops the token — no element anywhere on the stack matches.

Source

Thrown at crates/swc_html_parser/src/parser/mod.rs:1040

            // token, pop elements from the stack of open elements until node has been popped from
            // the stack, and then return.
            //
            // Set node to the previous entry in the stack of open elements.
            //
            // If node is not an element in the HTML namespace, return to the step labeled loop.
            //
            // Otherwise, process the token according to the rules given in the section
            // corresponding to the current insertion mode in HTML content.
            Token::EndTag { tag_name, .. } => {
                let mut node = self.open_elements_stack.items.last();
                let mut stack_idx = self.open_elements_stack.items.len() - 1;

                if let Some(node) = &node {
                    let node_tag_name = get_tag_name!(node);

                    if node_tag_name.to_ascii_lowercase() != **tag_name {
                        if stack_idx == 0 {
                            self.errors.push(Error::new(
                                token_and_info.span,
                                ErrorKind::StrayEndTag(tag_name.clone()),
                            ));
                        } else {
                            self.errors.push(Error::new(
                                token_and_info.span,
                                ErrorKind::EndTagDidNotMatchCurrentOpenElement(
                                    tag_name.clone(),
                                    node_tag_name.into(),
                                ),
                            ));
                        }
                    }
                }

                loop {
                    if stack_idx == 0 || node.is_none() {
                        return Ok(());

View on GitHub (pinned to 5176682b65)

Solutions

  1. Remove the orphan end tag, or open the element it was meant to close
  2. Emit each element's open and close tags from the same template block so they travel together
  3. Filter ErrorKind::StrayEndTag(_) when orphan closers are tolerated in your pipeline

Example fix

<!-- before -->
<svg><rect/></svg></group>

<!-- after -->
<svg><rect/></svg>
Defensive patterns

Strategy: validation

Validate before calling

fn has_orphan_end_tags(html: &str) -> Vec<String> {
    let lower = html.to_ascii_lowercase();
    let mut stack: Vec<String> = Vec::new();
    let mut orphan = Vec::new();
    const VOID: &[&str] = &["br","img","meta","input","hr","link","source","area","base","col","embed","param","track","wbr"];
    for (i, _) in lower.match_indices('<') {
        let rest = &lower[i + 1..];
        let (closing, body) = match rest.strip_prefix('/') {
            Some(b) => (true, b),
            None => (false, rest),
        };
        let name: String = body.chars().take_while(char::is_ascii_alphanumeric).collect();
        if name.is_empty() { continue; }
        if closing {
            if !stack.contains(&name) {
                orphan.push(format!("</{}> at byte {}", name, i));
            } else if stack.last().as_deref() == Some(&name) {
                stack.pop();
            }
        } else if !VOID.contains(&name.as_str()) {
            stack.push(name);
        }
    }
    orphan
}

Try / catch

use swc_html_parser::error::ErrorKind;

let mut errors = Vec::new();
let doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;

for err in &errors {
    if let ErrorKind::StrayEndTag(tag) = err.kind() {
        // Token ignored by recovery; tree unchanged at this point.
        log::warn!("orphan end tag </{}>", tag);
    }
}

Prevention

When it happens

Trigger: An end tag whose name matches nothing on the stack while inside <svg>/<math>, e.g. `<svg><rect/></svg></group>` or `<math><mi>x</mi></bar>`. Distinguished from error 726: here even the walk-to-bottom cannot find a match, so the closer is fully orphaned.

Common situations: Cut-and-paste markup leaving orphan closers, templating layers that always emit a closing tag for a section whose opening tag was conditionally skipped, string-concatenated fragments.

Related errors


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