forge/cmd/
lint.rs
1use clap::{Parser, ValueHint};
2use eyre::{Result, eyre};
3use forge_lint::{
4 linter::Linter,
5 sol::{SolLint, SolLintError, SolidityLinter},
6};
7use foundry_cli::{
8 opts::{BuildOpts, solar_pcx_from_build_opts},
9 utils::{FoundryPathExt, LoadConfig},
10};
11use foundry_compilers::{solc::SolcLanguage, utils::SOLC_EXTENSIONS};
12use foundry_config::{filter::expand_globs, lint::Severity};
13use std::path::PathBuf;
14
15#[derive(Clone, Debug, Parser)]
17pub struct LintArgs {
18 #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))]
20 paths: Vec<PathBuf>,
21
22 #[arg(long, value_name = "SEVERITY", num_args(1..))]
26 severity: Option<Vec<Severity>>,
27
28 #[arg(long = "only-lint", value_name = "LINT_ID", num_args(1..))]
31 lint: Option<Vec<String>>,
32
33 #[arg(long)]
35 json: bool,
36
37 #[command(flatten)]
38 build: BuildOpts,
39}
40
41foundry_config::impl_figment_convert!(LintArgs, build);
42
43impl LintArgs {
44 pub fn run(self) -> Result<()> {
45 let config = self.load_config()?;
46 let project = config.project()?;
47 let path_config = config.project_paths();
48
49 let ignored = expand_globs(&config.root, config.lint.ignore.iter())?
51 .iter()
52 .flat_map(foundry_common::fs::canonicalize_path)
53 .collect::<Vec<_>>();
54
55 let cwd = std::env::current_dir()?;
56 let input = match &self.paths[..] {
57 [] => {
58 config
60 .project_paths::<SolcLanguage>()
61 .input_files_iter()
62 .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p))))
63 .collect()
64 }
65 paths => {
66 let mut inputs = Vec::with_capacity(paths.len());
68 for path in paths {
69 if path.is_dir() {
70 inputs
71 .extend(foundry_compilers::utils::source_files(path, SOLC_EXTENSIONS));
72 } else if path.is_sol() {
73 inputs.push(path.to_path_buf());
74 } else {
75 warn!("Cannot process path {}", path.display());
76 }
77 }
78 inputs
79 }
80 };
81
82 if input.is_empty() {
83 sh_println!("Nothing to lint")?;
84 return Ok(());
85 }
86
87 let parse_lints = |lints: &[String]| -> Result<Vec<SolLint>, SolLintError> {
88 lints.iter().map(|s| SolLint::try_from(s.as_str())).collect()
89 };
90
91 let (include, exclude) = match &self.lint {
93 Some(cli_lints) => (Some(parse_lints(cli_lints)?), None),
94 None => (None, Some(parse_lints(&config.lint.exclude_lints)?)),
95 };
96
97 let severity = match self.severity {
99 Some(target) => target,
100 None => config.lint.severity,
101 };
102
103 if project.compiler.solc.is_none() {
104 return Err(eyre!("Linting not supported for this language"));
105 }
106
107 let linter = SolidityLinter::new(path_config)
108 .with_json_emitter(self.json)
109 .with_description(true)
110 .with_lints(include)
111 .without_lints(exclude)
112 .with_severity(if severity.is_empty() { None } else { Some(severity) });
113
114 let sess = linter.init();
115
116 let pcx = solar_pcx_from_build_opts(&sess, &self.build, Some(&project), Some(&input))?;
117 linter.early_lint(&input, pcx);
118
119 let pcx = solar_pcx_from_build_opts(&sess, &self.build, Some(&project), Some(&input))?;
120 linter.late_lint(&input, pcx);
121
122 Ok(())
123 }
124}