foundry_config/
lint.rs

1//! Configuration specific to the `forge lint` command and the `forge_lint` package
2
3use clap::ValueEnum;
4use core::fmt;
5use serde::{Deserialize, Deserializer, Serialize};
6use solar_interface::diagnostics::Level;
7use std::str::FromStr;
8use yansi::Paint;
9
10/// Contains the config and rule set
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct LinterConfig {
13    /// Specifies which lints to run based on severity.
14    ///
15    /// If uninformed, all severities are checked.
16    pub severity: Vec<Severity>,
17
18    /// Deny specific lints based on their ID (e.g. "mixed-case-function").
19    pub exclude_lints: Vec<String>,
20
21    /// Globs to ignore
22    pub ignore: Vec<String>,
23
24    /// Whether to run linting during `forge build`.
25    ///
26    /// Defaults to true. Set to false to disable automatic linting during builds.
27    pub lint_on_build: bool,
28
29    /// Configurable patterns that should be excluded when performing `mixedCase` lint checks.
30    ///
31    /// Default's to ["ERC"] to allow common names like `rescueERC20` or `ERC721TokenReceiver`.
32    pub mixed_case_exceptions: Vec<String>,
33}
34
35impl Default for LinterConfig {
36    fn default() -> Self {
37        Self {
38            lint_on_build: true,
39            severity: Vec::new(),
40            exclude_lints: Vec::new(),
41            ignore: Vec::new(),
42            mixed_case_exceptions: vec!["ERC".to_string()],
43        }
44    }
45}
46
47/// Severity of a lint
48#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize)]
49pub enum Severity {
50    High,
51    Med,
52    Low,
53    Info,
54    Gas,
55    CodeSize,
56}
57
58impl Severity {
59    pub fn color(&self, message: &str) -> String {
60        match self {
61            Self::High => Paint::red(message).bold().to_string(),
62            Self::Med => Paint::rgb(message, 255, 135, 61).bold().to_string(),
63            Self::Low => Paint::yellow(message).bold().to_string(),
64            Self::Info => Paint::cyan(message).bold().to_string(),
65            Self::Gas => Paint::green(message).bold().to_string(),
66            Self::CodeSize => Paint::green(message).bold().to_string(),
67        }
68    }
69}
70
71impl From<Severity> for Level {
72    fn from(severity: Severity) -> Self {
73        match severity {
74            Severity::High | Severity::Med | Severity::Low => Self::Warning,
75            Severity::Info | Severity::Gas | Severity::CodeSize => Self::Note,
76        }
77    }
78}
79
80impl fmt::Display for Severity {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let colored = match self {
83            Self::High => self.color("High"),
84            Self::Med => self.color("Med"),
85            Self::Low => self.color("Low"),
86            Self::Info => self.color("Info"),
87            Self::Gas => self.color("Gas"),
88            Self::CodeSize => self.color("CodeSize"),
89        };
90        write!(f, "{colored}")
91    }
92}
93
94// Custom deserialization to make `Severity` parsing case-insensitive
95impl<'de> Deserialize<'de> for Severity {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: Deserializer<'de>,
99    {
100        let s = String::deserialize(deserializer)?;
101        FromStr::from_str(&s).map_err(serde::de::Error::custom)
102    }
103}
104
105impl FromStr for Severity {
106    type Err = String;
107
108    fn from_str(s: &str) -> Result<Self, Self::Err> {
109        match s.to_lowercase().as_str() {
110            "high" => Ok(Self::High),
111            "med" | "medium" => Ok(Self::Med),
112            "low" => Ok(Self::Low),
113            "info" => Ok(Self::Info),
114            "gas" => Ok(Self::Gas),
115            "size" | "codesize" | "code-size" => Ok(Self::CodeSize),
116            _ => Err(format!(
117                "unknown variant: found `{s}`, expected `one of `High`, `Med`, `Low`, `Info`, `Gas`, `CodeSize`"
118            )),
119        }
120    }
121}