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