diff --git a/Cargo.toml b/Cargo.toml index 6ba28c7..342dcf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,16 @@ bench = false [dependencies] proc-macro2 = { version = "1.0.47", features = ["span-locations"] } quote = "1.0.21" -syn = { version = "2.0.15", features = ["visit-mut", "full", "parsing", "extra-traits"] } +syn = { version = "2.0.15", features = [ + "visit-mut", + "full", + "parsing", + "extra-traits", +] } thiserror = "1.0.37" syn_derive = "0.1.6" proc-macro2-diagnostics = { version = "0.10", default-features = false } +derive-where = "1.2.5" [dev-dependencies] proc-macro2 = { version = "1.0.47", features = ["span-locations"] } diff --git a/examples/html-to-string-macro/src/lib.rs b/examples/html-to-string-macro/src/lib.rs index 120bb3a..6124c4e 100644 --- a/examples/html-to-string-macro/src/lib.rs +++ b/examples/html-to-string-macro/src/lib.rs @@ -5,8 +5,7 @@ use quote::{quote, quote_spanned, ToTokens}; use rstml::{ node::{Node, NodeAttribute, NodeName}, visitor::{visit_attributes, visit_nodes, Visitor}, - Parser, ParserConfig, - Infallible, + Infallible, Parser, ParserConfig, }; use syn::spanned::Spanned; // mod escape; @@ -61,7 +60,7 @@ impl<'a> Visitor for WalkNodes<'a> { self.output.static_format.push_str(&node.value_string()); false } - fn visit_raw_node( + fn visit_raw_node( &mut self, node: &mut rstml::node::RawText, ) -> bool { diff --git a/rstml-controll-flow/src/extendable.rs b/rstml-controll-flow/src/extendable.rs index b85585a..1f39078 100644 --- a/rstml-controll-flow/src/extendable.rs +++ b/rstml-controll-flow/src/extendable.rs @@ -115,8 +115,7 @@ macro_rules! impl_tuple { } } fn peek(input: ParseStream) -> bool { - dbg!(&input); - $(dbg!($name::peek_element(&input.fork())))||* + $($name::peek_element(&input.fork()))||* } } }; diff --git a/src/node/raw_text.rs b/src/node/raw_text.rs index c7c13b8..f3f7e88 100644 --- a/src/node/raw_text.rs +++ b/src/node/raw_text.rs @@ -1,19 +1,13 @@ use std::marker::PhantomData; +use derive_where::derive_where; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::ToTokens; -use syn::{ - parse::ParseStream, - spanned::Spanned, - token::Brace, - LitStr, Token, -}; +use syn::{parse::ParseStream, spanned::Spanned, token::Brace, LitStr, Token}; +use super::{CustomNode, Infallible, Node}; use crate::recoverable::ParseRecoverable; -use super::Infallible; -use super::{CustomNode, Node}; - /// Raw unquoted text /// /// Internally it is valid `TokenStream` (stream of rust code tokens). @@ -30,7 +24,7 @@ use super::{CustomNode, Node}; /// source_text method is not available in `quote!` context, or in context where /// input is generated by another macro. In still can return default formatting /// for TokenStream. -#[derive(Clone, Debug)] +#[derive_where(Clone, Debug)] pub struct RawText { token_stream: TokenStream, // Span that started before previous token, and after next. @@ -54,6 +48,8 @@ impl Default for RawText { } impl RawText { + /// Custom node type parameter is used only for parsing, so it can be + /// changed during usage. pub fn convert_custom(self) -> RawText { RawText { token_stream: self.token_stream, @@ -188,7 +184,10 @@ impl RawText { } impl ParseRecoverable for RawText { - fn parse_recoverable(parser: &mut crate::recoverable::RecoverableContext, input: ParseStream) -> Option { + fn parse_recoverable( + parser: &mut crate::recoverable::RecoverableContext, + input: ParseStream, + ) -> Option { let mut token_stream = TokenStream::new(); let any_node = |input: ParseStream| { input.peek(Token![<]) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 98c26e6..abba8ed 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -136,8 +136,6 @@ impl Parser { top_level_nodes += 1; nodes.push(parsed_node) } - dbg!(input.cursor().eof()); - dbg!(&parser, input); // its important to skip tokens, to avoid Unexpected tokens errors. if !input.is_empty() { diff --git a/src/visitor.rs b/src/visitor.rs index e540518..3fd906a 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -1,8 +1,10 @@ -use std::{ fmt::Debug, marker::PhantomData}; +use std::marker::PhantomData; -use crate::Infallible; use super::node::*; -use crate::atoms::{CloseTag, OpenTag}; +use crate::{ + atoms::{CloseTag, OpenTag}, + Infallible, +}; /// Enum that represents the different types with valid Rust code that can be /// visited using `syn::Visitor`. Usually `syn::Block` or `syn::Expr`. @@ -34,10 +36,7 @@ pub trait Visitor { fn visit_doctype(&mut self, _node: &mut NodeDoctype) -> bool { true } - fn visit_raw_node( - &mut self, - _node: &mut RawText, - ) -> bool { + fn visit_raw_node(&mut self, _node: &mut RawText) -> bool { true } fn visit_custom(&mut self, _node: &mut Self::Custom) -> bool { @@ -111,14 +110,87 @@ macro_rules! try_visit { /// Wrapper for visitor that calls inner visitors. /// Inner visitor should implement `Visitor` trait and /// `syn::visit_mut::VisitMut`. +/// +/// For regular usecases it is recommended to use `visit_nodes`, +/// `visit_nodes_with_custom_handler` or `visit_attributes` functions. +/// +/// But if you need it can be used by calling `visit_*` methods directly. +/// +/// Example: +/// ```rust +/// use quote::quote; +/// use rstml::{ +/// node::{Node, NodeText}, +/// visitor::{Visitor, VisitorWithDefault}, +/// Infallible, +/// }; +/// use syn::parse_quote; +/// +/// struct TestVisitor; +/// impl Visitor for TestVisitor { +/// type Custom = Infallible; +/// fn visit_text_node(&mut self, node: &mut NodeText) -> bool { +/// *node = parse_quote!("modified"); +/// true +/// } +/// } +/// impl syn::visit_mut::VisitMut for TestVisitor {} +/// +/// let mut visitor = VisitorWithDefault::new(TestVisitor); +/// +/// let tokens = quote! { +///
+/// "Some raw text" +/// "And text after span" +///
+/// }; +/// let mut nodes = rstml::parse2(tokens).unwrap(); +/// for node in &mut nodes { +/// visitor.visit_node(node); +/// } +/// let result = quote! { +/// #(#nodes)* +/// }; +/// assert_eq!( +/// result.to_string(), +/// quote! { +///
+/// "modified" +/// "modified" +///
+/// } +/// .to_string() +/// ); +/// ``` pub struct VisitorWithDefault { custom: PhantomData, visitor: V, + custom_handler: Option bool>>, +} +impl VisitorWithDefault +where + C: CustomNode, + V: Visitor + syn::visit_mut::VisitMut, +{ + pub fn new(visitor: V) -> Self { + Self { + custom: PhantomData, + visitor, + custom_handler: None, + } + } + pub fn with_custom_handler(visitor: V, handler: Box bool>) -> Self { + Self { + custom: PhantomData, + visitor, + custom_handler: Some(handler), + } + } } impl Visitor for VisitorWithDefault where - C: CustomNode + Debug + Clone, + C: CustomNode, V: Visitor + syn::visit_mut::VisitMut, { type Custom = C; @@ -154,10 +226,7 @@ where self.visit_raw_node(&mut node.value) } - fn visit_raw_node( - &mut self, - node: &mut RawText, - ) -> bool { + fn visit_raw_node(&mut self, node: &mut RawText) -> bool { visit_child!(self.visitor.visit_raw_node(node)); true @@ -165,7 +234,11 @@ where fn visit_custom(&mut self, node: &mut Self::Custom) -> bool { visit_child!(self.visitor.visit_custom(node)); - true + if let Some(ref mut handler) = &mut self.custom_handler { + handler(node) + } else { + true + } } fn visit_text_node(&mut self, node: &mut NodeText) -> bool { visit_child!(self.visitor.visit_text_node(node)); @@ -285,14 +358,48 @@ where true } } +/// Visitor entrypoint. +/// Visit nodes in array calling visitor methods. +/// Recursively visit nodes in children, and attributes. +/// +/// Return modified visitor back +pub fn visit_nodes(nodes: &mut [Node], visitor: V) -> V +where + V: Visitor + syn::visit_mut::VisitMut, + ::Custom: CustomNode, +{ + let mut visitor = VisitorWithDefault { + custom: PhantomData, + visitor, + custom_handler: None, + }; + for node in nodes { + visitor.visit_node(node); + } + visitor.visitor +} -pub fn visit_nodes(nodes: &mut [Node], visitor: V) -> V +/// Visitor entrypoint. +/// Visit nodes in array calling visitor methods. +/// Recursively visit nodes in children, and attributes. +/// Provide custom handler that is used to visit custom nodes. +/// Custom handler should return true if visitor should continue to traverse, +/// and call visitor methods for its children. +/// +/// Return modified visitor back +pub fn visit_nodes_with_custom_handler( + nodes: &mut [Node], + handler: Box bool>, + visitor: V, +) -> V where - V: Visitor + syn::visit_mut::VisitMut, + V: Visitor + syn::visit_mut::VisitMut, + ::Custom: CustomNode, { let mut visitor = VisitorWithDefault { custom: PhantomData, visitor, + custom_handler: Some(handler), }; for node in nodes { visitor.visit_node(node); @@ -300,6 +407,7 @@ where visitor.visitor } +/// Visit attributes in array calling visitor methods. pub fn visit_attributes(attributes: &mut [NodeAttribute], visitor: V) -> V where V: Visitor + syn::visit_mut::VisitMut, @@ -307,6 +415,7 @@ where let mut visitor = VisitorWithDefault { custom: PhantomData, visitor, + custom_handler: None, }; for attribute in attributes { visitor.visit_attribute(attribute); @@ -316,10 +425,11 @@ where #[cfg(test)] mod tests { - use crate::Infallible; use quote::{quote, ToTokens}; + use syn::parse_quote; use super::*; + use crate::Infallible; #[test] fn collect_node_names() { #[derive(Default)] @@ -411,10 +521,7 @@ mod tests { impl Visitor for TestVisitor { type Custom = Infallible; - fn visit_raw_node( - &mut self, - node: &mut RawText, - ) -> bool { + fn visit_raw_node(&mut self, node: &mut RawText) -> bool { let raw = node.clone().convert_custom::(); self.collected_raw_text.push(raw); true @@ -485,4 +592,43 @@ mod tests { vec!["Some raw text", "And text after span", "comment"] ); } + + #[test] + fn modify_text_visitor() { + struct TestVisitor; + impl Visitor for TestVisitor { + type Custom = Infallible; + fn visit_text_node(&mut self, node: &mut NodeText) -> bool { + *node = parse_quote!("modified"); + true + } + } + impl syn::visit_mut::VisitMut for TestVisitor {} + + let mut visitor = VisitorWithDefault::new(TestVisitor); + + let tokens = quote! { +
+ "Some raw text" + "And text after span" +
+ }; + let mut nodes = crate::parse2(tokens).unwrap(); + for node in &mut nodes { + visitor.visit_node(node); + } + let result = quote! { + #(#nodes)* + }; + assert_eq!( + result.to_string(), + quote! { +
+ "modified" + "modified" +
+ } + .to_string() + ); + } }