Skip to main content

forge_lint/sol/info/
boolean_cst.rs

1use super::BooleanCst;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{BinOp, BinOpKind, Expr, ExprKind, LitKind, Stmt, StmtKind, VariableDefinition},
8    interface::SpannedOption,
9};
10
11declare_forge_lint!(BOOLEAN_CST, Severity::Med, "boolean-cst", "misuse of a boolean constant");
12
13impl<'ast> EarlyLintPass<'ast> for BooleanCst {
14    fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
15        match &stmt.kind {
16            StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => {
17                check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false });
18            }
19            StmtKind::While(cond, _) => {
20                check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: true });
21            }
22            StmtKind::For { cond: Some(cond), .. } => {
23                check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false });
24            }
25            StmtKind::DeclMulti(_, expr) => check_allowed_bare_expr(ctx, expr),
26            StmtKind::Expr(expr) | StmtKind::Return(Some(expr)) => {
27                check_allowed_bare_expr(ctx, expr);
28            }
29            _ => {}
30        }
31    }
32
33    fn check_variable_definition(
34        &mut self,
35        ctx: &LintContext,
36        var: &'ast VariableDefinition<'ast>,
37    ) {
38        if let Some(initializer) = &var.initializer {
39            check_allowed_bare_expr(ctx, initializer);
40        }
41    }
42}
43
44#[derive(Clone, Copy)]
45enum ExprContext {
46    Condition { allow_bare_true: bool },
47    General,
48    AllowedBare,
49}
50
51fn check_allowed_bare_expr(ctx: &LintContext, expr: &Expr<'_>) {
52    let context =
53        if bool_literal(expr).is_some() { ExprContext::AllowedBare } else { ExprContext::General };
54    check_expr(ctx, expr, context);
55}
56
57fn check_expr(ctx: &LintContext, expr: &Expr<'_>, context: ExprContext) {
58    if let Some(value) = bool_literal(expr) {
59        match context {
60            ExprContext::AllowedBare => {}
61            ExprContext::Condition { allow_bare_true: true } if value => {}
62            ExprContext::Condition { .. } | ExprContext::General => {
63                ctx.emit(&BOOLEAN_CST, expr.span);
64            }
65        }
66        return;
67    }
68
69    match &expr.kind {
70        ExprKind::Assign(_, _, rhs) => check_allowed_bare_expr(ctx, rhs),
71        ExprKind::Binary(left, op, right) => check_binary_expr(ctx, left, *op, right),
72        ExprKind::Call(_, args) => {
73            for arg in args.exprs() {
74                check_allowed_bare_expr(ctx, arg);
75            }
76        }
77        ExprKind::Delete(expr) | ExprKind::Unary(_, expr) => {
78            check_expr(ctx, expr, ExprContext::General);
79        }
80        ExprKind::Ternary(cond, true_expr, false_expr) => {
81            check_expr(ctx, cond, ExprContext::Condition { allow_bare_true: false });
82            check_expr(ctx, true_expr, ExprContext::General);
83            check_expr(ctx, false_expr, ExprContext::General);
84        }
85        ExprKind::Tuple(exprs) => {
86            for opt_expr in exprs.iter() {
87                if let SpannedOption::Some(expr) = opt_expr.as_ref() {
88                    check_expr(ctx, expr, ExprContext::General);
89                }
90            }
91        }
92        _ => {}
93    }
94}
95
96fn check_binary_expr(ctx: &LintContext, left: &Expr<'_>, op: BinOp, right: &Expr<'_>) {
97    if matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne)
98        && (bool_literal(left).is_some() || bool_literal(right).is_some())
99    {
100        return;
101    }
102
103    check_expr(ctx, left, ExprContext::General);
104    check_expr(ctx, right, ExprContext::General);
105}
106
107fn bool_literal(expr: &Expr<'_>) -> Option<bool> {
108    let expr = expr.peel_parens();
109    if let ExprKind::Lit(lit, _) = &expr.kind
110        && let LitKind::Bool(value) = lit.kind
111    {
112        Some(value)
113    } else {
114        None
115    }
116}