From 698dfe26ed4160b9c4de5fb8af8400a041c3bc5f Mon Sep 17 00:00:00 2001
From: Ryan Balsdon
Date: Thu, 13 Jul 2023 14:01:47 -0400
Subject: [PATCH 1/3] Reworked proc macros to allow for tests and added some
tests
---
macros/Cargo.toml | 2 +
macros/src/grammar.lalrpop | 6 +-
macros/src/lib.rs | 83 +-
macros/src/span.rs | 8 +-
macros/src/tests/declare-anchor-element.rs | 805 ++++++++++++++++++++
macros/src/tests/generate-anchor-element.rs | 20 +
macros/src/tests/generate-button-event.rs | 6 +
tests/main.rs | 129 ++++
8 files changed, 1046 insertions(+), 13 deletions(-)
create mode 100644 macros/src/tests/declare-anchor-element.rs
create mode 100644 macros/src/tests/generate-anchor-element.rs
create mode 100644 macros/src/tests/generate-button-event.rs
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 62ec8a7..931febc 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -20,6 +20,8 @@ lalrpop-util = "0.19"
proc-macro2 = "1.0.54"
quote = "1.0.26"
console = "0.15.5"
+rust-format = { version="0.3.4", features=["pretty_please"]}
+pretty_assertions = "1.4.0"
[build-dependencies]
lalrpop = "0.19.9"
diff --git a/macros/src/grammar.lalrpop b/macros/src/grammar.lalrpop
index 172313e..198c83d 100644
--- a/macros/src/grammar.lalrpop
+++ b/macros/src/grammar.lalrpop
@@ -60,14 +60,14 @@ HtmlIdent: Ident = {
"-")*> => {
let mut init = init;
init.push(last);
- let (span, name) = init.into_iter().fold((None, String::new()), |(span, name), token| {
+ let (span, name): (Option, String) = init.into_iter().fold((None, String::new()), |(span, name), token| {
(
match span {
- None => Some(token.span().unstable()),
+ None => Some(token.span()),
Some(span) => {
#[cfg(can_join_spans)]
{
- span.join(token.span().unstable())
+ span.join(token.span())
}
#[cfg(not(can_join_spans))]
{
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 298758c..ac11056 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,9 +1,10 @@
#![recursion_limit = "128"]
#![cfg_attr(can_show_location_of_runtime_parse_error, feature(proc_macro_span))]
-extern crate proc_macro;
+#[allow(unused_imports)]
+use rust_format::Formatter;
-use proc_macro::TokenStream;
+extern crate proc_macro;
mod config;
mod declare;
@@ -21,10 +22,13 @@ mod span;
///
/// [axohtml]: https://docs.rs/axohtml/
#[proc_macro]
-pub fn html(input: TokenStream) -> TokenStream {
+pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ html_impl(input.into()).into()
+}
+fn html_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let stream = lexer::unroll_stream(input.into(), false);
let result = html::expand_html(&stream);
- TokenStream::from(match result {
+ proc_macro2::TokenStream::from(match result {
Err(err) => error::parse_error(&stream, &err),
Ok((node, ty)) => match node.into_token_stream(&ty) {
Err(err) => err,
@@ -56,10 +60,14 @@ pub fn dodrio(input: TokenStream) -> TokenStream {
/// This macro is used by `axohtml` internally to generate types and
/// implementations for HTML elements.
#[proc_macro]
-pub fn declare_elements(input: TokenStream) -> TokenStream {
+pub fn declare_elements(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ declare_elements_impl(input.into()).into()
+}
+
+fn declare_elements_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let stream = lexer::keywordise(lexer::unroll_stream(input.into(), true));
let result = declare::expand_declare(&stream);
- TokenStream::from(match result {
+ proc_macro2::TokenStream::from(match result {
Err(err) => error::parse_error(&stream, &err),
Ok(decls) => {
let mut out = proc_macro2::TokenStream::new();
@@ -70,3 +78,66 @@ pub fn declare_elements(input: TokenStream) -> TokenStream {
}
})
}
+
+#[test]
+fn test_declare_anchor_element() {
+ use quote::quote;
+
+ let input = quote!(
+ a {
+ download: String,
+ href: Uri,
+ hreflang: LanguageTag,
+ ping: SpacedList,
+ rel: SpacedList,
+ target: Target,
+ type: Mime,
+ } in [FlowContent, PhrasingContent, InteractiveContent] with FlowContent;
+ );
+
+ let output = declare_elements_impl(input);
+
+ pretty_assertions::assert_eq!(
+ rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ include_str!("tests/declare-anchor-element.rs")
+ )
+}
+
+#[test]
+fn test_html_anchor_element() {
+ use quote::quote;
+
+ let input = quote!(
+
+ "Visit W3Schools.com!"
+
+ );
+
+ let output = html_impl(input);
+ let output = format!("fn html() {}", output.to_string());
+
+ pretty_assertions::assert_eq!(
+ rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ include_str!("tests/generate-anchor-element.rs")
+ )
+}
+
+#[test]
+fn test_html_button_event() {
+ use quote::quote;
+
+ let input = quote!(
+
+ : String
+ );
+
+ let output = html_impl(input);
+ let output = format!("fn html() {}", output.to_string());
+
+ pretty_assertions::assert_eq!(
+ rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ include_str!("tests/generate-button-event.rs")
+ )
+}
\ No newline at end of file
diff --git a/macros/src/span.rs b/macros/src/span.rs
index 6f1cb10..3c1c003 100644
--- a/macros/src/span.rs
+++ b/macros/src/span.rs
@@ -1,7 +1,7 @@
-pub fn from_unstable(span: proc_macro::Span) -> proc_macro2::Span {
- let ident = proc_macro::Ident::new("_", span);
- let tt = proc_macro::TokenTree::Ident(ident);
- let tts = proc_macro::TokenStream::from(tt);
+pub fn from_unstable(span: proc_macro2::Span) -> proc_macro2::Span {
+ let ident = proc_macro2::Ident::new("_", span);
+ let tt = proc_macro2::TokenTree::Ident(ident);
+ let tts = proc_macro2::TokenStream::from(tt);
let tts2 = proc_macro2::TokenStream::from(tts);
tts2.into_iter().next().unwrap().span()
}
diff --git a/macros/src/tests/declare-anchor-element.rs b/macros/src/tests/declare-anchor-element.rs
new file mode 100644
index 0000000..d2ec972
--- /dev/null
+++ b/macros/src/tests/declare-anchor-element.rs
@@ -0,0 +1,805 @@
+pub struct Attrs_a {
+ pub r#accesskey: Option,
+ pub r#aria_autocomplete: Option,
+ pub r#aria_checked: Option,
+ pub r#aria_disabled: Option,
+ pub r#aria_errormessage: Option,
+ pub r#aria_expanded: Option,
+ pub r#aria_haspopup: Option,
+ pub r#aria_hidden: Option,
+ pub r#aria_invalid: Option,
+ pub r#aria_label: Option,
+ pub r#aria_modal: Option,
+ pub r#aria_multiline: Option,
+ pub r#aria_multiselectable: Option,
+ pub r#aria_orientation: Option,
+ pub r#aria_placeholder: Option,
+ pub r#aria_pressed: Option,
+ pub r#aria_readonly: Option,
+ pub r#aria_required: Option,
+ pub r#aria_selected: Option,
+ pub r#aria_sort: Option,
+ pub r#aria_valuemax: Option,
+ pub r#aria_valuemin: Option,
+ pub r#aria_valuenow: Option,
+ pub r#aria_valuetext: Option,
+ pub r#autocapitalize: Option,
+ pub r#class: Option,
+ pub r#contenteditable: Option,
+ pub r#contextmenu: Option,
+ pub r#dir: Option,
+ pub r#download: Option,
+ pub r#draggable: Option,
+ pub r#hidden: Option,
+ pub r#href: Option,
+ pub r#hreflang: Option,
+ pub r#id: Option,
+ pub r#is: Option,
+ pub r#lang: Option,
+ pub r#ping: Option>,
+ pub r#rel: Option>,
+ pub r#role: Option,
+ pub r#style: Option,
+ pub r#tabindex: Option,
+ pub r#target: Option,
+ pub r#title: Option,
+ pub r#type: Option,
+}
+pub struct a
+where
+ T: crate::OutputType + Send,
+{
+ pub attrs: Attrs_a,
+ pub data_attributes: Vec<(&'static str, String)>,
+ pub aria_attributes: Vec<(&'static str, String)>,
+ pub events: T::Events,
+ pub children: Vec>>,
+}
+impl a
+where
+ T: crate::OutputType + Send,
+{
+ pub fn new() -> Self {
+ a {
+ events: T::Events::default(),
+ attrs: Attrs_a {
+ r#accesskey: None,
+ r#aria_autocomplete: None,
+ r#aria_checked: None,
+ r#aria_disabled: None,
+ r#aria_errormessage: None,
+ r#aria_expanded: None,
+ r#aria_haspopup: None,
+ r#aria_hidden: None,
+ r#aria_invalid: None,
+ r#aria_label: None,
+ r#aria_modal: None,
+ r#aria_multiline: None,
+ r#aria_multiselectable: None,
+ r#aria_orientation: None,
+ r#aria_placeholder: None,
+ r#aria_pressed: None,
+ r#aria_readonly: None,
+ r#aria_required: None,
+ r#aria_selected: None,
+ r#aria_sort: None,
+ r#aria_valuemax: None,
+ r#aria_valuemin: None,
+ r#aria_valuenow: None,
+ r#aria_valuetext: None,
+ r#autocapitalize: None,
+ r#class: None,
+ r#contenteditable: None,
+ r#contextmenu: None,
+ r#dir: None,
+ r#download: None,
+ r#draggable: None,
+ r#hidden: None,
+ r#href: None,
+ r#hreflang: None,
+ r#id: None,
+ r#is: None,
+ r#lang: None,
+ r#ping: None,
+ r#rel: None,
+ r#role: None,
+ r#style: None,
+ r#tabindex: None,
+ r#target: None,
+ r#title: None,
+ r#type: None,
+ },
+ data_attributes: Vec::new(),
+ aria_attributes: Vec::new(),
+ children: Vec::new(),
+ }
+ }
+}
+impl crate::dom::Node for a
+where
+ T: crate::OutputType + Send,
+{
+ fn vnode(&'_ mut self) -> crate::dom::VNode<'_, T> {
+ let mut attributes = Vec::new();
+ if let Some(ref value) = self.attrs.r#accesskey {
+ attributes.push(("accesskey", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_autocomplete {
+ attributes.push(("aria_autocomplete", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_checked {
+ attributes.push(("aria_checked", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_disabled {
+ attributes.push(("aria_disabled", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_errormessage {
+ attributes.push(("aria_errormessage", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_expanded {
+ attributes.push(("aria_expanded", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_haspopup {
+ attributes.push(("aria_haspopup", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_hidden {
+ attributes.push(("aria_hidden", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_invalid {
+ attributes.push(("aria_invalid", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_label {
+ attributes.push(("aria_label", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_modal {
+ attributes.push(("aria_modal", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiline {
+ attributes.push(("aria_multiline", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiselectable {
+ attributes.push(("aria_multiselectable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_orientation {
+ attributes.push(("aria_orientation", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_placeholder {
+ attributes.push(("aria_placeholder", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_pressed {
+ attributes.push(("aria_pressed", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_readonly {
+ attributes.push(("aria_readonly", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_required {
+ attributes.push(("aria_required", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_selected {
+ attributes.push(("aria_selected", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_sort {
+ attributes.push(("aria_sort", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemax {
+ attributes.push(("aria_valuemax", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemin {
+ attributes.push(("aria_valuemin", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuenow {
+ attributes.push(("aria_valuenow", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuetext {
+ attributes.push(("aria_valuetext", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#autocapitalize {
+ attributes.push(("autocapitalize", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#class {
+ attributes.push(("class", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#contenteditable {
+ attributes.push(("contenteditable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#contextmenu {
+ attributes.push(("contextmenu", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#dir {
+ attributes.push(("dir", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#download {
+ attributes.push(("download", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#draggable {
+ attributes.push(("draggable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#hidden {
+ attributes.push(("hidden", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#href {
+ attributes.push(("href", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#hreflang {
+ attributes.push(("hreflang", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#id {
+ attributes.push(("id", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#is {
+ attributes.push(("is", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#lang {
+ attributes.push(("lang", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#ping {
+ attributes.push(("ping", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#rel {
+ attributes.push(("rel", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#role {
+ attributes.push(("role", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#style {
+ attributes.push(("style", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#tabindex {
+ attributes.push(("tabindex", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#target {
+ attributes.push(("target", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#title {
+ attributes.push(("title", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#type {
+ attributes.push(("type", value.to_string()));
+ }
+ attributes.extend(self.data_attributes.clone());
+ attributes.extend(self.aria_attributes.clone());
+ let mut children = Vec::new();
+ for child in &mut self.children {
+ children.push(child.vnode());
+ }
+ crate::dom::VNode::Element(crate::dom::VElement {
+ name: "a",
+ attributes,
+ events: &mut self.events,
+ children,
+ })
+ }
+}
+impl crate::dom::Element for a
+where
+ T: crate::OutputType + Send,
+{
+ fn name() -> &'static str {
+ "a"
+ }
+ fn attribute_names() -> &'static [&'static str] {
+ &[
+ "accesskey",
+ "aria_autocomplete",
+ "aria_checked",
+ "aria_disabled",
+ "aria_errormessage",
+ "aria_expanded",
+ "aria_haspopup",
+ "aria_hidden",
+ "aria_invalid",
+ "aria_label",
+ "aria_modal",
+ "aria_multiline",
+ "aria_multiselectable",
+ "aria_orientation",
+ "aria_placeholder",
+ "aria_pressed",
+ "aria_readonly",
+ "aria_required",
+ "aria_selected",
+ "aria_sort",
+ "aria_valuemax",
+ "aria_valuemin",
+ "aria_valuenow",
+ "aria_valuetext",
+ "autocapitalize",
+ "class",
+ "contenteditable",
+ "contextmenu",
+ "dir",
+ "download",
+ "draggable",
+ "hidden",
+ "href",
+ "hreflang",
+ "id",
+ "is",
+ "lang",
+ "ping",
+ "rel",
+ "role",
+ "style",
+ "tabindex",
+ "target",
+ "title",
+ "type",
+ ]
+ }
+ fn required_children() -> &'static [&'static str] {
+ &[]
+ }
+ fn attributes(&self) -> Vec<(&'static str, String)> {
+ let mut out = Vec::new();
+ if let Some(ref value) = self.attrs.r#accesskey {
+ out.push(("accesskey", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_autocomplete {
+ out.push(("aria_autocomplete", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_checked {
+ out.push(("aria_checked", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_disabled {
+ out.push(("aria_disabled", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_errormessage {
+ out.push(("aria_errormessage", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_expanded {
+ out.push(("aria_expanded", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_haspopup {
+ out.push(("aria_haspopup", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_hidden {
+ out.push(("aria_hidden", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_invalid {
+ out.push(("aria_invalid", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_label {
+ out.push(("aria_label", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_modal {
+ out.push(("aria_modal", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiline {
+ out.push(("aria_multiline", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiselectable {
+ out.push(("aria_multiselectable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_orientation {
+ out.push(("aria_orientation", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_placeholder {
+ out.push(("aria_placeholder", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_pressed {
+ out.push(("aria_pressed", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_readonly {
+ out.push(("aria_readonly", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_required {
+ out.push(("aria_required", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_selected {
+ out.push(("aria_selected", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_sort {
+ out.push(("aria_sort", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemax {
+ out.push(("aria_valuemax", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemin {
+ out.push(("aria_valuemin", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuenow {
+ out.push(("aria_valuenow", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuetext {
+ out.push(("aria_valuetext", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#autocapitalize {
+ out.push(("autocapitalize", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#class {
+ out.push(("class", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#contenteditable {
+ out.push(("contenteditable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#contextmenu {
+ out.push(("contextmenu", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#dir {
+ out.push(("dir", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#download {
+ out.push(("download", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#draggable {
+ out.push(("draggable", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#hidden {
+ out.push(("hidden", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#href {
+ out.push(("href", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#hreflang {
+ out.push(("hreflang", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#id {
+ out.push(("id", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#is {
+ out.push(("is", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#lang {
+ out.push(("lang", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#ping {
+ out.push(("ping", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#rel {
+ out.push(("rel", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#role {
+ out.push(("role", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#style {
+ out.push(("style", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#tabindex {
+ out.push(("tabindex", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#target {
+ out.push(("target", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#title {
+ out.push(("title", value.to_string()));
+ }
+ if let Some(ref value) = self.attrs.r#type {
+ out.push(("type", value.to_string()));
+ }
+ for (key, value) in &self.data_attributes {
+ out.push((key, value.to_string()));
+ }
+ for (key, value) in &self.aria_attributes {
+ out.push((key, value.to_string()));
+ }
+ out
+ }
+}
+impl FlowContent for a
+where
+ T: crate::OutputType + Send,
+{}
+impl PhrasingContent for a
+where
+ T: crate::OutputType + Send,
+{}
+impl InteractiveContent for a
+where
+ T: crate::OutputType + Send,
+{}
+impl std::fmt::Display for a
+where
+ T: crate::OutputType + Send,
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ write!(f, "<{}", "a")?;
+ if let Some(ref value) = self.attrs.r#accesskey {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "accesskey", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_autocomplete {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_autocomplete", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_checked {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_checked", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_disabled {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_disabled", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_errormessage {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_errormessage", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_expanded {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_expanded", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_haspopup {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_haspopup", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_hidden {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_hidden", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_invalid {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_invalid", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_label {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_label", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_modal {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_modal", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiline {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_multiline", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_multiselectable {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_multiselectable", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_orientation {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_orientation", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_placeholder {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_placeholder", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_pressed {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_pressed", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_readonly {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_readonly", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_required {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_required", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_selected {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_selected", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_sort {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_sort", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemax {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_valuemax", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuemin {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_valuemin", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuenow {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_valuenow", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#aria_valuetext {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "aria_valuetext", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#autocapitalize {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "autocapitalize", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#class {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "class", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#contenteditable {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "contenteditable", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#contextmenu {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "contextmenu", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#dir {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "dir", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#download {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "download", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#draggable {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "draggable", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#hidden {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "hidden", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#href {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "href", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#hreflang {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "hreflang", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#id {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "id", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#is {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "is", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#lang {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "lang", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#ping {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "ping", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#rel {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "rel", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#role {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "role", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#style {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "style", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#tabindex {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "tabindex", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#target {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "target", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#title {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "title", value)?;
+ }
+ }
+ if let Some(ref value) = self.attrs.r#type {
+ let value = crate::escape_html_attribute(value.to_string());
+ if !value.is_empty() {
+ write!(f, " {}=\"{}\"", "type", value)?;
+ }
+ }
+ for (key, value) in &self.data_attributes {
+ write!(
+ f, " data-{}=\"{}\"", key, crate ::escape_html_attribute(value
+ .to_string())
+ )?;
+ }
+ for (key, value) in &self.aria_attributes {
+ write!(
+ f, " aria-{}=\"{}\"", key, crate ::escape_html_attribute(value
+ .to_string())
+ )?;
+ }
+ write!(f, "{}", self.events)?;
+ write!(f, ">")?;
+ for child in &self.children {
+ child.fmt(f)?;
+ }
+ write!(f, "{}>", "a")
+ }
+}
+impl IntoIterator for a
+where
+ T: crate::OutputType + Send,
+{
+ type Item = a;
+ type IntoIter = std::vec::IntoIter>;
+ fn into_iter(self) -> Self::IntoIter {
+ vec![self].into_iter()
+ }
+}
+impl IntoIterator for Box>
+where
+ T: crate::OutputType + Send,
+{
+ type Item = Box>;
+ type IntoIter = std::vec::IntoIter>>;
+ fn into_iter(self) -> Self::IntoIter {
+ vec![self].into_iter()
+ }
+}
diff --git a/macros/src/tests/generate-anchor-element.rs b/macros/src/tests/generate-anchor-element.rs
new file mode 100644
index 0000000..f10f065
--- /dev/null
+++ b/macros/src/tests/generate-anchor-element.rs
@@ -0,0 +1,20 @@
+fn html() {
+ let mut element = axohtml::elements::a::new();
+ element
+ .attrs
+ .r#href = Some(
+ "https://www.w3schools.com"
+ .parse()
+ .unwrap_or_else(|err| {
+ eprintln!(
+ "ERROR: failed to parse attribute value: {}\nERROR: rebuild with nightly to print source location",
+ err
+ );
+ panic!("failed to parse string literal");
+ }),
+ );
+ element
+ .children
+ .push(Box::new(axohtml::dom::TextNode::new("Visit W3Schools.com!".to_string())));
+ Box::new(element)
+}
diff --git a/macros/src/tests/generate-button-event.rs b/macros/src/tests/generate-button-event.rs
new file mode 100644
index 0000000..e6d8089
--- /dev/null
+++ b/macros/src/tests/generate-button-event.rs
@@ -0,0 +1,6 @@
+fn html() {
+ let mut element: axohtml::elements::button = axohtml::elements::button::new();
+ element.children.push(Box::new(axohtml::dom::TextNode::new("Click me".to_string())));
+ element.events.r#click = Some("alert(1)".into());
+ Box::new(element)
+}
diff --git a/tests/main.rs b/tests/main.rs
index 5bb3fbc..c1e99a2 100644
--- a/tests/main.rs
+++ b/tests/main.rs
@@ -23,3 +23,132 @@ fn ui() {
compiletest::run_tests(&config);
}
+
+#[test]
+fn test_subtree() {
+ use axohtml::html;
+ use axohtml::dom::DOMTree;
+
+ let output: DOMTree = html!(
+ "This is HTML"
+ );
+
+ assert_eq!(
+ output.to_string(),
+ "This is HTML
"
+ )
+}
+
+#[test]
+fn test_button_onclick() {
+ use axohtml::html;
+ use axohtml::dom::DOMTree;
+
+ let output: DOMTree = html!(
+
+ : String
+ );
+
+ assert_eq!(
+ output.to_string(),
+ ""
+ )
+}
+
+#[test]
+fn test_flowcontent_function() {
+ use axohtml::html;
+ use axohtml::dom::DOMTree;
+ use axohtml::elements::FlowContent;
+
+ fn content() -> Box> {
+ return html!(
+
+ "This is HTML"
+
+ )
+ }
+
+ let output: DOMTree = html!(
+
+ { content() }
+
+ );
+
+ assert_eq!(
+ output.to_string(),
+ "This is HTML
"
+ )
+}
+
+#[test]
+fn test_phrasingcontent_function() {
+ use axohtml::{html, text};
+ use axohtml::dom::DOMTree;
+ use axohtml::elements::PhrasingContent;
+
+ fn italicized(content: &str) -> Box> {
+ return html!(
+
+ { text!(content) }
+
+ )
+ }
+
+ let output: DOMTree = html!(
+
+ "This is "
+ { italicized("HTML") }
+
+ );
+
+ assert_eq!(
+ output.to_string(),
+ "This is HTML
"
+ )
+}
+
+#[test]
+fn test_template_function() {
+ use axohtml::html;
+ use axohtml::dom::DOMTree;
+ use axohtml::elements::FlowContent;
+
+ fn layout(body: impl FnOnce() -> Box>) -> DOMTree {
+ return html!(
+
+ "Header"
+ { body() }
+
+ )
+ }
+
+ let output: DOMTree = layout(|| {html!(
+
+
"Subheading"
+
"This is content!"
+
+ )});
+
+ assert_eq!(
+ output.to_string(),
+ "Header
Subheading
This is content!
"
+ )
+}
+
+#[test]
+fn test_inline_script() {
+ use axohtml::{html, unsafe_text};
+ use axohtml::dom::DOMTree;
+
+ let output: DOMTree = html!(
+
+ );
+
+ assert_eq!(
+ output.to_string(),
+ ""
+ )
+}
From 2f811afc53d1ec7a07739e8b51fd15b9abe3d858 Mon Sep 17 00:00:00 2001
From: Ryan Balsdon
Date: Wed, 26 Jul 2023 11:03:19 -0400
Subject: [PATCH 2/3] fix: clippy warnings about useless conversion
---
macros/src/lib.rs | 12 ++++++------
macros/src/span.rs | 3 +--
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index ac11056..81576f4 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -26,15 +26,15 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
html_impl(input.into()).into()
}
fn html_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
- let stream = lexer::unroll_stream(input.into(), false);
+ let stream = lexer::unroll_stream(input, false);
let result = html::expand_html(&stream);
- proc_macro2::TokenStream::from(match result {
+ match result {
Err(err) => error::parse_error(&stream, &err),
Ok((node, ty)) => match node.into_token_stream(&ty) {
Err(err) => err,
Ok(success) => success,
},
- })
+ }
}
/// Construct a Dodrio node.
@@ -65,9 +65,9 @@ pub fn declare_elements(input: proc_macro::TokenStream) -> proc_macro::TokenStre
}
fn declare_elements_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
- let stream = lexer::keywordise(lexer::unroll_stream(input.into(), true));
+ let stream = lexer::keywordise(lexer::unroll_stream(input, true));
let result = declare::expand_declare(&stream);
- proc_macro2::TokenStream::from(match result {
+ match result {
Err(err) => error::parse_error(&stream, &err),
Ok(decls) => {
let mut out = proc_macro2::TokenStream::new();
@@ -76,7 +76,7 @@ fn declare_elements_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenS
}
out
}
- })
+ }
}
#[test]
diff --git a/macros/src/span.rs b/macros/src/span.rs
index 3c1c003..2d77489 100644
--- a/macros/src/span.rs
+++ b/macros/src/span.rs
@@ -2,6 +2,5 @@ pub fn from_unstable(span: proc_macro2::Span) -> proc_macro2::Span {
let ident = proc_macro2::Ident::new("_", span);
let tt = proc_macro2::TokenTree::Ident(ident);
let tts = proc_macro2::TokenStream::from(tt);
- let tts2 = proc_macro2::TokenStream::from(tts);
- tts2.into_iter().next().unwrap().span()
+ tts.into_iter().next().unwrap().span()
}
From 238556ee844660faea2c950856027ff255fc9ac7 Mon Sep 17 00:00:00 2001
From: Ryan Balsdon
Date: Wed, 26 Jul 2023 11:05:29 -0400
Subject: [PATCH 3/3] fix: rust-fmt wants things prettier
---
macros/src/lib.rs | 14 +++++++++----
tests/main.rs | 52 +++++++++++++++++++----------------------------
2 files changed, 31 insertions(+), 35 deletions(-)
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 81576f4..f2f9eb4 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -98,7 +98,9 @@ fn test_declare_anchor_element() {
let output = declare_elements_impl(input);
pretty_assertions::assert_eq!(
- rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ rust_format::PrettyPlease::default()
+ .format_str(output.to_string())
+ .unwrap(),
include_str!("tests/declare-anchor-element.rs")
)
}
@@ -117,7 +119,9 @@ fn test_html_anchor_element() {
let output = format!("fn html() {}", output.to_string());
pretty_assertions::assert_eq!(
- rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ rust_format::PrettyPlease::default()
+ .format_str(output.to_string())
+ .unwrap(),
include_str!("tests/generate-anchor-element.rs")
)
}
@@ -137,7 +141,9 @@ fn test_html_button_event() {
let output = format!("fn html() {}", output.to_string());
pretty_assertions::assert_eq!(
- rust_format::PrettyPlease::default().format_str(output.to_string()).unwrap(),
+ rust_format::PrettyPlease::default()
+ .format_str(output.to_string())
+ .unwrap(),
include_str!("tests/generate-button-event.rs")
)
-}
\ No newline at end of file
+}
diff --git a/tests/main.rs b/tests/main.rs
index c1e99a2..d1778ac 100644
--- a/tests/main.rs
+++ b/tests/main.rs
@@ -26,23 +26,20 @@ fn ui() {
#[test]
fn test_subtree() {
- use axohtml::html;
use axohtml::dom::DOMTree;
+ use axohtml::html;
let output: DOMTree = html!(
"This is HTML"
);
- assert_eq!(
- output.to_string(),
- "This is HTML
"
- )
+ assert_eq!(output.to_string(), "This is HTML
")
}
#[test]
fn test_button_onclick() {
- use axohtml::html;
use axohtml::dom::DOMTree;
+ use axohtml::html;
let output: DOMTree = html!(
@@ -57,16 +54,16 @@ fn test_button_onclick() {
#[test]
fn test_flowcontent_function() {
- use axohtml::html;
use axohtml::dom::DOMTree;
use axohtml::elements::FlowContent;
+ use axohtml::html;
fn content() -> Box> {
return html!(
"This is HTML"
- )
+ );
}
let output: DOMTree = html!(
@@ -75,24 +72,21 @@ fn test_flowcontent_function() {
);
- assert_eq!(
- output.to_string(),
- "This is HTML
"
- )
+ assert_eq!(output.to_string(), "This is HTML
")
}
#[test]
fn test_phrasingcontent_function() {
- use axohtml::{html, text};
use axohtml::dom::DOMTree;
use axohtml::elements::PhrasingContent;
+ use axohtml::{html, text};
fn italicized(content: &str) -> Box> {
return html!(
{ text!(content) }
- )
+ );
}
let output: DOMTree = html!(
@@ -102,17 +96,14 @@ fn test_phrasingcontent_function() {
);
- assert_eq!(
- output.to_string(),
- "This is HTML
"
- )
+ assert_eq!(output.to_string(), "This is HTML
")
}
#[test]
fn test_template_function() {
- use axohtml::html;
use axohtml::dom::DOMTree;
use axohtml::elements::FlowContent;
+ use axohtml::html;
fn layout(body: impl FnOnce() -> Box>) -> DOMTree {
return html!(
@@ -120,15 +111,17 @@ fn test_template_function() {
"Header"
{ body() }
- )
+ );
}
- let output: DOMTree = layout(|| {html!(
-
-
"Subheading"
-
"This is content!"
-
- )});
+ let output: DOMTree = layout(|| {
+ html!(
+
+
"Subheading"
+
"This is content!"
+
+ )
+ });
assert_eq!(
output.to_string(),
@@ -138,8 +131,8 @@ fn test_template_function() {
#[test]
fn test_inline_script() {
- use axohtml::{html, unsafe_text};
use axohtml::dom::DOMTree;
+ use axohtml::{html, unsafe_text};
let output: DOMTree = html!(
);
- assert_eq!(
- output.to_string(),
- ""
- )
+ assert_eq!(output.to_string(), "")
}