use std::collections::HashMap;
use super::AlefConfig;
use super::output::{BuildCommandConfig, CleanConfig, LintConfig, SetupConfig, StringOrVec, TestConfig, UpdateConfig};
use super::tools::LangContext;
use super::{build_defaults, clean_defaults, lint_defaults, setup_defaults, test_defaults, update_defaults};
use crate::error::AlefError;
pub fn validate(config: &AlefConfig) -> Result<(), AlefError> {
validate_tools(&config.tools)?;
if let Some(map) = &config.lint {
validate_section("lint", map, lint_main_fields, |c| c.precondition.as_deref())?;
}
if let Some(map) = &config.test {
validate_section("test", map, test_main_fields, |c| c.precondition.as_deref())?;
}
if let Some(map) = &config.build_commands {
validate_section("build_commands", map, build_main_fields, |c| c.precondition.as_deref())?;
}
if let Some(map) = &config.setup {
validate_section("setup", map, setup_main_fields, |c| c.precondition.as_deref())?;
}
if let Some(map) = &config.update {
validate_section("update", map, update_main_fields, |c| c.precondition.as_deref())?;
}
if let Some(map) = &config.clean {
validate_section("clean", map, clean_main_fields, |c| c.precondition.as_deref())?;
}
warn_redundant_defaults(config);
Ok(())
}
fn validate_section<C, F, P>(
section: &str,
table: &HashMap<String, C>,
main_fields: F,
precondition: P,
) -> Result<(), AlefError>
where
F: Fn(&C) -> Vec<&'static str>,
P: Fn(&C) -> Option<&str>,
{
for (lang, cfg) in table {
let main = main_fields(cfg);
if !main.is_empty() && precondition(cfg).is_none() {
let fields = main.iter().map(|f| format!("`{f}`")).collect::<Vec<_>>().join("/");
return Err(AlefError::Config(format!(
"[{section}.{lang}] sets a main command ({fields}) without `precondition`. \
Custom commands must declare a `precondition` so the step degrades gracefully \
when the tool is missing on the user's system. Use a POSIX check such as \
`precondition = \"command -v <tool> >/dev/null 2>&1\"`."
)));
}
}
Ok(())
}
fn lint_main_fields(c: &LintConfig) -> Vec<&'static str> {
let mut v = Vec::new();
if c.format.is_some() {
v.push("format");
}
if c.check.is_some() {
v.push("check");
}
if c.typecheck.is_some() {
v.push("typecheck");
}
v
}
fn test_main_fields(c: &TestConfig) -> Vec<&'static str> {
let mut v = Vec::new();
if c.command.is_some() {
v.push("command");
}
if c.e2e.is_some() {
v.push("e2e");
}
if c.coverage.is_some() {
v.push("coverage");
}
v
}
fn build_main_fields(c: &BuildCommandConfig) -> Vec<&'static str> {
let mut v = Vec::new();
if c.build.is_some() {
v.push("build");
}
if c.build_release.is_some() {
v.push("build_release");
}
v
}
fn setup_main_fields(c: &SetupConfig) -> Vec<&'static str> {
if c.install.is_some() {
vec!["install"]
} else {
Vec::new()
}
}
fn update_main_fields(c: &UpdateConfig) -> Vec<&'static str> {
let mut v = Vec::new();
if c.update.is_some() {
v.push("update");
}
if c.upgrade.is_some() {
v.push("upgrade");
}
v
}
fn clean_main_fields(c: &CleanConfig) -> Vec<&'static str> {
if c.clean.is_some() { vec!["clean"] } else { Vec::new() }
}
fn validate_tools(tools: &super::tools::ToolsConfig) -> Result<(), AlefError> {
if let Some(pm) = tools.python_package_manager.as_deref() {
ensure_well_formed_tool_name("tools.python_package_manager", pm)?;
}
if let Some(pm) = tools.node_package_manager.as_deref() {
ensure_well_formed_tool_name("tools.node_package_manager", pm)?;
}
if let Some(list) = tools.rust_dev_tools.as_deref() {
for tool in list {
ensure_well_formed_tool_name("tools.rust_dev_tools[]", tool)?;
}
}
Ok(())
}
fn is_well_formed_tool_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')
}
fn ensure_well_formed_tool_name(field: &str, value: &str) -> Result<(), AlefError> {
if value.is_empty() || !value.chars().all(is_well_formed_tool_char) {
return Err(AlefError::Config(format!(
"{field} = {value:?} is not a well-formed tool name. \
Tool names must match `[A-Za-z0-9._-]+` (single executable, no spaces or shell metacharacters)."
)));
}
Ok(())
}
fn parse_language(lang_str: &str) -> Option<super::extras::Language> {
match lang_str {
"python" => Some(super::extras::Language::Python),
"node" => Some(super::extras::Language::Node),
"ruby" => Some(super::extras::Language::Ruby),
"php" => Some(super::extras::Language::Php),
"elixir" => Some(super::extras::Language::Elixir),
"wasm" => Some(super::extras::Language::Wasm),
"ffi" => Some(super::extras::Language::Ffi),
"go" => Some(super::extras::Language::Go),
"java" => Some(super::extras::Language::Java),
"csharp" => Some(super::extras::Language::Csharp),
"r" => Some(super::extras::Language::R),
"rust" => Some(super::extras::Language::Rust),
_ => None,
}
}
fn commands_eq(a: &Option<StringOrVec>, b: &Option<StringOrVec>) -> bool {
match (a, b) {
(None, None) => true,
(Some(x), Some(y)) => x.commands() == y.commands(),
_ => false,
}
}
fn warn_lint_defaults(lang_str: &str, user_cfg: &LintConfig, default_cfg: &LintConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[lint.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[lint.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.format, &default_cfg.format) && user_cfg.format.is_some() {
tracing::warn!(
"[lint.{lang}] field `format` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.check, &default_cfg.check) && user_cfg.check.is_some() {
tracing::warn!(
"[lint.{lang}] field `check` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.typecheck, &default_cfg.typecheck) && user_cfg.typecheck.is_some() {
tracing::warn!(
"[lint.{lang}] field `typecheck` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_test_defaults(lang_str: &str, user_cfg: &TestConfig, default_cfg: &TestConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[test.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[test.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.command, &default_cfg.command) && user_cfg.command.is_some() {
tracing::warn!(
"[test.{lang}] field `command` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.e2e, &default_cfg.e2e) && user_cfg.e2e.is_some() {
tracing::warn!(
"[test.{lang}] field `e2e` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.coverage, &default_cfg.coverage) && user_cfg.coverage.is_some() {
tracing::warn!(
"[test.{lang}] field `coverage` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_build_defaults(lang_str: &str, user_cfg: &BuildCommandConfig, default_cfg: &BuildCommandConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[build_commands.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[build_commands.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.build, &default_cfg.build) && user_cfg.build.is_some() {
tracing::warn!(
"[build_commands.{lang}] field `build` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.build_release, &default_cfg.build_release) && user_cfg.build_release.is_some() {
tracing::warn!(
"[build_commands.{lang}] field `build_release` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_setup_defaults(lang_str: &str, user_cfg: &SetupConfig, default_cfg: &SetupConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[setup.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[setup.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.install, &default_cfg.install) && user_cfg.install.is_some() {
tracing::warn!(
"[setup.{lang}] field `install` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_update_defaults(lang_str: &str, user_cfg: &UpdateConfig, default_cfg: &UpdateConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[update.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[update.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.update, &default_cfg.update) && user_cfg.update.is_some() {
tracing::warn!(
"[update.{lang}] field `update` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.upgrade, &default_cfg.upgrade) && user_cfg.upgrade.is_some() {
tracing::warn!(
"[update.{lang}] field `upgrade` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_clean_defaults(lang_str: &str, user_cfg: &CleanConfig, default_cfg: &CleanConfig) {
if let (Some(u), Some(d)) = (&user_cfg.precondition, &default_cfg.precondition) {
if u == d {
tracing::warn!(
"[clean.{lang}] field `precondition` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
if commands_eq(&user_cfg.before, &default_cfg.before) && user_cfg.before.is_some() {
tracing::warn!(
"[clean.{lang}] field `before` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
if commands_eq(&user_cfg.clean, &default_cfg.clean) && user_cfg.clean.is_some() {
tracing::warn!(
"[clean.{lang}] field `clean` matches the built-in default — remove it from alef.toml to avoid drift",
lang = lang_str
);
}
}
fn warn_redundant_defaults(config: &AlefConfig) {
let output_dir = |lang| config.package_dir(lang);
let tools = &config.tools;
if let Some(map) = &config.lint {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg = lint_defaults::default_lint_config(lang, &output_dir(lang), &ctx);
warn_lint_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
if let Some(map) = &config.test {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg = test_defaults::default_test_config(lang, &output_dir(lang), &ctx);
warn_test_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
if let Some(map) = &config.build_commands {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg =
build_defaults::default_build_config(lang, &output_dir(lang), &config.crate_config.name, &ctx);
warn_build_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
if let Some(map) = &config.setup {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg = setup_defaults::default_setup_config(lang, &output_dir(lang), &ctx);
warn_setup_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
if let Some(map) = &config.update {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg = update_defaults::default_update_config(lang, &output_dir(lang), &ctx);
warn_update_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
if let Some(map) = &config.clean {
for (lang_str, user_cfg) in map {
if let Some(lang) = parse_language(lang_str) {
let ctx = LangContext::default(tools);
let default_cfg = clean_defaults::default_clean_config(lang, &output_dir(lang), &ctx);
warn_clean_defaults(lang_str, user_cfg, &default_cfg);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_test::traced_test;
fn parse(toml_str: &str) -> AlefConfig {
toml::from_str(toml_str).expect("config should parse")
}
fn base_config() -> &'static str {
r#"
languages = ["python"]
[crate]
name = "test-lib"
sources = ["src/lib.rs"]
"#
}
#[test]
fn no_user_overrides_is_valid() {
let config = parse(base_config());
validate(&config).expect("default config should validate");
}
#[test]
fn lint_override_with_main_cmd_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nformat = \"black .\"\n",
base = base_config()
));
let err = validate(&config).expect_err("missing precondition should error");
let msg = format!("{err}");
assert!(msg.contains("[lint.python]"), "error should name the section: {msg}");
assert!(msg.contains("precondition"), "error should mention precondition: {msg}");
}
#[test]
fn lint_override_with_main_cmd_and_precondition_is_ok() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nprecondition = \"command -v black\"\nformat = \"black .\"\n",
base = base_config()
));
validate(&config).expect("config with precondition should validate");
}
#[test]
fn lint_override_with_only_before_no_precondition_is_ok() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nbefore = \"echo hi\"\n",
base = base_config()
));
validate(&config).expect("table with only `before` should validate");
}
#[test]
fn test_override_with_main_cmd_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[test.python]\ncommand = \"pytest\"\n",
base = base_config()
));
let err = validate(&config).expect_err("missing precondition should error");
assert!(format!("{err}").contains("[test.python]"));
}
#[test]
fn test_override_with_only_e2e_requires_precondition() {
let config = parse(&format!(
"{base}\n\n[test.python]\ne2e = \"pytest tests/e2e\"\n",
base = base_config()
));
validate(&config).expect_err("e2e without precondition should error");
}
#[test]
fn build_override_with_main_cmd_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[build_commands.python]\nbuild = \"maturin develop\"\n",
base = base_config()
));
let err = validate(&config).expect_err("missing precondition should error");
assert!(format!("{err}").contains("[build_commands.python]"));
}
#[test]
fn setup_override_with_install_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[setup.python]\ninstall = \"uv sync\"\n",
base = base_config()
));
validate(&config).expect_err("setup install without precondition should error");
}
#[test]
fn update_override_with_main_cmd_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[update.python]\nupdate = \"uv sync --upgrade\"\n",
base = base_config()
));
validate(&config).expect_err("update without precondition should error");
}
#[test]
fn clean_override_with_main_cmd_no_precondition_errors() {
let config = parse(&format!(
"{base}\n\n[clean.python]\nclean = \"rm -rf dist\"\n",
base = base_config()
));
validate(&config).expect_err("clean without precondition should error");
}
#[test]
fn error_message_lists_only_actually_set_main_fields() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nformat = \"black .\"\n",
base = base_config()
));
let msg = format!("{}", validate(&config).unwrap_err());
assert!(msg.contains("`format`"), "expected `format`, got: {msg}");
assert!(!msg.contains("`check`"), "should not mention unset `check`: {msg}");
assert!(
!msg.contains("`typecheck`"),
"should not mention unset `typecheck`: {msg}"
);
}
#[test]
fn before_plus_main_cmd_without_precondition_still_errors() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nbefore = \"echo hi\"\nformat = \"black .\"\n",
base = base_config()
));
validate(&config).expect_err("before + main without precondition must error");
}
#[test]
fn malformed_python_package_manager_value_is_rejected() {
let config = parse(&format!(
"{base}\n\n[tools]\npython_package_manager = \"uv; rm -rf /\"\n",
base = base_config()
));
let err = validate(&config).expect_err("non-identifier tool name must be rejected");
assert!(format!("{err}").contains("well-formed"));
}
#[test]
fn malformed_node_package_manager_value_is_rejected() {
let config = parse(&format!(
"{base}\n\n[tools]\nnode_package_manager = \"pnpm$(echo bad)\"\n",
base = base_config()
));
validate(&config).expect_err("non-identifier tool name must be rejected");
}
#[test]
fn malformed_rust_dev_tool_entry_is_rejected() {
let config = parse(&format!(
"{base}\n\n[tools]\nrust_dev_tools = [\"cargo-edit\", \"cargo`evil`\"]\n",
base = base_config()
));
validate(&config).expect_err("non-identifier tool name must be rejected");
}
#[test]
fn whitespace_in_tool_name_is_rejected() {
let config = parse(&format!(
"{base}\n\n[tools]\npython_package_manager = \"uv \"\n",
base = base_config()
));
validate(&config).expect_err("trailing whitespace must be rejected");
}
#[test]
fn empty_tool_name_is_rejected() {
let config = parse(&format!(
"{base}\n\n[tools]\npython_package_manager = \"\"\n",
base = base_config()
));
validate(&config).expect_err("empty tool name must be rejected");
}
#[test]
fn safe_tool_names_are_accepted() {
let config = parse(&format!(
"{base}\n\n[tools]\npython_package_manager = \"uv\"\n\
node_package_manager = \"pnpm\"\n\
rust_dev_tools = [\"cargo-edit\", \"cargo_sort\", \"tool.v2\"]\n",
base = base_config()
));
validate(&config).expect("normal tool names should validate");
}
#[test]
fn override_with_main_cmd_and_precondition_validates_for_each_section() {
let cases = [
("lint.python", "format", "command -v black"),
("test.python", "command", "command -v pytest"),
("build_commands.python", "build", "command -v maturin"),
("setup.python", "install", "command -v uv"),
("update.python", "update", "command -v uv"),
("clean.python", "clean", "command -v rm"),
];
for (header, field, pre) in cases {
let toml_str = format!(
"{base}\n\n[{header}]\nprecondition = \"{pre}\"\n{field} = \"echo run\"\n",
base = base_config()
);
let config = parse(&toml_str);
validate(&config).unwrap_or_else(|_| panic!("[{header}] with precondition should validate"));
}
}
#[test]
#[traced_test]
fn lint_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nformat = \"ruff format packages/python\"\nprecondition = \"command -v ruff\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[lint.python] field `format` matches the built-in default"
));
}
#[test]
#[traced_test]
fn lint_partial_default_warns_only_for_default_field() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nformat = \"ruff format packages/python\"\ncheck = \"custom check\"\nprecondition = \"command -v ruff\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[lint.python] field `format` matches the built-in default"
));
assert!(!logs_contain(
"[lint.python] field `check` matches the built-in default"
));
}
#[test]
#[traced_test]
fn lint_all_custom_emits_no_warning() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nformat = \"black .\"\ncheck = \"pylint .\"\nprecondition = \"command -v black\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(!logs_contain(
"[lint.python] field `format` matches the built-in default"
));
assert!(!logs_contain(
"[lint.python] field `check` matches the built-in default"
));
}
#[test]
#[traced_test]
fn test_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[test.python]\ncommand = \"cd packages/python && uv run pytest\"\nprecondition = \"command -v uv\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[test.python] field `command` matches the built-in default"
));
}
#[test]
#[traced_test]
fn build_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[build_commands.python]\nbuild = \"maturin develop --manifest-path crates/test-lib-py/Cargo.toml\"\nprecondition = \"command -v maturin\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[build_commands.python] field `build` matches the built-in default"
));
}
#[test]
#[traced_test]
fn setup_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[setup.python]\ninstall = \"cd packages/python && uv sync\"\nprecondition = \"command -v uv\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[setup.python] field `install` matches the built-in default"
));
}
#[test]
#[traced_test]
fn update_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[update.python]\nupdate = \"cd packages/python && uv sync --upgrade\"\nprecondition = \"command -v uv\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[update.python] field `update` matches the built-in default"
));
}
#[test]
#[traced_test]
fn clean_verbatim_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[clean.python]\nclean = \"rm -rf packages/python/build\"\nprecondition = \"command -v rm\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
}
#[test]
#[traced_test]
fn precondition_redundant_default_emits_warning() {
let config = parse(&format!(
"{base}\n\n[lint.python]\nprecondition = \"command -v ruff >/dev/null 2>&1\"\n",
base = base_config()
));
validate(&config).expect("config should validate");
assert!(logs_contain(
"[lint.python] field `precondition` matches the built-in default"
));
}
#[test]
#[traced_test]
fn node_custom_value_no_warning() {
let config = parse(&format!(
"{base}\nlanguages = [\"node\"]\n\n[lint.node]\nformat = \"prettier --write .\"\nprecondition = \"command -v npm\"\n",
base = base_config().lines().skip(1).collect::<Vec<_>>().join("\n")
));
validate(&config).expect("config should validate");
assert!(!logs_contain("[lint.node] field `format` matches the built-in default"));
}
}