diff --git a/Cargo.lock b/Cargo.lock index b3e1b607a4..78ee25cfa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1944,6 +1944,15 @@ dependencies = [ [[package]] name = "leptos_macro" version = "0.8.0-rc3" +dependencies = [ + "leptos_macro_core", + "proc-macro-error2", + "proc-macro2", +] + +[[package]] +name = "leptos_macro_core" +version = "0.8.0-rc3" dependencies = [ "attribute-derive", "cfg-if", @@ -2557,9 +2566,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 036e854888..390d78160c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,42 @@ [workspace] resolver = "2" members = [ - # utilities - "oco", - "any_spawner", - "const_str_slice_concat", - "either_of", - "next_tuple", - "oco", - "or_poisoned", + # utilities + "oco", + "any_spawner", + "const_str_slice_concat", + "either_of", + "next_tuple", + "oco", + "or_poisoned", - # core - "hydration_context", - "leptos", - "leptos_dom", - "leptos_config", - "leptos_hot_reload", - "leptos_macro", - "leptos_server", - "reactive_graph", - "reactive_stores", - "reactive_stores_macro", - "server_fn", - "server_fn_macro", - "server_fn/server_fn_macro_default", - "tachys", + # core + "hydration_context", + "leptos", + "leptos_dom", + "leptos_config", + "leptos_hot_reload", + "leptos_macro", + "leptos_macro/leptos_macro_core", + "leptos_server", + "reactive_graph", + "reactive_stores", + "reactive_stores_macro", + "server_fn", + "server_fn_macro", + "server_fn/server_fn_macro_default", + "tachys", - # integrations - "integrations/actix", - "integrations/axum", - "integrations/utils", + # integrations + "integrations/actix", + "integrations/axum", + "integrations/utils", - # libraries - "meta", - "router", - "router_macro", - "any_error", + # libraries + "meta", + "router", + "router_macro", + "any_error", ] exclude = ["benchmarks", "examples", "projects"] @@ -58,6 +59,7 @@ leptos_dom = { path = "./leptos_dom", version = "0.8.0-rc3" } leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-rc3" } leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-rc3" } leptos_macro = { path = "./leptos_macro", version = "0.8.0-rc3" } +leptos_macro_core = { path = "./leptos_macro/leptos_macro_core", version = "0.8.0-rc3" } leptos_router = { path = "./router", version = "0.8.0-rc3" } leptos_router_macro = { path = "./router_macro", version = "0.8.0-rc3" } leptos_server = { path = "./leptos_server", version = "0.8.0-rc3" } @@ -90,6 +92,6 @@ max_combination_size = 2 [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(leptos_debuginfo)', - 'cfg(erase_components)', + 'cfg(leptos_debuginfo)', + 'cfg(erase_components)', ] } diff --git a/leptos_macro/Cargo.toml b/leptos_macro/Cargo.toml index 578c5be29e..483cc21922 100644 --- a/leptos_macro/Cargo.toml +++ b/leptos_macro/Cargo.toml @@ -2,7 +2,6 @@ name = "leptos_macro" version = { workspace = true } authors = ["Greg Johnston"] -license = "MIT" repository = "https://github.com/leptos-rs/leptos" description = "view macro for the Leptos web framework." readme = "../README.md" @@ -13,90 +12,26 @@ edition.workspace = true proc-macro = true [dependencies] -attribute-derive = { version = "0.10.3", features = ["syn-full"] } -cfg-if = "1.0" -html-escape = "0.2.13" -itertools = { workspace = true } -prettyplease = "0.2.25" -proc-macro-error2 = { version = "2.0", default-features = false } -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full"] } -rstml = "0.12.0" -leptos_hot_reload = { workspace = true } -server_fn_macro = { workspace = true } -convert_case = { workspace = true } -uuid = { version = "1.11", features = ["v4"] } -tracing = { version = "0.1.41", optional = true } - -[dev-dependencies] -log = "0.4.22" -typed-builder = "0.20.0" -trybuild = { workspace = true } -leptos = { path = "../leptos" } -leptos_router = { path = "../router", features = ["ssr"] } -server_fn = { path = "../server_fn", features = ["cbor"] } -insta = "1.41" -serde = "1.0" - -[build-dependencies] -rustc_version = "0.4.1" +leptos_macro_core.workspace = true +proc-macro-error2 = "2.0.1" +proc-macro2 = "1.0.95" [features] -csr = [] -hydrate = [] -ssr = ["server_fn_macro/ssr", "leptos/ssr"] -nightly = ["server_fn_macro/nightly"] -tracing = ["dep:tracing"] -islands = [] -trace-components = [] -trace-component-props = [] -actix = ["server_fn_macro/actix"] -axum = ["server_fn_macro/axum"] -generic = ["server_fn_macro/generic"] +csr = ["leptos_macro_core/csr"] +hydrate = ["leptos_macro_core/hydrate"] +ssr = ["leptos_macro_core/ssr"] +nightly = ["leptos_macro_core/nightly"] +tracing = ["leptos_macro_core/tracing"] +islands = ["leptos_macro_core/islands"] +trace-components = ["leptos_macro_core/trace-components"] +trace-component-props = ["leptos_macro_core/trace-component-props"] +actix = ["leptos_macro_core/actix"] +axum = ["leptos_macro_core/axum"] +generic = ["leptos_macro_core/generic"] # Having an erasure feature rather than normal --cfg erase_components for the proc macro crate is a workaround for this rust issue: # https://github.com/rust-lang/cargo/issues/4423 # TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command. # This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't. # This is automatic as long as the leptos crate is depended upon, # downstream usage should never manually enable this feature. -__internal_erase_components = [] - -[package.metadata.cargo-all-features] -denylist = ["tracing", "trace-component-props", "trace-components"] -skip_feature_sets = [ - [ - "csr", - "hydrate", - ], - [ - "hydrate", - "csr", - ], - [ - "hydrate", - "ssr", - ], - [ - "actix", - "axum", - ], - [ - "actix", - "generic", - ], - [ - "generic", - "axum", - ], - [ - "nightly", - ], -] -max_combination_size = 2 - -[package.metadata.docs.rs] -rustdoc-args = ["--generate-link-to-definition"] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rustc_nightly)'] } +__internal_erase_components = ["leptos_macro_core/__internal_erase_components"] diff --git a/leptos_macro/leptos_macro_core/Cargo.toml b/leptos_macro/leptos_macro_core/Cargo.toml new file mode 100644 index 0000000000..87d47602be --- /dev/null +++ b/leptos_macro/leptos_macro_core/Cargo.toml @@ -0,0 +1,99 @@ +[package] +name = "leptos_macro_core" +version = { workspace = true } +authors = ["Greg Johnston"] +license = "MIT" +repository = "https://github.com/leptos-rs/leptos" +description = "Internal code for the view macro for the Leptos web framework." +readme = "../../README.md" +rust-version.workspace = true +edition.workspace = true + +[dependencies] +attribute-derive = { version = "0.10.3", features = ["syn-full"] } +cfg-if = "1.0" +html-escape = "0.2.13" +itertools = { workspace = true } +prettyplease = "0.2.25" +proc-macro-error2 = { version = "2.0", default-features = false } +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } +rstml = "0.12.0" +leptos_hot_reload = { workspace = true } +server_fn_macro = { workspace = true } +convert_case = { workspace = true } +uuid = { version = "1.11", features = ["v4"] } +tracing = { version = "0.1.41", optional = true } + +[dev-dependencies] +log = "0.4.22" +typed-builder = "0.20.0" +trybuild = { workspace = true } +leptos = { workspace = true } +leptos_router = { workspace = true, features = ["ssr"] } +server_fn = { workspace = true, features = ["cbor"] } +insta = "1.41" +serde = "1.0" + +[build-dependencies] +rustc_version = "0.4.1" + +[features] +csr = [] +hydrate = [] +ssr = ["server_fn_macro/ssr", "leptos/ssr"] +nightly = ["server_fn_macro/nightly"] +tracing = ["dep:tracing"] +islands = [] +trace-components = [] +trace-component-props = [] +actix = ["server_fn_macro/actix"] +axum = ["server_fn_macro/axum"] +generic = ["server_fn_macro/generic"] +# Having an erasure feature rather than normal --cfg erase_components for the proc macro crate is a workaround for this rust issue: +# https://github.com/rust-lang/cargo/issues/4423 +# TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command. +# This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't. +# This is automatic as long as the leptos crate is depended upon, +# downstream usage should never manually enable this feature. +__internal_erase_components = [] + +[package.metadata.cargo-all-features] +denylist = ["tracing", "trace-component-props", "trace-components"] +skip_feature_sets = [ + [ + "csr", + "hydrate", + ], + [ + "hydrate", + "csr", + ], + [ + "hydrate", + "ssr", + ], + [ + "actix", + "axum", + ], + [ + "actix", + "generic", + ], + [ + "generic", + "axum", + ], + [ + "nightly", + ], +] +max_combination_size = 2 + +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rustc_nightly)'] } diff --git a/leptos_macro/build.rs b/leptos_macro/leptos_macro_core/build.rs similarity index 100% rename from leptos_macro/build.rs rename to leptos_macro/leptos_macro_core/build.rs diff --git a/leptos_macro/src/component.rs b/leptos_macro/leptos_macro_core/src/component.rs similarity index 100% rename from leptos_macro/src/component.rs rename to leptos_macro/leptos_macro_core/src/component.rs diff --git a/leptos_macro/src/lazy.rs b/leptos_macro/leptos_macro_core/src/lazy.rs similarity index 82% rename from leptos_macro/src/lazy.rs rename to leptos_macro/leptos_macro_core/src/lazy.rs index 65fc2bce92..5bdd2285ec 100644 --- a/leptos_macro/src/lazy.rs +++ b/leptos_macro/leptos_macro_core/src/lazy.rs @@ -1,15 +1,14 @@ use convert_case::{Case, Casing}; -use proc_macro::TokenStream; -use proc_macro2::Ident; +use proc_macro2::{Ident, TokenStream}; use proc_macro_error2::abort; use quote::quote; use syn::{spanned::Spanned, ItemFn}; pub fn lazy_impl( - _args: proc_macro::TokenStream, + _args: proc_macro2::TokenStream, s: TokenStream, ) -> TokenStream { - let fun = syn::parse::(s).unwrap_or_else(|e| { + let fun = syn::parse2::(s).unwrap_or_else(|e| { abort!(e.span(), "`lazy` can only be used on a function") }); if fun.sig.asyncness.is_none() { diff --git a/leptos_macro/leptos_macro_core/src/lib.rs b/leptos_macro/leptos_macro_core/src/lib.rs new file mode 100644 index 0000000000..9a5a4f90d8 --- /dev/null +++ b/leptos_macro/leptos_macro_core/src/lib.rs @@ -0,0 +1,125 @@ +//! Internal code powering the `leptos::view!` macro, and all other leptos macros + +#![cfg_attr(all(feature = "nightly", rustc_nightly), feature(proc_macro_span))] +#![forbid(unsafe_code)] +// to prevent warnings from popping up when a nightly feature is stabilized +#![allow(stable_features)] +// FIXME? every use of quote! {} is warning here -- false positive? +#![allow(unknown_lints)] +#![allow(private_macro_use)] +#![deny(missing_docs)] + +#[macro_use] +extern crate proc_macro_error2; + +use component::DummyModel; +use proc_macro2::{Span, TokenTree}; +use quote::{quote, ToTokens}; +use std::str::FromStr; +use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility}; + +mod params; +mod view; +use crate::component::unmodified_fn_name_from_fn_name; +mod component; +mod lazy; +mod memo; +mod slice; +mod slot; + +fn handle_global_class( + tokens: proc_macro2::TokenStream, +) -> (proc_macro2::TokenStream, Option) { + let mut tokens = tokens.into_iter(); + + let first = tokens.next(); + let second = tokens.next(); + let third = tokens.next(); + let fourth = tokens.next(); + let global_class = match (&first, &second) { + (Some(TokenTree::Ident(first)), Some(TokenTree::Punct(eq))) + if *first == "class" && eq.as_char() == '=' => + { + match &fourth { + Some(TokenTree::Punct(comma)) if comma.as_char() == ',' => { + third.clone() + } + _ => { + abort!( + second, "To create a scope class with the view! macro you must put a comma `,` after the value"; + help = r#"e.g., view!{ class="my-class",
...
}"# + ) + } + } + } + _ => None, + }; + let tokens = if global_class.is_some() { + tokens.collect::() + } else { + [first, second, third, fourth] + .into_iter() + .flatten() + .chain(tokens) + .collect() + }; + (tokens, global_class) +} + +/// The actual implementation of the [`leptos::view!`](https://docs.rs/leptos/0.8.0-rc3/leptos/macro.view.html) macro +pub fn view_macro_impl( + tokens: proc_macro2::TokenStream, + template: bool, +) -> proc_macro2::TokenStream { + let tokens: proc_macro2::TokenStream = tokens.into(); + let (tokens, global_class) = handle_global_class(tokens); + + let config = rstml::ParserConfig::default().recover_block(true); + let parser = rstml::Parser::new(config); + let (mut nodes, errors) = parser.parse_recoverable(tokens).split_vec(); + let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens()); + let nodes_output = view::render_view( + &mut nodes, + global_class.as_ref(), + normalized_call_site(proc_macro2::Span::call_site()), + template, + ); + + // The allow lint needs to be put here instead of at the expansion of + // view::attribute_value(). Adding this next to the expanded expression + // seems to break rust-analyzer, but it works when the allow is put here. + let output = quote! { + { + #[allow(unused_braces)] + { + #(#errors;)* + #nodes_output + } + } + }; + + if template { + quote! { + ::leptos::prelude::ViewTemplate::new(#output) + } + } else { + output + } + .into() +} + +fn normalized_call_site(site: proc_macro2::Span) -> Option { + cfg_if::cfg_if! { + if #[cfg(all(debug_assertions, feature = "nightly", rustc_nightly))] { + Some(leptos_hot_reload::span_to_stable_id( + site.file(), + site.start().line() + )) + } else { + _ = site; + None + } + } +} + +pub fn include_view_impl(tokens: proc_macro2::TokenStream) \ No newline at end of file diff --git a/leptos_macro/src/memo.rs b/leptos_macro/leptos_macro_core/src/memo.rs similarity index 100% rename from leptos_macro/src/memo.rs rename to leptos_macro/leptos_macro_core/src/memo.rs diff --git a/leptos_macro/src/params.rs b/leptos_macro/leptos_macro_core/src/params.rs similarity index 94% rename from leptos_macro/src/params.rs rename to leptos_macro/leptos_macro_core/src/params.rs index 077562f5ab..bae6feea14 100644 --- a/leptos_macro/src/params.rs +++ b/leptos_macro/leptos_macro_core/src/params.rs @@ -1,7 +1,7 @@ use quote::{quote, quote_spanned}; use syn::spanned::Spanned; -pub fn params_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { +pub fn params_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let name = &ast.ident; let fields = if let syn::Data::Struct(syn::DataStruct { diff --git a/leptos_macro/src/slice.rs b/leptos_macro/leptos_macro_core/src/slice.rs similarity index 100% rename from leptos_macro/src/slice.rs rename to leptos_macro/leptos_macro_core/src/slice.rs diff --git a/leptos_macro/src/slot.rs b/leptos_macro/leptos_macro_core/src/slot.rs similarity index 100% rename from leptos_macro/src/slot.rs rename to leptos_macro/leptos_macro_core/src/slot.rs diff --git a/leptos_macro/src/view/component_builder.rs b/leptos_macro/leptos_macro_core/src/view/component_builder.rs similarity index 100% rename from leptos_macro/src/view/component_builder.rs rename to leptos_macro/leptos_macro_core/src/view/component_builder.rs diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/leptos_macro_core/src/view/mod.rs similarity index 100% rename from leptos_macro/src/view/mod.rs rename to leptos_macro/leptos_macro_core/src/view/mod.rs diff --git a/leptos_macro/src/view/slot_helper.rs b/leptos_macro/leptos_macro_core/src/view/slot_helper.rs similarity index 100% rename from leptos_macro/src/view/slot_helper.rs rename to leptos_macro/leptos_macro_core/src/view/slot_helper.rs diff --git a/leptos_macro/src/view/snapshots/leptos_macro__view__tests__client_template__full_span__counter_component.snap b/leptos_macro/leptos_macro_core/src/view/snapshots/leptos_macro__view__tests__client_template__full_span__counter_component.snap similarity index 100% rename from leptos_macro/src/view/snapshots/leptos_macro__view__tests__client_template__full_span__counter_component.snap rename to leptos_macro/leptos_macro_core/src/view/snapshots/leptos_macro__view__tests__client_template__full_span__counter_component.snap diff --git a/leptos_macro/src/view/utils.rs b/leptos_macro/leptos_macro_core/src/view/utils.rs similarity index 100% rename from leptos_macro/src/view/utils.rs rename to leptos_macro/leptos_macro_core/src/view/utils.rs diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index c84fc10406..908ffd7cec 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -1,33 +1,5 @@ //! Macros for use with the Leptos framework. -#![cfg_attr(all(feature = "nightly", rustc_nightly), feature(proc_macro_span))] -#![forbid(unsafe_code)] -// to prevent warnings from popping up when a nightly feature is stabilized -#![allow(stable_features)] -// FIXME? every use of quote! {} is warning here -- false positive? -#![allow(unknown_lints)] -#![allow(private_macro_use)] -#![deny(missing_docs)] - -#[macro_use] -extern crate proc_macro_error2; - -use component::DummyModel; -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenTree}; -use quote::{quote, ToTokens}; -use std::str::FromStr; -use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility}; - -mod params; -mod view; -use crate::component::unmodified_fn_name_from_fn_name; -mod component; -mod lazy; -mod memo; -mod slice; -mod slot; - /// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the /// same rules as HTML, with the following differences: /// @@ -269,8 +241,8 @@ mod slot; #[proc_macro_error2::proc_macro_error] #[proc_macro] #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] -pub fn view(tokens: TokenStream) -> TokenStream { - view_macro_impl(tokens, false) +pub fn view(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + leptos_macro_core::view_macro_impl(tokens.into(), false).into() } /// The `template` macro behaves like [`view`](view!), except that it wraps the entire tree in a @@ -280,94 +252,11 @@ pub fn view(tokens: TokenStream) -> TokenStream { #[proc_macro_error2::proc_macro_error] #[proc_macro] #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] -pub fn template(tokens: TokenStream) -> TokenStream { +pub fn template(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { if cfg!(feature = "__internal_erase_components") { view(tokens) } else { - view_macro_impl(tokens, true) - } -} - -fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream { - let tokens: proc_macro2::TokenStream = tokens.into(); - let mut tokens = tokens.into_iter(); - - let first = tokens.next(); - let second = tokens.next(); - let third = tokens.next(); - let fourth = tokens.next(); - let global_class = match (&first, &second) { - (Some(TokenTree::Ident(first)), Some(TokenTree::Punct(eq))) - if *first == "class" && eq.as_char() == '=' => - { - match &fourth { - Some(TokenTree::Punct(comma)) if comma.as_char() == ',' => { - third.clone() - } - _ => { - abort!( - second, "To create a scope class with the view! macro you must put a comma `,` after the value"; - help = r#"e.g., view!{ class="my-class",
...
}"# - ) - } - } - } - _ => None, - }; - let tokens = if global_class.is_some() { - tokens.collect::() - } else { - [first, second, third, fourth] - .into_iter() - .flatten() - .chain(tokens) - .collect() - }; - let config = rstml::ParserConfig::default().recover_block(true); - let parser = rstml::Parser::new(config); - let (mut nodes, errors) = parser.parse_recoverable(tokens).split_vec(); - let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens()); - let nodes_output = view::render_view( - &mut nodes, - global_class.as_ref(), - normalized_call_site(proc_macro::Span::call_site()), - template, - ); - - // The allow lint needs to be put here instead of at the expansion of - // view::attribute_value(). Adding this next to the expanded expression - // seems to break rust-analyzer, but it works when the allow is put here. - let output = quote! { - { - #[allow(unused_braces)] - { - #(#errors;)* - #nodes_output - } - } - }; - - if template { - quote! { - ::leptos::prelude::ViewTemplate::new(#output) - } - } else { - output - } - .into() -} - -fn normalized_call_site(site: proc_macro::Span) -> Option { - cfg_if::cfg_if! { - if #[cfg(all(debug_assertions, feature = "nightly", rustc_nightly))] { - Some(leptos_hot_reload::span_to_stable_id( - site.file(), - site.start().line() - )) - } else { - _ = site; - None - } + leptos_macro_core::view_macro_impl(tokens.into(), true).into() } } @@ -380,7 +269,9 @@ fn normalized_call_site(site: proc_macro::Span) -> Option { /// the crate root rather than relative to the file from which it is called. #[proc_macro_error2::proc_macro_error] #[proc_macro] -pub fn include_view(tokens: TokenStream) -> TokenStream { +pub fn include_view( + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { let file_name = syn::parse::(tokens).unwrap_or_else(|_| { abort!( Span::call_site(),