1mod classes;
50mod derive_props;
51mod function_component;
52mod hook;
53mod html_tree;
54mod props;
55mod stringify;
56mod use_prepared_state;
57mod use_transitive_state;
58
59use std::fmt::{Display, Write};
60
61use derive_props::DerivePropsInput;
62use function_component::{function_component_impl, FunctionComponent, FunctionComponentName};
63use hook::{hook_impl, HookFn};
64use html_tree::{HtmlRoot, HtmlRootVNode};
65use proc_macro::TokenStream;
66use quote::ToTokens;
67use syn::buffer::Cursor;
68use syn::parse_macro_input;
69use use_prepared_state::PreparedState;
70use use_transitive_state::TransitiveState;
71
72trait Peek<'a, T> {
73 fn peek(cursor: Cursor<'a>) -> Option<(T, Cursor<'a>)>;
74}
75
76trait PeekValue<T> {
77 fn peek(cursor: Cursor) -> Option<T>;
78}
79
80pub(crate) trait DisplayExt: Display {
87 fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
90 struct X<'src> {
95 other: &'src str,
96 }
97
98 impl Write for X<'_> {
99 fn write_str(&mut self, self_chunk: &str) -> std::fmt::Result {
100 if self.other.len() < self_chunk.len() {
101 return Err(std::fmt::Error); }
103 let other_chunk;
104 (other_chunk, self.other) = self.other.split_at(self_chunk.len());
106 self_chunk
108 .eq_ignore_ascii_case(other_chunk)
109 .then_some(())
110 .ok_or(std::fmt::Error)
111 }
112 }
113
114 let mut writer = X { other };
115 write!(writer, "{self}").is_ok_and(|_| writer.other.is_empty())
119 }
120
121 fn eq_str(&self, other: &str) -> bool {
123 struct X<'src> {
127 other: &'src str,
128 }
129
130 impl Write for X<'_> {
131 fn write_str(&mut self, chunk: &str) -> std::fmt::Result {
132 self.other
133 .strip_prefix(chunk) .map(|rest| self.other = rest) .ok_or(std::fmt::Error) }
137 }
138
139 let mut writer = X { other };
140 write!(writer, "{self}").is_ok_and(|_| writer.other.is_empty())
144 }
145
146 fn starts_with(&self, prefix: &str) -> bool {
149 struct X<'src> {
151 prefix: &'src str,
152 }
153
154 impl Write for X<'_> {
155 fn write_str(&mut self, chunk: &str) -> std::fmt::Result {
156 match self.prefix.strip_prefix(chunk) {
157 Some(rest) => self.prefix = rest, None => {
160 chunk.strip_prefix(self.prefix).ok_or(std::fmt::Error)?;
163 self.prefix = ""; }
165 }
166
167 Ok(())
168 }
169 }
170
171 let mut writer = X { prefix };
172 write!(writer, "{self}").is_ok()
173 }
174
175 fn is_non_capitalized_ascii(&self) -> bool {
177 struct X {
181 empty: bool,
183 }
184
185 impl Write for X {
186 fn write_str(&mut self, s: &str) -> std::fmt::Result {
187 if self.empty {
188 self.empty = s.is_empty();
190 if s.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
192 return Err(std::fmt::Error);
194 }
195 }
196
197 s.is_ascii().then_some(()).ok_or(std::fmt::Error)
199 }
200 }
201
202 let mut writer = X { empty: true };
203 write!(writer, "{self}").is_ok_and(|_| !writer.empty)
205 }
206}
207
208impl<T: Display> DisplayExt for T {}
209
210fn join_errors(mut it: impl Iterator<Item = syn::Error>) -> syn::Result<()> {
213 it.next().map_or(Ok(()), |mut err| {
214 for other in it {
215 err.combine(other);
216 }
217 Err(err)
218 })
219}
220
221fn is_ide_completion() -> bool {
222 match std::env::var_os("RUST_IDE_PROC_MACRO_COMPLETION_DUMMY_IDENTIFIER") {
223 None => false,
224 Some(dummy_identifier) => !dummy_identifier.is_empty(),
225 }
226}
227
228#[proc_macro_derive(Properties, attributes(prop_or, prop_or_else, prop_or_default))]
229pub fn derive_props(input: TokenStream) -> TokenStream {
230 let mut input = parse_macro_input!(input as DerivePropsInput);
231 input.normalise();
232 TokenStream::from(input.into_token_stream())
233}
234
235#[proc_macro_error::proc_macro_error]
236#[proc_macro]
237pub fn html_nested(input: TokenStream) -> TokenStream {
238 let root = parse_macro_input!(input as HtmlRoot);
239 TokenStream::from(root.into_token_stream())
240}
241
242#[proc_macro_error::proc_macro_error]
243#[proc_macro]
244pub fn html(input: TokenStream) -> TokenStream {
245 let root = parse_macro_input!(input as HtmlRootVNode);
246 TokenStream::from(root.into_token_stream())
247}
248
249#[proc_macro]
250pub fn props(input: TokenStream) -> TokenStream {
251 let props = parse_macro_input!(input as props::PropsMacroInput);
252 TokenStream::from(props.into_token_stream())
253}
254
255#[proc_macro]
256pub fn classes(input: TokenStream) -> TokenStream {
257 let classes = parse_macro_input!(input as classes::Classes);
258 TokenStream::from(classes.into_token_stream())
259}
260
261#[proc_macro_error::proc_macro_error]
262#[proc_macro_attribute]
263pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream {
264 let item = parse_macro_input!(item as FunctionComponent);
265 let attr = parse_macro_input!(attr as FunctionComponentName);
266
267 function_component_impl(attr, item)
268 .unwrap_or_else(|err| err.to_compile_error())
269 .into()
270}
271
272#[proc_macro_error::proc_macro_error]
273#[proc_macro_attribute]
274pub fn hook(attr: TokenStream, item: TokenStream) -> TokenStream {
275 let item = parse_macro_input!(item as HookFn);
276
277 if let Some(m) = proc_macro2::TokenStream::from(attr).into_iter().next() {
278 return syn::Error::new_spanned(m, "hook attribute does not accept any arguments")
279 .into_compile_error()
280 .into();
281 }
282
283 hook_impl(item)
284 .unwrap_or_else(|err| err.to_compile_error())
285 .into()
286}
287
288#[proc_macro]
289pub fn use_prepared_state_with_closure(input: TokenStream) -> TokenStream {
290 let prepared_state = parse_macro_input!(input as PreparedState);
291 prepared_state.to_token_stream_with_closure().into()
292}
293
294#[proc_macro]
295pub fn use_prepared_state_without_closure(input: TokenStream) -> TokenStream {
296 let prepared_state = parse_macro_input!(input as PreparedState);
297 prepared_state.to_token_stream_without_closure().into()
298}
299
300#[proc_macro]
301pub fn use_transitive_state_with_closure(input: TokenStream) -> TokenStream {
302 let transitive_state = parse_macro_input!(input as TransitiveState);
303 transitive_state.to_token_stream_with_closure().into()
304}
305
306#[proc_macro]
307pub fn use_transitive_state_without_closure(input: TokenStream) -> TokenStream {
308 let transitive_state = parse_macro_input!(input as TransitiveState);
309 transitive_state.to_token_stream_without_closure().into()
310}
311
312#[cfg(test)]
313mod tests {
314 use std::fmt::{Display, Formatter, Write};
315
316 use rand::rngs::SmallRng;
317 use rand::{Rng, SeedableRng};
318
319 use crate::DisplayExt;
320
321 const N_ITERS: usize = 0x4000;
322 const STR_LEN: usize = 32;
323
324 struct DisplayObfuscator<'a>(&'a str);
328
329 impl Display for DisplayObfuscator<'_> {
330 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
331 for ch in self.0.chars() {
332 f.write_char(ch)?;
333 }
334 Ok(())
335 }
336 }
337
338 struct Lowercaser<'a>(&'a str);
340
341 impl Display for Lowercaser<'_> {
342 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
343 for ch in self.0.chars() {
344 f.write_char(ch.to_ascii_lowercase())?;
345 }
346 Ok(())
347 }
348 }
349
350 #[test]
351 fn display_ext_works() {
352 let rng = &mut SmallRng::from_os_rng();
353 let mut s = String::with_capacity(STR_LEN);
354
355 for i in 0..N_ITERS {
356 s.clear();
357 s.extend(
359 rng.random_iter::<u8>()
360 .take(STR_LEN)
361 .map(|b| (b & 127) as char),
362 );
363
364 assert!(Lowercaser(&s).eq_str_ignore_ascii_case(&s));
365 assert!(DisplayObfuscator(&s).eq_str(&s));
366 assert!(DisplayObfuscator(&s).starts_with(&s[..i % STR_LEN]));
367 assert_eq!(
368 DisplayObfuscator(&s).is_non_capitalized_ascii(),
369 s.is_non_capitalized_ascii()
370 );
371 }
372 }
373}