forge_lint/sol/info/
interface_naming.rs1use super::InterfaceFileNaming;
2use crate::{
3 linter::{EarlyLintPass, Lint, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::ast;
7
8declare_forge_lint!(
9 INTERFACE_FILE_NAMING,
10 Severity::Info,
11 "interface-file-naming",
12 "interface file name is missing the `I` prefix"
13);
14
15declare_forge_lint!(
16 INTERFACE_NAMING,
17 Severity::Info,
18 "interface-naming",
19 "interface name is missing the `I` prefix"
20);
21
22impl<'ast> EarlyLintPass<'ast> for InterfaceFileNaming {
23 fn check_full_source_unit(
24 &mut self,
25 ctx: &LintContext<'ast, '_>,
26 unit: &'ast ast::SourceUnit<'ast>,
27 ) {
28 if !ctx.is_lint_enabled(INTERFACE_FILE_NAMING.id()) {
29 return;
30 }
31 let mut contracts = unit.items.iter().filter_map(|item| match &item.kind {
33 ast::ItemKind::Contract(c) => Some(c),
34 _ => None,
35 });
36 if let Some(first) = contracts.next()
37 && std::iter::once(first)
38 .chain(contracts)
39 .all(|c| c.kind == ast::ContractKind::Interface)
40 && let Some(file_name) = file_name(ctx, unit)
41 && !file_name.starts_with('I')
42 {
43 ctx.emit(&INTERFACE_FILE_NAMING, first.name.span);
44 }
45 }
46
47 fn check_item_contract(&mut self, ctx: &LintContext, contract: &'ast ast::ItemContract<'ast>) {
48 if contract.kind == ast::ContractKind::Interface && !contract.name.as_str().starts_with('I')
49 {
50 ctx.emit(&INTERFACE_NAMING, contract.name.span);
51 }
52 }
53}
54
55fn file_name(ctx: &LintContext, unit: &ast::SourceUnit<'_>) -> Option<String> {
56 let first_item_span = unit.items.first()?.span;
57 let file = ctx.session().source_map().lookup_source_file(first_item_span.lo());
58 Some(file.name.as_real()?.file_name()?.to_str()?.to_string())
59}