mirror of
https://github.com/swc-project/swc.git
synced 2024-11-22 06:46:41 +03:00
fix(html/parser): Fix bugs (#4502)
This commit is contained in:
parent
6cd5cfe045
commit
fca84eb0f2
7
.gitmodules
vendored
7
.gitmodules
vendored
@ -2,4 +2,9 @@
|
||||
path = crates/swc_ecma_parser/tests/test262-parser
|
||||
url = https://github.com/tc39/test262-parser-tests.git
|
||||
shallow = true
|
||||
ignore = dirty
|
||||
ignore = dirty
|
||||
[submodule "html/html5lib-tests"]
|
||||
path = crates/swc_html_parser/tests/html5lib-tests
|
||||
url = https://github.com/html5lib/html5lib-tests.git
|
||||
shallow = true
|
||||
ignore = dirty
|
@ -54,3 +54,54 @@ fn minify_fixtures(input: PathBuf) {
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/recovery/**/input.html")]
|
||||
fn minify_recovery(input: PathBuf) {
|
||||
let dir = input.parent().unwrap();
|
||||
let output = dir.join(format!(
|
||||
"output.min.{}",
|
||||
input.extension().unwrap().to_string_lossy()
|
||||
));
|
||||
let mut recovered = false;
|
||||
|
||||
testing::run_test(false, |cm, handler| {
|
||||
let fm = cm.load_file(&input).unwrap();
|
||||
|
||||
let mut errors = vec![];
|
||||
let res: Result<Document, _> = parse_file(&fm, Default::default(), &mut errors);
|
||||
|
||||
for err in errors {
|
||||
err.to_diagnostics(handler).emit();
|
||||
}
|
||||
|
||||
if handler.has_errors() {
|
||||
recovered = true;
|
||||
}
|
||||
|
||||
let mut ss = res.unwrap();
|
||||
|
||||
// Apply transforms
|
||||
minify(&mut ss);
|
||||
|
||||
let mut html_str = String::new();
|
||||
{
|
||||
let wr = BasicHtmlWriter::new(&mut html_str, None, BasicHtmlWriterConfig::default());
|
||||
let mut gen = CodeGenerator::new(wr, CodegenConfig { minify: true });
|
||||
|
||||
gen.emit(&ss).unwrap();
|
||||
}
|
||||
|
||||
NormalizedOutput::from(html_str)
|
||||
.compare_to_file(&output)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if !recovered {
|
||||
panic!(
|
||||
"Parser should emit errors (recover mode), but parser parsed everything successfully"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -180,34 +180,13 @@ where
|
||||
// When a start tag token is emitted with its self-closing flag set,
|
||||
// if the flag is not acknowledged when it is processed by the tree
|
||||
// construction stage, that is a parse error.
|
||||
//
|
||||
// When an end tag token is emitted with attributes, that is an
|
||||
// end-tag-with-attributes parse error.
|
||||
//
|
||||
// When an end tag token is emitted with its self-closing flag set, that is an
|
||||
// end-tag-with-trailing-solidus parse error.
|
||||
match &token_and_info.token {
|
||||
Token::StartTag { self_closing, .. } => {
|
||||
if *self_closing && !token_and_info.acknowledged {
|
||||
self.errors.push(Error::new(
|
||||
token_and_info.span,
|
||||
ErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
|
||||
));
|
||||
}
|
||||
}
|
||||
Token::EndTag { attributes, .. } if !attributes.is_empty() => {
|
||||
if let Token::StartTag { self_closing, .. } = &token_and_info.token {
|
||||
if *self_closing && !token_and_info.acknowledged {
|
||||
self.errors.push(Error::new(
|
||||
token_and_info.span,
|
||||
ErrorKind::EndTagWithAttributes,
|
||||
ErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
|
||||
));
|
||||
}
|
||||
Token::EndTag { self_closing, .. } if *self_closing => {
|
||||
self.errors.push(Error::new(
|
||||
token_and_info.span,
|
||||
ErrorKind::EndTagWithTrailingSolidus,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,20 @@
|
||||
#![allow(clippy::needless_update)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, mem::take, path::PathBuf};
|
||||
|
||||
use swc_common::{errors::Handler, input::SourceFileInput, Spanned};
|
||||
use serde_json::Value;
|
||||
use swc_atoms::JsWord;
|
||||
use swc_common::{
|
||||
collections::AHashSet,
|
||||
errors::Handler,
|
||||
input::{SourceFileInput, StringInput},
|
||||
BytePos, Spanned,
|
||||
};
|
||||
use swc_html_ast::*;
|
||||
use swc_html_parser::{
|
||||
lexer::Lexer,
|
||||
parser::{PResult, Parser, ParserConfig},
|
||||
error::ErrorKind,
|
||||
lexer::{Lexer, State},
|
||||
parser::{input::ParserInput, PResult, Parser, ParserConfig},
|
||||
};
|
||||
use swc_html_visit::{Visit, VisitWith};
|
||||
use testing::NormalizedOutput;
|
||||
@ -217,3 +225,540 @@ fn span_visualizer(input: PathBuf) {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn unescape(s: &str) -> Option<String> {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
let mut it = s.chars().peekable();
|
||||
|
||||
loop {
|
||||
match it.next() {
|
||||
None => return Some(out),
|
||||
Some('\\') => {
|
||||
if it.peek() != Some(&'u') {
|
||||
panic!("can't understand escape");
|
||||
}
|
||||
|
||||
drop(it.next());
|
||||
|
||||
let hex: String = it.by_ref().take(4).collect();
|
||||
|
||||
match u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
|
||||
// TODO fix me surrogate paris
|
||||
// Some of the tests use lone surrogates, but we have no
|
||||
// way to represent them in the UTF-8 input to our parser.
|
||||
// Since these can only come from script, we will catch
|
||||
// them there.
|
||||
None => return None,
|
||||
Some(c) => out.push(c),
|
||||
}
|
||||
}
|
||||
Some(c) => out.push(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO we need to enable `preserve_order` for serde, but we can't https://github.com/tkaitchuck/aHash/issues/95, so we sort attributes
|
||||
#[testing::fixture("tests/html5lib-tests/tokenizer/**/*.test")]
|
||||
fn html5lib_test_tokenizer(input: PathBuf) {
|
||||
let filename = input.to_str().expect("failed to parse path");
|
||||
let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");
|
||||
let obj: Value = serde_json::from_str(&contents)
|
||||
.ok()
|
||||
.expect("json parse error");
|
||||
let tests = match obj.get(&"tests".to_string()) {
|
||||
Some(&Value::Array(ref tests)) => tests,
|
||||
_ => {
|
||||
return ();
|
||||
}
|
||||
};
|
||||
|
||||
for test in tests.iter() {
|
||||
let description = test
|
||||
.get("description")
|
||||
.expect("failed to get input in test");
|
||||
|
||||
let states = if let Some(initial_states) = test.get("initialStates") {
|
||||
let mut states = vec![];
|
||||
let json_states: Vec<String> = serde_json::from_value(initial_states.clone())
|
||||
.expect("failed to get input in test");
|
||||
|
||||
for json_state in json_states {
|
||||
match &*json_state {
|
||||
"Data state" => {
|
||||
states.push(State::Data);
|
||||
}
|
||||
"PLAINTEXT state" => {
|
||||
states.push(State::PlainText);
|
||||
}
|
||||
"RCDATA state" => {
|
||||
states.push(State::Rcdata);
|
||||
}
|
||||
"RAWTEXT state" => {
|
||||
states.push(State::Rawtext);
|
||||
}
|
||||
"Script data state" => {
|
||||
states.push(State::ScriptData);
|
||||
}
|
||||
"CDATA section state" => {
|
||||
states.push(State::CdataSection);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states
|
||||
} else {
|
||||
vec![State::Data]
|
||||
};
|
||||
|
||||
for state in states.iter() {
|
||||
eprintln!("==== ==== Description ==== ====\n{}\n", description);
|
||||
|
||||
let json_input = test["input"].clone();
|
||||
let mut input: String =
|
||||
serde_json::from_value(json_input).expect("failed to get input in test");
|
||||
|
||||
let need_double_escaped = test.get("doubleEscaped").is_some();
|
||||
|
||||
if need_double_escaped {
|
||||
input = match unescape(&input) {
|
||||
Some(unescaped) => unescaped,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
eprintln!("==== ==== Input ==== ====\n{}\n", input);
|
||||
|
||||
let json_output = test["output"].clone();
|
||||
let output = json_output.to_string();
|
||||
|
||||
eprintln!("==== ==== Output ==== ====\n{}\n", output);
|
||||
|
||||
// TODO keep `\r` in raw when we implement them in AST or implement an option
|
||||
let lexer_input = input.replace("\r\n", "\n").replace('\r', "\n");
|
||||
let lexer_str_input =
|
||||
StringInput::new(&lexer_input, BytePos(0), BytePos(input.len() as u32));
|
||||
let mut lexer = Lexer::new(
|
||||
lexer_str_input,
|
||||
ParserConfig {
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
lexer.set_input_state(state.clone());
|
||||
|
||||
if let Some(last_start_tag) = test.get("lastStartTag") {
|
||||
let last_start_tag: String = serde_json::from_value(last_start_tag.clone())
|
||||
.expect("failed to get lastStartTag in test");
|
||||
|
||||
lexer.last_start_tag_token = Some(Token::StartTag {
|
||||
tag_name: last_start_tag.into(),
|
||||
raw_tag_name: None,
|
||||
self_closing: false,
|
||||
attributes: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let mut actual_tokens = vec![];
|
||||
|
||||
loop {
|
||||
match lexer.next() {
|
||||
Ok(TokenAndSpan { token, .. }) => {
|
||||
let mut new_token = token.clone();
|
||||
|
||||
match new_token {
|
||||
Token::Doctype {
|
||||
ref mut raw_keyword,
|
||||
ref mut raw_name,
|
||||
ref mut public_quote,
|
||||
ref mut raw_public_keyword,
|
||||
ref mut system_quote,
|
||||
ref mut raw_system_keyword,
|
||||
..
|
||||
} => {
|
||||
*raw_keyword = None;
|
||||
*raw_name = None;
|
||||
*public_quote = None;
|
||||
*raw_public_keyword = None;
|
||||
*system_quote = None;
|
||||
*raw_system_keyword = None;
|
||||
}
|
||||
Token::StartTag {
|
||||
ref mut raw_tag_name,
|
||||
ref mut attributes,
|
||||
..
|
||||
} => {
|
||||
*raw_tag_name = None;
|
||||
|
||||
let mut new_attributes = vec![];
|
||||
let mut already_seen: AHashSet<JsWord> = Default::default();
|
||||
|
||||
for mut attribute in take(attributes) {
|
||||
if already_seen.contains(&attribute.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
already_seen.insert(attribute.name.clone());
|
||||
|
||||
if attribute.value.is_none() {
|
||||
attribute.value = Some("".into());
|
||||
}
|
||||
|
||||
attribute.raw_name = None;
|
||||
attribute.raw_value = None;
|
||||
|
||||
new_attributes.push(attribute);
|
||||
}
|
||||
|
||||
new_attributes.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||
|
||||
*attributes = new_attributes;
|
||||
}
|
||||
Token::EndTag {
|
||||
ref mut raw_tag_name,
|
||||
ref mut attributes,
|
||||
ref mut self_closing,
|
||||
..
|
||||
} => {
|
||||
*raw_tag_name = None;
|
||||
*self_closing = false;
|
||||
*attributes = vec![];
|
||||
}
|
||||
Token::Character { ref mut raw, .. } => {
|
||||
*raw = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
actual_tokens.push(new_token);
|
||||
}
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::Eof => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let mut expected_tokens: Vec<Token> = vec![];
|
||||
|
||||
for output_tokens in json_output.as_array() {
|
||||
for output_token in output_tokens {
|
||||
match output_token {
|
||||
Value::Array(token_parts) => {
|
||||
let tokens = match &*token_parts[0].as_str().expect("failed") {
|
||||
"DOCTYPE" => {
|
||||
let name: Option<String> =
|
||||
serde_json::from_value(token_parts[1].clone())
|
||||
.expect("failed to deserialize");
|
||||
let public_id: Option<String> =
|
||||
serde_json::from_value(token_parts[2].clone())
|
||||
.expect("failed to deserialize");
|
||||
let system_id: Option<String> =
|
||||
serde_json::from_value(token_parts[3].clone())
|
||||
.expect("failed to deserialize");
|
||||
let correctness: bool =
|
||||
serde_json::from_value(token_parts[4].clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
vec![Token::Doctype {
|
||||
raw_keyword: None,
|
||||
name: name.map(|v| v.into()),
|
||||
raw_name: None,
|
||||
force_quirks: !correctness,
|
||||
raw_public_keyword: None,
|
||||
public_quote: None,
|
||||
public_id: public_id.map(|v| v.into()),
|
||||
raw_system_keyword: None,
|
||||
system_quote: None,
|
||||
system_id: system_id.map(|v| v.into()),
|
||||
}]
|
||||
}
|
||||
"StartTag" => {
|
||||
let tag_name: String =
|
||||
serde_json::from_value(token_parts[1].clone())
|
||||
.expect("failed to deserialize");
|
||||
let mut attributes = vec![];
|
||||
|
||||
if let Some(json_attributes) = token_parts.get(2).clone() {
|
||||
let obj_attributes: Value =
|
||||
serde_json::from_value(json_attributes.clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
match obj_attributes {
|
||||
Value::Object(obj) => {
|
||||
for key in obj.keys() {
|
||||
let json_value = obj.get(key).expect(
|
||||
"failed to get value for attribute",
|
||||
);
|
||||
let value: Option<String> =
|
||||
serde_json::from_value(json_value.clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
attributes.push(AttributeToken {
|
||||
name: key.clone().into(),
|
||||
raw_name: None,
|
||||
value: value.map(|v| v.into()),
|
||||
raw_value: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut self_closing = false;
|
||||
|
||||
if let Some(json_self_closing) = token_parts.get(3).clone() {
|
||||
let value: bool =
|
||||
serde_json::from_value(json_self_closing.clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
self_closing = value;
|
||||
}
|
||||
|
||||
attributes.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||
|
||||
vec![Token::StartTag {
|
||||
tag_name: tag_name.into(),
|
||||
raw_tag_name: None,
|
||||
self_closing,
|
||||
attributes,
|
||||
}]
|
||||
}
|
||||
"EndTag" => {
|
||||
let tag_name: String =
|
||||
serde_json::from_value(token_parts[1].clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
vec![Token::EndTag {
|
||||
tag_name: tag_name.into(),
|
||||
raw_tag_name: None,
|
||||
self_closing: false,
|
||||
attributes: vec![],
|
||||
}]
|
||||
}
|
||||
"Character" => {
|
||||
let mut data: String =
|
||||
serde_json::from_value(token_parts[1].clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
if need_double_escaped {
|
||||
data = match unescape(&data) {
|
||||
Some(v) => v,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut tokens = vec![];
|
||||
|
||||
for c in data.chars() {
|
||||
tokens.push(Token::Character {
|
||||
value: c,
|
||||
raw: None,
|
||||
})
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
"Comment" => {
|
||||
let mut data: String =
|
||||
serde_json::from_value(token_parts[1].clone())
|
||||
.expect("failed to deserialize");
|
||||
|
||||
if need_double_escaped {
|
||||
data = match unescape(&data) {
|
||||
Some(v) => v,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
vec![Token::Comment { data: data.into() }]
|
||||
}
|
||||
_ => {
|
||||
unreachable!("unknown token {}", token_parts[0])
|
||||
}
|
||||
};
|
||||
|
||||
expected_tokens.extend(tokens);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let actual =
|
||||
serde_json::to_string(&actual_tokens).expect("failed to serialize actual tokens");
|
||||
let expected = serde_json::to_string(&expected_tokens)
|
||||
.expect("failed to serialize expected tokens");
|
||||
|
||||
if let Some(json_errors) = test.get("errors") {
|
||||
let expected_errors = json_errors.as_array().expect("failed to deserialize error");
|
||||
let actual_errors = lexer.take_errors();
|
||||
|
||||
eprintln!("==== ==== Errors ==== ====\n{:?}\n", actual_errors);
|
||||
|
||||
assert_eq!(actual_errors.len(), expected_errors.len());
|
||||
|
||||
for expected_error in expected_errors.iter() {
|
||||
let obj_expected_code =
|
||||
expected_error.as_object().expect("failed to get error");
|
||||
let expected_code = match obj_expected_code.get("code") {
|
||||
Some(expected_code) => match expected_code.as_str() {
|
||||
Some("eof-in-doctype") => ErrorKind::EofInDoctype,
|
||||
Some("eof-in-comment") => ErrorKind::EofInComment,
|
||||
Some("eof-in-cdata") => ErrorKind::EofInCdata,
|
||||
Some("eof-in-tag") => ErrorKind::EofInTag,
|
||||
Some("eof-before-tag-name") => ErrorKind::EofBeforeTagName,
|
||||
Some("eof-in-script-html-comment-like-text") => {
|
||||
ErrorKind::EofInScriptHtmlCommentLikeText
|
||||
}
|
||||
Some("unknown-named-character-reference") => {
|
||||
ErrorKind::UnknownNamedCharacterReference
|
||||
}
|
||||
Some("incorrectly-opened-comment") => {
|
||||
ErrorKind::IncorrectlyOpenedComment
|
||||
}
|
||||
Some("abrupt-closing-of-empty-comment") => {
|
||||
ErrorKind::AbruptClosingOfEmptyComment
|
||||
}
|
||||
Some("abrupt-doctype-public-identifier") => {
|
||||
ErrorKind::AbruptDoctypePublicIdentifier
|
||||
}
|
||||
Some("abrupt-doctype-system-identifier") => {
|
||||
ErrorKind::AbruptDoctypeSystemIdentifier
|
||||
}
|
||||
Some("absence-of-digits-in-numeric-character-reference") => {
|
||||
ErrorKind::AbsenceOfDigitsInNumericCharacterReference
|
||||
}
|
||||
Some("surrogate-character-reference") => {
|
||||
ErrorKind::SurrogateCharacterReference
|
||||
}
|
||||
Some("nested-comment") => ErrorKind::NestedComment,
|
||||
Some("end-tag-with-trailing-solidus") => {
|
||||
ErrorKind::EndTagWithTrailingSolidus
|
||||
}
|
||||
Some("null-character-reference") => ErrorKind::NullCharacterReference,
|
||||
Some("cdata-in-html-content") => ErrorKind::CdataInHtmlContent,
|
||||
Some("character-reference-outside-unicode-range") => {
|
||||
ErrorKind::CharacterReferenceOutsideUnicodeRange
|
||||
}
|
||||
Some("control-character-in-input-stream") => {
|
||||
ErrorKind::ControlCharacterInInputStream
|
||||
}
|
||||
Some("control-character-reference") => {
|
||||
ErrorKind::ControlCharacterReference
|
||||
}
|
||||
Some("noncharacter-in-input-stream") => {
|
||||
ErrorKind::NoncharacterInInputStream
|
||||
}
|
||||
Some("noncharacter-character-reference") => {
|
||||
ErrorKind::NoncharacterCharacterReference
|
||||
}
|
||||
Some("unexpected-equals-sign-before-attribute-name") => {
|
||||
ErrorKind::UnexpectedEqualsSignBeforeAttributeName
|
||||
}
|
||||
Some("unexpected-question-mark-instead-of-tag-name") => {
|
||||
ErrorKind::UnexpectedQuestionMarkInsteadOfTagName
|
||||
}
|
||||
Some("unexpected-character-after-doctype-system-identifier") => {
|
||||
ErrorKind::UnexpectedCharacterAfterDoctypeSystemIdentifier
|
||||
}
|
||||
Some("unexpected-null-character") => ErrorKind::UnexpectedNullCharacter,
|
||||
Some("unexpected-solidus-in-tag") => ErrorKind::UnexpectedSolidusInTag,
|
||||
Some("unexpected-character-in-attribute-name") => {
|
||||
ErrorKind::UnexpectedCharacterInAttributeName
|
||||
}
|
||||
Some("unexpected-character-in-unquoted-attribute-value") => {
|
||||
ErrorKind::UnexpectedCharacterInUnquotedAttributeValue
|
||||
}
|
||||
Some("duplicate-attribute") => ErrorKind::DuplicateAttribute,
|
||||
Some("end-tag-with-attributes") => ErrorKind::EndTagWithAttributes,
|
||||
Some("missing-whitespace-before-doctype-name") => {
|
||||
ErrorKind::MissingWhitespaceBeforeDoctypeName
|
||||
}
|
||||
Some("missing-attribute-value") => ErrorKind::MissingAttributeValue,
|
||||
Some("missing-doctype-public-identifier") => {
|
||||
ErrorKind::MissingDoctypePublicIdentifier
|
||||
}
|
||||
Some("missing-end-tag-name") => ErrorKind::MissingEndTagName,
|
||||
Some("missing-doctype-name") => ErrorKind::MissingDoctypeName,
|
||||
Some("missing-doctype-system-identifier") => {
|
||||
ErrorKind::MissingDoctypeSystemIdentifier
|
||||
}
|
||||
Some("missing-whitespace-after-doctype-system-keyword") => {
|
||||
ErrorKind::MissingWhitespaceAfterDoctypeSystemKeyword
|
||||
}
|
||||
Some("missing-whitespace-after-doctype-public-keyword") => {
|
||||
ErrorKind::MissingWhitespaceAfterDoctypePublicKeyword
|
||||
}
|
||||
Some("missing-quote-before-doctype-public-identifier") => {
|
||||
ErrorKind::MissingQuoteBeforeDoctypePublicIdentifier
|
||||
}
|
||||
Some("missing-quote-before-doctype-system-identifier") => {
|
||||
ErrorKind::MissingQuoteBeforeDoctypeSystemIdentifier
|
||||
}
|
||||
Some("incorrectly-closed-comment") => {
|
||||
ErrorKind::IncorrectlyClosedComment
|
||||
}
|
||||
Some("invalid-character-sequence-after-doctype-name") => {
|
||||
ErrorKind::InvalidCharacterSequenceAfterDoctypeName
|
||||
}
|
||||
Some(
|
||||
"missing-whitespace-between-doctype-public-and-system-identifiers",
|
||||
) => {
|
||||
ErrorKind::MissingWhitespaceBetweenDoctypePublicAndSystemIdentifiers
|
||||
}
|
||||
Some("missing-whitespace-between-attributes") => {
|
||||
ErrorKind::MissingWhitespaceBetweenAttributes
|
||||
}
|
||||
Some("missing-semicolon-after-character-reference") => {
|
||||
ErrorKind::MissingSemicolonAfterCharacterReference
|
||||
}
|
||||
Some("invalid-first-character-of-tag-name") => {
|
||||
ErrorKind::InvalidFirstCharacterOfTagName
|
||||
}
|
||||
_ => {
|
||||
unreachable!("unknown error {:?}", expected_code);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO validate error positions
|
||||
assert_eq!(
|
||||
!actual_errors
|
||||
.iter()
|
||||
.filter(|&error| *error.kind() == expected_code)
|
||||
.collect::<Vec<_>>()
|
||||
.is_empty(),
|
||||
true
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let errors = lexer.take_errors();
|
||||
|
||||
assert_eq!(errors.len(), 0);
|
||||
}
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,3 +3,8 @@
|
||||
document.write("<");
|
||||
//]]>
|
||||
</script>
|
||||
<script>
|
||||
let foo = "<!--<script>-->";
|
||||
|
||||
console.log(foo);
|
||||
</script>
|
@ -2,7 +2,7 @@
|
||||
"type": "Document",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 81,
|
||||
"end": 155,
|
||||
"ctxt": 0
|
||||
},
|
||||
"mode": "no-quirks",
|
||||
@ -11,7 +11,7 @@
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 81,
|
||||
"end": 155,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "html",
|
||||
@ -22,7 +22,7 @@
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 81,
|
||||
"end": 146,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "head",
|
||||
@ -73,6 +73,29 @@
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "\n"
|
||||
},
|
||||
{
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 81,
|
||||
"end": 146,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "script",
|
||||
"namespace": "http://www.w3.org/1999/xhtml",
|
||||
"attributes": [],
|
||||
"children": [
|
||||
{
|
||||
"type": "Text",
|
||||
"span": {
|
||||
"start": 89,
|
||||
"end": 146,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "\n let foo = \"<!--<script>-->\";\n\n console.log(foo);\n"
|
||||
}
|
||||
],
|
||||
"content": null
|
||||
}
|
||||
],
|
||||
"content": null
|
||||
@ -81,7 +104,7 @@
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 81,
|
||||
"end": 155,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "body",
|
||||
|
@ -1,30 +1,73 @@
|
||||
|
||||
x Document
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | `-> </script>
|
||||
`----
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | | </script>
|
||||
6 | | <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | | console.log(foo);
|
||||
10 | `-> </script>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | `-> </script>
|
||||
`----
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | | </script>
|
||||
6 | | <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | | console.log(foo);
|
||||
10 | `-> </script>
|
||||
`----
|
||||
|
||||
x Element
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | `-> </script>
|
||||
`----
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | | </script>
|
||||
6 | | <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | | console.log(foo);
|
||||
10 | `-> </script>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | | </script>
|
||||
6 | | <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
||||
x Element
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
1 | ,-> <script type="text/javascript">
|
||||
2 | | //<![CDATA[
|
||||
3 | | document.write("<");
|
||||
4 | | //]]>
|
||||
5 | | </script>
|
||||
6 | | <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:1:1]
|
||||
@ -72,10 +115,48 @@
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:5:1]
|
||||
5 | </script>
|
||||
: ^
|
||||
6 | <script>
|
||||
`----
|
||||
|
||||
x Text
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:5:1]
|
||||
5 | </script>
|
||||
: ^
|
||||
6 | <script>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:6:1]
|
||||
6 | ,-> <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
||||
x Element
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:6:1]
|
||||
6 | ,-> <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:6:1]
|
||||
6 | ,-> <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
||||
x Text
|
||||
,-[$DIR/tests/fixture/element/script-cdata/input.html:6:1]
|
||||
6 | ,-> <script>
|
||||
7 | | let foo = "<!--<script>-->";
|
||||
8 | |
|
||||
9 | `-> console.log(foo);
|
||||
10 | </script>
|
||||
`----
|
||||
|
@ -58,7 +58,6 @@
|
||||
<div>$</div>
|
||||
|
||||
<div>I'm ∉ I tell you</div>
|
||||
<div>I'm ¬it; I tell you</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,7 +2,7 @@
|
||||
"type": "Document",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 1570,
|
||||
"end": 1536,
|
||||
"ctxt": 0
|
||||
},
|
||||
"mode": "no-quirks",
|
||||
@ -22,7 +22,7 @@
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 16,
|
||||
"end": 1570,
|
||||
"end": 1536,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "html",
|
||||
@ -46,7 +46,7 @@
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 23,
|
||||
"end": 1570,
|
||||
"end": 1536,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "body",
|
||||
@ -1471,39 +1471,7 @@
|
||||
"type": "Text",
|
||||
"span": {
|
||||
"start": 1517,
|
||||
"end": 1518,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "\n"
|
||||
},
|
||||
{
|
||||
"type": "Element",
|
||||
"span": {
|
||||
"start": 1518,
|
||||
"end": 1545,
|
||||
"ctxt": 0
|
||||
},
|
||||
"tagName": "div",
|
||||
"namespace": "http://www.w3.org/1999/xhtml",
|
||||
"attributes": [],
|
||||
"children": [
|
||||
{
|
||||
"type": "Text",
|
||||
"span": {
|
||||
"start": 1523,
|
||||
"end": 1545,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "I'm ¬it; I tell you"
|
||||
}
|
||||
],
|
||||
"content": null
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"span": {
|
||||
"start": 1551,
|
||||
"end": 1570,
|
||||
"end": 1536,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "\n\n\n\n\n"
|
||||
|
@ -61,11 +61,10 @@
|
||||
58 | | <div>$</div>
|
||||
59 | |
|
||||
60 | | <div>I'm ∉ I tell you</div>
|
||||
61 | | <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Child
|
||||
@ -141,11 +140,10 @@
|
||||
58 | | <div>$</div>
|
||||
59 | |
|
||||
60 | | <div>I'm ∉ I tell you</div>
|
||||
61 | | <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Element
|
||||
@ -209,11 +207,10 @@
|
||||
58 | | <div>$</div>
|
||||
59 | |
|
||||
60 | | <div>I'm ∉ I tell you</div>
|
||||
61 | | <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Child
|
||||
@ -290,11 +287,10 @@
|
||||
58 | | <div>$</div>
|
||||
59 | |
|
||||
60 | | <div>I'm ∉ I tell you</div>
|
||||
61 | | <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Element
|
||||
@ -357,11 +353,10 @@
|
||||
58 | | <div>$</div>
|
||||
59 | |
|
||||
60 | | <div>I'm ∉ I tell you</div>
|
||||
61 | | <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Child
|
||||
@ -2010,56 +2005,18 @@
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:60:1]
|
||||
60 | <div>I'm ∉ I tell you</div>
|
||||
: ^
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
60 | ,-> <div>I'm ∉ I tell you</div>
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
||||
x Text
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:60:1]
|
||||
60 | <div>I'm ∉ I tell you</div>
|
||||
: ^
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x Element
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x Text
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | <div>I'm ¬it; I tell you</div>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x Child
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | ,-> <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
`----
|
||||
|
||||
x Text
|
||||
,-[$DIR/tests/fixture/text/entity/input.html:61:1]
|
||||
61 | ,-> <div>I'm ¬it; I tell you</div>
|
||||
62 | |
|
||||
63 | | </body>
|
||||
64 | | </html>
|
||||
65 | `->
|
||||
60 | ,-> <div>I'm ∉ I tell you</div>
|
||||
61 | |
|
||||
62 | | </body>
|
||||
63 | | </html>
|
||||
64 | `->
|
||||
`----
|
||||
|
1
crates/swc_html_parser/tests/html5lib-tests
Submodule
1
crates/swc_html_parser/tests/html5lib-tests
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 538a6cd2a014eff08a35964a1995643b63fc02b9
|
@ -14,7 +14,7 @@
|
||||
"end": 10,
|
||||
"ctxt": 0
|
||||
},
|
||||
"name": "<22>",
|
||||
"name": null,
|
||||
"publicId": null,
|
||||
"systemId": null
|
||||
},
|
||||
|
@ -2,5 +2,5 @@
|
||||
x Invalid character sequence after doctype name
|
||||
,-[$DIR/tests/recovery/document_type/wrong-name/input.html:1:1]
|
||||
1 | <!DOCTYPE html broken>
|
||||
: ^^^^^^^^^^^^^^^^
|
||||
: ^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
@ -17,6 +17,12 @@
|
||||
: ^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x End tag with trailing solidus
|
||||
,-[$DIR/tests/recovery/element/broken-end-tags/input.html:28:1]
|
||||
28 | <div>test</div foo="bar" />
|
||||
: ^^^^^^^^^^^^^^^^^^
|
||||
`----
|
||||
|
||||
x End tag with trailing solidus
|
||||
,-[$DIR/tests/recovery/element/broken-end-tags/input.html:29:1]
|
||||
29 | <div>test</div/>
|
||||
|
Loading…
Reference in New Issue
Block a user