swc-project/swc · warning · swc_html_parser::error::Error
UnclosedElementsCell
UnclosedElementsCell
Error message
A table cell was implicitly closed, but there were open elements
What it means
`close_the_cell` runs when a table cell must be closed (a new td/th or row starts). After generating implied end tags, the current node should be the td/th itself; if formatting/inline elements are still open on top of it, `UnclosedElementsCell` is reported (crates/swc_html_parser/src/parser/mod.rs:7765, using the offending node's start span). The parser then pops until a td/th is popped, implicitly closing those elements.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:7765
// 3. Pop elements from the stack of open elements until a p element has been
// popped from the stack.
let popped = self.open_elements_stack.pop_until_tag_name_popped(&["p"]);
if is_close_p {
self.update_end_tag_span(popped.as_ref(), token_and_info.span)
}
}
fn close_the_cell(&mut self) {
// Generate implied end tags.
self.open_elements_stack.generate_implied_end_tags();
// If the current node is not now a td element or a th element, then this is a
// parse error.
match self.open_elements_stack.items.last() {
Some(node) if !is_html_element!(node, "td" | "th") => {
self.errors.push(Error::new(
*node.start_span.borrow(),
ErrorKind::UnclosedElementsCell,
));
}
_ => {}
}
// Pop elements from the stack of open elements stack until a td
// element or a th element has been popped from the stack.
self.open_elements_stack
.pop_until_tag_name_popped(&["td", "th"]);
// Clear the list of active formatting elements up to the last marker.
self.active_formatting_elements.clear_to_last_marker();
// Switch the insertion mode to "in row".
self.insertion_mode = InsertionMode::InRow;
View on GitHub (pinned to 5176682b65)
Solutions
- Close inline/formatting tags before the next <td>/<th> or end of row
- Fix the table exporter that leaves formatting open between cells
- Re-serialize through a tolerant parser to normalize cells before downstream processing
- Note the error span points at the still-open node's start tag, which helps locate the unclosed element
Example fix
<!-- before --> <table><tr><td><b>x<td>y</td></tr></table> <!-- after --> <table><tr><td><b>x</b></td><td>y</td></tr></table>
Defensive patterns
Strategy: validation
Validate before calling
// Ensure inline tags are closed before the next cell starts
function assertCellsSelfContained(rowHtml: string): void {
for (const m of rowHtml.matchAll(/<t[dh]\b[^>]*>([\s\S]*?)(?=<t[dh]\b|<\/tr>)/gi)) {
const inner = m[1];
const opens = (inner.match(/<(b|i|em|strong|span|a|u|s|font)\b/gi) || []).length;
const closes = (inner.match(/<\/(b|i|em|strong|span|a|u|s|font)>/gi) || []).length;
if (opens !== closes) throw new Error('inline tag not closed at end of cell');
}
} Try / catch
for err in parser.take_errors() {
if matches!(err.kind, ErrorKind::UnclosedElementsCell) {
// error span = start tag of the still-open node; use it to locate the culprit
}
} Prevention
- Close formatting tags inside each td/th before the next cell
- Have table exporters close per-cell markup instead of batching closers
- Use the diagnostic's span (node start tag) to pinpoint unclosed elements in large tables
When it happens
Trigger: Parsing `<table><tr><td><b>x<td>y</td></tr></table>` — the second `<td>` triggers close_the_cell; implied end tags do not pop `<b>` (formatting elements are not in the implied set), the current node is `b`, so the error fires and b is force-closed with the cell.
Common situations: Spreadsheet-style exports where cell content keeps formatting tags open across cells; CMS table editors that inject `<b>`/`<span>` per cell but close them later; table fragments concatenated without closing inline tags.
Related errors
- No cell to close
- "{tag_name}" end tag with "select" open
- StartTagInTable
- NonSpaceCharacterInTable
- UnclosedElements
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/7290050a1ced5c2c.
Report an issue: GitHub.