Skip to main content

forge_lint/sol/info/
boolean_cst.rs

1use super::BooleanCst;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint, analysis::ast_bool_literal},
5};
6use solar::ast::{BinOpKind, Expr, ExprKind, Stmt, StmtKind, VariableDefinition};
7
8declare_forge_lint!(BOOLEAN_CST, Severity::Med, "boolean-cst", "misuse of a boolean constant");
9
10impl<'ast> EarlyLintPass<'ast> for BooleanCst {
11    fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
12        match &stmt.kind {
13            StmtKind::If(cond, ..)
14            | StmtKind::DoWhile(_, cond)
15            | StmtKind::For { cond: Some(cond), .. } => {
16                check_expr(ctx, cond, false);
17            }
18            // `while (true)` is the idiomatic infinite loop.
19            StmtKind::While(cond, _) => check_expr(ctx, cond, ast_bool_literal(cond) == Some(true)),
20            StmtKind::DeclMulti(_, expr) | StmtKind::Expr(expr) | StmtKind::Return(Some(expr)) => {
21                check_expr(ctx, expr, true);
22            }
23            _ => {}
24        }
25    }
26
27    fn check_variable_definition(
28        &mut self,
29        ctx: &LintContext,
30        var: &'ast VariableDefinition<'ast>,
31    ) {
32        if let Some(initializer) = &var.initializer {
33            check_expr(ctx, initializer, true);
34        }
35    }
36}
37
38/// Reports boolean literals in `expr` that are not `allow_bare` at the top level: a literal
39/// stored, returned or passed as an argument is fine, one combined into a larger expression or
40/// used as a condition is a misuse.
41fn check_expr(ctx: &LintContext, expr: &Expr<'_>, allow_bare: bool) {
42    if ast_bool_literal(expr).is_some() {
43        if !allow_bare {
44            ctx.emit(&BOOLEAN_CST, expr.span);
45        }
46        return;
47    }
48    match &expr.kind {
49        ExprKind::Assign(_, _, rhs) => check_expr(ctx, rhs, true),
50        // `x == true` is boolean-equal's business.
51        ExprKind::Binary(left, op, right)
52            if !(matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne)
53                && (ast_bool_literal(left).is_some() || ast_bool_literal(right).is_some())) =>
54        {
55            check_expr(ctx, left, false);
56            check_expr(ctx, right, false);
57        }
58        ExprKind::Call(_, args) => args.exprs().for_each(|arg| check_expr(ctx, arg, true)),
59        ExprKind::Delete(expr) | ExprKind::Unary(_, expr) => check_expr(ctx, expr, false),
60        ExprKind::Ternary(cond, true_expr, false_expr) => {
61            check_expr(ctx, cond, false);
62            check_expr(ctx, true_expr, false);
63            check_expr(ctx, false_expr, false);
64        }
65        ExprKind::Tuple(exprs) => exprs
66            .iter()
67            .filter_map(|expr| Option::from(expr.as_deref()))
68            .for_each(|expr| check_expr(ctx, expr, false)),
69        _ => {}
70    }
71}