From 3d203d558019824044a03f4b651fdd754470adfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 4 Feb 2024 02:53:41 +0100 Subject: [PATCH 1/4] Correctly render void elements A [void element] is an HTML element that **cannot** have any children. In XML you'd use such an element like `
`. HTML5 is very lenient, in that you don't have to write the trailing slash. A correctly implemented HTML parser *knows* which elements are void elements, and it closes the element automagically for you. The parser even "corrects" an input like `a

b` to `a

b` for you, which might not desirable. If you want to render SVG, XHTML, or other more strict XML applications, you have to close void elements correctly: `
`, NOT `
` or `

`. This PR adds the missing slash to generated void elements, so `markup` can be used to render e.g. SVG files. [void element]: https://developer.mozilla.org/en-US/docs/Glossary/Void_element --- markup-proc-macro/src/ast.rs | 3 +-- markup-proc-macro/src/generate.rs | 11 +++++------ markup-proc-macro/src/parse.rs | 13 +++++-------- markup/tests/tests.rs | 10 +++++----- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/markup-proc-macro/src/ast.rs b/markup-proc-macro/src/ast.rs index a88a666..93bd1c4 100644 --- a/markup-proc-macro/src/ast.rs +++ b/markup-proc-macro/src/ast.rs @@ -30,8 +30,7 @@ pub struct Element { pub id: Option, pub classes: Vec, pub attributes: Vec, - pub children: Vec, - pub close: bool, + pub children: Option>, } #[derive(Debug)] diff --git a/markup-proc-macro/src/generate.rs b/markup-proc-macro/src/generate.rs index de8a367..1609043 100644 --- a/markup-proc-macro/src/generate.rs +++ b/markup-proc-macro/src/generate.rs @@ -125,7 +125,6 @@ impl Generate for Element { classes, attributes, children, - close, } = self; stream.raw("<"); stream.expr(name, writer); @@ -203,14 +202,14 @@ impl Generate for Element { } } - stream.raw(">"); - - children.generate(stream, writer); - - if *close { + if let Some(children) = children { + stream.raw(">"); + children.generate(stream, writer); stream.raw(""); + } else { + stream.raw(" />"); } } } diff --git a/markup-proc-macro/src/parse.rs b/markup-proc-macro/src/parse.rs index a3d574a..e836aec 100644 --- a/markup-proc-macro/src/parse.rs +++ b/markup-proc-macro/src/parse.rs @@ -165,21 +165,19 @@ impl Parse for Element { } }; - let (children, close) = { + let children = { let lookahead = input.lookahead1(); if lookahead.peek(syn::token::Semi) { let _: syn::Token![;] = input.parse()?; - (Vec::new(), false) + None } else if lookahead.peek(syn::token::Brace) { let children; syn::braced!(children in input); - (children.parse::>()?.0, true) + let Many(children) = children.parse()?; + Some(children) } else if lookahead.peek(syn::LitStr) { let string = input.parse::()?; - ( - vec![syn::parse_quote_spanned!(string.span() => #string)], - true, - ) + Some(vec![syn::parse_quote_spanned!(string.span() => #string)]) } else { return Err(lookahead.error()); } @@ -191,7 +189,6 @@ impl Parse for Element { classes, attributes, children, - close, }) } } diff --git a/markup/tests/tests.rs b/markup/tests/tests.rs index 0ca768d..2c1c33f 100644 --- a/markup/tests/tests.rs +++ b/markup/tests/tests.rs @@ -55,7 +55,7 @@ t! { br; } }, - A {} => "

", + A {} => "

", } t! { @@ -100,7 +100,7 @@ t! { } }, A {} => r#"7"#, - B {} => r#"barbazquuxquux"#, + B {} => r#"barbazquuxquux"#, C {} => r#""#, } @@ -132,7 +132,7 @@ t! { br[k = 6]; } }, - A {} => r#"

"#, + A {} => r#"

"#, } t! { @@ -338,8 +338,8 @@ t! { ${name} {} } }, - A {} => r#""#, - B { name: "foo-bar" } => r#""#, + A {} => r#""#, + B { name: "foo-bar" } => r#""#, } mod inner { From b736a2a3560c4720f0fd3043248eb6d72fd1444b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 4 Feb 2024 04:37:49 +0100 Subject: [PATCH 2/4] Update ui tests for nightly --- ui-tests/fail-1.78/not-render.rs | 16 +++++++++++++ ui-tests/fail-1.78/not-render.stderr | 35 ++++++++++++++++++++++++++++ ui-tests/src/lib.rs | 4 +++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 ui-tests/fail-1.78/not-render.rs create mode 100644 ui-tests/fail-1.78/not-render.stderr diff --git a/ui-tests/fail-1.78/not-render.rs b/ui-tests/fail-1.78/not-render.rs new file mode 100644 index 0000000..d13c0e1 --- /dev/null +++ b/ui-tests/fail-1.78/not-render.rs @@ -0,0 +1,16 @@ +struct NotRender; + +markup::define! { + Layout(name: NotRender) { + @markup::doctype() + html["attr-user"=name] { + body { + strong { "Hello " @name "!" } + } + } + } +} + +fn main() { + +} diff --git a/ui-tests/fail-1.78/not-render.stderr b/ui-tests/fail-1.78/not-render.stderr new file mode 100644 index 0000000..d92da9f --- /dev/null +++ b/ui-tests/fail-1.78/not-render.stderr @@ -0,0 +1,35 @@ +error[E0277]: the trait bound `NotRender: RenderAttributeValue` is not satisfied + --> fail-1.78/not-render.rs:6:26 + | +6 | html["attr-user"=name] { + | ^^^^ the trait `RenderAttributeValue` is not implemented for `NotRender`, which is required by `&NotRender: RenderAttributeValue` + | + = help: the following other types implement trait `RenderAttributeValue`: + bool + char + isize + i8 + i16 + i32 + i64 + i128 + and $N others + = note: required for `&NotRender` to implement `RenderAttributeValue` + +error[E0277]: the trait bound `NotRender: Render` is not satisfied + --> fail-1.78/not-render.rs:8:36 + | +8 | strong { "Hello " @name "!" } + | ^^^^ the trait `Render` is not implemented for `NotRender`, which is required by `&NotRender: Render` + | + = help: the following other types implement trait `Render`: + bool + char + isize + i8 + i16 + i32 + i64 + i128 + and $N others + = note: required for `&NotRender` to implement `Render` diff --git a/ui-tests/src/lib.rs b/ui-tests/src/lib.rs index b2b5415..97fa05a 100644 --- a/ui-tests/src/lib.rs +++ b/ui-tests/src/lib.rs @@ -6,7 +6,9 @@ fn ui() { let fail = trybuild::TestCases::new(); if version.minor <= 65 { fail.compile_fail("fail-1.65/*.rs"); - } else { + } else if version.minor <= 77 { fail.compile_fail("fail-1.72/*.rs"); + } else { + fail.compile_fail("fail-1.78/*.rs"); } } From 432a36e837848c63d68b77ca528c2aecba33c56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 4 Feb 2024 04:45:03 +0100 Subject: [PATCH 3/4] Fix clippy warnings --- markup/src/lib.rs | 6 ++++-- markup/tests/tests.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/markup/src/lib.rs b/markup/src/lib.rs index d87f171..2bdaf6f 100644 --- a/markup/src/lib.rs +++ b/markup/src/lib.rs @@ -121,7 +121,7 @@ impl Render for Raw { impl RenderAttributeValue for Raw {} #[inline] -pub fn raw(value: impl std::fmt::Display) -> impl Render + RenderAttributeValue { +pub fn raw(value: impl std::fmt::Display) -> impl RenderAttributeValue { Raw(value) } @@ -222,8 +222,10 @@ tuple_impl! { A B C D E F G H } tuple_impl! { A B C D E F G H I } tuple_impl! { A B C D E F G H I J } +pub type RenderFn<'a> = dyn Fn(&mut dyn std::fmt::Write) -> std::fmt::Result + 'a; + pub struct DynRender<'a> { - f: Box std::fmt::Result + 'a>, + f: Box>, } pub fn new<'a, F>(f: F) -> DynRender<'a> diff --git a/markup/tests/tests.rs b/markup/tests/tests.rs index 2c1c33f..c58e95d 100644 --- a/markup/tests/tests.rs +++ b/markup/tests/tests.rs @@ -358,7 +358,7 @@ t! { t15, { A() { - @for | Ok(x) | Err(x) in vec![Ok(1), Err(2), Ok(3)] { + @for | Ok(x) | Err(x) in [Ok(1), Err(2), Ok(3)] { @x } } From d5ea4a0355f861a5383ab02ae009abf5c112641d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 12 Feb 2024 19:23:49 +0100 Subject: [PATCH 4/4] Update CI Make sure to lint and check everything in every configuration. Don't fail if an updated (transitive) dependency requires a newer msrv than the current msrv of markup. --- .github/workflows/build.yml | 50 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47e2e0c..fa65a04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,23 +3,51 @@ name: Build on: [push, pull_request] jobs: - build: - name: ${{ matrix.os }} - ${{ matrix.rust }} + clippy: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + rust: [stable, nightly] + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + components: clippy + - run: cargo clippy --workspace --no-default-features + - run: cargo clippy --workspace --all-features + + fmt: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + rust: [nightly] + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + components: rustfmt + - run: cargo fmt --all -- --check + + test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - rust: [stable, nightly, 1.65.0] + rust: [stable, nightly, '1.65.0'] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - components: clippy, rustfmt - - run: cargo build - - run: cargo test - - run: cargo test --features itoa - working-directory: markup - - run: cargo fmt -- --check - if: ${{ matrix.os == 'ubuntu-latest' }} - - run: cargo clippy --all-targets + - uses: taiki-e/install-action@v2 + with: + tool: cargo-hack + - run: cargo generate-lockfile -Zminimal-versions + env: + RUSTC_BOOTSTRAP: 1 + if: ${{ matrix.rust == '1.65.0' }} + - run: cargo hack test --workspace --feature-powerset --optional-deps