{"record":{"id":"73254d7be761c8a3","repo":"swc-project/swc","slug":"stray-end-tag-tag-name","errorCode":null,"errorMessage":"Stray end tag \"{tag_name}\"","messagePattern":"Stray end tag \"(.+?)\"","errorType":"exception","errorClass":"swc_html_parser::error::Error","httpStatus":null,"severity":"warning","filePath":"crates/swc_html_parser/src/parser/mod.rs","lineNumber":1040,"sourceCode":"            // token, pop elements from the stack of open elements until node has been popped from\n            // the stack, and then return.\n            //\n            // Set node to the previous entry in the stack of open elements.\n            //\n            // If node is not an element in the HTML namespace, return to the step labeled loop.\n            //\n            // Otherwise, process the token according to the rules given in the section\n            // corresponding to the current insertion mode in HTML content.\n            Token::EndTag { tag_name, .. } => {\n                let mut node = self.open_elements_stack.items.last();\n                let mut stack_idx = self.open_elements_stack.items.len() - 1;\n\n                if let Some(node) = &node {\n                    let node_tag_name = get_tag_name!(node);\n\n                    if node_tag_name.to_ascii_lowercase() != **tag_name {\n                        if stack_idx == 0 {\n                            self.errors.push(Error::new(\n                                token_and_info.span,\n                                ErrorKind::StrayEndTag(tag_name.clone()),\n                            ));\n                        } else {\n                            self.errors.push(Error::new(\n                                token_and_info.span,\n                                ErrorKind::EndTagDidNotMatchCurrentOpenElement(\n                                    tag_name.clone(),\n                                    node_tag_name.into(),\n                                ),\n                            ));\n                        }\n                    }\n                }\n\n                loop {\n                    if stack_idx == 0 || node.is_none() {\n                        return Ok(());","sourceCodeStart":1022,"sourceCodeEnd":1058,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/swc_html_parser/src/parser/mod.rs#L1022-L1058","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the orphan end tag, or open the element it was meant to close","Emit each element's open and close tags from the same template block so they travel together","Filter ErrorKind::StrayEndTag(_) when orphan closers are tolerated in your pipeline"],"exampleFix":"<!-- before -->\n<svg><rect/></svg></group>\n\n<!-- after -->\n<svg><rect/></svg>","handlingStrategy":"validation","validationCode":"fn has_orphan_end_tags(html: &str) -> Vec<String> {\n    let lower = html.to_ascii_lowercase();\n    let mut stack: Vec<String> = Vec::new();\n    let mut orphan = Vec::new();\n    const VOID: &[&str] = &[\"br\",\"img\",\"meta\",\"input\",\"hr\",\"link\",\"source\",\"area\",\"base\",\"col\",\"embed\",\"param\",\"track\",\"wbr\"];\n    for (i, _) in lower.match_indices('<') {\n        let rest = &lower[i + 1..];\n        let (closing, body) = match rest.strip_prefix('/') {\n            Some(b) => (true, b),\n            None => (false, rest),\n        };\n        let name: String = body.chars().take_while(char::is_ascii_alphanumeric).collect();\n        if name.is_empty() { continue; }\n        if closing {\n            if !stack.contains(&name) {\n                orphan.push(format!(\"</{}> at byte {}\", name, i));\n            } else if stack.last().as_deref() == Some(&name) {\n                stack.pop();\n            }\n        } else if !VOID.contains(&name.as_str()) {\n            stack.push(name);\n        }\n    }\n    orphan\n}","typeGuard":null,"tryCatchPattern":"use swc_html_parser::error::ErrorKind;\n\nlet mut errors = Vec::new();\nlet doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;\n\nfor err in &errors {\n    if let ErrorKind::StrayEndTag(tag) = err.kind() {\n        // Token ignored by recovery; tree unchanged at this point.\n        log::warn!(\"orphan end tag </{}>\", tag);\n    }\n}","preventionTips":["Never emit a closing tag without its matching opener in the same code path","Run a tag-balance check over machine-generated HTML before parsing","Treat orphan closers in logs as generator bugs, not parser noise"],"tags":["html","parser","end-tag","orphan","foreign-content","swc"],"backgroundTag":"stray-end-tag","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}