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

  1. Close all open children inside `<ruby>` before emitting `<rb>`/`<rtc>`
  2. Keep ruby children limited to the spec set (rb, rt, rtc, rp, plus text) and let tooling enforce it
  3. Add a structural lint for ruby content in your template pipeline
  4. 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

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


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