awsl_syn/
lib.rs

1//! [`syn`]-powered parser for JSX-like [`TokenStream`]s, aka RSX. The parsed result is a
2//! nested [`Node`] structure, similar to the browser DOM, where node name and
3//! value are syn expressions to support building proc macros.
4//!
5//! ```rust
6//! use quote::quote;
7//! use syn_rsx::parse2;
8//!
9//! let tokens = quote! { <hello world>"hi"</hello> };
10//!
11//! let nodes = parse2(tokens).unwrap();
12//! assert_eq!(nodes[0].name_as_string().unwrap(), "hello");
13//! assert_eq!(nodes[0].attributes[0].name_as_string().unwrap(), "world");
14//! assert_eq!(nodes[0].children[0].value_as_string().unwrap(), "hi");
15//! ```
16//!
17//! ## Features
18//!
19//!
20//! - **Not opinionated**
21//!
22//!   Every tag or attribute name is valid
23//!
24//!   ```rust
25//!   # use quote::quote;
26//!   # use syn_rsx::parse2;
27//!   # parse2(quote! {
28//!   <hello world />
29//!   # }).unwrap();
30//!   ```
31//!
32//! - **Text nodes**
33//!
34//!   ```rust
35//!   # use quote::quote;
36//!   # use syn_rsx::parse2;
37//!   # parse2(quote! {
38//!   <div>"String literal"</div>
39//!   # }).unwrap();
40//!   ```
41//!
42//!   Support for [unquoted text is planned]
43//!
44//! - **Node names separated by dash, colon or double colon**
45//!
46//!   ```rust
47//!   # use quote::quote;
48//!   # use syn_rsx::parse2;
49//!   # parse2(quote! {
50//!   <tag-name attribute-key="value" />
51//!   <tag:name attribute:key="value" />
52//!   <tag::name attribute::key="value" />
53//!   # }).unwrap();
54//!   ```
55//!
56//! - **Node names with reserved keywords**
57//!
58//!   ```rust
59//!   # use quote::quote;
60//!   # use syn_rsx::parse2;
61//!   # parse2(quote! {
62//!   <input type="submit" />
63//!   # }).unwrap();
64//!   ```
65//!
66//! - **Attribute values can be any valid syn expression without requiring braces**
67//!
68//!   ```rust
69//!   # use quote::quote;
70//!   # use syn_rsx::parse2;
71//!   # parse2(quote! {
72//!   <div key=some::value() />
73//!   # }).unwrap();
74//!   ```
75//!
76//! - **Doctypes, Comments and Fragments**
77//!
78//!   ```rust
79//!   # use quote::quote;
80//!   # use syn_rsx::parse2;
81//!   # parse2(quote! {
82//!   <!DOCTYPE html>
83//!   <!-- "comment" -->
84//!   <></>
85//!   # }).unwrap();
86//!   ```
87//!
88//! - **Braced blocks are parsed as arbitrary Rust code**
89//!
90//!   ```rust
91//!   # use quote::quote;
92//!   # use syn_rsx::parse2;
93//!   # parse2(quote! {
94//!   <{ let block = "in node name position"; } />
95//!   <div>{ let block = "in node position"; }</div>
96//!   <div { let block = "in attribute position"; } />
97//!   <div key={ let block = "in attribute value position"; } />
98//!   # }).unwrap();
99//!   ```
100//!
101//! - **Helpful error reporting out of the box**
102//!
103//!   ```ignore
104//!   error: open tag has no corresponding close tag and is not self-closing
105//!   --> examples/html-to-string-macro/tests/lib.rs:5:24
106//!     |
107//!   5 |     html_to_string! { <div> };
108//!     |                        ^^^
109//!   ```
110//!
111//! - **Customization**
112//!
113//!   A [`ParserConfig`] to customize parsing behavior is available, so if you have
114//!   slightly different requirements for parsing and it's not yet customizable
115//!   feel free to open an issue or pull request to extend the configuration.
116//!
117//!   One highlight with regards to customization is the [`transform_block`]
118//!   configuration, which takes a closure that receives raw block content as
119//!   `ParseStream` and lets you optionally convert it to a `TokenStream`. That makes it
120//!   possible to have custom syntax in blocks. More details in [#9]
121//!
122//!
123//! [`syn`]: /syn
124//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
125//! [`Node`]: struct.Node.html
126//! [`ParserConfig`]: struct.ParserConfig.html
127//! [mod style path]: https://docs.rs/syn/1.0.40/syn/struct.Path.html#method.parse_mod_style
128//! [unquoted text is planned]: https://github.com/stoically/syn-rsx/issues/2
129//! [`transform_block`]: struct.ParserConfig.html#method.transform_block
130//! [#9]: https://github.com/stoically/syn-rsx/issues/9
131
132extern crate proc_macro;
133
134use syn::{
135    parse::{ParseStream, Parser as _},
136    Result,
137};
138
139mod node;
140mod parser;
141
142pub mod punctuation {
143    //! Custom syn punctuations
144    use syn::custom_punctuation;
145
146    custom_punctuation!(Dash, -);
147}
148
149pub use node::{Node, NodeName, NodeType};
150pub use parser::{Parser, ParserConfig};
151
152/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree
153///
154/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
155/// [`Node`]: struct.Node.html
156pub fn parse(tokens: proc_macro::TokenStream) -> Result<Vec<Node>> {
157    let parser = move |input: ParseStream| Parser::new(ParserConfig::default()).parse(input);
158
159    parser.parse(tokens)
160}
161
162/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree with custom [`ParserConfig`]
163///
164/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
165/// [`Node`]: struct.Node.html
166/// [`ParserConfig`]: struct.ParserConfig.html
167pub fn parse_with_config(
168    tokens: proc_macro::TokenStream,
169    config: ParserConfig,
170) -> Result<Vec<Node>> {
171    let parser = move |input: ParseStream| Parser::new(config).parse(input);
172
173    parser.parse(tokens)
174}
175
176/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree
177///
178/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
179/// [`Node`]: struct.Node.html
180pub fn parse2(tokens: proc_macro2::TokenStream) -> Result<Vec<Node>> {
181    let parser = move |input: ParseStream| Parser::new(ParserConfig::default()).parse(input);
182
183    parser.parse2(tokens)
184}
185
186/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree with custom [`ParserConfig`]
187///
188/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
189/// [`Node`]: struct.Node.html
190/// [`ParserConfig`]: struct.ParserConfig.html
191pub fn parse2_with_config(
192    tokens: proc_macro2::TokenStream,
193    config: ParserConfig,
194) -> Result<Vec<Node>> {
195    let parser = move |input: ParseStream| Parser::new(config).parse(input);
196
197    parser.parse2(tokens)
198}