forge_lint/sol/info/
boolean_equal.rs1use super::BooleanEqual;
2use crate::{
3 linter::{EarlyLintPass, LintContext, Suggestion},
4 sol::{Severity, SolLint, analysis::ast_bool_literal},
5};
6use solar::{
7 ast::{BinOpKind, Expr, ExprKind},
8 interface::diagnostics::Applicability,
9};
10
11declare_forge_lint!(
12 BOOLEAN_EQUAL,
13 Severity::Info,
14 "boolean-equal",
15 "boolean comparison to a constant can be simplified"
16);
17
18impl<'ast> EarlyLintPass<'ast> for BooleanEqual {
19 fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
20 let ExprKind::Binary(left, op, right) = &expr.kind else { return };
21 if !matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne) {
22 return;
23 }
24 let simplified = match (ast_bool_literal(left), ast_bool_literal(right)) {
25 (None, None) => return,
26 (Some(_), Some(_)) => None,
27 (Some(constant), None) => simplify(ctx, right, op.kind, constant),
28 (None, Some(constant)) => simplify(ctx, left, op.kind, constant),
29 };
30 match simplified {
31 Some(simplified) => ctx.emit_with_suggestion(
32 &BOOLEAN_EQUAL,
33 expr.span,
34 Suggestion::fix(simplified, Applicability::MachineApplicable)
35 .with_desc("consider simplifying to"),
36 ),
37 None => ctx.emit(&BOOLEAN_EQUAL, expr.span),
38 }
39 }
40}
41
42fn simplify(ctx: &LintContext, expr: &Expr<'_>, op: BinOpKind, constant: bool) -> Option<String> {
44 let snippet = ctx.span_to_snippet(expr.span)?;
45 let negate = (op == BinOpKind::Eq) != constant;
46 let atomic = matches!(
47 expr.peel_parens().kind,
48 ExprKind::Call(..)
49 | ExprKind::CallOptions(..)
50 | ExprKind::Ident(_)
51 | ExprKind::Index(..)
52 | ExprKind::Lit(..)
53 | ExprKind::Member(..)
54 );
55 Some(match (negate, atomic) {
56 (false, _) => snippet,
57 (true, true) => format!("!{snippet}"),
58 (true, false) => format!("!({snippet})"),
59 })
60}