foundry_config/providers/
warnings.rs

1use crate::{Config, DEPRECATIONS, Warning};
2use figment::{
3    Error, Figment, Metadata, Profile, Provider,
4    value::{Dict, Map, Value},
5};
6use heck::ToSnakeCase;
7use std::collections::BTreeMap;
8
9/// Generate warnings for unknown sections and deprecated keys
10pub struct WarningsProvider<P> {
11    provider: P,
12    profile: Profile,
13    old_warnings: Result<Vec<Warning>, Error>,
14}
15
16impl<P: Provider> WarningsProvider<P> {
17    const WARNINGS_KEY: &'static str = "__warnings";
18
19    /// Creates a new warnings provider.
20    pub fn new(
21        provider: P,
22        profile: impl Into<Profile>,
23        old_warnings: Result<Vec<Warning>, Error>,
24    ) -> Self {
25        Self { provider, profile: profile.into(), old_warnings }
26    }
27
28    /// Creates a new figment warnings provider.
29    pub fn for_figment(provider: P, figment: &Figment) -> Self {
30        let old_warnings = {
31            let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
32            if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
33                Ok(vec![])
34            } else {
35                warnings_res
36            }
37        };
38        Self::new(provider, figment.profile().clone(), old_warnings)
39    }
40
41    /// Collects all warnings.
42    pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
43        let data = self.provider.data().unwrap_or_default();
44
45        let mut out = self.old_warnings.clone()?;
46
47        // Add warning for unknown sections.
48        out.extend(data.keys().filter(|k| !Config::is_standalone_section(k.as_str())).map(
49            |unknown_section| {
50                let source = self.provider.metadata().source.map(|s| s.to_string());
51                Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
52            },
53        ));
54
55        // Add warning for deprecated keys.
56        let deprecated_key_warning = |key| {
57            DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
58                if key == *deprecated_key {
59                    Some(Warning::DeprecatedKey {
60                        old: deprecated_key.to_string(),
61                        new: new_value.to_string(),
62                    })
63                } else {
64                    None
65                }
66            })
67        };
68        let profiles = data
69            .iter()
70            .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
71            .map(|(_, dict)| dict);
72
73        out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
74        out.extend(
75            profiles
76                .clone()
77                .filter_map(|dict| dict.get(self.profile.as_str().as_str()))
78                .filter_map(Value::as_dict)
79                .flat_map(BTreeMap::keys)
80                .filter_map(deprecated_key_warning),
81        );
82
83        // Add warning for unknown keys within profiles (root keys only here).
84        if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
85            && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
86        {
87            let allowed_keys: std::collections::BTreeSet<String> =
88                default_dict.keys().cloned().collect();
89            for profile_map in profiles {
90                for (profile, value) in profile_map {
91                    let Some(profile_dict) = value.as_dict() else {
92                        continue;
93                    };
94
95                    let source = self
96                        .provider
97                        .metadata()
98                        .source
99                        .map(|s| s.to_string())
100                        .unwrap_or(Config::FILE_NAME.to_string());
101                    for key in profile_dict.keys() {
102                        let is_not_deprecated =
103                            !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key);
104                        let is_not_allowed = !allowed_keys.contains(key)
105                            && !allowed_keys.contains(&key.to_snake_case());
106                        let is_not_reserved = key != "extends" && key != Self::WARNINGS_KEY;
107                        let is_not_backward_compatible = key != "solc_version";
108
109                        if is_not_deprecated
110                            && is_not_allowed
111                            && is_not_reserved
112                            && is_not_backward_compatible
113                        {
114                            out.push(Warning::UnknownKey {
115                                key: key.clone(),
116                                profile: profile.clone(),
117                                source: source.clone(),
118                            });
119                        }
120                    }
121                }
122            }
123        }
124
125        Ok(out)
126    }
127}
128
129impl<P: Provider> Provider for WarningsProvider<P> {
130    fn metadata(&self) -> Metadata {
131        if let Some(source) = self.provider.metadata().source {
132            Metadata::from("Warnings", source)
133        } else {
134            Metadata::named("Warnings")
135        }
136    }
137
138    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
139        let warnings = self.collect_warnings()?;
140        Ok(Map::from([(
141            self.profile.clone(),
142            Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
143        )]))
144    }
145
146    fn profile(&self) -> Option<Profile> {
147        Some(self.profile.clone())
148    }
149}