forge_lint/sol/codesize/
unwrapped_modifier_logic.rs1use super::UnwrappedModifierLogic;
2use crate::{
3 linter::{LateLintPass, LintContext, Suggestion},
4 sol::{Severity, SolLint},
5};
6use solar::{
7 ast,
8 sema::hir::{self, Res},
9};
10
11declare_forge_lint!(
12 UNWRAPPED_MODIFIER_LOGIC,
13 Severity::CodeSize,
14 "unwrapped-modifier-logic",
15 "wrap modifier logic to reduce code size"
16);
17
18impl<'hir> LateLintPass<'hir> for UnwrappedModifierLogic {
19 fn check_function(
20 &mut self,
21 ctx: &LintContext,
22 hir: &'hir hir::Hir<'hir>,
23 func: &'hir hir::Function<'hir>,
24 ) {
25 let body = match (func.kind, &func.body, func.name) {
27 (ast::FunctionKind::Modifier, Some(body), Some(_)) => body,
28 _ => return,
29 };
30
31 let stmts = body.stmts[..].as_ref();
33 let (before, after) = stmts
34 .iter()
35 .position(|s| matches!(s.kind, hir::StmtKind::Placeholder))
36 .map_or((stmts, &[][..]), |idx| (&stmts[..idx], &stmts[idx + 1..]));
37
38 if let Some(suggestion) = self.get_snippet(ctx, hir, func, before, after) {
40 ctx.emit_with_suggestion(
41 &UNWRAPPED_MODIFIER_LOGIC,
42 func.span.to(func.body_span),
43 suggestion,
44 );
45 }
46 }
47}
48
49impl UnwrappedModifierLogic {
50 fn is_valid_expr(&self, hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
52 if let hir::ExprKind::Call(func_expr, _, _) = &expr.kind {
53 if let hir::ExprKind::Ident(resolutions) = &func_expr.kind {
54 return !resolutions.iter().any(|r| matches!(r, Res::Builtin(_)));
55 }
56
57 if let hir::ExprKind::Member(base, _) = &func_expr.kind
58 && let hir::ExprKind::Ident(resolutions) = &base.kind
59 {
60 return resolutions.iter().any(|r| {
61 matches!(r, Res::Item(hir::ItemId::Contract(id)) if hir.contract(*id).kind == ast::ContractKind::Library)
62 });
63 }
64 }
65
66 false
67 }
68
69 fn stmts_require_wrapping(&self, hir: &hir::Hir<'_>, stmts: &[hir::Stmt<'_>]) -> bool {
79 let (mut res, mut has_valid_stmt) = (false, false);
80 for stmt in stmts {
81 match &stmt.kind {
82 hir::StmtKind::Placeholder => continue,
83 hir::StmtKind::Expr(expr) => {
84 if !self.is_valid_expr(hir, expr) || has_valid_stmt {
85 res = true;
86 }
87 has_valid_stmt = true;
88 }
89 hir::StmtKind::Err(_) => return false,
92 _ => res = true,
93 }
94 }
95
96 res
97 }
98
99 fn get_snippet<'a>(
100 &self,
101 ctx: &LintContext,
102 hir: &hir::Hir<'_>,
103 func: &hir::Function<'_>,
104 before: &'a [hir::Stmt<'a>],
105 after: &'a [hir::Stmt<'a>],
106 ) -> Option<Suggestion> {
107 let wrap_before = !before.is_empty() && self.stmts_require_wrapping(hir, before);
108 let wrap_after = !after.is_empty() && self.stmts_require_wrapping(hir, after);
109
110 if !(wrap_before || wrap_after) {
111 return None;
112 }
113
114 let binding = func.name.unwrap();
115 let modifier_name = binding.name.as_str();
116 let mut param_list = vec![];
117 let mut param_decls = vec![];
118
119 for var_id in func.parameters {
120 let var = hir.variable(*var_id);
121 let ty = ctx
122 .span_to_snippet(var.ty.span)
123 .unwrap_or_else(|| "/* unknown type */".to_string());
124
125 if let Some(ident) = var.name {
127 param_list.push(ident.to_string());
128 param_decls.push(format!("{ty} {}", ident.to_string()));
129 }
130 }
131
132 let param_list = param_list.join(", ");
133 let param_decls = param_decls.join(", ");
134
135 let body_indent = " ".repeat(ctx.get_span_indentation(
136 before.first().or(after.first()).map(|stmt| stmt.span).unwrap_or(func.span),
137 ));
138 let body = match (wrap_before, wrap_after) {
139 (true, true) => format!(
140 "{body_indent}_{modifier_name}Before({param_list});\n{body_indent}_;\n{body_indent}_{modifier_name}After({param_list});"
141 ),
142 (true, false) => {
143 format!("{body_indent}_{modifier_name}({param_list});\n{body_indent}_;")
144 }
145 (false, true) => {
146 format!("{body_indent}_;\n{body_indent}_{modifier_name}({param_list});")
147 }
148 _ => unreachable!(),
149 };
150
151 let mod_indent = " ".repeat(ctx.get_span_indentation(func.span));
152 let mut replacement =
153 format!("modifier {modifier_name}({param_decls}) {{\n{body}\n{mod_indent}}}");
154
155 let build_func = |stmts: &[hir::Stmt<'_>], suffix: &str| {
156 let body_stmts = stmts
157 .iter()
158 .filter_map(|s| ctx.span_to_snippet(s.span))
159 .map(|code| format!("\n{body_indent}{code}"))
160 .collect::<String>();
161 format!(
162 "\n\n{mod_indent}function _{modifier_name}{suffix}({param_decls}) internal {{{body_stmts}\n{mod_indent}}}"
163 )
164 };
165
166 if wrap_before {
167 replacement.push_str(&build_func(before, if wrap_after { "Before" } else { "" }));
168 }
169 if wrap_after {
170 replacement.push_str(&build_func(after, if wrap_before { "After" } else { "" }));
171 }
172
173 Some(
174 Suggestion::fix(
175 replacement,
176 ast::interface::diagnostics::Applicability::MachineApplicable,
177 )
178 .with_desc("wrap modifier logic to reduce code size"),
179 )
180 }
181}