use std::path::PathBuf;
use derive_more::IsVariant;
use figment::{
Figment, Metadata, Profile, Provider,
providers::{Env, Format as _, Toml},
value::{Dict, Map},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, IsVariant)]
#[serde(rename_all = "lowercase")]
pub enum Req {
#[default]
Ignored,
Required,
Forbidden,
}
impl Req {
#[must_use]
pub fn is_required_or_ignored(&self) -> bool {
match self {
Req::Required | Req::Ignored => true,
Req::Forbidden => false,
}
}
#[must_use]
pub fn is_forbidden_or_ignored(&self) -> bool {
match self {
Req::Forbidden | Req::Ignored => true,
Req::Required => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct FunctionRules {
#[builder(default = Req::Required)]
pub notice: Req,
#[builder(default)]
pub dev: Req,
#[builder(default = Req::Required)]
pub param: Req,
#[serde(rename = "return")]
#[builder(default = Req::Required)]
pub returns: Req,
}
impl Default for FunctionRules {
fn default() -> Self {
Self {
notice: Req::Required,
dev: Req::default(),
param: Req::Required,
returns: Req::Required,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct FunctionConfig {
#[builder(default)]
pub private: FunctionRules,
#[builder(default)]
pub internal: FunctionRules,
#[builder(default)]
pub public: FunctionRules,
#[builder(default)]
pub external: FunctionRules,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct WithReturnsRules {
#[builder(default = Req::Required)]
pub notice: Req,
#[builder(default)]
pub dev: Req,
#[serde(rename = "return")]
#[builder(default = Req::Required)]
pub returns: Req,
}
impl Default for WithReturnsRules {
fn default() -> Self {
Self {
notice: Req::Required,
dev: Req::default(),
returns: Req::Required,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct NoticeDevRules {
#[builder(default = Req::Required)]
pub notice: Req,
#[builder(default)]
pub dev: Req,
}
impl Default for NoticeDevRules {
fn default() -> Self {
Self {
notice: Req::Required,
dev: Req::default(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct ContractRules {
#[builder(default)]
pub title: Req,
#[builder(default)]
pub author: Req,
#[builder(default)]
pub notice: Req,
#[builder(default)]
pub dev: Req,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct VariableConfig {
#[builder(default)]
pub private: NoticeDevRules,
#[builder(default)]
pub internal: NoticeDevRules,
#[builder(default)]
pub public: WithReturnsRules,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct WithParamsRules {
#[builder(default = Req::Required)]
pub notice: Req,
#[builder(default)]
pub dev: Req,
#[builder(default)]
pub param: Req,
}
impl Default for WithParamsRules {
fn default() -> Self {
Self {
notice: Req::Required,
dev: Req::default(),
param: Req::default(),
}
}
}
impl WithParamsRules {
#[must_use]
pub fn required() -> Self {
Self {
param: Req::Required,
..Default::default()
}
}
#[must_use]
pub fn default_constructor() -> Self {
Self {
notice: Req::Ignored,
param: Req::Required,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
#[expect(clippy::struct_excessive_bools)]
pub struct BaseConfig {
#[builder(default)]
pub paths: Vec<PathBuf>,
#[builder(default)]
pub exclude: Vec<PathBuf>,
#[builder(default = true)]
pub inheritdoc: bool,
#[builder(default = false)]
pub inheritdoc_override: bool,
#[builder(default)]
pub notice_or_dev: bool,
#[cfg_attr(not(feature = "slang"), serde(skip))]
#[builder(default)]
pub skip_version_detection: bool,
}
impl Default for BaseConfig {
fn default() -> Self {
Self {
paths: Vec::default(),
exclude: Vec::default(),
inheritdoc: true,
inheritdoc_override: false,
notice_or_dev: false,
skip_version_detection: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct OutputConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub out: Option<PathBuf>,
#[builder(default)]
pub json: bool,
#[builder(default)]
pub compact: bool,
#[builder(default)]
pub sort: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
pub struct Config {
#[builder(default)]
pub lintspec: BaseConfig,
#[builder(default)]
pub output: OutputConfig,
#[serde(rename = "constructor")]
#[builder(default = WithParamsRules::default_constructor())]
pub constructors: WithParamsRules,
#[serde(rename = "contract")]
#[builder(default)]
pub contracts: ContractRules,
#[serde(rename = "interface")]
#[builder(default)]
pub interfaces: ContractRules,
#[serde(rename = "library")]
#[builder(default)]
pub libraries: ContractRules,
#[serde(rename = "enum")]
#[builder(default)]
pub enums: WithParamsRules,
#[serde(rename = "error")]
#[builder(default = WithParamsRules::required())]
pub errors: WithParamsRules,
#[serde(rename = "event")]
#[builder(default = WithParamsRules::required())]
pub events: WithParamsRules,
#[serde(rename = "function")]
#[builder(default)]
pub functions: FunctionConfig,
#[serde(rename = "modifier")]
#[builder(default = WithParamsRules::required())]
pub modifiers: WithParamsRules,
#[serde(rename = "struct")]
#[builder(default)]
pub structs: WithParamsRules,
#[serde(rename = "variable")]
#[builder(default)]
pub variables: VariableConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
lintspec: BaseConfig::default(),
output: OutputConfig::default(),
contracts: ContractRules::default(),
interfaces: ContractRules::default(),
libraries: ContractRules::default(),
constructors: WithParamsRules::default_constructor(),
enums: WithParamsRules::default(),
errors: WithParamsRules::required(),
events: WithParamsRules::required(),
functions: FunctionConfig::default(),
modifiers: WithParamsRules::required(),
structs: WithParamsRules::default(),
variables: VariableConfig::default(),
}
}
}
impl Config {
pub fn from(provider: impl Provider) -> Result<Config, Box<figment::Error>> {
Figment::from(provider).extract().map_err(Box::new)
}
#[must_use]
pub fn figment(config_path: Option<PathBuf>) -> Figment {
Figment::from(Config::default())
.admerge(Toml::file(config_path.unwrap_or(".lintspec.toml".into())))
.admerge(Env::prefixed("LS_").split("_").map(|k| {
match k.as_str() {
"LINTSPEC.NOTICE.OR.DEV" => "LINTSPEC.NOTICE_OR_DEV".into(),
"LINTSPEC.SKIP.VERSION.DETECTION" => "LINTSPEC.SKIP_VERSION_DETECTION".into(),
_ => k.into(),
}
}))
}
}
impl Provider for Config {
fn metadata(&self) -> figment::Metadata {
Metadata::named("LintSpec Config")
}
fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
figment::providers::Serialized::defaults(Config::default()).data()
}
}
#[cfg(test)]
mod tests {
use similar_asserts::assert_eq;
use super::*;
#[test]
fn test_default_builder() {
assert_eq!(FunctionRules::default(), FunctionRules::builder().build());
assert_eq!(FunctionConfig::default(), FunctionConfig::builder().build());
assert_eq!(
WithReturnsRules::default(),
WithReturnsRules::builder().build()
);
assert_eq!(NoticeDevRules::default(), NoticeDevRules::builder().build());
assert_eq!(ContractRules::default(), ContractRules::builder().build());
assert_eq!(VariableConfig::default(), VariableConfig::builder().build());
assert_eq!(
WithParamsRules::default(),
WithParamsRules::builder().build()
);
assert_eq!(BaseConfig::default(), BaseConfig::builder().build());
assert_eq!(OutputConfig::default(), OutputConfig::builder().build());
assert_eq!(Config::default(), Config::builder().build());
}
}