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(
49            data.keys()
50                .filter(|k| {
51                    **k != Config::PROFILE_SECTION
52                        && **k != Config::EXTERNAL_SECTION
53                        && !Config::STANDALONE_SECTIONS.iter().any(|s| s == k)
54                })
55                .map(|unknown_section| {
56                    let source = self.provider.metadata().source.map(|s| s.to_string());
57                    Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
58                }),
59        );
60
61        // Add warning for deprecated keys.
62        let deprecated_key_warning = |key| {
63            DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
64                if key == *deprecated_key {
65                    Some(Warning::DeprecatedKey {
66                        old: deprecated_key.to_string(),
67                        new: new_value.to_string(),
68                    })
69                } else {
70                    None
71                }
72            })
73        };
74        let profiles = data
75            .iter()
76            .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
77            .map(|(_, dict)| dict);
78
79        out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
80        out.extend(
81            profiles
82                .clone()
83                .filter_map(|dict| dict.get(self.profile.as_str().as_str()))
84                .filter_map(Value::as_dict)
85                .flat_map(BTreeMap::keys)
86                .filter_map(deprecated_key_warning),
87        );
88
89        // Add warning for unknown keys within profiles (root keys only here).
90        if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
91            && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
92        {
93            let allowed_keys: std::collections::BTreeSet<String> =
94                default_dict.keys().cloned().collect();
95            for profile_map in profiles {
96                for (profile, value) in profile_map {
97                    let Some(profile_dict) = value.as_dict() else {
98                        continue;
99                    };
100
101                    let source = self
102                        .provider
103                        .metadata()
104                        .source
105                        .map(|s| s.to_string())
106                        .unwrap_or(Config::FILE_NAME.to_string());
107                    for key in profile_dict.keys() {
108                        let is_not_deprecated =
109                            !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key);
110                        let is_not_allowed = !allowed_keys.contains(key)
111                            && !allowed_keys.contains(&key.to_snake_case());
112                        let is_not_reserved = key != "extends" && key != Self::WARNINGS_KEY;
113                        let is_not_backward_compatible = key != "solc_version";
114
115                        if is_not_deprecated
116                            && is_not_allowed
117                            && is_not_reserved
118                            && is_not_backward_compatible
119                        {
120                            out.push(Warning::UnknownKey {
121                                key: key.clone(),
122                                profile: profile.clone(),
123                                source: source.clone(),
124                            });
125                        }
126                    }
127                }
128            }
129        }
130
131        Ok(out)
132    }
133}
134
135impl<P: Provider> Provider for WarningsProvider<P> {
136    fn metadata(&self) -> Metadata {
137        if let Some(source) = self.provider.metadata().source {
138            Metadata::from("Warnings", source)
139        } else {
140            Metadata::named("Warnings")
141        }
142    }
143
144    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
145        let warnings = self.collect_warnings()?;
146        Ok(Map::from([(
147            self.profile.clone(),
148            Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
149        )]))
150    }
151
152    fn profile(&self) -> Option<Profile> {
153        Some(self.profile.clone())
154    }
155}