forge_lint/sol/gas/
custom_errors.rs1use super::CustomErrors;
2use crate::{
3 linter::{EarlyLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::ast::{CallArgsKind, Expr, ExprKind};
7
8declare_forge_lint!(
9 CUSTOM_ERRORS,
10 Severity::Gas,
11 "custom-errors",
12 "prefer using custom errors on revert and require calls"
13);
14
15impl<'ast> EarlyLintPass<'ast> for CustomErrors {
16 fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
17 if let ExprKind::Call(callee, args) = &expr.kind
18 && ((is_require_call(callee) && should_lint_require(args))
19 || (is_revert_call(callee) && should_lint_revert(args)))
20 {
21 ctx.emit(&CUSTOM_ERRORS, expr.span);
22 }
23 }
24}
25
26fn is_require_call(callee: &Expr<'_>) -> bool {
28 matches!(&callee.kind, ExprKind::Ident(ident) if ident.as_str() == "require")
29}
30
31fn is_revert_call(callee: &Expr<'_>) -> bool {
33 matches!(&callee.kind, ExprKind::Ident(ident) if ident.as_str() == "revert")
34}
35
36fn should_lint_revert(args: &solar::ast::CallArgs<'_>) -> bool {
38 matches!(&args.kind, CallArgsKind::Unnamed(arg_exprs) if {
39 arg_exprs.is_empty() || arg_exprs.first().is_some_and(|e| is_string_literal(e))
40 })
41}
42
43fn should_lint_require(args: &solar::ast::CallArgs<'_>) -> bool {
45 matches!(&args.kind, CallArgsKind::Unnamed(arg_exprs) if {
46 arg_exprs.get(1).is_some_and(|e| is_string_literal(e))
47 })
48}
49
50fn is_string_literal(expr: &Expr<'_>) -> bool {
52 matches!(&expr.kind, ExprKind::Lit(lit, _) if matches!(lit.kind, solar::ast::LitKind::Str(..)))
53}