forge_lint/sol/info/
modifier_used_only_once.rs1use super::ModifierUsedOnlyOnce;
2use crate::{
3 linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource},
4 sol::{Severity, SolLint},
5};
6use solar::{ast::FunctionKind, interface::source_map::FileName, sema::hir};
7use std::collections::HashMap;
8
9declare_forge_lint!(
10 MODIFIER_USED_ONLY_ONCE,
11 Severity::Info,
12 "modifier-used-only-once",
13 "this modifier is used only once"
14);
15
16impl<'ast> ProjectLintPass<'ast> for ModifierUsedOnlyOnce {
17 fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) {
18 if !ctx.is_lint_enabled(MODIFIER_USED_ONLY_ONCE.id()) {
19 return;
20 }
21
22 let input_source_idx: HashMap<_, _> = ctx
25 .gcx()
26 .hir
27 .sources_enumerated()
28 .filter_map(|(sid, src)| {
29 let FileName::Real(path) = &src.file.name else { return None };
30 Some((sid, sources.iter().position(|s| &s.path == path)?))
31 })
32 .collect();
33 if input_source_idx.is_empty() {
34 return;
35 }
36
37 let mut counts = HashMap::<hir::FunctionId, usize>::new();
40 for function_id in ctx.gcx().hir.function_ids() {
41 for invocation in ctx.gcx().hir.function(function_id).modifiers {
42 if let hir::ItemId::Function(modifier_id) = invocation.id {
43 *counts.entry(modifier_id).or_default() += 1;
44 }
45 }
46 }
47
48 for function_id in ctx.gcx().hir.function_ids() {
49 let function = ctx.gcx().hir.function(function_id);
50 let Some(&src_idx) = input_source_idx.get(&function.source) else { continue };
51 if function.kind == FunctionKind::Modifier
55 && function.body.is_some()
56 && !function.virtual_
57 && !function.override_
58 && counts.get(&function_id) == Some(&1)
59 {
60 ctx.emit(&sources[src_idx], &MODIFIER_USED_ONLY_ONCE, function.keyword_span());
61 }
62 }
63 }
64}