use std::iter;
use chumsky::Parser;
use crate::builtins::contextualizers::standard::TmplLitState;
use super::{
ContextualizerState, CssState, DelimiterKind, HtmlState, JsState, JsSubState, ScriptState,
TagState, TagSubState, UnknownScriptState,
};
#[test]
#[allow(clippy::too_many_lines)]
fn test_contextualizer() {
run_total_test(
&ContextualizerState::Html(HtmlState::Regular),
&[
(
r"<!DocTYpE
hTmL >",
ContextualizerState::Html(HtmlState::Regular),
),
("Hello", ContextualizerState::Html(HtmlState::Regular)),
("<p>", ContextualizerState::Html(HtmlState::Regular)),
(
"<p ",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
(
"hidden checked ",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
("attr=bla>", ContextualizerState::Html(HtmlState::Regular)),
(
"<p name",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::AttributeName,
})),
),
(" = ''>", ContextualizerState::Html(HtmlState::Regular)),
("<!--", ContextualizerState::Html(HtmlState::Comment)),
(
"arstrast ratrsat -->",
ContextualizerState::Html(HtmlState::Regular),
),
("</p>", ContextualizerState::Html(HtmlState::Regular)),
("<p/>", ContextualizerState::Html(HtmlState::Regular)),
(
"<script ",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
(
"attr = 'blub'",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
("/>", ContextualizerState::Html(HtmlState::Regular)),
(
"<p onclick=\"",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::AttributeValue(
Box::new(ContextualizerState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
DelimiterKind::Double,
),
})),
),
(
"let t = '",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::AttributeValue(
Box::new(ContextualizerState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::StringSingle,
})),
DelimiterKind::Double,
),
})),
),
(
"'",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::AttributeValue(
Box::new(ContextualizerState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
DelimiterKind::Double,
),
})),
),
(
"\" ",
ContextualizerState::Html(HtmlState::Tag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
(">", ContextualizerState::Html(HtmlState::Regular)),
(
"<script ",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
(
"type=blub ",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: false,
sub_state: TagSubState::Attributes,
})),
),
(
">",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: false,
sub_state: TagSubState::Inner(ScriptState::UnknownScriptState(
UnknownScriptState,
)),
})),
),
(
"arstrsat ratrsa arst arst",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: false,
sub_state: TagSubState::Inner(ScriptState::UnknownScriptState(
UnknownScriptState,
)),
})),
),
("</script>", ContextualizerState::Html(HtmlState::Regular)),
(
"<script> let bla = /bla",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regex { in_charset: false },
})),
})),
),
(
"/;",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
(
"//",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::LineComment,
})),
})),
),
(
"\n/*",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::MultiLineComment,
})),
})),
),
(
"*/",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
(
"\n<!--",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::LineComment,
})),
})),
),
(
"\n-->",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::LineComment,
})),
})),
),
(
"\n#!",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::LineComment,
})),
})),
),
(
"\n 1 /",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
(
"return /arst",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regex { in_charset: false },
})),
})),
),
(
"[",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regex { in_charset: true },
})),
})),
),
(
"]",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regex { in_charset: false },
})),
})),
),
(
"/",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
(
"`",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::TmplLit(TmplLitState::Regular),
})),
})),
),
(
"hello, \\</script\\> \\` bla",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::TmplLit(TmplLitState::Regular),
})),
})),
),
(
"${",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![1],
sub_state: JsSubState::Regular,
})),
})),
),
(
"let t = /Hello \\/ ",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![1],
sub_state: JsSubState::Regex { in_charset: false },
})),
})),
),
(
"/",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![1],
sub_state: JsSubState::Regular,
})),
})),
),
(
"; { \"",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![2],
sub_state: JsSubState::StringDouble,
})),
})),
),
(
"{}{}}}}{{{\"",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![2],
sub_state: JsSubState::Regular,
})),
})),
),
(
" { 'bla' { \"hello\" }{",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![4],
sub_state: JsSubState::Regular,
})),
})),
),
(
"{{{}",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![6],
sub_state: JsSubState::Regular,
})),
})),
),
(
"}}}}}}",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::TmplLit(TmplLitState::Regular),
})),
})),
),
(
"`",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
(
"`{{{ bla blub ${ \"}}}\" {{}}/Hello",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![1],
sub_state: JsSubState::Regex { in_charset: false },
})),
})),
),
(
"/}",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::TmplLit(TmplLitState::Regular),
})),
})),
),
(
"blafasel blub ${ {}{{{",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![4],
sub_state: JsSubState::Regular,
})),
})),
),
(
"`",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![4],
sub_state: JsSubState::TmplLit(TmplLitState::Regular),
})),
})),
),
(
"${",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![4, 1],
sub_state: JsSubState::Regular,
})),
})),
),
(
"}`}}}}`</script>",
ContextualizerState::Html(HtmlState::Regular),
),
(
"<script>",
ContextualizerState::Html(HtmlState::ScriptTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(ScriptState::Js(JsState {
tmpl_literal_brace_counts: vec![],
sub_state: JsSubState::Regular,
})),
})),
),
("</script>", ContextualizerState::Html(HtmlState::Regular)),
(
"<style ",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Attributes,
})),
),
(
">",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
(
"//",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::LineComment),
})),
),
(
"\n/*",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::MultiLineComment),
})),
),
(
"\n*/",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
(
"'\\'",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Single)),
})),
),
(
"'",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
(
"\"\\\"",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Double)),
})),
),
(
"\"",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
(
"url( \t\n",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Bare)),
})),
),
(
"http://localhost:8080",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Bare)),
})),
),
(
"?bla=",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Bare)),
})),
),
(
")",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
(
"url \t\n(",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Bare)),
})),
),
(
" http://localhost:8080\\3f bla=",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::String(DelimiterKind::Bare)),
})),
),
(
")",
ContextualizerState::Html(HtmlState::StyleTag(TagState {
type_attr_value_is_js_or_unspecified: true,
sub_state: TagSubState::Inner(CssState::Regular),
})),
),
("</style>", ContextualizerState::Html(HtmlState::Regular)),
],
);
}
type TestSegment = (&'static str, ContextualizerState);
fn run_total_test(initial_state: &ContextualizerState, segments: &[TestSegment]) {
test_state_string_roundtrip(initial_state);
let total: String = segments.iter().map(|(s, _state)| *s).collect();
let offsets: Vec<usize> =
iter::once(0)
.chain(segments.iter().map(|(s, _st)| s.len()).scan(
0,
|count: &mut usize, len: usize| {
*count += len;
Some(*count)
},
))
.collect();
for start in 0..=segments.len() {
for end in start..=segments.len() {
let substr = &total[offsets[start]..offsets[end]];
let start_state = if start == 0 {
initial_state
} else {
&segments[start - 1].1
};
let end_state = if end == 0 {
initial_state
} else {
&segments[end - 1].1
};
test_state_string_roundtrip(start_state);
let gotten_state = start_state
.parser()
.parse(substr)
.into_result()
.unwrap_or_else(|_| {
panic!("Parser failed; Starting in {start_state} parsing {substr:?}")
});
assert_eq!(&gotten_state, end_state, "Starting in {start_state} parsing {substr:?} yields {gotten_state} but expected {end_state}; {gotten_state:?} vs {end_state:?}");
test_state_string_roundtrip(&gotten_state);
}
}
}
fn test_state_string_roundtrip(state: &ContextualizerState) {
let text = state.to_string();
match ContextualizerState::try_from(text.as_str()) {
Ok(gotten_state) => {
assert_eq!(state, &gotten_state, "Did not receive expected state when roundtripping {text}; got {gotten_state}; {state:?} vs {gotten_state:?}");
}
Err(()) => {
panic!("State {state} does not roundtrip")
}
}
}