* Fix: clippy, hotreload css blocks, raw blocks * fix test * okay nevermind
277 lines
8.7 KiB
Rust
277 lines
8.7 KiB
Rust
use proc_macro2::Span;
|
|
use quote::quote;
|
|
use quote::ToTokens;
|
|
use std::fmt::Display;
|
|
use std::ops::Deref;
|
|
use syn::{
|
|
parse::{Parse, ParseStream},
|
|
Lit, LitBool, LitFloat, LitInt, LitStr,
|
|
};
|
|
|
|
use crate::{location::DynIdx, IfmtInput, Segment};
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
/// A literal value in the rsx! macro
|
|
///
|
|
/// These get hotreloading super powers, making them a bit more complex than a normal literal.
|
|
/// In debug mode we need to generate a bunch of extra code to support hotreloading.
|
|
///
|
|
/// Eventually we want to remove this notion of hot literals since we're generating different code
|
|
/// in debug than in release, which is harder to maintain and can lead to bugs.
|
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
|
pub enum HotLiteral {
|
|
/// A *formatted* string literal
|
|
/// We know this will generate a String, not an &'static str
|
|
///
|
|
/// The raw str type will generate a &'static str, but we need to distinguish the two for component props
|
|
///
|
|
/// "hello {world}"
|
|
Fmted(HotReloadFormattedSegment),
|
|
|
|
/// A float literal
|
|
///
|
|
/// 1.0
|
|
Float(LitFloat),
|
|
|
|
/// An int literal
|
|
///
|
|
/// 1
|
|
Int(LitInt),
|
|
|
|
/// A bool literal
|
|
///
|
|
/// true
|
|
Bool(LitBool),
|
|
}
|
|
|
|
impl HotLiteral {
|
|
pub fn quote_as_hot_reload_literal(&self) -> TokenStream2 {
|
|
match &self {
|
|
HotLiteral::Fmted(f) => quote! { dioxus_core::internal::HotReloadLiteral::Fmted(#f) },
|
|
HotLiteral::Float(f) => {
|
|
quote! { dioxus_core::internal::HotReloadLiteral::Float(#f as _) }
|
|
}
|
|
HotLiteral::Int(f) => quote! { dioxus_core::internal::HotReloadLiteral::Int(#f as _) },
|
|
HotLiteral::Bool(f) => quote! { dioxus_core::internal::HotReloadLiteral::Bool(#f) },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parse for HotLiteral {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
let raw = input.parse::<Lit>()?;
|
|
|
|
let value = match raw.clone() {
|
|
Lit::Int(a) => HotLiteral::Int(a),
|
|
Lit::Bool(a) => HotLiteral::Bool(a),
|
|
Lit::Float(a) => HotLiteral::Float(a),
|
|
Lit::Str(a) => HotLiteral::Fmted(IfmtInput::new_litstr(a)?.into()),
|
|
_ => {
|
|
return Err(syn::Error::new(
|
|
raw.span(),
|
|
"Only string, int, float, and bool literals are supported",
|
|
))
|
|
}
|
|
};
|
|
|
|
Ok(value)
|
|
}
|
|
}
|
|
|
|
impl ToTokens for HotLiteral {
|
|
fn to_tokens(&self, out: &mut proc_macro2::TokenStream) {
|
|
match &self {
|
|
HotLiteral::Fmted(f) => {
|
|
f.formatted_input.to_tokens(out);
|
|
}
|
|
HotLiteral::Float(f) => f.to_tokens(out),
|
|
HotLiteral::Int(f) => f.to_tokens(out),
|
|
HotLiteral::Bool(f) => f.to_tokens(out),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HotLiteral {
|
|
pub fn span(&self) -> Span {
|
|
match self {
|
|
HotLiteral::Fmted(f) => f.span(),
|
|
HotLiteral::Float(f) => f.span(),
|
|
HotLiteral::Int(f) => f.span(),
|
|
HotLiteral::Bool(f) => f.span(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HotLiteral {
|
|
// We can only handle a few types of literals - the rest need to be expressions
|
|
// todo on adding more of course - they're not hard to support, just work
|
|
pub fn peek(input: ParseStream) -> bool {
|
|
if input.peek(Lit) {
|
|
let lit = input.fork().parse::<Lit>().unwrap();
|
|
|
|
matches!(
|
|
lit,
|
|
Lit::Str(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_)
|
|
)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn is_static(&self) -> bool {
|
|
match &self {
|
|
HotLiteral::Fmted(fmt) => fmt.is_static(),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn from_raw_text(text: &str) -> Self {
|
|
HotLiteral::Fmted(HotReloadFormattedSegment::from(IfmtInput {
|
|
source: LitStr::new(text, Span::call_site()),
|
|
segments: vec![],
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl Display for HotLiteral {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match &self {
|
|
HotLiteral::Fmted(l) => l.to_string_with_quotes().fmt(f),
|
|
HotLiteral::Float(l) => l.fmt(f),
|
|
HotLiteral::Int(l) => l.fmt(f),
|
|
HotLiteral::Bool(l) => l.value().fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A formatted segment that can be hot reloaded
|
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
|
pub struct HotReloadFormattedSegment {
|
|
pub formatted_input: IfmtInput,
|
|
pub dynamic_node_indexes: Vec<DynIdx>,
|
|
}
|
|
|
|
impl HotReloadFormattedSegment {
|
|
/// This method is very important!
|
|
/// Deref + Spanned + .span() methods leads to name collisions
|
|
pub fn span(&self) -> Span {
|
|
self.formatted_input.span()
|
|
}
|
|
}
|
|
|
|
impl Deref for HotReloadFormattedSegment {
|
|
type Target = IfmtInput;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.formatted_input
|
|
}
|
|
}
|
|
|
|
impl From<IfmtInput> for HotReloadFormattedSegment {
|
|
fn from(input: IfmtInput) -> Self {
|
|
let mut dynamic_node_indexes = Vec::new();
|
|
for segment in &input.segments {
|
|
if let Segment::Formatted { .. } = segment {
|
|
dynamic_node_indexes.push(DynIdx::default());
|
|
}
|
|
}
|
|
Self {
|
|
formatted_input: input,
|
|
dynamic_node_indexes,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parse for HotReloadFormattedSegment {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
let ifmt: IfmtInput = input.parse()?;
|
|
Ok(Self::from(ifmt))
|
|
}
|
|
}
|
|
|
|
impl ToTokens for HotReloadFormattedSegment {
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
let mut idx = 0_usize;
|
|
let segments = self.segments.iter().map(|s| match s {
|
|
Segment::Literal(lit) => quote! {
|
|
dioxus_core::internal::FmtSegment::Literal { value: #lit }
|
|
},
|
|
Segment::Formatted(_fmt) => {
|
|
// increment idx for the dynamic segment so we maintain the mapping
|
|
let _idx = self.dynamic_node_indexes[idx].get();
|
|
idx += 1;
|
|
quote! {
|
|
dioxus_core::internal::FmtSegment::Dynamic { id: #_idx }
|
|
}
|
|
}
|
|
});
|
|
|
|
// The static segments with idxs for locations
|
|
tokens.extend(quote! {
|
|
dioxus_core::internal::FmtedSegments::new( vec![ #(#segments),* ], )
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use prettier_please::PrettyUnparse;
|
|
|
|
#[test]
|
|
fn parses_lits() {
|
|
let _ = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
|
|
let _ = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
|
|
let _ = syn::parse2::<HotLiteral>(quote! { 1 }).unwrap();
|
|
let _ = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();
|
|
let _ = syn::parse2::<HotLiteral>(quote! { false }).unwrap();
|
|
let _ = syn::parse2::<HotLiteral>(quote! { true }).unwrap();
|
|
|
|
// Refuses the other unsupported types - we could add them if we wanted to
|
|
assert!(syn::parse2::<HotLiteral>(quote! { b"123" }).is_err());
|
|
assert!(syn::parse2::<HotLiteral>(quote! { 'a' }).is_err());
|
|
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
|
|
assert!(matches!(lit, HotLiteral::Fmted(_)));
|
|
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
|
|
assert!(matches!(lit, HotLiteral::Fmted(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn outputs_a_signal() {
|
|
// Should output a type of f64 which we convert into whatever the expected type is via "into"
|
|
// todo: hmmmmmmmmmmmm might not always work
|
|
let lit = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();
|
|
println!("{}", lit.to_token_stream().pretty_unparse());
|
|
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hi" }).unwrap();
|
|
println!("{}", lit.to_token_stream().pretty_unparse());
|
|
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hi {world}" }).unwrap();
|
|
println!("{}", lit.to_token_stream().pretty_unparse());
|
|
}
|
|
|
|
#[test]
|
|
fn static_str_becomes_str() {
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
|
|
let HotLiteral::Fmted(segments) = &lit else {
|
|
panic!("expected a formatted string");
|
|
};
|
|
assert!(segments.is_static());
|
|
assert_eq!(r##""hello""##, segments.to_string_with_quotes());
|
|
println!("{}", lit.to_token_stream().pretty_unparse());
|
|
}
|
|
|
|
#[test]
|
|
fn formatted_prints_as_formatted() {
|
|
let lit = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
|
|
let HotLiteral::Fmted(segments) = &lit else {
|
|
panic!("expected a formatted string");
|
|
};
|
|
assert!(!segments.is_static());
|
|
assert_eq!(r##""hello {world}""##, segments.to_string_with_quotes());
|
|
println!("{}", lit.to_token_stream().pretty_unparse());
|
|
}
|
|
}
|