1use std::path::PathBuf;
3
4use derive_more::IsVariant;
5use figment::{
6 Figment, Metadata, Profile, Provider,
7 providers::{Env, Format as _, Toml},
8 value::{Dict, Map},
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, IsVariant)]
14#[serde(rename_all = "lowercase")]
15pub enum Req {
16 #[default]
18 Ignored,
19
20 Required,
22
23 Forbidden,
25}
26
27impl Req {
28 #[must_use]
30 pub fn is_required_or_ignored(&self) -> bool {
31 match self {
32 Req::Required | Req::Ignored => true,
33 Req::Forbidden => false,
34 }
35 }
36
37 #[must_use]
39 pub fn is_forbidden_or_ignored(&self) -> bool {
40 match self {
41 Req::Forbidden | Req::Ignored => true,
42 Req::Required => false,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
49#[non_exhaustive]
50pub struct FunctionRules {
51 #[builder(default = Req::Required)]
53 pub notice: Req,
54
55 #[builder(default)]
57 pub dev: Req,
58
59 #[builder(default = Req::Required)]
61 pub param: Req,
62
63 #[serde(rename = "return")]
65 #[builder(default = Req::Required)]
66 pub returns: Req,
67}
68
69impl Default for FunctionRules {
70 fn default() -> Self {
71 Self {
72 notice: Req::Required,
73 dev: Req::default(),
74 param: Req::Required,
75 returns: Req::Required,
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
82#[non_exhaustive]
83pub struct FunctionConfig {
84 #[builder(default)]
86 pub private: FunctionRules,
87
88 #[builder(default)]
90 pub internal: FunctionRules,
91
92 #[builder(default)]
94 pub public: FunctionRules,
95
96 #[builder(default)]
98 pub external: FunctionRules,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
103#[non_exhaustive]
104pub struct WithReturnsRules {
105 #[builder(default = Req::Required)]
107 pub notice: Req,
108
109 #[builder(default)]
111 pub dev: Req,
112
113 #[serde(rename = "return")]
115 #[builder(default = Req::Required)]
116 pub returns: Req,
117}
118
119impl Default for WithReturnsRules {
120 fn default() -> Self {
121 Self {
122 notice: Req::Required,
123 dev: Req::default(),
124 returns: Req::Required,
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
131#[non_exhaustive]
132pub struct NoticeDevRules {
133 #[builder(default = Req::Required)]
134 pub notice: Req,
135 #[builder(default)]
136 pub dev: Req,
137}
138
139impl Default for NoticeDevRules {
140 fn default() -> Self {
141 Self {
142 notice: Req::Required,
143 dev: Req::default(),
144 }
145 }
146}
147
148#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
150#[non_exhaustive]
151pub struct ContractRules {
152 #[builder(default)]
153 pub title: Req,
154 #[builder(default)]
155 pub author: Req,
156 #[builder(default)]
157 pub notice: Req,
158 #[builder(default)]
159 pub dev: Req,
160}
161
162#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
164#[non_exhaustive]
165pub struct VariableConfig {
166 #[builder(default)]
167 pub private: NoticeDevRules,
168 #[builder(default)]
169 pub internal: NoticeDevRules,
170 #[builder(default)]
171 pub public: WithReturnsRules,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
179#[non_exhaustive]
180pub struct WithParamsRules {
181 #[builder(default = Req::Required)]
182 pub notice: Req,
183 #[builder(default)]
184 pub dev: Req,
185 #[builder(default)]
186 pub param: Req,
187}
188
189impl Default for WithParamsRules {
190 fn default() -> Self {
191 Self {
192 notice: Req::Required,
193 dev: Req::default(),
194 param: Req::default(),
195 }
196 }
197}
198
199impl WithParamsRules {
200 #[must_use]
202 pub fn required() -> Self {
203 Self {
204 param: Req::Required,
205 ..Default::default()
206 }
207 }
208
209 #[must_use]
211 pub fn default_constructor() -> Self {
212 Self {
213 notice: Req::Ignored,
214 param: Req::Required,
215 ..Default::default()
216 }
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
222#[non_exhaustive]
223#[expect(clippy::struct_excessive_bools)]
224pub struct BaseConfig {
225 #[builder(default)]
227 pub paths: Vec<PathBuf>,
228
229 #[builder(default)]
231 pub exclude: Vec<PathBuf>,
232
233 #[builder(default = true)]
235 pub inheritdoc: bool,
236
237 #[builder(default = false)]
239 pub inheritdoc_override: bool,
240
241 #[builder(default)]
243 pub notice_or_dev: bool,
244
245 #[cfg_attr(not(feature = "slang"), serde(skip))]
247 #[builder(default)]
248 pub skip_version_detection: bool,
249}
250
251impl Default for BaseConfig {
252 fn default() -> Self {
253 Self {
254 paths: Vec::default(),
255 exclude: Vec::default(),
256 inheritdoc: true,
257 inheritdoc_override: false,
258 notice_or_dev: false,
259 skip_version_detection: false,
260 }
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
266#[non_exhaustive]
267pub struct OutputConfig {
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub out: Option<PathBuf>,
271
272 #[builder(default)]
274 pub json: bool,
275
276 #[builder(default)]
278 pub compact: bool,
279
280 #[builder(default)]
282 pub sort: bool,
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
287#[non_exhaustive]
288pub struct Config {
289 #[builder(default)]
291 pub lintspec: BaseConfig,
292
293 #[builder(default)]
295 pub output: OutputConfig,
296
297 #[serde(rename = "constructor")]
299 #[builder(default = WithParamsRules::default_constructor())]
300 pub constructors: WithParamsRules,
301
302 #[serde(rename = "contract")]
304 #[builder(default)]
305 pub contracts: ContractRules,
306
307 #[serde(rename = "interface")]
309 #[builder(default)]
310 pub interfaces: ContractRules,
311
312 #[serde(rename = "library")]
314 #[builder(default)]
315 pub libraries: ContractRules,
316
317 #[serde(rename = "enum")]
319 #[builder(default)]
320 pub enums: WithParamsRules,
321
322 #[serde(rename = "error")]
324 #[builder(default = WithParamsRules::required())]
325 pub errors: WithParamsRules,
326
327 #[serde(rename = "event")]
329 #[builder(default = WithParamsRules::required())]
330 pub events: WithParamsRules,
331
332 #[serde(rename = "function")]
334 #[builder(default)]
335 pub functions: FunctionConfig,
336
337 #[serde(rename = "modifier")]
339 #[builder(default = WithParamsRules::required())]
340 pub modifiers: WithParamsRules,
341
342 #[serde(rename = "struct")]
344 #[builder(default)]
345 pub structs: WithParamsRules,
346
347 #[serde(rename = "variable")]
349 #[builder(default)]
350 pub variables: VariableConfig,
351}
352
353impl Default for Config {
354 fn default() -> Self {
355 Self {
356 lintspec: BaseConfig::default(),
357 output: OutputConfig::default(),
358 contracts: ContractRules::default(),
359 interfaces: ContractRules::default(),
360 libraries: ContractRules::default(),
361 constructors: WithParamsRules::default_constructor(),
362 enums: WithParamsRules::default(),
363 errors: WithParamsRules::required(),
364 events: WithParamsRules::required(),
365 functions: FunctionConfig::default(),
366 modifiers: WithParamsRules::required(),
367 structs: WithParamsRules::default(),
368 variables: VariableConfig::default(),
369 }
370 }
371}
372
373impl Config {
374 pub fn from(provider: impl Provider) -> Result<Config, Box<figment::Error>> {
375 Figment::from(provider).extract().map_err(Box::new)
376 }
377
378 #[must_use]
380 pub fn figment(config_path: Option<PathBuf>) -> Figment {
381 Figment::from(Config::default())
382 .admerge(Toml::file(config_path.unwrap_or(".lintspec.toml".into())))
383 .admerge(Env::prefixed("LS_").split("_").map(|k| {
384 match k.as_str() {
386 "LINTSPEC.NOTICE.OR.DEV" => "LINTSPEC.NOTICE_OR_DEV".into(),
387 "LINTSPEC.SKIP.VERSION.DETECTION" => "LINTSPEC.SKIP_VERSION_DETECTION".into(),
388 _ => k.into(),
389 }
390 }))
391 }
392}
393
394impl Provider for Config {
396 fn metadata(&self) -> figment::Metadata {
397 Metadata::named("LintSpec Config")
398 }
399
400 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
401 figment::providers::Serialized::defaults(Config::default()).data()
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use similar_asserts::assert_eq;
408
409 use super::*;
410
411 #[test]
412 fn test_default_builder() {
413 assert_eq!(FunctionRules::default(), FunctionRules::builder().build());
414 assert_eq!(FunctionConfig::default(), FunctionConfig::builder().build());
415 assert_eq!(
416 WithReturnsRules::default(),
417 WithReturnsRules::builder().build()
418 );
419 assert_eq!(NoticeDevRules::default(), NoticeDevRules::builder().build());
420 assert_eq!(ContractRules::default(), ContractRules::builder().build());
421 assert_eq!(VariableConfig::default(), VariableConfig::builder().build());
422 assert_eq!(
423 WithParamsRules::default(),
424 WithParamsRules::builder().build()
425 );
426 assert_eq!(BaseConfig::default(), BaseConfig::builder().build());
427 assert_eq!(OutputConfig::default(), OutputConfig::builder().build());
428 assert_eq!(Config::default(), Config::builder().build());
429 }
430}