use solang_parser::pt::{
ContractTy, FunctionAttribute, FunctionDefinition, FunctionTy, Visibility,
};
use crate::{
parser::{CommentTag, CommentsRef, ParseItem},
rules::violation_error::ViolationError,
};
use super::super::{Rule, Violation};
pub struct MissingInheritdoc;
impl Rule for MissingInheritdoc {
type Target = FunctionDefinition;
const NAME: &'static str = "MissingInheritdoc";
const DESCRIPTION: &'static str =
"Public and override functions must have an inheritdoc comment.";
fn check(
parent: Option<&ParseItem>,
func: &FunctionDefinition,
comments: &CommentsRef,
) -> Option<Violation> {
match parent?.as_contract()?.ty {
ContractTy::Interface(_) | ContractTy::Library(_) => return None,
ContractTy::Contract(_) | ContractTy::Abstract(_) => (),
}
match func.ty {
FunctionTy::Function => (),
FunctionTy::Receive
| FunctionTy::Fallback
| FunctionTy::Constructor
| FunctionTy::Modifier => return None,
}
func.attributes.iter().find(|attr| match attr {
FunctionAttribute::Visibility(Visibility::Public(_) | Visibility::External(_))
| FunctionAttribute::Override(..) => true,
FunctionAttribute::Visibility(Visibility::Private(_) | Visibility::Internal(_))
| FunctionAttribute::Mutability(_)
| FunctionAttribute::Virtual(_)
| FunctionAttribute::Immutable(_)
| FunctionAttribute::BaseOrModifier(..)
| FunctionAttribute::Error(_) => false,
})?;
if comments.include_tag(CommentTag::Inheritdoc).is_empty() {
return Some(Violation::new(
Self::NAME,
Self::DESCRIPTION,
ViolationError::MissingComment(CommentTag::Inheritdoc),
func.loc,
));
}
None
}
}
#[cfg(test)]
mod tests {
use super::{
CommentTag, CommentsRef, FunctionDefinition, MissingInheritdoc, Rule, Violation,
ViolationError,
};
use crate::{generate_missing_comment_test_cases, parser::Parser};
use forge_fmt::Visitable;
use solang_parser::parse;
fn parse_source(src: &str) -> Parser {
let (mut source, comments) = parse(src, 0).expect("failed to parse source");
let mut doc = Parser::new(comments, src.to_owned());
source.visit(&mut doc).expect("failed to visit source");
doc
}
macro_rules! test_missinginheritdoc {
($name:ident, $source:expr, $expected:expr) => {
#[test]
fn $name() {
let src = parse_source($source);
let parent = src.items_ref().first().unwrap();
let child = parent.children.first().unwrap();
let func = child.as_function().unwrap();
let comments = CommentsRef::from(&child.comments);
let expected = $expected(func);
assert_eq!(
MissingInheritdoc::check(Some(parent), func, &comments),
expected
);
}
};
}
mod public_test {
use super::*;
generate_missing_comment_test_cases!(
Inheritdoc,
test_missinginheritdoc,
MissingInheritdoc,
r"
function test() public {}
",
"@inheritdoc",
FunctionDefinition
);
}
mod external_test {
use super::*;
generate_missing_comment_test_cases!(
Inheritdoc,
test_missinginheritdoc,
MissingInheritdoc,
r"
function test() external {}
",
"@inheritdoc",
FunctionDefinition
);
}
mod override_test {
use super::*;
generate_missing_comment_test_cases!(
Inheritdoc,
test_missinginheritdoc,
MissingInheritdoc,
r"
function test() override {}
",
"@inheritdoc",
FunctionDefinition
);
}
test_missinginheritdoc!(
private_no_violation,
r"
contract Test {
function test() private {}
}
",
|_| None
);
test_missinginheritdoc!(
internal_no_violation,
r"
contract Test {
function test() internal {}
}
",
|_| None
);
test_missinginheritdoc!(
modifier_no_violation,
r"
contract Test {
modifier test() {}
}
",
|_| None
);
test_missinginheritdoc!(
constructor_no_violation,
r"
contract Test {
constructor() {}
}
",
|_| None
);
test_missinginheritdoc!(
receive_no_violation,
r"
contract Test {
receive() {}
}
",
|_| None
);
test_missinginheritdoc!(
fallback_no_violation,
r"
contract Test {
fallback() {}
}
",
|_| None
);
test_missinginheritdoc!(
interface_no_violation,
r"
interface Test {
function test() external;
}
",
|_| None
);
test_missinginheritdoc!(
library_no_violation,
r"
library Test {
function test() internal;
}
",
|_| None
);
test_missinginheritdoc!(
abstract_public_violation,
r"
abstract contract Test {
function test() public;
}
",
|func: &FunctionDefinition| Some(Violation::new(
MissingInheritdoc::NAME,
MissingInheritdoc::DESCRIPTION,
ViolationError::MissingComment(CommentTag::Inheritdoc),
func.loc
))
);
}