use proc_macro2::Literal;
use syn::parse::{Parse, ParseStream, Result};
use syn::{braced, Attribute, Ident, Stmt, Token, Type, UseTree};
mod keyword {
use syn::custom_keyword;
custom_keyword!(before);
custom_keyword!(after);
custom_keyword!(describe);
custom_keyword!(context);
custom_keyword!(it);
custom_keyword!(test);
}
pub(crate) struct Root(pub(crate) Vec<Describe>);
impl Parse for Root {
fn parse(input: ParseStream) -> Result<Self> {
let mut blocks = Vec::new();
while !input.is_empty() {
blocks.push(input.parse::<Describe>()?);
}
Ok(Root(blocks))
}
}
pub(crate) enum Block {
Describe(Describe),
Test(Test),
}
impl Parse for Block {
fn parse(input: ParseStream) -> Result<Self> {
let fork = input.fork();
let _attibutes = fork.call(Attribute::parse_outer)?;
let _async_token = fork.parse::<Option<Token![async]>>()?;
let lookahead = fork.lookahead1();
if lookahead.peek(keyword::it) || lookahead.peek(keyword::test) {
Ok(Block::Test(input.parse::<Test>()?))
} else if lookahead.peek(keyword::describe) || lookahead.peek(keyword::context) {
Ok(Block::Describe(input.parse::<Describe>()?))
} else {
Err(lookahead.error())
}
}
}
pub(crate) struct Describe {
pub(crate) properties: DescribeProps,
pub(crate) blocks: Vec<Block>,
}
impl Parse for Describe {
fn parse(input: ParseStream) -> Result<Self> {
let block_props = input.parse::<BlockProps>()?;
let content;
braced!(content in input);
let mut uses = Vec::new();
let mut before = None;
let mut after = None;
let mut blocks = Vec::new();
while !content.is_empty() {
while content.parse::<Option<Token![use]>>()?.is_some() {
uses.push(content.parse::<UseTree>()?);
content.parse::<Token![;]>()?;
}
let block = content.parse::<DescribeBlock>()?;
match block {
DescribeBlock::Before(BasicBlock(block)) => {
if before.is_none() {
before = Some(BasicBlock(block));
} else {
return Err(
content.error("Only one `before` statement per describe/context block")
);
}
}
DescribeBlock::After(BasicBlock(block)) => {
if after.is_none() {
after = Some(BasicBlock(block));
} else {
return Err(
content.error("Only one `after` statement per describe/context block")
);
}
}
DescribeBlock::Regular(block) => blocks.push(block),
}
}
Ok(Describe {
properties: DescribeProps {
block_props,
uses,
before,
after,
},
blocks,
})
}
}
#[derive(Clone)]
pub(crate) struct DescribeProps {
pub(crate) block_props: BlockProps,
pub(crate) uses: Vec<UseTree>,
pub(crate) before: Option<BasicBlock>,
pub(crate) after: Option<BasicBlock>,
}
enum DescribeBlock {
Regular(Block),
Before(BasicBlock),
After(BasicBlock),
}
impl Parse for DescribeBlock {
fn parse(input: ParseStream) -> Result<Self> {
if input.parse::<Option<keyword::before>>()?.is_some() {
Ok(DescribeBlock::Before(input.parse::<BasicBlock>()?))
} else if input.parse::<Option<keyword::after>>()?.is_some() {
Ok(DescribeBlock::After(input.parse::<BasicBlock>()?))
} else {
Ok(DescribeBlock::Regular(input.parse::<Block>()?))
}
}
}
pub(crate) struct Test {
pub(crate) properties: BlockProps,
pub(crate) content: BasicBlock,
}
impl Parse for Test {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Test {
properties: input.parse::<BlockProps>()?,
content: input.parse::<BasicBlock>()?,
})
}
}
#[derive(Clone)]
pub(crate) struct BasicBlock(pub(crate) Vec<Stmt>);
impl Parse for BasicBlock {
fn parse(input: ParseStream) -> Result<Self> {
let content;
braced!(content in input);
Ok(BasicBlock(content.call(syn::Block::parse_within)?))
}
}
#[derive(Clone)]
pub(crate) struct BlockProps {
pub(crate) attributes: Vec<Attribute>,
pub(crate) is_async: bool,
pub(crate) name: String,
pub(crate) return_type: Option<Type>,
}
impl Parse for BlockProps {
fn parse(input: ParseStream) -> Result<Self> {
let attributes = input.call(Attribute::parse_outer)?;
let is_async = input.parse::<Option<Token![async]>>()?.is_some();
let _block_type = input.parse::<Ident>()?;
let name = input.parse::<Literal>()?.to_string();
let return_type = if input.parse::<Option<Token![->]>>()?.is_some() {
Some(input.parse::<Type>()?)
} else {
None
};
Ok(BlockProps {
attributes,
is_async,
name,
return_type,
})
}
}