Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ pub fn load_config(start: &Path, explicit: Option<&Path>) -> Result<ConfigResolu
chain.push(path);
}
let mut config: Config = serde_json::from_value(value).context("invalid configuration")?;
validate_languages(&config)?;
config.hook.max_blocks = config.hook.max_blocks.max(1);
Ok(ConfigResolution { config, chain })
}
Expand Down Expand Up @@ -282,16 +281,31 @@ fn validate_test_exempt(root: &Map<String, Value>, path: &Path) -> Result<()> {
}

fn validate_language_keys(root: &Map<String, Value>, path: &Path) -> Result<()> {
const LANGUAGES: &[&str] = &[
"javascript",
"typescript",
"svelte",
"dart",
"rust",
"python",
"go",
];
let Some(value) = root.get("languages") else {
return Ok(());
};
let languages = value
.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(
Expand Down Expand Up @@ -319,24 +333,6 @@ fn allowed(object: &Map<String, Value>, 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
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
reported with its `languages.<name>` path, for example
`languages.go.limits.widgetdepth`, instead of a bare `limits.widgetdepth`.
(#41)
- An unknown language name or a non-object `languages.<name>` 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

Expand Down
Loading