1use super::install;
2use clap::{Parser, ValueHint};
3use eyre::{Result, eyre};
4use forge_lint::{
5 linter::Linter,
6 sol::{SolLint, SolLintError, SolidityLinter},
7};
8use foundry_cli::{
9 opts::{BuildOpts, configure_pcx_all_sources},
10 utils::{FoundryPathExt, LoadConfig},
11};
12use foundry_common::shell;
13use foundry_compilers::{FileFilter, solc::SolcLanguage, utils::SOLC_EXTENSIONS};
14use foundry_config::{SkipBuildFilters, filter::expand_globs, lint::Severity};
15use std::path::PathBuf;
16
17#[derive(Clone, Debug, Parser)]
19pub struct LintArgs {
20 #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))]
22 pub(crate) paths: Vec<PathBuf>,
23
24 #[arg(long, value_name = "SEVERITY", num_args(1..))]
28 pub(crate) severity: Option<Vec<Severity>>,
29
30 #[arg(long = "only-lint", value_name = "LINT_ID", num_args(1..))]
33 pub(crate) lint: Option<Vec<String>>,
34
35 #[command(flatten)]
36 pub(crate) build: BuildOpts,
37}
38
39foundry_config::impl_figment_convert!(LintArgs, build);
40
41impl LintArgs {
42 pub async fn run(self) -> Result<()> {
43 let format_json = shell::is_json();
44 let mut config = self.load_config()?;
45
46 if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings
47 {
48 config = self.load_config()?;
50 }
51
52 let project = config.ephemeral_project()?;
53 let path_config = config.project_paths();
54
55 let ignored = expand_globs(&config.root, config.lint.ignore.iter())?
57 .iter()
58 .flat_map(foundry_common::fs::canonicalize_path)
59 .collect::<Vec<_>>();
60
61 let cwd = std::env::current_dir()?;
62 let mut input = match &self.paths[..] {
63 [] => {
64 config
66 .project_paths::<SolcLanguage>()
67 .input_files_iter()
68 .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p))))
69 .collect()
70 }
71 paths => {
72 let mut inputs = Vec::with_capacity(paths.len());
74 for path in paths {
75 if path.is_dir() {
76 inputs
77 .extend(foundry_compilers::utils::source_files(path, SOLC_EXTENSIONS));
78 } else if path.is_sol() {
79 inputs.push(path.clone());
80 } else {
81 warn!("cannot process path {}", path.display());
82 }
83 }
84 inputs
85 }
86 };
87 let skip = SkipBuildFilters::new(config.skip.clone(), config.root.clone());
88 input.retain(|path| skip.is_match(path));
89
90 if input.is_empty() {
91 sh_status!("nothing to lint")?;
92 return Ok(());
93 }
94
95 let parse_lints = |lints: &[String]| -> Result<Vec<SolLint>, SolLintError> {
96 lints.iter().map(|s| SolLint::try_from(s.as_str())).collect()
97 };
98
99 let (include, exclude, severity) = match &self.lint {
102 Some(cli_lints) => (Some(parse_lints(cli_lints)?), None, vec![]),
103 None => {
104 let severity = self.severity.clone().unwrap_or(config.lint.severity.clone());
105 (None, Some(parse_lints(&config.lint.exclude_lints)?), severity)
106 }
107 };
108
109 if project.compiler.solc.is_none() {
110 return Err(eyre!("linting not supported for this language"));
111 }
112
113 let linter = SolidityLinter::new(path_config)
114 .with_json_emitter(format_json)
115 .with_json_emitter_stdout(format_json)
116 .with_description(true)
117 .with_lints(include)
118 .without_lints(exclude)
119 .with_severity(if severity.is_empty() { None } else { Some(severity) })
120 .with_lint_specific(&config.lint.lint_specific);
121
122 let mut opts = solar::interface::config::CompileOpts::default();
123 if format_json {
124 opts.error_format = solar::interface::config::ErrorFormat::RustcJson;
125 }
126 let session = solar::interface::Session::builder().opts(opts);
127 let session =
128 if format_json { session.build() } else { session.with_stderr_emitter().build() };
129 if format_json {
130 let writer = Box::new(std::io::BufWriter::new(std::io::stdout()));
131 let emitter = solar::interface::diagnostics::JsonEmitter::new(
132 writer,
133 session.clone_source_map(),
134 solar::interface::ColorChoice::Never,
135 )
136 .rustc_like(true);
137 session.dcx.set_emitter(Box::new(emitter));
138 }
139 let mut compiler = solar::sema::Compiler::new(session);
140
141 let has_sources = compiler.enter_mut(|compiler| -> Result<bool> {
143 let mut pcx = compiler.parse();
144 pcx.set_resolve_imports(true);
145 let has_sources =
146 configure_pcx_all_sources(&mut pcx, &config, Some(&project), Some(&input))?;
147 pcx.parse();
148 Ok(has_sources)
149 })?;
150 if !has_sources {
151 return Err(eyre!("unable to lint. Solar only supports Solidity versions >=0.8.0"));
152 }
153 if let Err(err) = linter.lint(&input, config.deny, &mut compiler) {
154 if format_json && compiler.dcx().has_errors().is_err() {
155 std::process::exit(1);
157 }
158 return Err(err);
159 }
160
161 Ok(())
162 }
163}