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 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
58fn 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}