ruukh_codegen/
lib.rs

1#![recursion_limit = "256"]
2#![cfg_attr(feature = "cargo-clippy", feature(tool_lints))]
3#![cfg_attr(feature = "cargo-clippy", warn(clippy::all))]
4//! The crate which removes most of the boilerplate from Ruukh apps.
5//!
6//! This lib defines `#[component]`, `#[derive(Lifecycle)]` and `html!` macros.
7extern crate proc_macro;
8
9use crate::{component::ComponentMeta, html::HtmlRoot};
10use proc_macro2::Span;
11use quote::quote;
12use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput, Item};
13
14mod component;
15mod html;
16mod suffix;
17
18/// A convenient auto derive for `Lifecycle` trait. It could be simply written
19/// as `impl Lifecycle for MyComponent {}` instead, but why not save some chars.
20/// You may use it like:
21///
22/// # Example
23/// ```ignore,compile_fail
24/// #[derive(Lifecycle)]
25/// struct Button;
26/// ```
27#[proc_macro_derive(Lifecycle)]
28pub fn derive_lifecycle(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
29    let input = parse_macro_input!(input as DeriveInput);
30    let ident = input.ident;
31
32    let expanded = quote! {
33        impl Lifecycle for #ident {}
34    };
35
36    expanded.into()
37}
38
39/// `#[component]` macro to derive `Component` trait as well as to do
40/// modifications to the struct. It does all the heavy lifting which the user
41/// would have to do, to make the component work.
42///
43/// # Example
44/// ```ignore,compile_fail
45/// #[component]
46/// struct MyButton {
47///     disabled: bool,
48/// }
49/// ```
50///
51/// You may declare events available on the component by placing `#[events]`
52/// attribute. Like:
53/// # Example
54/// ```ignore,compile_fail
55/// #[component]
56/// #[events(
57///     fn event_name(&self, arg: type) -> return_type;  
58/// )]
59/// struct MyButton {
60///     disabled: bool,
61/// }
62/// ```
63/// You may place multiple event declarations. Also, these event declarations
64/// require that the event handlers be passed compulsorily.
65///
66/// `#[component]` also allows to annotate the struct fields with additional
67/// attributes. Such as:
68///
69/// 1. `#[prop]` attribute: This attribute defines that a field is a prop
70/// field, though this attribute is optional. This attribute allows passing a
71/// default value for the prop field. Like `#[prop(default)]` which delegates
72/// it to the types `Default` implementation or `#[prop(default = val)]` which
73/// uses the `val` as its default value.
74/// Any fields which has a given `default` value or any `Option` type is
75/// optional while passing props.
76///
77/// 2. `#[state]` attribute: This attributes is required to define a field as a
78/// state field. If a `#[state]` or `#[state(default)]` is specified then the
79/// `Default` value of the field is used. If you want to provide a more
80/// specific value, then pass it by using `#[state(default = val)]` attribute.
81#[proc_macro_attribute]
82#[cfg_attr(
83    feature = "cargo-clippy",
84    allow(clippy::needless_pass_by_value)
85)]
86pub fn component(
87    metadata: proc_macro::TokenStream,
88    input: proc_macro::TokenStream,
89) -> proc_macro::TokenStream {
90    if !metadata.is_empty() {
91        return Error::new(
92            Span::call_site(),
93            "`#[component]` does not support attribute arguments.",
94        ).to_compile_error()
95        .into();
96    }
97
98    let input = parse_macro_input!(input as Item);
99
100    let expanded = match input {
101        Item::Struct(struct_) => ComponentMeta::parse(struct_)
102            .map(|s| s.expand())
103            .unwrap_or_else(|e| e.to_compile_error()),
104        _ => {
105            Error::new(input.span(), "Only structs are allowed to be Component").to_compile_error()
106        }
107    };
108
109    expanded.into()
110}
111
112/// `html!` macro to parse `vue`-inspired syntax to generate Markup.
113///
114/// The basics of using html! macro:
115///
116/// ## Text
117/// ```ignore,compile_fail
118/// html! {
119///     "This is a sample text."
120/// }
121/// ```
122///
123/// ## Empty Markup
124/// ```ignore,compile_fail
125/// html!()
126/// ```
127///
128/// ## Self-closing tags
129/// Only html specified self-closing tags can be self-closing tags.
130///
131/// ```ignore,compile_fail
132/// html! {
133///     <br>
134/// }
135/// ```
136///
137/// ## Normal tags
138/// ```ignore,compile_fail
139/// html! {
140///     <div></div>
141///     <my-custom-tag></my-custom-tag>
142/// }
143/// ```
144///
145/// ## Component tags
146/// ```ignore,compile_fail
147/// html! {
148///     <MyComponent></MyComponent>
149/// }
150/// ```
151///
152/// ## List of tags
153/// ```ignore,compile_fail
154/// html! {
155///     <div></div>
156///     <span></span>
157///     <button></button>
158/// }
159/// ```
160///
161/// ## Nested markup
162/// ```ignore,compile_fail
163/// html! {
164///     <div>
165///         <span></span>
166///     </div>
167/// }
168/// ```
169///
170/// ## Expressions in between
171/// ```ignore,compile_fail
172/// html! {
173///     "There are "{ count }" people."
174/// }
175/// ```
176#[proc_macro]
177pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
178    let parsed = parse_macro_input!(input as HtmlRoot);
179    parsed.expand().into()
180}