Skip to main content

forge_lint/sol/info/
pragma_directive.rs

1use crate::{
2    linter::{Lint, ProjectLintEmitter, ProjectLintPass, ProjectSource},
3    sol::{Severity, SolLint, info::PragmaDirective},
4};
5use solar::{ast, interface::Span};
6
7declare_forge_lint!(
8    PRAGMA_INCONSISTENT,
9    Severity::Info,
10    "pragma-inconsistent",
11    "inconsistent Solidity pragma version requirements across the project"
12);
13
14impl<'ast> ProjectLintPass<'ast> for PragmaDirective {
15    fn check_project(&mut self, ctx: &ProjectLintEmitter<'_, '_>, sources: &[ProjectSource<'ast>]) {
16        if !ctx.is_lint_enabled(PRAGMA_INCONSISTENT.id()) {
17            return;
18        }
19
20        // Collect every `pragma solidity` directive across input sources, with its rendered
21        // version-requirement string for grouping. Stores source index to avoid lifetime
22        // invariance issues with `&ProjectSource<'ast>`.
23        let mut entries: Vec<(usize, Span, String)> = Vec::new();
24        for (idx, source) in sources.iter().enumerate() {
25            for (span, req) in solidity_pragmas(source.ast) {
26                entries.push((idx, span, req.to_string()));
27            }
28        }
29
30        // Stable order for snapshots and JSON output.
31        entries.sort_by(|a, b| {
32            sources[a.0].path.cmp(&sources[b.0].path).then(a.1.lo().cmp(&b.1.lo()))
33        });
34
35        // Build the distinct list once and bail if all sources agree.
36        let mut distinct: Vec<&str> = entries.iter().map(|(_, _, s)| s.as_str()).collect();
37        distinct.sort_unstable();
38        distinct.dedup();
39        if distinct.len() < 2 {
40            return;
41        }
42
43        for (idx, span, req_str) in &entries {
44            let others = distinct
45                .iter()
46                .filter(|v| **v != req_str.as_str())
47                .copied()
48                .collect::<Vec<_>>()
49                .join(", ");
50            let msg = format!(
51                "'pragma solidity {req_str};' conflicts with other version requirements in the project: {others}"
52            );
53            ctx.emit_with_msg(&sources[*idx], &PRAGMA_INCONSISTENT, *span, msg);
54        }
55    }
56}
57
58/// Yields every top-level `pragma solidity ...;` directive in `unit`.
59fn solidity_pragmas<'ast>(
60    unit: &'ast ast::SourceUnit<'ast>,
61) -> impl Iterator<Item = (Span, &'ast ast::SemverReq<'ast>)> + 'ast {
62    unit.items.iter().filter_map(|item| match &item.kind {
63        ast::ItemKind::Pragma(p) => match &p.tokens {
64            ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => {
65                Some((item.span, req))
66            }
67            _ => None,
68        },
69        _ => None,
70    })
71}