forge_lint/sol/info/
boolean_cst.rs1use 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}