diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 448f8bc..8570324 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -174,7 +174,6 @@ pub fn load_config(start: &Path, explicit: Option<&Path>) -> Result, path: &Path) -> Result<()> { } fn validate_language_keys(root: &Map, path: &Path) -> Result<()> { + const LANGUAGES: &[&str] = &[ + "javascript", + "typescript", + "svelte", + "dart", + "rust", + "python", + "go", + ]; let Some(value) = root.get("languages") else { return Ok(()); }; @@ -289,9 +297,15 @@ fn validate_language_keys(root: &Map, path: &Path) -> Result<()> .as_object() .ok_or_else(|| anyhow::anyhow!("languages in {} must be an object", path.display()))?; for (name, value) in languages { - let object = value - .as_object() - .ok_or_else(|| anyhow::anyhow!("languages.{name} must be an object"))?; + if !LANGUAGES.contains(&name.as_str()) { + bail!( + "unknown config key `languages.{name}` in {}", + path.display() + ); + } + let object = value.as_object().ok_or_else(|| { + anyhow::anyhow!("languages.{name} in {} must be an object", path.display()) + })?; let prefix = format!("languages.{name}"); allowed(object, &["limits"], &prefix, path)?; nested_keys( @@ -319,24 +333,6 @@ fn allowed(object: &Map, keys: &[&str], prefix: &str, path: &Path Ok(()) } -fn validate_languages(config: &Config) -> Result<()> { - const LANGUAGES: &[&str] = &[ - "javascript", - "typescript", - "svelte", - "dart", - "rust", - "python", - "go", - ]; - for name in config.languages.keys() { - if !LANGUAGES.contains(&name.as_str()) { - bail!("unknown config key `languages.{name}`"); - } - } - Ok(()) -} - impl Config { pub fn limits_for(&self, language: &str) -> Limits { let Some(overrides) = self @@ -506,6 +502,34 @@ mod tests { assert!(error.contains("languages.go.limits in ") && error.contains("must be an object")); } + #[test] + fn non_object_language_entry_names_config_path() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.json"); + fs::write(&path, r#"{"languages":{"go":1}}"#).unwrap(); + let error = load_config(dir.path(), Some(&path)) + .unwrap_err() + .to_string(); + assert_eq!( + error, + format!("languages.go in {} must be an object", path.display()) + ); + } + + #[test] + fn unknown_language_names_config_path() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.json"); + fs::write(&path, r#"{"languages":{"cobol":1}}"#).unwrap(); + let error = load_config(dir.path(), Some(&path)) + .unwrap_err() + .to_string(); + assert_eq!( + error, + format!("unknown config key `languages.cobol` in {}", path.display()) + ); + } + #[test] fn unknown_top_level_limit_key_has_no_language_path() { let dir = tempfile::tempdir().unwrap(); diff --git a/docs/releases/UNRELEASED.md b/docs/releases/UNRELEASED.md index 4ac267d..1defc9c 100644 --- a/docs/releases/UNRELEASED.md +++ b/docs/releases/UNRELEASED.md @@ -8,6 +8,10 @@ reported with its `languages.` path, for example `languages.go.limits.widgetdepth`, instead of a bare `limits.widgetdepth`. (#41) +- An unknown language name or a non-object `languages.` entry is now + reported with the config file it came from, so it is clear whether the user + or the repo config holds the typo. An unknown language is reported before + any error inside its entry. (#43) ## Validation