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