forge_lint/sol/med/
dangerous_unary_operator.rs1use super::DangerousUnaryOperator;
2use crate::{
3 linter::{EarlyLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::ast::{Expr, ExprKind, UnOpKind};
7
8declare_forge_lint!(
9 DANGEROUS_UNARY_OPERATOR,
10 Severity::Med,
11 "dangerous-unary-operator",
12 "unary operator fused to `=`: `x =- 1` parses as `x = -1`, not `x -= 1`"
13);
14
15impl<'ast> EarlyLintPass<'ast> for DangerousUnaryOperator {
16 fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
17 if let ExprKind::Assign(lhs, None, rhs) = &expr.kind
24 && leads_with_fusable_unary(rhs)
25 && ctx.span_to_snippet(lhs.span.between(rhs.span)).is_some_and(|gap| gap.ends_with('='))
26 {
27 ctx.emit(&DANGEROUS_UNARY_OPERATOR, expr.span);
28 }
29 }
30}
31
32fn leads_with_fusable_unary(expr: &Expr<'_>) -> bool {
35 match &expr.kind {
36 ExprKind::Unary(op, _) => matches!(op.kind, UnOpKind::Neg | UnOpKind::BitNot),
37 ExprKind::Binary(lhs, _, _) | ExprKind::Ternary(lhs, _, _) => leads_with_fusable_unary(lhs),
38 _ => false,
39 }
40}