Skip to main content

forge_lint/sol/info/
multi_contract_file.rs

1use crate::{
2    linter::{EarlyLintPass, Lint, LintContext},
3    sol::{Severity, SolLint},
4};
5
6use foundry_config::lint::LintSpecificConfig;
7use solar::ast;
8use std::sync::Arc;
9
10declare_forge_lint!(
11    MULTI_CONTRACT_FILE,
12    Severity::Info,
13    "multi-contract-file",
14    "prefer having only one contract, interface or library per file"
15);
16
17#[derive(Debug)]
18pub(super) struct MultiContractFilePass {
19    config: Arc<LintSpecificConfig>,
20}
21
22impl MultiContractFilePass {
23    pub(super) const fn new(config: Arc<LintSpecificConfig>) -> Self {
24        Self { config }
25    }
26}
27
28impl<'ast> EarlyLintPass<'ast> for MultiContractFilePass {
29    fn check_full_source_unit(
30        &mut self,
31        ctx: &LintContext<'ast, '_>,
32        unit: &'ast ast::SourceUnit<'ast>,
33    ) {
34        if !ctx.is_lint_enabled(MULTI_CONTRACT_FILE.id()) {
35            return;
36        }
37
38        // Collect spans of all contract-like items, skipping those that are exempted
39        let relevant_spans: Vec<_> = unit
40            .items
41            .iter()
42            .filter_map(|item| match &item.kind {
43                ast::ItemKind::Contract(c) => {
44                    (!self.config.is_exempted(&c.kind)).then_some(c.name.span)
45                }
46                _ => None,
47            })
48            .collect();
49
50        // Flag all if there's more than one
51        if relevant_spans.len() > 1 {
52            for span in relevant_spans {
53                ctx.emit(&MULTI_CONTRACT_FILE, span);
54            }
55        }
56    }
57}