cucumber_codegen/
lib.rs

1// Copyright (c) 2020-2024  Brendan Molloy <brendan@bbqsrc.net>,
2//                          Ilya Solovyiov <ilya.solovyiov@gmail.com>,
3//                          Kai Ren <tyranron@gmail.com>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11#![doc(
12    html_logo_url = "https://avatars.githubusercontent.com/u/91469139?s=128",
13    html_favicon_url = "https://avatars.githubusercontent.com/u/91469139?s=256"
14)]
15#![doc = include_str!("../README.md")]
16#![deny(
17    macro_use_extern_crate,
18    nonstandard_style,
19    rust_2018_idioms,
20    rustdoc::all,
21    trivial_casts,
22    trivial_numeric_casts
23)]
24#![forbid(non_ascii_idents, unsafe_code)]
25#![warn(
26    clippy::absolute_paths,
27    clippy::as_conversions,
28    clippy::as_ptr_cast_mut,
29    clippy::assertions_on_result_states,
30    clippy::branches_sharing_code,
31    clippy::clear_with_drain,
32    clippy::clone_on_ref_ptr,
33    clippy::collection_is_never_read,
34    clippy::create_dir,
35    clippy::dbg_macro,
36    clippy::debug_assert_with_mut_call,
37    clippy::decimal_literal_representation,
38    clippy::default_union_representation,
39    clippy::derive_partial_eq_without_eq,
40    clippy::else_if_without_else,
41    clippy::empty_drop,
42    clippy::empty_line_after_outer_attr,
43    clippy::empty_structs_with_brackets,
44    clippy::equatable_if_let,
45    clippy::empty_enum_variants_with_brackets,
46    clippy::exit,
47    clippy::expect_used,
48    clippy::fallible_impl_from,
49    clippy::filetype_is_file,
50    clippy::float_cmp_const,
51    clippy::fn_to_numeric_cast,
52    clippy::fn_to_numeric_cast_any,
53    clippy::format_push_string,
54    clippy::get_unwrap,
55    clippy::if_then_some_else_none,
56    clippy::imprecise_flops,
57    clippy::index_refutable_slice,
58    clippy::infinite_loop,
59    clippy::iter_on_empty_collections,
60    clippy::iter_on_single_items,
61    clippy::iter_over_hash_type,
62    clippy::iter_with_drain,
63    clippy::large_include_file,
64    clippy::large_stack_frames,
65    clippy::let_underscore_untyped,
66    clippy::lossy_float_literal,
67    clippy::manual_c_str_literals,
68    clippy::map_err_ignore,
69    clippy::mem_forget,
70    clippy::missing_assert_message,
71    clippy::missing_asserts_for_indexing,
72    clippy::missing_const_for_fn,
73    clippy::missing_docs_in_private_items,
74    clippy::multiple_inherent_impl,
75    clippy::multiple_unsafe_ops_per_block,
76    clippy::mutex_atomic,
77    clippy::mutex_integer,
78    clippy::needless_collect,
79    clippy::needless_pass_by_ref_mut,
80    clippy::needless_raw_strings,
81    clippy::nonstandard_macro_braces,
82    clippy::option_if_let_else,
83    clippy::or_fun_call,
84    clippy::panic_in_result_fn,
85    clippy::partial_pub_fields,
86    clippy::pedantic,
87    clippy::print_stderr,
88    clippy::print_stdout,
89    clippy::pub_without_shorthand,
90    clippy::ref_as_ptr,
91    clippy::rc_buffer,
92    clippy::rc_mutex,
93    clippy::read_zero_byte_vec,
94    clippy::redundant_clone,
95    clippy::redundant_type_annotations,
96    clippy::ref_patterns,
97    clippy::rest_pat_in_fully_bound_structs,
98    clippy::same_name_method,
99    clippy::semicolon_inside_block,
100    clippy::shadow_unrelated,
101    clippy::significant_drop_in_scrutinee,
102    clippy::significant_drop_tightening,
103    clippy::str_to_string,
104    clippy::string_add,
105    clippy::string_lit_as_bytes,
106    clippy::string_lit_chars_any,
107    clippy::string_slice,
108    clippy::string_to_string,
109    clippy::suboptimal_flops,
110    clippy::suspicious_operation_groupings,
111    clippy::suspicious_xor_used_as_pow,
112    clippy::tests_outside_test_module,
113    clippy::todo,
114    clippy::trailing_empty_array,
115    clippy::transmute_undefined_repr,
116    clippy::trivial_regex,
117    clippy::try_err,
118    clippy::undocumented_unsafe_blocks,
119    clippy::unimplemented,
120    clippy::uninhabited_references,
121    clippy::unnecessary_safety_comment,
122    clippy::unnecessary_safety_doc,
123    clippy::unnecessary_self_imports,
124    clippy::unnecessary_struct_initialization,
125    clippy::unneeded_field_pattern,
126    clippy::unused_peekable,
127    clippy::unwrap_in_result,
128    clippy::unwrap_used,
129    clippy::use_debug,
130    clippy::use_self,
131    clippy::useless_let_if_seq,
132    clippy::verbose_file_reads,
133    clippy::wildcard_enum_match_arm,
134    explicit_outlives_requirements,
135    future_incompatible,
136    let_underscore_drop,
137    meta_variable_misuse,
138    missing_abi,
139    missing_copy_implementations,
140    missing_debug_implementations,
141    missing_docs,
142    redundant_lifetimes,
143    semicolon_in_expressions_from_macros,
144    single_use_lifetimes,
145    unit_bindings,
146    unnameable_types,
147    unreachable_pub,
148    unsafe_op_in_unsafe_fn,
149    unstable_features,
150    unused_crate_dependencies,
151    unused_extern_crates,
152    unused_import_braces,
153    unused_lifetimes,
154    unused_macro_rules,
155    unused_qualifications,
156    unused_results,
157    variant_size_differences
158)]
159
160mod attribute;
161mod parameter;
162mod world;
163
164use proc_macro::TokenStream;
165
166/// Helper macro for generating public shims for [`macro@given`], [`macro@when`]
167/// and [`macro@then`] attributes.
168macro_rules! step_attribute {
169    ($name:ident) => {
170        /// Attribute to auto-wire the test to the [`World`] implementer.
171        ///
172        /// There are 3 step-specific attributes:
173        /// - [`macro@given`]
174        /// - [`macro@when`]
175        /// - [`macro@then`]
176        ///
177        /// # Example
178        ///
179        /// ```
180        /// # use std::{convert::Infallible};
181        /// #
182        /// use cucumber::{given, when, World};
183        ///
184        /// #[derive(Debug, Default, World)]
185        /// struct MyWorld;
186        ///
187        /// #[given(regex = r"(\S+) is (\d+)")]
188        /// #[when(expr = "{word} is {int}")]
189        /// fn test(w: &mut MyWorld, param: String, num: i32) {
190        ///     assert_eq!(param, "foo");
191        ///     assert_eq!(num, 0);
192        /// }
193        ///
194        /// #[tokio::main]
195        /// async fn main() {
196        ///     MyWorld::run("./tests/features/doctests.feature").await;
197        /// }
198        /// ```
199        ///
200        /// # Attribute arguments
201        ///
202        /// - `#[given(regex = "regex")]`
203        ///
204        ///   Uses [`Regex`] for matching the step. [`Regex`] is checked at
205        ///   compile time to have valid syntax.
206        ///
207        /// - `#[given(expr = "cucumber-expression")]`
208        ///
209        ///   Uses [Cucumber Expression][1] for matching the step. It's checked
210        ///   at compile time to have valid syntax.
211        ///
212        /// - `#[given("literal")]`
213        ///
214        ///   Matches the step with an **exact** literal only. Doesn't allow any
215        ///   values capturing to use as function arguments.
216        ///
217        /// # Function arguments
218        ///
219        /// - First argument has to be mutable reference to the [`World`]
220        ///   deriver.
221        /// - Other argument's types have to implement [`FromStr`] or it has to
222        ///   be a slice where the element type also implements [`FromStr`].
223        /// - To use [`gherkin::Step`], name the argument as `step`,
224        ///   **or** mark the argument with a `#[step]` attribute.
225        ///
226        /// ```rust
227        /// # use std::convert::Infallible;
228        /// #
229        /// # use cucumber::{gherkin::Step, given, World};
230        /// #
231        /// # #[derive(Debug, Default, World)]
232        /// # struct MyWorld;
233        /// #
234        /// #[given(regex = r"(\S+) is not (\S+)")]
235        /// fn test_step(
236        ///     w: &mut MyWorld,
237        ///     #[step] s: &Step,
238        ///     matches: &[String],
239        /// ) {
240        ///     assert_eq!(matches[0], "foo");
241        ///     assert_eq!(matches[1], "bar");
242        ///     assert_eq!(s.value, "foo is not bar");
243        /// }
244        /// #
245        /// # #[tokio::main]
246        /// # async fn main() {
247        /// #     MyWorld::run("./tests/features/doctests.feature").await;
248        /// # }
249        /// ```
250        ///
251        /// # Return value
252        ///
253        /// A function may also return a [`Result`], which [`Err`] is expected
254        /// to implement [`Display`], so returning it will cause the step to
255        /// fail.
256        ///
257        /// [`Display`]: std::fmt::Display
258        /// [`FromStr`]: std::str::FromStr
259        /// [`Regex`]: regex::Regex
260        /// [`gherkin::Step`]: https://bit.ly/3j42hcd
261        /// [`World`]: https://bit.ly/3j0aWw7
262        /// [1]: cucumber_expressions
263        #[proc_macro_attribute]
264        pub fn $name(args: TokenStream, input: TokenStream) -> TokenStream {
265            attribute::step(std::stringify!($name), args.into(), input.into())
266                .unwrap_or_else(syn::Error::into_compile_error)
267                .into()
268        }
269    };
270}
271
272/// Helper macro for generating public shim of [`macro@given`], [`macro@when`]
273/// and [`macro@then`] attributes.
274macro_rules! steps {
275    ($($name:ident),*) => {
276        $(step_attribute!($name);)*
277    }
278}
279
280steps!(given, when, then);
281
282/// Derive macro for implementing a [`World`] trait.
283///
284/// # Example
285///
286/// ```rust
287/// #[derive(cucumber::World)]
288/// #[world(init = Self::new)] // optional, uses `Default::default()` if omitted
289/// struct World(usize);
290///
291/// impl World {
292///     fn new() -> Self {
293///         Self(42)
294///     }
295/// }
296/// ```
297///
298/// # Attribute arguments
299///
300/// - `#[world(init = path::to::fn)]`
301///
302///   Path to a function to be used for a [`World`] instance construction.
303///   Specified function can be either sync or `async`, and either fallible
304///   (return [`Result`]) or infallible (return [`World`] itself). In case no
305///   function is specified, the [`Default::default()`] will be used for
306///   construction.
307#[proc_macro_derive(World, attributes(world))]
308pub fn world(input: TokenStream) -> TokenStream {
309    world::derive(input.into())
310        .unwrap_or_else(syn::Error::into_compile_error)
311        .into()
312}
313
314/// In addition to [default parameters] of [Cucumber Expressions], you may
315/// implement and use custom ones.
316///
317/// # Example
318///
319/// ```rust
320/// # use std::{convert::Infallible};
321/// #
322/// use cucumber::{given, when, Parameter, World};
323/// use derive_more::{Deref, FromStr};
324///
325/// #[derive(Debug, Default, World)]
326/// struct MyWorld;
327///
328/// #[given(regex = r"^(\S+) is (\d+)$")]
329/// #[when(expr = "{word} is {u64}")]
330/// fn test(w: &mut MyWorld, param: String, num: CustomU64) {
331///     assert_eq!(param, "foo");
332///     assert_eq!(*num, 0);
333/// }
334///
335/// #[derive(Deref, FromStr, Parameter)]
336/// #[param(regex = r"\d+", name = "u64")]
337/// struct CustomU64(u64);
338/// #
339/// # #[tokio::main]
340/// # async fn main() {
341/// #     MyWorld::run("./tests/features/doctests.feature").await;
342/// # }
343/// ```
344///
345/// # Attribute arguments
346///
347/// - `#[param(regex = "regex")]`
348///
349///   [`Regex`] to match this parameter. Usually shouldn't contain any capturing
350///   groups, but in case it requires to do so, only the first non-empty group
351///   will be matched as the result.
352///
353/// - `#[param(name = "name")]` (optional)
354///
355///   Name of this parameter to reference it by. If not specified, then
356///   lower-cased type name will be used by default.
357///
358/// [`Regex`]: regex::Regex
359/// [Cucumber Expressions]: https://cucumber.github.io/cucumber-expressions
360/// [default parameters]: cucumber_expressions::Expression#parameter-types
361#[proc_macro_derive(Parameter, attributes(param))]
362pub fn parameter(input: TokenStream) -> TokenStream {
363    parameter::derive(input.into())
364        .unwrap_or_else(syn::Error::into_compile_error)
365        .into()
366}