swc-project/swc · warning · Error
StrayDoctype
StrayDoctype
Error message
Stray doctype
What it means
A DOCTYPE token arrived while the parser was in the 'in head' insertion mode (crates/swc_html_parser/src/parser/mod.rs:1702) — inside the head element. Spec: parse error, ignore the token. This is the most common StrayDoctype in practice: a duplicated doctype appearing after head has started.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:1702
// Insert the character.
Token::Character {
value: '\x09' | '\x0A' | '\x0C' | '\x0D' | '\x20',
..
} => {
self.insert_character(token_and_info)?;
}
// A comment token
//
// Insert a comment.
Token::Comment { .. } => {
self.insert_comment(token_and_info)?;
}
// A DOCTYPE token
//
// Parse error. Ignore the token.
Token::Doctype { .. } => {
self.errors
.push(Error::new(token_and_info.span, ErrorKind::StrayDoctype));
}
// A start tag whose tag name is "html"
//
// Process the token using the rules for the "in body" insertion mode.
Token::StartTag { tag_name, .. } if tag_name == "html" => {
self.process_token_using_rules(token_and_info, InsertionMode::InBody)?;
}
// A start tag whose tag name is one of: "base", "basefont", "bgsound", "link"
//
// Insert an HTML element for the token. Immediately pop the current node off
// the stack of open elements.
//
// Acknowledge the token's self-closing flag, if it is set.
Token::StartTag {
tag_name,
is_self_closing,
..
} if matches!(&**tag_name, "base" | "basefont" | "bgsound" | "link") => {View on GitHub (pinned to 5176682b65)
Solutions
- Delete the doctype from head-included partials; keep exactly one at the document top
- Audit includes/snippets for embedded document skeletons (doctype, html, head)
- Filter ErrorKind::StrayDoctype when duplicates are acceptable
Example fix
<!-- before --> <head> <meta charset="utf-8"> <!DOCTYPE html> <title>t</title> </head> <!-- after --> <head> <meta charset="utf-8"> <title>t</title> </head>
Defensive patterns
Strategy: validation
Validate before calling
fn doctype_inside_head(html: &str) -> bool {
let lower = html.to_ascii_lowercase();
match (lower.find("<head"), lower.rfind("</head")) {
(Some(open), Some(close)) => lower[open..close].contains("<!doctype"),
(Some(open), None) => lower[open..].contains("<!doctype"),
_ => false,
}
} Try / catch
use swc_html_parser::error::ErrorKind;
let mut errors = Vec::new();
let doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;
for err in &errors {
if matches!(err.kind(), ErrorKind::StrayDoctype) {
// Ignored inside head; head content parsed normally.
log::warn!("doctype inside <head> ignored");
}
} Prevention
- Head includes must contain only head-legal elements (meta/link/title/style/script)
- Never paste a full document skeleton into a head partial
- Lint for a second `<!doctype` occurrence anywhere in the file
When it happens
Trigger: `<!DOCTYPE html><html><head><meta charset="utf-8"><!DOCTYPE html>` or any doctype token whose preceding content already opened head. Comments and metadata before it do not close head, so the stray doctype is still processed by in-head rules.
Common situations: A doctype line inside a shared <head> include partial, meta-tag snippets that carry a full document skeleton, duplicate doctypes from wrapper + content concatenation.
Related errors
- SomethingSeenWhenSomethingOpen
- Stray doctype
- Non conforming doctype
- Start tag seen without seeing a doctype first, expected "<!D
- End tag seen without seeing a doctype first, expected "<!DOC
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/90ad130718ae9632.
Report an issue: GitHub.