mod directives;
pub(crate) mod events;
mod functions;
mod imports;
mod model;
mod module;
use quote::quote;
use syn::{Item, ItemMod, Path};
pub(crate) use self::model::{
Analysis, EventInfo, FunctionInfo, ImportInfo, ParameterInfo, Receiver,
};
use crate::validate;
pub(crate) fn analyze<'a>(
module: &'a ItemMod,
items: &'a [Item],
registered_events: &[Path],
) -> Result<Analysis, syn::Error> {
let imports = module::imports(items)?;
let struct_ = module::contract_struct(module, items)?;
let contract_name = struct_.ident.to_string();
let impl_blocks = module::impl_blocks(items, &contract_name);
if impl_blocks.is_empty() {
return Err(syn::Error::new_spanned(
struct_,
format!("#[contract] module must contain an impl block for `{contract_name}`"),
));
}
for impl_block in &impl_blocks {
validate::impl_block_methods(impl_block)?;
}
validate::new_constructor(&contract_name, &impl_blocks, struct_)?;
validate::init_method(&contract_name, &impl_blocks)?;
let trait_impls = module::trait_impls(items, &contract_name)?;
let registered_keys = events::registered_keys(registered_events, &imports);
for impl_block in &impl_blocks {
events::validate_emitted_types(impl_block, ®istered_keys, &imports)?;
}
for trait_impl in &trait_impls {
events::validate_emitted_types(trait_impl.impl_block, ®istered_keys, &imports)?;
}
let mut functions = Vec::new();
for impl_block in &impl_blocks {
functions.extend(functions::public_methods(impl_block)?);
}
for trait_impl in &trait_impls {
functions.extend(functions::trait_methods(trait_impl)?);
}
let events = registered_events
.iter()
.map(|path| EventInfo {
data_type: quote! { #path },
})
.collect();
Ok(Analysis {
contract_ident: struct_.ident.clone(),
contract_name,
imports,
functions,
events,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn analyze_no_impl_block() {
let module: ItemMod = syn::parse_quote! {
mod my_contract {
pub struct MyContract {
value: u64,
}
}
};
let items = module.content.as_ref().unwrap().1.clone();
let Err(err) = analyze(&module, &items, &[]) else {
panic!("expected error for missing impl block");
};
let msg = err.to_string();
assert!(
msg.contains("impl block"),
"error should mention 'impl block': {msg}"
);
assert!(
msg.contains("MyContract"),
"error should mention contract name: {msg}"
);
}
#[test]
fn analyze_impl_for_different_type() {
let module: ItemMod = syn::parse_quote! {
mod my_contract {
pub struct MyContract {
value: u64,
}
struct Helper;
impl Helper {
pub const fn new() -> Self { Self }
}
}
};
let items = module.content.as_ref().unwrap().1.clone();
let Err(err) = analyze(&module, &items, &[]) else {
panic!("expected error for impl on wrong type");
};
let msg = err.to_string();
assert!(
msg.contains("impl block"),
"error should mention 'impl block': {msg}"
);
}
#[test]
fn analyze_glob_import_rejected() {
let module: ItemMod = syn::parse_quote! {
mod my_contract {
use some_crate::*;
pub struct MyContract {
value: u64,
}
impl MyContract {
pub const fn new() -> Self { Self { value: 0 } }
}
}
};
let items = module.content.as_ref().unwrap().1.clone();
let Err(err) = analyze(&module, &items, &[]) else {
panic!("expected error for glob import");
};
let msg = err.to_string();
assert!(
msg.contains("glob import"),
"error should mention 'glob import': {msg}"
);
}
#[test]
fn analyze_relative_import_rejected() {
let module: ItemMod = syn::parse_quote! {
mod my_contract {
use super::SomeType;
pub struct MyContract {
value: u64,
}
impl MyContract {
pub const fn new() -> Self { Self { value: 0 } }
}
}
};
let items = module.content.as_ref().unwrap().1.clone();
let Err(err) = analyze(&module, &items, &[]) else {
panic!("expected error for relative import");
};
let msg = err.to_string();
assert!(
msg.contains("relative import"),
"error should mention 'relative import': {msg}"
);
}
}