Skip to main content

forge_lint/sol/gas/
custom_errors.rs

1use super::CustomErrors;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{CallArgs, CallArgsKind, Expr, ExprKind, LitKind},
8    interface::{kw, sym},
9};
10
11declare_forge_lint!(
12    CUSTOM_ERRORS,
13    Severity::Gas,
14    "custom-errors",
15    "`revert` or `require` call does not use a custom error"
16);
17
18impl<'ast> EarlyLintPass<'ast> for CustomErrors {
19    fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
20        let ExprKind::Call(callee, CallArgs { kind: CallArgsKind::Unnamed(args), .. }) = &expr.kind
21        else {
22            return;
23        };
24        let ExprKind::Ident(ident) = &callee.kind else { return };
25        // `require(cond)` / `require(cond, "reason")` and `revert()` / `revert("reason")`.
26        let lint = match ident.name {
27            sym::require => args.len() == 1 || args.get(1).is_some_and(|e| is_string_literal(e)),
28            kw::Revert => args.first().is_none_or(|e| is_string_literal(e)),
29            _ => false,
30        };
31        if lint {
32            ctx.emit(&CUSTOM_ERRORS, expr.span);
33        }
34    }
35}
36
37const fn is_string_literal(expr: &Expr<'_>) -> bool {
38    matches!(&expr.kind, ExprKind::Lit(lit, _) if matches!(lit.kind, LitKind::Str(..)))
39}