Skip to content

Commit

Permalink
feat: add support for action descriptions (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfertel authored Oct 3, 2023
1 parent de35f26 commit 6472002
Show file tree
Hide file tree
Showing 14 changed files with 674 additions and 205 deletions.
12 changes: 6 additions & 6 deletions src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ impl Check {
)?);
}

if !violations.is_empty() {
for violation in violations {
eprintln!("{violation}");
}
std::process::exit(1);
} else {
if violations.is_empty() {
println!(
"{}",
"All checks completed successfully! No issues found.".green()
);
} else {
for violation in violations {
eprintln!("{violation}");
}
std::process::exit(1);
}

Ok(())
Expand Down
14 changes: 7 additions & 7 deletions src/check/rules/structural_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ fn check_fns_structure(
fn find_matching_fn<'a>(
contract_sol: &'a pt::ContractDefinition,
fn_hir: &'a hir::FunctionDefinition,
) -> Option<(usize, &'a Box<pt::FunctionDefinition>)> {
) -> Option<(usize, &'a pt::FunctionDefinition)> {
contract_sol
.parts
.iter()
.enumerate()
.find_map(|(idx, part)| {
if let pt::ContractPart::FunctionDefinition(fn_sol) = part {
if fns_match(fn_hir, fn_sol) {
return Some((idx, fn_sol));
return Some((idx, &**fn_sol));
}
};

Expand All @@ -186,7 +186,7 @@ fn fns_match(fn_hir: &hir::FunctionDefinition, fn_sol: &pt::FunctionDefinition)
.name
.clone()
.is_some_and(|pt::Identifier { ref name, .. }| {
name == &fn_hir.identifier && fn_types_match(&fn_hir.ty, &fn_sol.ty)
name == &fn_hir.identifier && fn_types_match(&fn_hir.ty, fn_sol.ty)
})
}

Expand All @@ -196,7 +196,7 @@ fn fns_match(fn_hir: &hir::FunctionDefinition, fn_sol: &pt::FunctionDefinition)
/// We check that the function types match, even though we know that the
/// name not matching is enough, since a modifier will never be
/// named the same as a function per Foundry's best practices.
const fn fn_types_match(ty_hir: &hir::FunctionTy, ty_sol: &pt::FunctionTy) -> bool {
const fn fn_types_match(ty_hir: &hir::FunctionTy, ty_sol: pt::FunctionTy) -> bool {
match ty_hir {
hir::FunctionTy::Function => matches!(ty_sol, pt::FunctionTy::Function),
hir::FunctionTy::Modifier => matches!(ty_sol, pt::FunctionTy::Modifier),
Expand All @@ -215,11 +215,11 @@ mod tests {
fn test_fn_types_match() {
assert!(fn_types_match(
&hir::FunctionTy::Function,
&pt::FunctionTy::Function
pt::FunctionTy::Function
));
assert!(fn_types_match(
&hir::FunctionTy::Modifier,
&pt::FunctionTy::Modifier
pt::FunctionTy::Modifier
));
}

Expand Down Expand Up @@ -298,7 +298,7 @@ mod tests {

let expected = needle_sol;
let actual = find_matching_fn(&contract, &needle_hir).unwrap();
assert_eq!((2, &Box::new(expected)), actual);
assert_eq!((2, &expected), actual);

let haystack = vec![];
let needle_hir = fn_hir("needle", hir::FunctionTy::Function);
Expand Down
48 changes: 32 additions & 16 deletions src/hir/translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ impl<'a> Visitor for TranslatorI<'a> {
let mut contract_children = Vec::new();
for ast in &root.children {
match ast {
// A root node cannot be a child of a root node.
ast::Ast::Root(_) => unreachable!(),
// Root or ActionDescription nodes cannot be children of a root node. This
// must be handled in a previous pass.
ast::Ast::Root(_) | ast::Ast::ActionDescription(_) => unreachable!(),
// Found a top-level action. This corresponds to a function.
ast::Ast::Action(action) => {
let words = action.title.split_whitespace();
Expand Down Expand Up @@ -146,19 +147,16 @@ impl<'a> Visitor for TranslatorI<'a> {
// If this condition only has actions as children, then we don't generate
// a modifier for it, since it would only be used in the emitted function.
if condition.children.len() != action_count {
match self.modifiers.get(&condition.title) {
Some(modifier) => {
self.modifier_stack.push(modifier);
// Add a modifier node.
let hir = Hir::FunctionDefinition(hir::FunctionDefinition {
identifier: modifier.clone(),
ty: hir::FunctionTy::Modifier,
modifiers: None,
children: None,
});
children.push(hir);
}
None => (),
if let Some(modifier) = self.modifiers.get(&condition.title) {
self.modifier_stack.push(modifier);
// Add a modifier node.
let hir = Hir::FunctionDefinition(hir::FunctionDefinition {
identifier: modifier.clone(),
ty: hir::FunctionTy::Modifier,
modifiers: None,
children: None,
});
children.push(hir);
};
}

Expand Down Expand Up @@ -253,8 +251,26 @@ impl<'a> Visitor for TranslatorI<'a> {
&mut self,
action: &crate::syntax::ast::Action,
) -> Result<Self::Output, Self::Error> {
Ok(vec![hir::Hir::Comment(hir::Comment {
let mut descriptions = vec![];
for description in &action.children {
if let ast::Ast::ActionDescription(description) = description {
descriptions.append(&mut self.visit_description(description)?);
}
}

Ok(std::iter::once(hir::Hir::Comment(hir::Comment {
lexeme: action.title.clone(),
}))
.chain(descriptions)
.collect())
}

fn visit_description(
&mut self,
description: &crate::syntax::ast::Description,
) -> Result<Self::Output, Self::Error> {
Ok(vec![hir::Hir::Comment(hir::Comment {
lexeme: description.text.clone(),
})])
}
}
Expand Down
39 changes: 36 additions & 3 deletions src/scaffold/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,7 @@ contract FileTest {

#[test]
fn test_actions_without_conditions() -> Result<()> {
let file_contents =
String::from("FileTest\n├── it should do st-ff\n └── It never reverts.");
let file_contents = String::from("FileTest\n├── it should do st-ff\n└── It never reverts.");

assert_eq!(
&scaffold_with_flags(&file_contents, true, 2, "0.8.0")?,
Expand Down Expand Up @@ -533,6 +532,34 @@ contract ActionsTest {
Ok(())
}

#[test]
fn action_descriptions() -> Result<()> {
let file_contents = String::from(
r"DescriptionsTest
└── when something bad happens
└── it should try to revert
├── some stuff happened
│ └── and that stuff
└── was very _bad_",
);

assert_eq!(
&scaffold(&file_contents)?,
r"pragma solidity 0.8.0;
contract DescriptionsTest {
function test_WhenSomethingBadHappens() external {
// it should try to revert
// some stuff happened
// and that stuff
// was very _bad_
}
}"
);

Ok(())
}

#[test]
fn test_deep_tree() -> Result<()> {
let file_contents = String::from(
Expand All @@ -554,7 +581,10 @@ contract ActionsTest {
│ └── it should emit a {MultipleChildren} event
└── when the asset does not miss the ERC-20 return value
├── it should create the child
└── it should emit a {MultipleChildren} event"#,
└── it should emit a {MultipleChildren} event
├── - Because the deposit should not be 0.
├── - The number count is > 0.
└── - Events should be emitted."#,
);

assert_eq!(
Expand Down Expand Up @@ -620,6 +650,9 @@ contract DeepTest {
{
// it should create the child
// it should emit a {MultipleChildren} event
// - Because the deposit should not be 0.
// - The number count is > 0.
// - Events should be emitted.
}
}"
);
Expand Down
8 changes: 8 additions & 0 deletions src/scaffold/modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ impl Visitor for ModifierDiscoverer {
// No-op.
Ok(())
}

fn visit_description(
&mut self,
_description: &ast::Description,
) -> Result<Self::Output, Self::Error> {
// No-op.
Ok(())
}
}

#[cfg(test)]
Expand Down
31 changes: 26 additions & 5 deletions src/syntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@ pub enum Ast {
///
/// This node corresponds to a leaf node of the tree.
Action(Action),
/// Additional action description.
///
/// This node can only appear as a child of an action.
ActionDescription(Description),
}

impl Ast {
/// Return the span of this abstract syntax tree.
#[must_use]
pub fn span(&self) -> &Span {
match *self {
Self::Root(ref x) => &x.span,
Self::Condition(ref x) => &x.span,
Self::Action(ref x) => &x.span,
match self {
Self::Root(x) => &x.span,
Self::Condition(x) => &x.span,
Self::Action(x) => &x.span,
Self::ActionDescription(x) => &x.span,
}
}

Expand Down Expand Up @@ -67,8 +72,24 @@ pub struct Condition {
pub struct Action {
/// The title of this action.
///
/// For example: "it should revert"
/// For example: "It should revert."
pub title: String,
/// The span that encompasses this node.
pub span: Span,
/// The children AST nodes of this node.
///
/// For now we only support action description
/// nodes.
pub children: Vec<Ast>,
}

/// A description node of the AST.
#[derive(Debug, PartialEq, Eq)]
pub struct Description {
/// The text of this action.
///
/// For example: "Describe your actions."
pub text: String,
/// The span that encompasses this node.
pub span: Span,
}
1 change: 1 addition & 0 deletions src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
pub mod ast;
pub mod parser;
pub mod semantics;
mod test_utils;
pub mod tokenizer;
pub mod visitor;

Expand Down
Loading

0 comments on commit 6472002

Please sign in to comment.