use inflections::case::to_pascal_case;
use itertools::Itertools as _;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse_quote;
use synthez::{ParseAttrs, ToTokens};
pub(crate) fn derive(input: TokenStream) -> syn::Result<TokenStream> {
let input = syn::parse2::<syn::DeriveInput>(input)?;
let definition = Definition::try_from(input)?;
Ok(quote! { #definition })
}
#[derive(Debug, Default, ParseAttrs)]
struct Attrs {
#[parse(value)]
init: Option<syn::ExprPath>,
}
#[derive(Debug, ToTokens)]
#[to_tokens(append(impl_world_inventory, impl_world, impl_step_constructors))]
struct Definition {
ident: syn::Ident,
generics: syn::Generics,
vis: syn::Visibility,
init: Option<syn::ExprPath>,
}
impl TryFrom<syn::DeriveInput> for Definition {
type Error = syn::Error;
fn try_from(input: syn::DeriveInput) -> syn::Result<Self> {
let attrs: Attrs = Attrs::parse_attrs("world", &input)?;
Ok(Self {
ident: input.ident,
generics: input.generics,
vis: input.vis,
init: attrs.init,
})
}
}
impl Definition {
const STEPS: &'static [&'static str] = &["given", "when", "then"];
#[allow(clippy::manual_assert)] const EXACTLY_3_STEPS: () = if Self::STEPS.len() != 3 {
panic!("Expected exactly 3 step names");
};
fn impl_world_inventory(&self) -> TokenStream {
let world = &self.ident;
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
let (given_ty, when_step_ty, then_ty) = self
.step_types()
.collect_tuple()
.unwrap_or_else(|| unreachable!("{:?}", Self::EXACTLY_3_STEPS));
quote! {
#[automatically_derived]
impl #impl_gens ::cucumber::codegen::WorldInventory
for #world #ty_gens
#where_clause
{
type Given = #given_ty;
type When = #when_step_ty;
type Then = #then_ty;
}
}
}
fn impl_world(&self) -> TokenStream {
let world = &self.ident;
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
let init = self.init.clone().unwrap_or_else(
|| parse_quote! { <Self as ::std::default::Default>::default },
);
quote! {
#[automatically_derived]
#[::cucumber::codegen::async_trait(?Send)]
impl #impl_gens ::cucumber::World for #world #ty_gens
#where_clause
{
type Error = ::cucumber::codegen::anyhow::Error;
async fn new() -> ::std::result::Result<Self, Self::Error> {
use ::cucumber::codegen::{
IntoWorldResult as _, ToWorldFuture as _,
};
fn as_fn_ptr<T>(v: fn() -> T) -> fn() -> T {
v
}
(&as_fn_ptr(#init))
.to_world_future()
.await
.into_world_result()
.map_err(::std::convert::Into::into)
}
}
}
}
#[must_use]
fn impl_step_constructors(&self) -> TokenStream {
let world = &self.ident;
let world_vis = &self.vis;
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
self.step_types()
.map(|ty| {
quote! {
#[automatically_derived]
#[doc(hidden)]
#world_vis struct #ty {
#[doc(hidden)]
#world_vis loc: ::cucumber::step::Location,
#[doc(hidden)]
#world_vis regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
#world_vis func: ::cucumber::Step<#world>,
}
#[automatically_derived]
impl #impl_gens
::cucumber::codegen::StepConstructor<#world #ty_gens>
for #ty #where_clause
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<#world>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(#ty);
}
})
.collect()
}
fn step_types(&self) -> impl Iterator<Item = syn::Ident> + '_ {
Self::STEPS.iter().map(|step| {
format_ident!("Cucumber{}{}", to_pascal_case(step), self.ident)
})
}
}
#[cfg(test)]
mod spec {
use quote::quote;
use syn::parse_quote;
#[test]
fn derives_impl() {
let input = parse_quote! {
pub struct World;
};
let output = quote! {
#[automatically_derived]
impl ::cucumber::codegen::WorldInventory for World {
type Given = CucumberGivenWorld;
type When = CucumberWhenWorld;
type Then = CucumberThenWorld;
}
#[automatically_derived]
#[::cucumber::codegen::async_trait(?Send)]
impl ::cucumber::World for World {
type Error = ::cucumber::codegen::anyhow::Error;
async fn new() -> ::std::result::Result<Self, Self::Error> {
use ::cucumber::codegen::{
IntoWorldResult as _, ToWorldFuture as _,
};
fn as_fn_ptr<T>(v: fn() -> T) -> fn() -> T {
v
}
(&as_fn_ptr(<Self as ::std::default::Default>::default))
.to_world_future()
.await
.into_world_result()
.map_err(::std::convert::Into::into)
}
}
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberGivenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberGivenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberGivenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberWhenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberWhenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberWhenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberThenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberThenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberThenWorld);
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_generics() {
let input = parse_quote! {
pub struct World<T>(T);
};
let output = quote! {
#[automatically_derived]
impl<T> ::cucumber::codegen::WorldInventory for World<T> {
type Given = CucumberGivenWorld;
type When = CucumberWhenWorld;
type Then = CucumberThenWorld;
}
#[automatically_derived]
#[::cucumber::codegen::async_trait(?Send)]
impl<T> ::cucumber::World for World<T> {
type Error = ::cucumber::codegen::anyhow::Error;
async fn new() -> ::std::result::Result<Self, Self::Error> {
use ::cucumber::codegen::{
IntoWorldResult as _, ToWorldFuture as _,
};
fn as_fn_ptr<T>(v: fn() -> T) -> fn() -> T {
v
}
(&as_fn_ptr(<Self as ::std::default::Default>::default))
.to_world_future()
.await
.into_world_result()
.map_err(::std::convert::Into::into)
}
}
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberGivenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberGivenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberGivenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberWhenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberWhenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberWhenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberThenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberThenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberThenWorld);
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_init_fn() {
let input = parse_quote! {
#[world(init = Self::custom)]
pub struct World<T>(T);
};
let output = quote! {
#[automatically_derived]
impl<T> ::cucumber::codegen::WorldInventory for World<T> {
type Given = CucumberGivenWorld;
type When = CucumberWhenWorld;
type Then = CucumberThenWorld;
}
#[automatically_derived]
#[::cucumber::codegen::async_trait(?Send)]
impl<T> ::cucumber::World for World<T> {
type Error = ::cucumber::codegen::anyhow::Error;
async fn new() -> ::std::result::Result<Self, Self::Error> {
use ::cucumber::codegen::{
IntoWorldResult as _, ToWorldFuture as _,
};
fn as_fn_ptr<T>(v: fn() -> T) -> fn() -> T {
v
}
(&as_fn_ptr(Self::custom))
.to_world_future()
.await
.into_world_result()
.map_err(::std::convert::Into::into)
}
}
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberGivenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberGivenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberGivenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberWhenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberWhenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberWhenWorld);
#[automatically_derived]
#[doc(hidden)]
pub struct CucumberThenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,
#[doc(hidden)]
pub regex: ::cucumber::codegen::LazyRegex,
#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}
#[automatically_derived]
impl<T> ::cucumber::codegen::StepConstructor<World<T> > for
CucumberThenWorld
{
fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::LazyRegex,
::cucumber::Step<World>,
) {
(self.loc, self.regex, self.func)
}
}
#[automatically_derived]
::cucumber::codegen::collect!(CucumberThenWorld);
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
}