diff --git a/Cargo.toml b/Cargo.toml index e3257964..9988f307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ structopt = "0.3.25" # "macros" feature dependencies cucumber-codegen = { version = "0.11.0-dev", path = "./codegen", optional = true } -inventory = { version = "0.1.10", optional = true } +inventory = { version = "0.2", optional = true } # "output-json" feature dependencies serde = { version = "1.0.103", features = ["derive"], optional = true } diff --git a/codegen/src/attribute.rs b/codegen/src/attribute.rs index 6a65c448..c9c9e3d5 100644 --- a/codegen/src/attribute.rs +++ b/codegen/src/attribute.rs @@ -12,6 +12,7 @@ use std::mem; +use inflections::case::to_pascal_case; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::{self, Regex}; @@ -101,7 +102,7 @@ impl Step { let func_name = &func.sig.ident; let world = parse_world_from_args(&self.func.sig)?; - let constructor_method = self.constructor_method(); + let step_type = self.step_type(); let (func_args, addon_parsing) = self.fn_arguments_and_additional_parsing()?; @@ -129,7 +130,8 @@ impl Step { ::std::boxed::Box::pin(f) } - #caller_name + let f: ::cucumber::Step<#world> = #caller_name; + f } }; @@ -137,22 +139,43 @@ impl Step { #func #[automatically_derived] - ::cucumber::codegen::submit!( - #![crate = ::cucumber::codegen] { - <#world as ::cucumber::codegen::WorldInventory< - _, _, _, - >>::#constructor_method( - ::cucumber::step::Location { - path: ::std::convert::From::from(::std::file!()), - line: ::std::line!(), - column: ::std::column!(), - }, - ::cucumber::codegen::Regex::new(#step_matcher) - .unwrap(), - #step_caller, - ) + ::cucumber::codegen::submit!({ + // TODO: Remove this, once `#![feature(more_qualified_paths)]` + // is stabilized: + // https://github.com/rust-lang/rust/issues/86935 + type StepAlias = + <#world as ::cucumber::codegen::WorldInventory>::#step_type; + + StepAlias { + loc: ::cucumber::step::Location { + path: ::std::file!(), + line: ::std::line!(), + column: ::std::column!(), + }, + regex: { + // This hack exists, as `fn item` to `fn pointer` + // coercion can be done inside `const`, but not + // `const fn`. + let lazy: ::cucumber::codegen::LazyRegex = || { + static LAZY: ::cucumber::codegen::Lazy< + ::cucumber::codegen::Regex + > = ::cucumber::codegen::Lazy::new(|| { + ::cucumber::codegen::Regex::new(#step_matcher) + .unwrap() + }); + LAZY.clone() + }; + lazy + }, + func: { + // This hack exists, as `fn item` to `fn pointer` + // coercion can be done inside `const`, but not + // `const fn`. + const F: ::cucumber::Step<#world> = #step_caller; + F + }, } - ); + }); }) } @@ -238,10 +261,10 @@ impl Step { } } - /// Composes a name of the `cucumber::codegen::WorldInventory` method to - /// wire this [`Step`] with. - fn constructor_method(&self) -> syn::Ident { - format_ident!("new_{}", self.attr_name) + /// Composes a name of the `cucumber::codegen::WorldInventory` associated + /// type to wire this [`Step`] with. + fn step_type(&self) -> syn::Ident { + format_ident!("{}", to_pascal_case(self.attr_name)) } /// Returns [`syn::Ident`] and parsing code of the given function's diff --git a/codegen/src/derive.rs b/codegen/src/derive.rs index 507de42a..cf5d3b8a 100644 --- a/codegen/src/derive.rs +++ b/codegen/src/derive.rs @@ -15,6 +15,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; /// Generates code of `#[derive(WorldInit)]` macro expansion. +#[allow(clippy::similar_names)] pub(crate) fn world_init( input: TokenStream, steps: &[&str], @@ -26,10 +27,16 @@ pub(crate) fn world_init( let step_types = step_types(steps, world); let step_structs = generate_step_structs(steps, &input); + let given_ty = &step_types[0]; + let when_ty = &step_types[1]; + let then_ty = &step_types[2]; + Ok(quote! { - impl ::cucumber::codegen::WorldInventory< - #( #step_types, )* - > for #world {} + impl ::cucumber::codegen::WorldInventory for #world { + type Given = #given_ty; + type When = #when_ty; + type Then = #then_ty; + } #( #step_structs )* }) @@ -41,10 +48,7 @@ pub(crate) fn world_init( fn step_types(steps: &[&str], world: &syn::Ident) -> Vec { steps .iter() - .map(|step| { - let step = to_pascal_case(step); - format_ident!("Cucumber{}{}", step, world) - }) + .map(|step| format_ident!("Cucumber{}{}", to_pascal_case(step), world)) .collect() } @@ -53,7 +57,8 @@ fn generate_step_structs( steps: &[&str], world: &syn::DeriveInput, ) -> Vec { - let (world, world_vis) = (&world.ident, &world.vis); + let world_vis = &world.vis; + let world = &world.ident; step_types(steps, world) .iter() @@ -63,35 +68,23 @@ fn generate_step_structs( #[doc(hidden)] #world_vis struct #ty { #[doc(hidden)] - pub loc: ::cucumber::step::Location, + #world_vis loc: ::cucumber::step::Location, #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + #world_vis regex: ::cucumber::codegen::LazyRegex, #[doc(hidden)] - pub func: ::cucumber::Step<#world>, + #world_vis func: ::cucumber::Step<#world>, } #[automatically_derived] impl ::cucumber::codegen::StepConstructor<#world> for #ty { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step<#world>, - ) -> Self { - Self { loc, regex, func } - } - fn inner(&self) -> ( ::cucumber::step::Location, - ::cucumber::codegen::Regex, + ::cucumber::codegen::LazyRegex, ::cucumber::Step<#world>, ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + (self.loc, self.regex, self.func) } } @@ -114,45 +107,35 @@ mod spec { }; let output = quote! { - impl ::cucumber::codegen::WorldInventory< - CucumberGivenWorld, CucumberWhenWorld, CucumberThenWorld, - > for World {} + impl ::cucumber::codegen::WorldInventory for World { + type Given = CucumberGivenWorld; + type When = CucumberWhenWorld; + type Then = CucumberThenWorld; + } #[automatically_derived] #[doc(hidden)] pub struct CucumberGivenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + #[doc(hidden)] + pub loc: ::cucumber::step::Location, - #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + #[doc(hidden)] + pub regex: ::cucumber::codegen::LazyRegex, - #[doc(hidden)] - pub func: ::cucumber::Step, + #[doc(hidden)] + pub func: ::cucumber::Step, } #[automatically_derived] impl ::cucumber::codegen::StepConstructor for CucumberGivenWorld { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } - } - fn inner(&self) -> ( ::cucumber::step::Location, - ::cucumber::codegen::Regex, + ::cucumber::codegen::LazyRegex, ::cucumber::Step, ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + (self.loc, self.regex, self.func) } } @@ -162,38 +145,26 @@ mod spec { #[automatically_derived] #[doc(hidden)] pub struct CucumberWhenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + #[doc(hidden)] + pub loc: ::cucumber::step::Location, - #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + #[doc(hidden)] + pub regex: ::cucumber::codegen::LazyRegex, - #[doc(hidden)] - pub func: ::cucumber::Step, + #[doc(hidden)] + pub func: ::cucumber::Step, } #[automatically_derived] impl ::cucumber::codegen::StepConstructor for CucumberWhenWorld { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } - } - fn inner(&self) -> ( ::cucumber::step::Location, - ::cucumber::codegen::Regex, + ::cucumber::codegen::LazyRegex, ::cucumber::Step, ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + (self.loc, self.regex, self.func) } } @@ -203,38 +174,26 @@ mod spec { #[automatically_derived] #[doc(hidden)] pub struct CucumberThenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + #[doc(hidden)] + pub loc: ::cucumber::step::Location, - #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + #[doc(hidden)] + pub regex: ::cucumber::codegen::LazyRegex, - #[doc(hidden)] - pub func: ::cucumber::Step, + #[doc(hidden)] + pub func: ::cucumber::Step, } #[automatically_derived] impl ::cucumber::codegen::StepConstructor for CucumberThenWorld { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } - } - fn inner(&self) -> ( ::cucumber::step::Location, - ::cucumber::codegen::Regex, + ::cucumber::codegen::LazyRegex, ::cucumber::Step, ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + (self.loc, self.regex, self.func) } } diff --git a/src/cli.rs b/src/cli.rs index af66cb4c..9b169374 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,8 @@ and may be extended with custom CLI options additionally. # } # } # -# let fut = async { +# #[tokio::main(flavor = "current_thread")] +# async fn main() { #[derive(StructOpt)] struct CustomOpts { /// Additional time to wait in before hook. @@ -83,13 +84,7 @@ MyWorld::cucumber() .with_cli(opts) .run_and_exit("tests/features/readme") .await; -# }; -# -# tokio::runtime::Builder::new_current_thread() -# .enable_all() -# .build() -# .unwrap() -# .block_on(fut); +# } ``` [`Cucumber`]: crate::Cucumber diff --git a/src/codegen.rs b/src/codegen.rs index 3aef7d0c..a774368e 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -18,17 +18,12 @@ use crate::{cucumber::DefaultCucumber, step, Cucumber, Step, World}; pub use futures::future::LocalBoxFuture; pub use inventory::{self, collect, submit}; +pub use once_cell::sync::Lazy; pub use regex::Regex; /// [`World`] extension with auto-wiring capabilities. #[async_trait(?Send)] -pub trait WorldInit: WorldInventory -where - Self: Debug, - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, -{ +pub trait WorldInit: Debug + WorldInventory { /// Returns runner for tests with auto-wired steps marked by [`given`], /// [`when`] and [`then`] attributes. /// @@ -39,19 +34,19 @@ where fn collection() -> step::Collection { let mut out = step::Collection::new(); - for given in Self::cucumber_given() { + for given in inventory::iter:: { let (loc, regex, fun) = given.inner(); - out = out.given(Some(loc), regex, fun); + out = out.given(Some(loc), regex(), fun); } - for when in Self::cucumber_when() { + for when in inventory::iter:: { let (loc, regex, fun) = when.inner(); - out = out.when(Some(loc), regex, fun); + out = out.when(Some(loc), regex(), fun); } - for then in Self::cucumber_then() { + for then in inventory::iter:: { let (loc, regex, fun) = then.inner(); - out = out.then(Some(loc), regex, fun); + out = out.then(Some(loc), regex(), fun); } out @@ -112,83 +107,36 @@ where } } -impl WorldInit for E -where - Self: Debug, - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, - E: WorldInventory, -{ -} +impl WorldInit for T where T: Debug + WorldInventory {} /// [`World`] extension allowing to register steps in [`inventory`]. -pub trait WorldInventory: World -where - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, -{ - /// Returns an [`Iterator`] over items with [`given`] attribute. - /// - /// [`given`]: crate::given - #[must_use] - fn cucumber_given() -> inventory::iter { - inventory::iter - } - - /// Creates a new [`Given`] [`Step`] value. Used by [`given`] attribute. +pub trait WorldInventory: World { + /// Struct [`submit`]ted in a [`given`] macro. /// /// [`given`]: crate::given - /// [Given]: https://cucumber.io/docs/gherkin/reference/#given - fn new_given(loc: step::Location, regex: Regex, fun: Step) -> G { - G::new(loc, regex, fun) - } - - /// Returns an [`Iterator`] over items with [`when`] attribute. - /// - /// [`when`]: crate::when - #[must_use] - fn cucumber_when() -> inventory::iter { - inventory::iter - } + type Given: inventory::Collect + StepConstructor; - /// Creates a new [`When`] [`Step`] value. Used by [`when`] attribute. + /// Struct [`submit`]ted in a [`when`] macro. /// /// [`when`]: crate::when - /// [When]: https://cucumber.io/docs/gherkin/reference/#when - fn new_when(loc: step::Location, regex: Regex, fun: Step) -> W { - W::new(loc, regex, fun) - } + type When: inventory::Collect + StepConstructor; - /// Returns an [`Iterator`] over items with [`then`] attribute. + /// Struct [`submit`]ted in a [`then`] macro. /// /// [`then`]: crate::then - #[must_use] - fn cucumber_then() -> inventory::iter { - inventory::iter - } - - /// Creates a new [`Then`] [`Step`] value. Used by [`then`] attribute. - /// - /// [`then`]: crate::then - /// [Then]: https://cucumber.io/docs/gherkin/reference/#then - fn new_then(loc: step::Location, regex: Regex, fun: Step) -> T { - T::new(loc, regex, fun) - } + type Then: inventory::Collect + StepConstructor; } -/// Trait for creating [`Step`]s to be registered by [`given`], [`when`] and -/// [`then`] attributes. +/// Alias for a [`fn`] returning a [`Lazy`] [`Regex`]. +pub type LazyRegex = fn() -> Regex; + +/// Trait for registering a [`Step`] with [`given`], [`when`] and [`then`] +/// attributes inside [`WorldInit::collection()`] method. /// /// [`given`]: crate::given /// [`when`]: crate::when /// [`then`]: crate::then pub trait StepConstructor { - /// Creates a new [`Step`] with the corresponding [`Regex`]. - #[must_use] - fn new(_: step::Location, _: Regex, _: Step) -> Self; - /// Returns an inner [`Step`] with the corresponding [`Regex`]. - fn inner(&self) -> (step::Location, Regex, Step); + fn inner(&self) -> (step::Location, LazyRegex, Step); } diff --git a/src/cucumber.rs b/src/cucumber.rs index 49206a98..c20c05cd 100644 --- a/src/cucumber.rs +++ b/src/cucumber.rs @@ -195,14 +195,13 @@ where /// # } /// # } /// # - /// # let fut = async { + /// # #[tokio::main(flavor = "current_thread")] + /// # async fn main() { /// MyWorld::cucumber() /// .repeat_skipped() /// .run_and_exit("tests/features/readme") /// .await; - /// # }; - /// # - /// # futures::executor::block_on(fut); + /// # } /// ``` ///