forge_lint/sol/
mod.rs
1use crate::linter::{EarlyLintPass, EarlyLintVisitor, Lint, LintContext, Linter};
2use foundry_compilers::{solc::SolcLanguage, ProjectPathsConfig};
3use foundry_config::lint::Severity;
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use solar_ast::{visit::Visit, Arena};
6use solar_interface::{
7 diagnostics::{self, DiagCtxt, JsonEmitter},
8 Session, SourceMap,
9};
10use std::{
11 path::{Path, PathBuf},
12 sync::Arc,
13};
14use thiserror::Error;
15
16pub mod macros;
17
18pub mod gas;
19pub mod high;
20pub mod info;
21pub mod med;
22
23#[derive(Debug, Clone)]
26pub struct SolidityLinter {
27 path_config: ProjectPathsConfig,
28 severity: Option<Vec<Severity>>,
29 lints_included: Option<Vec<SolLint>>,
30 lints_excluded: Option<Vec<SolLint>>,
31 with_description: bool,
32 with_json_emitter: bool,
33}
34
35impl SolidityLinter {
36 pub fn new(path_config: ProjectPathsConfig) -> Self {
37 Self {
38 path_config,
39 severity: None,
40 lints_included: None,
41 lints_excluded: None,
42 with_description: true,
43 with_json_emitter: false,
44 }
45 }
46
47 pub fn with_severity(mut self, severity: Option<Vec<Severity>>) -> Self {
48 self.severity = severity;
49 self
50 }
51
52 pub fn with_lints(mut self, lints: Option<Vec<SolLint>>) -> Self {
53 self.lints_included = lints;
54 self
55 }
56
57 pub fn without_lints(mut self, lints: Option<Vec<SolLint>>) -> Self {
58 self.lints_excluded = lints;
59 self
60 }
61
62 pub fn with_description(mut self, with: bool) -> Self {
63 self.with_description = with;
64 self
65 }
66
67 pub fn with_json_emitter(mut self, with: bool) -> Self {
68 self.with_json_emitter = with;
69 self
70 }
71
72 fn process_file(&self, sess: &Session, file: &Path) {
73 let arena = Arena::new();
74
75 let _ = sess.enter(|| -> Result<(), diagnostics::ErrorGuaranteed> {
76 let mut passes_and_lints = Vec::new();
78 passes_and_lints.extend(high::create_lint_passes());
79 passes_and_lints.extend(med::create_lint_passes());
80 passes_and_lints.extend(info::create_lint_passes());
81
82 if !self.path_config.is_test_or_script(file) {
84 passes_and_lints.extend(gas::create_lint_passes());
85 }
86
87 let mut passes: Vec<Box<dyn EarlyLintPass<'_>>> = passes_and_lints
89 .into_iter()
90 .filter_map(|(pass, lint)| {
91 let matches_severity = match self.severity {
92 Some(ref target) => target.contains(&lint.severity()),
93 None => true,
94 };
95 let matches_lints_inc = match self.lints_included {
96 Some(ref target) => target.contains(&lint),
97 None => true,
98 };
99 let matches_lints_exc = match self.lints_excluded {
100 Some(ref target) => target.contains(&lint),
101 None => false,
102 };
103
104 if matches_severity && matches_lints_inc && !matches_lints_exc {
105 Some(pass)
106 } else {
107 None
108 }
109 })
110 .collect();
111
112 let mut parser = solar_parse::Parser::from_file(sess, &arena, file)?;
114 let ast = parser.parse_file().map_err(|e| e.emit())?;
115
116 let ctx = LintContext::new(sess, self.with_description);
118 let mut visitor = EarlyLintVisitor { ctx: &ctx, passes: &mut passes };
119 _ = visitor.visit_source_unit(&ast);
120
121 Ok(())
122 });
123 }
124}
125
126impl Linter for SolidityLinter {
127 type Language = SolcLanguage;
128 type Lint = SolLint;
129
130 fn lint(&self, input: &[PathBuf]) {
131 let mut builder = Session::builder();
132
133 if self.with_json_emitter {
135 let map = Arc::<SourceMap>::default();
136 let json_emitter = JsonEmitter::new(Box::new(std::io::stderr()), map.clone())
137 .rustc_like(true)
138 .ui_testing(false);
139
140 builder = builder.dcx(DiagCtxt::new(Box::new(json_emitter))).source_map(map);
141 } else {
142 builder = builder.with_stderr_emitter();
143 };
144
145 let mut sess = builder.build();
147 sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false);
148
149 sess.enter_parallel(|| {
151 input.into_par_iter().for_each(|file| {
152 self.process_file(&sess, file);
153 });
154 });
155 }
156}
157
158#[derive(Error, Debug)]
159pub enum SolLintError {
160 #[error("Unknown lint ID: {0}")]
161 InvalidId(String),
162}
163
164#[derive(Debug, Clone, Copy, Eq, PartialEq)]
165pub struct SolLint {
166 id: &'static str,
167 description: &'static str,
168 help: &'static str,
169 severity: Severity,
170}
171
172impl Lint for SolLint {
173 fn id(&self) -> &'static str {
174 self.id
175 }
176 fn severity(&self) -> Severity {
177 self.severity
178 }
179 fn description(&self) -> &'static str {
180 self.description
181 }
182 fn help(&self) -> &'static str {
183 self.help
184 }
185}
186
187impl<'a> TryFrom<&'a str> for SolLint {
188 type Error = SolLintError;
189
190 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
191 for &lint in high::REGISTERED_LINTS {
192 if lint.id() == value {
193 return Ok(lint);
194 }
195 }
196
197 for &lint in med::REGISTERED_LINTS {
198 if lint.id() == value {
199 return Ok(lint);
200 }
201 }
202
203 for &lint in info::REGISTERED_LINTS {
204 if lint.id() == value {
205 return Ok(lint);
206 }
207 }
208
209 for &lint in gas::REGISTERED_LINTS {
210 if lint.id() == value {
211 return Ok(lint);
212 }
213 }
214
215 Err(SolLintError::InvalidId(value.to_string()))
216 }
217}