Skip to content

Commit 0a203c2

Browse files
feat(config): warn on unknown config keys in foundry.toml (#11816)
* feat(config): warn on unknown config keys in foundry.toml\n\n- Add Warning::UnknownKey and Display\n- Collect unknown top-level keys in active profile via WarningsProvider\n- Ignore casing-only differences (kebab/snake)\n- Add unit test for unknown key warning\n\nRefs: #10550, inspired by #10621 * Fix test, apply check for all profiles * Fmt --------- Co-authored-by: grandizzy <[email protected]>
1 parent 7cc0f57 commit 0a203c2

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

crates/config/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6322,4 +6322,23 @@ mod tests {
63226322
Ok(())
63236323
});
63246324
}
6325+
6326+
#[test]
6327+
fn warns_on_unknown_keys_in_profile() {
6328+
figment::Jail::expect_with(|jail| {
6329+
jail.create_file(
6330+
"foundry.toml",
6331+
r#"
6332+
[profile.default]
6333+
unknown_key_xyz = 123
6334+
"#,
6335+
)?;
6336+
6337+
let cfg = Config::load().unwrap();
6338+
assert!(cfg.warnings.iter().any(
6339+
|w| matches!(w, crate::Warning::UnknownKey { key, .. } if key == "unknown_key_xyz")
6340+
));
6341+
Ok(())
6342+
});
6343+
}
63256344
}

crates/config/src/providers/warnings.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use figment::{
33
Error, Figment, Metadata, Profile, Provider,
44
value::{Dict, Map, Value},
55
};
6+
use heck::ToSnakeCase;
67
use std::collections::BTreeMap;
78

89
/// Generate warnings for unknown sections and deprecated keys
@@ -74,15 +75,54 @@ impl<P: Provider> WarningsProvider<P> {
7475
.iter()
7576
.filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
7677
.map(|(_, dict)| dict);
78+
7779
out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
7880
out.extend(
7981
profiles
82+
.clone()
8083
.filter_map(|dict| dict.get(self.profile.as_str().as_str()))
8184
.filter_map(Value::as_dict)
8285
.flat_map(BTreeMap::keys)
8386
.filter_map(deprecated_key_warning),
8487
);
8588

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+
114+
if is_not_deprecated && is_not_allowed && is_not_reserved {
115+
out.push(Warning::UnknownKey {
116+
key: key.clone(),
117+
profile: profile.clone(),
118+
source: source.clone(),
119+
});
120+
}
121+
}
122+
}
123+
}
124+
}
125+
86126
Ok(out)
87127
}
88128
}

crates/config/src/warning.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ pub enum Warning {
4646
/// is being removed completely without replacement
4747
new: String,
4848
},
49+
/// An unknown key was encountered in a profile in a TOML file
50+
UnknownKey {
51+
/// The unknown key name
52+
key: String,
53+
/// The profile where the key was found, if applicable
54+
profile: String,
55+
/// The config file where the key was found
56+
source: String,
57+
},
4958
}
5059

5160
impl fmt::Display for Warning {
@@ -87,6 +96,12 @@ impl fmt::Display for Warning {
8796
"Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions."
8897
)
8998
}
99+
Self::UnknownKey { key, profile, source } => {
100+
write!(
101+
f,
102+
"Found unknown `{key}` config for profile `{profile}` defined in {source}."
103+
)
104+
}
90105
}
91106
}
92107
}

crates/forge/tests/cli/config.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,3 +1945,25 @@ forgetest!(no_warnings_on_external_sections, |prj, cmd| {
19451945
19461946
"#]]);
19471947
});
1948+
1949+
// <https://github.com/foundry-rs/foundry/issues/10550>
1950+
forgetest!(config_warnings_on_unknown_keys, |prj, cmd| {
1951+
cmd.git_init();
1952+
1953+
let faulty_toml = r"[profile.default]
1954+
src = 'src'
1955+
out = 'out'
1956+
foo = 'unknown'
1957+
1958+
[profile.another]
1959+
src = 'src'
1960+
out = 'out'
1961+
bar = 'another_unknown'";
1962+
1963+
fs::write(prj.root().join("foundry.toml"), faulty_toml).unwrap();
1964+
cmd.forge_fuse().args(["config"]).assert_success().stderr_eq(str![[r#"
1965+
Warning: Found unknown `bar` config for profile `another` defined in foundry.toml.
1966+
Warning: Found unknown `foo` config for profile `default` defined in foundry.toml.
1967+
1968+
"#]]);
1969+
});

0 commit comments

Comments
 (0)