use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::{Attribute, BinOp, Expr, ExprAssign, ItemFn};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum StandardLibraryAssertion {
AssertEq,
AssertNe,
Assert,
}
pub enum MacroKind {
Assertion(StandardLibraryAssertion),
Other,
}
impl From<StandardLibraryAssertion> for MacroKind {
fn from(ass: StandardLibraryAssertion) -> Self {
Self::Assertion(ass)
}
}
impl MacroKind {
pub fn is_assertion(&self) -> bool {
match self {
Self::Assertion(_) => true,
Self::Other => false,
}
}
pub fn is_binary_assertion(&self) -> bool {
match self {
Self::Assertion(StandardLibraryAssertion::AssertEq) => true,
Self::Assertion(StandardLibraryAssertion::AssertNe) => true,
Self::Assertion(StandardLibraryAssertion::Assert) => false,
Self::Other => false,
}
}
pub fn binary_operator(&self, span: Span) -> Option<BinOp> {
match self {
Self::Assertion(StandardLibraryAssertion::AssertEq) => {
Some(BinOp::Eq(syn::token::EqEq { spans: [span; 2] }))
}
Self::Assertion(StandardLibraryAssertion::AssertNe) => {
Some(BinOp::Ne(syn::token::Ne { spans: [span; 2] }))
}
Self::Assertion(StandardLibraryAssertion::Assert) => None,
Self::Other => None,
}
}
}
pub fn infer_macro_kind_from_path(path: &syn::Path) -> MacroKind {
let segments: Vec<syn::Ident> = path.segments.iter().map(|s| s.ident.clone()).collect();
fn macro_kind(ident: &syn::Ident) -> MacroKind {
let assert_eq = "assert_eq";
let assert_ne = "assert_ne";
let assert = "assert";
if ident == assert_eq {
MacroKind::from(StandardLibraryAssertion::AssertEq)
} else if ident == assert_ne {
MacroKind::from(StandardLibraryAssertion::AssertNe)
} else if ident == assert {
MacroKind::from(StandardLibraryAssertion::Assert)
} else {
MacroKind::Other
}
}
if segments.len() == 1 {
macro_kind(&segments[0])
} else if segments.len() == 2 {
if segments[0] == "std" {
macro_kind(&segments[1])
} else {
MacroKind::Other
}
} else {
MacroKind::Other
}
}
pub fn idents_from_assign_expression(assignment: &ExprAssign) -> Option<(syn::Ident, syn::Ident)> {
if let (Some(lhs), Some(rhs)) = (
ident_from_box_expr(assignment.left.clone()),
ident_from_box_expr(assignment.right.clone()),
) {
Some((lhs, rhs))
} else {
None
}
}
fn ident_from_box_expr(expr: Box<Expr>) -> Option<syn::Ident> {
match *expr {
syn::Expr::Path(syn::ExprPath { ref path, .. }) => {
if path.segments.len() == 1 {
Some(path.segments[0].ident.clone())
} else {
None
}
}
_ => None,
}
}
pub fn apply_unused_attributes_workaround(mut func: ItemFn) -> ItemFn {
if func
.attrs
.iter()
.find(|attr| is_attribute_name(attr, "test"))
.is_none()
{
func.attrs.retain(|attr| {
!is_attribute_name(attr, "should_panic") && !is_attribute_name(attr, "ignore")
});
}
func
}
fn is_attribute_name(attr: &Attribute, name: &str) -> bool {
if name.contains("::") || name.contains(':') {
panic!(
"Give only a single name for the attribute. This function does not deal with paths!"
);
}
attr.path
.segments
.first()
.map(|pathseg| pathseg.ident == name)
.unwrap_or(false)
}
pub fn check_redefinition_of_assert2ify(func: &ItemFn) -> Result<(), syn::Error> {
if let Some(other_assertify_macro) = func.attrs.iter().find(|attr| {
attr.path
.segments
.last()
.map(|s| s.ident == "assert2ify")
.unwrap_or(false)
}) {
Err(syn::Error::new(
other_assertify_macro.span(),
"Duplicate attribute. This attribute must only be specified once for each function",
))
} else {
Ok(())
}
}