forge_lint/sol/info/
pragma_directive.rs1use 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 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 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 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 let (idx, span, _) = entries[0];
44 let versions = distinct.join(", ");
45 let msg = format!(
46 "{} different Solidity pragma version requirements are used: {versions}",
47 distinct.len()
48 );
49 ctx.emit_with_msg(&sources[idx], &PRAGMA_INCONSISTENT, span, msg);
50 }
51}
52
53fn solidity_pragmas<'ast>(
55 unit: &'ast ast::SourceUnit<'ast>,
56) -> impl Iterator<Item = (Span, &'ast ast::SemverReq<'ast>)> + 'ast {
57 unit.items.iter().filter_map(|item| match &item.kind {
58 ast::ItemKind::Pragma(p) => match &p.tokens {
59 ast::PragmaTokens::Version(ident, req) if ident.as_str() == "solidity" => {
60 Some((item.span, req))
61 }
62 _ => None,
63 },
64 _ => None,
65 })
66}