diff --git a/src/err.rs b/src/err.rs index 0976a81..ca300d9 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,9 +1,10 @@ // Implement debug to allow .unwrap(). -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum ErrorType { ClosingTagMismatch { expected: String, got: String }, NotFound(&'static str), UnexpectedEnd, + UnexpectedClosingTag, } impl ErrorType { @@ -18,6 +19,9 @@ impl ErrorType { ErrorType::UnexpectedEnd => { format!("Unexpected end of source code.") } + ErrorType::UnexpectedClosingTag => { + format!("Unexpected closing tag.") + } } } } diff --git a/src/lib.rs b/src/lib.rs index 9241cd2..74b9ad7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,11 @@ mod unit; pub fn in_place(code: &mut [u8], cfg: &Cfg) -> Result { let mut proc = Processor::new(code); process_content(&mut proc, cfg, Namespace::Html, None) + .and_then(|_| if !proc.at_end() { + Err(ErrorType::UnexpectedClosingTag) + } else { + Ok(()) + }) .map_err(|error_type| Error { error_type, position: proc.read_len(), diff --git a/src/proc/mod.rs b/src/proc/mod.rs index 8bba8c5..36b0321 100644 --- a/src/proc/mod.rs +++ b/src/proc/mod.rs @@ -362,8 +362,7 @@ impl<'d> Processor<'d> { #[cfg(not(feature = "js-esbuild"))] #[inline(always)] pub fn finish(self) -> Result { - // NOTE: Do not assert that we are at the end, as invalid HTML can end prematurely e.g. - // `hellooutside`. + debug_assert!(self.at_end()); Ok(self.write_next) } @@ -371,8 +370,7 @@ impl<'d> Processor<'d> { #[cfg(feature = "js-esbuild")] #[inline(always)] pub fn finish(self) -> Result { - // NOTE: Do not assert that we are at the end, as invalid HTML can end prematurely e.g. - // `hellooutside`. + debug_assert!(self.at_end()); self.script_wg.wait(); let mut results = Arc::try_unwrap(self.script_results) .unwrap_or_else(|_| panic!("failed to acquire script results")) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 708e88a..c9efd95 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,8 @@ +#[cfg(test)] +use { + crate::ErrorType +}; + #[cfg(test)] fn _eval(src: &'static [u8], expected: &'static [u8], cfg: &super::Cfg) -> () { let mut code = src.to_vec(); @@ -13,6 +18,12 @@ fn _eval(src: &'static [u8], expected: &'static [u8], cfg: &super::Cfg) -> () { }; } +#[cfg(test)] +fn _eval_error(src: &'static [u8], expected: ErrorType, cfg: &super::Cfg) -> () { + let mut code = src.to_vec(); + assert_eq!(super::in_place(&mut code, cfg).unwrap_err().error_type, expected); +} + #[cfg(test)] fn eval(src: &'static [u8], expected: &'static [u8]) -> () { _eval(src, expected, &super::Cfg { @@ -20,6 +31,13 @@ fn eval(src: &'static [u8], expected: &'static [u8]) -> () { }); } +#[cfg(test)] +fn eval_error(src: &'static [u8], expected: ErrorType) -> () { + _eval_error(src, expected, &super::Cfg { + minify_js: false, + }); +} + #[cfg(test)] #[cfg(feature = "js-esbuild")] fn eval_with_js_min(src: &'static [u8], expected: &'static [u8]) -> () { @@ -79,10 +97,21 @@ fn test_parsing_with_omitted_tags() { eval(b"1
", b"1
"); eval(b"
", b"
"); eval(b"", b""); + eval(b"", b""); // Tag names should be case insensitive. eval(b"", b""); } +#[test] +fn test_unmatched_closing_tag() { + eval_error(b"Hello

Goodbye", ErrorType::UnexpectedClosingTag); + eval_error(b"Hello

Goodbye", ErrorType::UnexpectedClosingTag); + eval_error(b"
Hello

Goodbye", ErrorType::ClosingTagMismatch { expected: "div".to_string(), got: "p".to_string() }); + eval_error(b"
  • a

    ", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() }); + eval_error(b"
    • a

      ", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() }); + eval_error(b"
      • a

        ", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() }); +} + #[test] fn test_removal_of_optional_tags() { eval(b"
        • 1
        • 2
        • 3
        ", b"
        • 1
        • 2
        • 3
        "); diff --git a/src/unit/content.rs b/src/unit/content.rs index 4516f94..536ab68 100644 --- a/src/unit/content.rs +++ b/src/unit/content.rs @@ -47,7 +47,7 @@ impl ContentType { } pub struct ProcessedContent { - pub(crate) closing_tag_omitted: bool, + pub closing_tag_omitted: bool, } pub fn process_content(proc: &mut Processor, cfg: &Cfg, ns: Namespace, parent: Option) -> ProcessingResult {