1use clap::ValueEnum;
4use core::fmt;
5use serde::{Deserialize, Deserializer, Serialize};
6use solar::{ast, interface::diagnostics::Level};
7use std::str::FromStr;
8use yansi::Paint;
9
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct LinterConfig {
13 pub severity: Vec<Severity>,
17
18 pub exclude_lints: Vec<String>,
20
21 pub ignore: Vec<String>,
23
24 pub lint_on_build: bool,
28
29 pub lint_specific: LintSpecificConfig,
31}
32
33impl Default for LinterConfig {
34 fn default() -> Self {
35 Self {
36 lint_on_build: true,
37 severity: vec![Severity::High, Severity::Med, Severity::Low],
38 exclude_lints: Vec::new(),
39 ignore: Vec::new(),
40 lint_specific: LintSpecificConfig::default(),
41 }
42 }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum ContractException {
49 Interface,
50 Library,
51 AbstractContract,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(default)]
57pub struct LintSpecificConfig {
58 pub mixed_case_exceptions: Vec<String>,
64
65 pub multi_contract_file_exceptions: Vec<ContractException>,
72}
73
74impl Default for LintSpecificConfig {
75 fn default() -> Self {
76 Self {
77 mixed_case_exceptions: vec![
78 "ERC".to_string(),
79 "URI".to_string(),
80 "ID".to_string(),
81 "URL".to_string(),
82 "API".to_string(),
83 "JSON".to_string(),
84 "XML".to_string(),
85 "HTML".to_string(),
86 "HTTP".to_string(),
87 "HTTPS".to_string(),
88 ],
89 multi_contract_file_exceptions: Vec::new(),
90 }
91 }
92}
93
94impl LintSpecificConfig {
95 pub fn is_exempted(&self, contract_kind: &ast::ContractKind) -> bool {
97 let exception_to_check = match contract_kind {
98 ast::ContractKind::Interface => ContractException::Interface,
99 ast::ContractKind::Library => ContractException::Library,
100 ast::ContractKind::AbstractContract => ContractException::AbstractContract,
101 ast::ContractKind::Contract => return false,
103 };
104
105 self.multi_contract_file_exceptions.contains(&exception_to_check)
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
111pub enum Severity {
112 High,
113 Med,
114 Low,
115 Info,
116 Gas,
117 CodeSize,
118}
119
120impl Severity {
121 const fn to_str(self) -> &'static str {
122 match self {
123 Self::High => "High",
124 Self::Med => "Med",
125 Self::Low => "Low",
126 Self::Info => "Info",
127 Self::Gas => "Gas",
128 Self::CodeSize => "CodeSize",
129 }
130 }
131
132 const fn to_str_kebab(self) -> &'static str {
133 match self {
134 Self::High => "high",
135 Self::Med => "medium",
136 Self::Low => "low",
137 Self::Info => "info",
138 Self::Gas => "gas",
139 Self::CodeSize => "code-size",
140 }
141 }
142
143 pub fn color(&self, message: &str) -> String {
144 match self {
145 Self::High => Paint::red(message).bold().to_string(),
146 Self::Med => Paint::rgb(message, 255, 135, 61).bold().to_string(),
147 Self::Low => Paint::yellow(message).bold().to_string(),
148 Self::Info => Paint::cyan(message).bold().to_string(),
149 Self::Gas => Paint::green(message).bold().to_string(),
150 Self::CodeSize => Paint::green(message).bold().to_string(),
151 }
152 }
153}
154
155impl From<Severity> for Level {
156 fn from(severity: Severity) -> Self {
157 match severity {
158 Severity::High | Severity::Med | Severity::Low => Self::Warning,
159 Severity::Info | Severity::Gas | Severity::CodeSize => Self::Note,
160 }
161 }
162}
163
164impl fmt::Display for Severity {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(f, "{}", self.color(self.to_str()))
167 }
168}
169
170impl Serialize for Severity {
171 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172 where
173 S: serde::Serializer,
174 {
175 self.to_str_kebab().serialize(serializer)
176 }
177}
178
179impl<'de> Deserialize<'de> for Severity {
181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182 where
183 D: Deserializer<'de>,
184 {
185 let s = String::deserialize(deserializer)?;
186 FromStr::from_str(&s).map_err(serde::de::Error::custom)
187 }
188}
189
190impl FromStr for Severity {
191 type Err = String;
192
193 fn from_str(s: &str) -> Result<Self, Self::Err> {
194 match s.to_lowercase().as_str() {
195 "high" => Ok(Self::High),
196 "med" | "medium" => Ok(Self::Med),
197 "low" => Ok(Self::Low),
198 "info" => Ok(Self::Info),
199 "gas" => Ok(Self::Gas),
200 "size" | "codesize" | "code-size" => Ok(Self::CodeSize),
201 _ => Err(format!(
202 "unknown variant: found `{s}`, expected `one of `High`, `Med`, `Low`, `Info`, `Gas`, `CodeSize`"
203 )),
204 }
205 }
206}