forge_lint/sol/info/
too_many_digits.rs1use super::TooManyDigits;
2use crate::{
3 linter::{EarlyLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::ast::{Expr, ExprKind, Lit, LitKind, Stmt, StmtKind, yul};
7
8declare_forge_lint!(
9 TOO_MANY_DIGITS,
10 Severity::Info,
11 "too-many-digits",
12 "numeric literal with many digits is error-prone; \
13 use scientific notation, sub-denominations, or underscore separators"
14);
15
16impl<'ast> EarlyLintPass<'ast> for TooManyDigits {
17 fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
18 if let StmtKind::Assembly(assembly) = &stmt.kind {
19 check_yul_block(ctx, &assembly.block);
20 }
21 }
22
23 fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
24 let ExprKind::Lit(lit, sub_denom) = &expr.kind else { return };
25 check_lit(ctx, lit, sub_denom.is_some());
26 }
27}
28
29fn check_lit(ctx: &LintContext, lit: &Lit<'_>, has_sub_denom: bool) {
30 if !matches!(lit.kind, LitKind::Number(_)) {
33 return;
34 }
35
36 if has_sub_denom {
38 return;
39 }
40
41 let s = lit.symbol.as_str();
42 let is_hex = is_hex_literal(s);
43
44 if is_hex_address(s) {
47 return;
48 }
49
50 if !is_hex && (s.contains('e') || s.contains('E')) {
52 return;
53 }
54
55 if s.contains("00000") {
58 ctx.emit(&TOO_MANY_DIGITS, lit.span);
59 }
60}
61
62fn is_hex_literal(s: &str) -> bool {
63 s.starts_with("0x") || s.starts_with("0X")
64}
65
66fn is_hex_address(s: &str) -> bool {
67 let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) else { return false };
68 hex.len() == 40 && hex.bytes().all(|b| b.is_ascii_hexdigit())
69}
70
71fn check_yul_block(ctx: &LintContext, block: &yul::Block<'_>) {
72 for stmt in block.stmts.iter() {
73 check_yul_stmt(ctx, stmt);
74 }
75}
76
77fn check_yul_stmt(ctx: &LintContext, stmt: &yul::Stmt<'_>) {
78 match &stmt.kind {
79 yul::StmtKind::Block(block) => check_yul_block(ctx, block),
80 yul::StmtKind::AssignSingle(_, expr)
81 | yul::StmtKind::AssignMulti(_, expr)
82 | yul::StmtKind::Expr(expr) => check_yul_expr(ctx, expr),
83 yul::StmtKind::If(cond, block) => {
84 check_yul_expr(ctx, cond);
85 check_yul_block(ctx, block);
86 }
87 yul::StmtKind::For(for_stmt) => {
88 check_yul_block(ctx, &for_stmt.init);
89 check_yul_expr(ctx, &for_stmt.cond);
90 check_yul_block(ctx, &for_stmt.step);
91 check_yul_block(ctx, &for_stmt.body);
92 }
93 yul::StmtKind::Switch(switch) => {
94 check_yul_expr(ctx, &switch.selector);
95 for case in switch.cases.iter() {
96 if let Some(lit) = &case.constant {
97 check_lit(ctx, lit, false);
98 }
99 check_yul_block(ctx, &case.body);
100 }
101 }
102 yul::StmtKind::FunctionDef(func) => check_yul_block(ctx, &func.body),
103 yul::StmtKind::VarDecl(_, Some(init)) => check_yul_expr(ctx, init),
104 yul::StmtKind::Leave
105 | yul::StmtKind::Break
106 | yul::StmtKind::Continue
107 | yul::StmtKind::VarDecl(_, None) => {}
108 }
109}
110
111fn check_yul_expr(ctx: &LintContext, expr: &yul::Expr<'_>) {
112 match &expr.kind {
113 yul::ExprKind::Call(call) => {
114 for arg in call.arguments.iter() {
115 check_yul_expr(ctx, arg);
116 }
117 }
118 yul::ExprKind::Lit(lit) => check_lit(ctx, lit, false),
119 yul::ExprKind::Path(_) => {}
120 }
121}