lets_expect_core/core/
context.rs

1use super::{
2    after_block::AfterBlock, before_block::BeforeBlock, create_test::create_test, expect::Expect,
3    expect_block::ExpectBlock, keyword, mode::Mode, runtime::Runtime, story::Story,
4    story_block::StoryBlock, to::To, to_block::ToBlock, when::When, when_block::WhenBlock,
5};
6use proc_macro2::{Span, TokenStream};
7use quote::quote_spanned;
8use syn::{
9    parse::{Parse, ParseBuffer, ParseStream},
10    spanned::Spanned,
11    Block, Error, Ident, Local, Stmt, Token,
12};
13
14#[derive(Default)]
15pub struct Context {
16    lets: Vec<Local>,
17    tos: Vec<ToBlock>,
18
19    befores: Vec<BeforeBlock>,
20    afters: Vec<AfterBlock>,
21
22    expects: Vec<ExpectBlock>,
23    whens: Vec<WhenBlock>,
24    stories: Vec<StoryBlock>,
25
26    mode: Option<Mode>,
27}
28
29impl Parse for Context {
30    fn parse(input: ParseStream) -> syn::Result<Self> {
31        let mut context = Self::default();
32
33        if input.peek(Token![#]) {
34            input.parse::<Token![#]>()?;
35            let mode_ident = input.parse::<Ident>()?;
36
37            context.mode = Some(match mode_ident.to_string().as_str() {
38                "test" => Mode::Test,
39                "method" => Mode::PubMethod,
40                "method_async" => Mode::PubAsyncMethod,
41                #[cfg(feature = "tokio")]
42                "tokio_test" => Mode::TokioTest,
43                _ => return Err(Error::new(mode_ident.span(), "Unknown mode")),
44            });
45        }
46
47        while !input.is_empty() {
48            parse_single_context_item(input, &mut context)?;
49        }
50
51        Ok(context)
52    }
53}
54
55fn parse_single_context_item(input: &ParseBuffer, context: &mut Context) -> Result<(), Error> {
56    let next = input.lookahead1();
57
58    if next.peek(Token![let]) {
59        handle_let(&mut context.lets, input)?;
60    } else if next.peek(keyword::before) {
61        let keyword = input.parse::<keyword::before>()?;
62        let before = handle_before(keyword, input)?;
63        context.befores.push(before);
64    } else if next.peek(keyword::after) {
65        let keyword = input.parse::<keyword::after>()?;
66        let after = handle_after(keyword, input)?;
67        context.afters.push(after);
68    } else if next.peek(keyword::to) {
69        let keyword = input.parse::<keyword::to>()?;
70        let to = handle_to(keyword, input)?;
71        context.tos.push(to);
72    } else if next.peek(keyword::when) {
73        let keyword = input.parse::<keyword::when>()?;
74        let when = handle_when(keyword, input)?;
75        context.whens.push(when);
76    } else if next.peek(keyword::expect) {
77        let keyword = input.parse::<keyword::expect>()?;
78        let expect = handle_expect(keyword, input)?;
79        context.expects.push(expect);
80    } else if next.peek(keyword::story) {
81        let keyword = input.parse::<keyword::story>()?;
82        let story = handle_story(keyword, input)?;
83        context.stories.push(story);
84    } else {
85        return Err(next.error());
86    }
87
88    Ok(())
89}
90
91fn handle_before(keyword: keyword::before, input: ParseStream) -> syn::Result<BeforeBlock> {
92    let block = input.parse::<Block>()?;
93    Ok(BeforeBlock::new(keyword, block))
94}
95
96fn handle_after(keyword: keyword::after, input: ParseStream) -> syn::Result<AfterBlock> {
97    let block = input.parse::<Block>()?;
98    Ok(AfterBlock::new(keyword, block))
99}
100
101fn handle_expect(keyword: keyword::expect, input: &ParseBuffer) -> syn::Result<ExpectBlock> {
102    let expect = input.parse::<Expect>()?;
103    Ok(ExpectBlock::new(keyword, expect))
104}
105
106fn handle_when(keyword: keyword::when, input: &ParseBuffer) -> syn::Result<WhenBlock> {
107    let when = input.parse::<When>()?;
108    Ok(WhenBlock::new(keyword, when))
109}
110
111fn handle_to(keyword: keyword::to, input: &ParseBuffer) -> syn::Result<ToBlock> {
112    let to = input.parse::<To>()?;
113    Ok(ToBlock::new(keyword, to))
114}
115
116fn handle_let(lets: &mut Vec<Local>, input: &ParseBuffer) -> syn::Result<()> {
117    let r#let = input.parse::<Stmt>()?;
118
119    match r#let {
120        Stmt::Local(local) => {
121            lets.push(local);
122        }
123        _ => return Err(Error::new(r#let.span(), "Expected a `let` statement")),
124    }
125    Ok(())
126}
127
128fn handle_story(keyword: keyword::story, input: &ParseBuffer) -> Result<StoryBlock, Error> {
129    let story = input.parse::<Story>()?;
130    Ok(StoryBlock::new(keyword, story))
131}
132
133impl Context {
134    pub fn from_single_item(input: ParseStream) -> syn::Result<Self> {
135        let mut context = Self::default();
136
137        parse_single_context_item(input, &mut context)?;
138
139        Ok(context)
140    }
141
142    pub fn to_tokens(&self, span: &Span, runtime: &Runtime) -> TokenStream {
143        let runtime = runtime.extend(
144            None,
145            &self.lets,
146            &self
147                .befores
148                .iter()
149                .map(|before| before.before.clone())
150                .collect::<Vec<Block>>(),
151            &self
152                .afters
153                .iter()
154                .map(|before| before.after.clone())
155                .collect::<Vec<Block>>(),
156            self.mode,
157        );
158
159        let tos = self.tos.iter().map(|to| {
160            let (to_tokens, dependencies) = to.to_tokens(&runtime);
161            let identifier = to.identifier();
162
163            let content = quote_spanned! { identifier.span() =>
164                let test_case = {
165                    #to_tokens
166                };
167
168                vec![test_case]
169            };
170
171            create_test(&identifier, &runtime, &content, &dependencies)
172        });
173
174        let stories = self.stories.iter().map(|story| story.to_tokens(&runtime));
175        let expects = self.expects.iter().map(|child| child.to_tokens(&runtime));
176        let whens = self.whens.iter().map(|child| child.to_tokens(&runtime));
177
178        quote_spanned! { *span =>
179            #(#tos)*
180            #(#stories)*
181            #(#expects)*
182            #(#whens)*
183        }
184    }
185}