diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f50e52..266e95db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ [#492](https://github.com/lambda-fairy/maud/pull/492) - Remove Tide support and documentation references. [#501](https://github.com/lambda-fairy/maud/pull/501) +- Add check for correct closing method of html element. `html_unchecked!` skips this check. + [#505](https://github.com/lambda-fairy/maud/pull/505) ## [0.27.0] - 2025-02-02 diff --git a/maud/src/lib.rs b/maud/src/lib.rs index f885e6c0..b1fb7a50 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -14,7 +14,7 @@ extern crate alloc; use alloc::{borrow::Cow, boxed::Box, string::String}; use core::fmt::{self, Arguments, Display, Write}; -pub use maud_macros::html; +pub use maud_macros::{html, html_unchecked}; mod escape; diff --git a/maud/tests/unchecked.rs b/maud/tests/unchecked.rs new file mode 100644 index 00000000..3e78b918 --- /dev/null +++ b/maud/tests/unchecked.rs @@ -0,0 +1,21 @@ +use maud::html_unchecked; + +// These tests purposefully produce invalid html + +#[test] +fn unchecked_accepts_non_void_element_with_semicolon() { + let result = html_unchecked! { + div; + p; + }; + assert_eq!(result.into_string(), "
");
+}
+
+#[test]
+fn unchecked_accepts_void_element_with_braces() {
+ let result = html_unchecked! {
+ br {}
+ img {}
+ };
+ assert_eq!(result.into_string(), "");
+}
diff --git a/maud/tests/warnings/block-element-with-semicolon.rs b/maud/tests/warnings/block-element-with-semicolon.rs
new file mode 100644
index 00000000..ebf31e18
--- /dev/null
+++ b/maud/tests/warnings/block-element-with-semicolon.rs
@@ -0,0 +1,13 @@
+use maud::html;
+
+fn main() {
+ html! {
+ div;
+ p;
+ my-custom-element;
+ .thing;
+ @if true {
+ span;
+ }
+ };
+}
diff --git a/maud/tests/warnings/block-element-with-semicolon.stderr b/maud/tests/warnings/block-element-with-semicolon.stderr
new file mode 100644
index 00000000..52675d67
--- /dev/null
+++ b/maud/tests/warnings/block-element-with-semicolon.stderr
@@ -0,0 +1,39 @@
+error: `
` is not a void element, so it cannot be closed with `;`
+ --> tests/warnings/block-element-with-semicolon.rs:6:10
+ |
+6 | p;
+ | ^
+ |
+ = help: change this to `{}`
+
+error: `
` is a void element and cannot have a closing tag
+ --> $DIR/void-element-with-block.rs:5:12
+ |
+5 | br {}
+ | ^
+ |
+ = help: change this to `;`
+
+error: `` is a void element and cannot have a closing tag
+ --> $DIR/void-element-with-block.rs:6:29
+ |
+6 | img alt="some text" {}
+ | ^
+ |
+ = help: change this to `;`
+
+error: `` is a void element and cannot have a closing tag
+ --> $DIR/void-element-with-block.rs:7:30
+ |
+7 | meta charset="utf-8" {}
+ | ^
+ |
+ = help: change this to `;`
+
+error: `
` is a void element and cannot have a closing tag
+ --> $DIR/void-element-with-block.rs:9:20
+ |
+9 | a { hr {} }
+ | ^
+ |
+ = help: change this to `;`
diff --git a/maud_macros/src/check.rs b/maud_macros/src/check.rs
new file mode 100644
index 00000000..49ed1b0e
--- /dev/null
+++ b/maud_macros/src/check.rs
@@ -0,0 +1,93 @@
+use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
+use syn::spanned::Spanned;
+
+use crate::ast::{
+ Block, ControlFlow, ControlFlowKind, Element, ElementBody, IfExpr, IfOrBlock, Markup, Markups,
+};
+
+/// The void elements as defined by the HTML Living Standard.
+const VOID_ELEMENTS: &[&str] = &[
+ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track",
+ "wbr",
+];
+
+/// Checks whether each element is closed in a valid way. Non-void elements
+/// must be closed with `{ ... }`, and void elements with `;`.
+///
+/// Emits a diagnostic for each mismatch found.
+pub fn check_elements(markups: &Markups