swc-project/swc · warning · Error
Unclosed children in "ruby"
Error message
Unclosed children in "ruby"
What it means
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.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:4077
tag_name,
is_self_closing,
..
} if matches!(&**tag_name, "rb" | "rtc") => {
let is_scope = self.open_elements_stack.has_in_scope("ruby");
if is_scope {
self.open_elements_stack.generate_implied_end_tags();
}
match self.open_elements_stack.items.last() {
Some(node) if !is_html_element!(node, "ruby") => {
if !is_scope {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::StartTagSeenWithoutRuby(tag_name.clone()),
));
} else {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::UnclosedChildrenInRuby,
));
}
}
_ => {}
}
self.insert_html_element(token_and_info)?;
maybe_allow_self_closing!(is_self_closing, tag_name);
}
// A start tag whose tag name is one of: "rp", "rt"
//
// If the stack of open elements has a ruby element in scope, then generate
// implied end tags, except for rtc elements. If the current node is not now a
// rtc element or a ruby element, this is a parse error.
//
// Insert an HTML element for the token.View on GitHub (pinned to 5176682b65)
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
Example fix
<!-- before --> <ruby><span>漢<rb>字</rb></ruby> <!-- after --> <ruby><span>漢</span><rb>字</rb></ruby>
Defensive patterns
Strategy: validation
Validate before calling
// Inside <ruby>, warn when a non-ruby child is still open when <rb>/<rtc> starts
fn ruby_child_unbalanced(src: &str) -> bool {
let mut stack: Vec<String> = Vec::new();
for tag in html_tag_tokens(src) {
match tag {
Start(ref n) if !is_void(n) && !matches!(n.as_str(), "rb"|"rtc"|"rt"|"rp"|"ruby") => {
stack.push(n.clone())
}
Start(ref n) if matches!(n.as_str(), "rb"|"rtc") => {
let top_is_ruby = stack.last().map(|s| s == "ruby").unwrap_or(false);
if !top_is_ruby && !stack.is_empty() { return true; }
}
End(ref n) => { while let Some(t) = stack.pop() { if t == *n { break; } } }
_ => {}
}
}
false
} Type guard
fn is_unclosed_ruby_children(e: &Error) -> bool {
matches!(e.kind, ErrorKind::UnclosedChildrenInRuby)
} Try / catch
let doc = parser.parse_document()?;
for e in parser.take_errors() {
if is_unclosed_ruby_children(&e) {
log::warn!("unclosed child in ruby at {:?}; child force-closed", e.span);
}
} Prevention
- 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
When it happens
Trigger: `<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.
Common situations: 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.
Related errors
- Start tag "{tag_name}" seen without a "ruby" element being o
- UnclosedElements
- failed to parse input as document fragment
- failed to parse input as document
- failed to parse input as document fragment
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/6ccdc377193dfccd.
Report an issue: GitHub.