{"record":{"id":"6ccdc377193dfccd","repo":"swc-project/swc","slug":"unclosed-children-in-ruby","errorCode":null,"errorMessage":"Unclosed children in \"ruby\"","messagePattern":"Unclosed children in \"ruby\"","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"crates/swc_html_parser/src/parser/mod.rs","lineNumber":4077,"sourceCode":"                        tag_name,\n                        is_self_closing,\n                        ..\n                    } if matches!(&**tag_name, \"rb\" | \"rtc\") => {\n                        let is_scope = self.open_elements_stack.has_in_scope(\"ruby\");\n\n                        if is_scope {\n                            self.open_elements_stack.generate_implied_end_tags();\n                        }\n\n                        match self.open_elements_stack.items.last() {\n                            Some(node) if !is_html_element!(node, \"ruby\") => {\n                                if !is_scope {\n                                    self.errors.push(Error::new(\n                                        token_and_info.span,\n                                        ErrorKind::StartTagSeenWithoutRuby(tag_name.clone()),\n                                    ));\n                                } else {\n                                    self.errors.push(Error::new(\n                                        token_and_info.span,\n                                        ErrorKind::UnclosedChildrenInRuby,\n                                    ));\n                                }\n                            }\n                            _ => {}\n                        }\n\n                        self.insert_html_element(token_and_info)?;\n                        maybe_allow_self_closing!(is_self_closing, tag_name);\n                    }\n                    // A start tag whose tag name is one of: \"rp\", \"rt\"\n                    //\n                    // If the stack of open elements has a ruby element in scope, then generate\n                    // implied end tags, except for rtc elements. If the current node is not now a\n                    // rtc element or a ruby element, this is a parse error.\n                    //\n                    // Insert an HTML element for the token.","sourceCodeStart":4059,"sourceCodeEnd":4095,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/swc_html_parser/src/parser/mod.rs#L4059-L4095","documentation":"A start tag `rb`/`rtc` was seen with `ruby` in scope, but after `generate_implied_end_tags()` the current node is not the `ruby` element itself (parser/mod.rs:4068-4083). That means some non-implied child of `<ruby>` is still open — reported as `UnclosedChildrenInRuby`. The child is then implicitly terminated when the rb/rtc element is inserted.","triggerScenarios":"`<ruby><b>kan<b-unclosed><rb>` — concretely: `<ruby><span>漢<rb>字</rb></ruby>` where `<span>` (not an implied-end tag) is still the current node when `<rb>` arrives.","commonSituations":"Annotation templates that wrap text runs in spans/divs inside `<ruby>` without closing them, and user-generated annotation content pasted mid-element. Mostly seen in CJK publishing pipelines and subtitle/furigana tooling.","solutions":["Close all open children inside `<ruby>` before emitting `<rb>`/`<rtc>`","Keep ruby children limited to the spec set (rb, rt, rtc, rp, plus text) and let tooling enforce it","Add a structural lint for ruby content in your template pipeline","Log `UnclosedChildrenInRuby` from take_errors() to catch regressions in generators"],"exampleFix":"<!-- before -->\n<ruby><span>漢<rb>字</rb></ruby>\n\n<!-- after -->\n<ruby><span>漢</span><rb>字</rb></ruby>","handlingStrategy":"validation","validationCode":"// Inside <ruby>, warn when a non-ruby child is still open when <rb>/<rtc> starts\nfn ruby_child_unbalanced(src: &str) -> bool {\n    let mut stack: Vec<String> = Vec::new();\n    for tag in html_tag_tokens(src) {\n        match tag {\n            Start(ref n) if !is_void(n) && !matches!(n.as_str(), \"rb\"|\"rtc\"|\"rt\"|\"rp\"|\"ruby\") => {\n                stack.push(n.clone())\n            }\n            Start(ref n) if matches!(n.as_str(), \"rb\"|\"rtc\") => {\n                let top_is_ruby = stack.last().map(|s| s == \"ruby\").unwrap_or(false);\n                if !top_is_ruby && !stack.is_empty() { return true; }\n            }\n            End(ref n) => { while let Some(t) = stack.pop() { if t == *n { break; } } }\n            _ => {}\n        }\n    }\n    false\n}","typeGuard":"fn is_unclosed_ruby_children(e: &Error) -> bool {\n    matches!(e.kind, ErrorKind::UnclosedChildrenInRuby)\n}","tryCatchPattern":"let doc = parser.parse_document()?;\nfor e in parser.take_errors() {\n    if is_unclosed_ruby_children(&e) {\n        log::warn!(\"unclosed child in ruby at {:?}; child force-closed\", e.span);\n    }\n}","preventionTips":["Close spans/b inside <ruby> before rb/rtc","Style ruby base text via attributes/classes on rb instead of wrapping tags","Keep ruby children limited to rb, rt, rtc, rp and text"],"tags":["html","parser","ruby","unclosed-elements","cjk","annotation"],"backgroundTag":"unclosed-html-elements","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"}