From 4bd45e7d3c0ed17a7b8b36a0f45f93de058331be Mon Sep 17 00:00:00 2001 From: lew Date: Thu, 12 Feb 2026 00:35:28 +0000 Subject: [PATCH] feat(doctor): detects undecoded config keys --- cmd/config.go | 26 ++++++++++++++++---------- cmd/doctor.go | 6 ++++++ cmd/doctor_test.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index db252c4..94e6ac0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -63,7 +63,8 @@ type GitConfig struct { } var ( - config Config + config Config + configUndecodedKeys []string asciiArt string = ` ▄▄ ██ ██▄███▄ ▄███▄██ ▄█████▄ @@ -77,7 +78,7 @@ var ( ) func init() { - config, configErr = loadConfig() + config, configUndecodedKeys, configErr = loadConfig() } func defaultConfig() Config { @@ -105,24 +106,29 @@ func defaultConfig() Config { } } -func loadConfig() (Config, error) { +func loadConfig() (Config, []string, error) { cfg := defaultConfig() path, err := configPath() if err != nil { - return cfg, err + return cfg, nil, err } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { - return cfg, nil + return cfg, nil, nil } - return cfg, err + return cfg, nil, err } - _, err = toml.DecodeFile(path, &cfg) + meta, err := toml.DecodeFile(path, &cfg) if err != nil { - return cfg, fmt.Errorf("parse %s: %w", path, err) + return cfg, nil, fmt.Errorf("parse %s: %w", path, err) + } + + var undecoded []string + for _, key := range meta.Undecoded() { + undecoded = append(undecoded, key.String()) } if cfg.Store.DefaultStoreName == "" { @@ -133,10 +139,10 @@ func loadConfig() (Config, error) { cfg.List.DefaultListFormat = defaultConfig().List.DefaultListFormat } if err := validListFormat(cfg.List.DefaultListFormat); err != nil { - return cfg, fmt.Errorf("parse %s: list.default_list_format: %w", path, err) + return cfg, undecoded, fmt.Errorf("parse %s: list.default_list_format: %w", path, err) } - return cfg, nil + return cfg, undecoded, nil } func configPath() (string, error) { diff --git a/cmd/doctor.go b/cmd/doctor.go index cb1f2f9..dcb1b26 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -161,6 +161,12 @@ func runDoctor(w io.Writer) bool { } } + // 7b. Unrecognised config keys + if len(configUndecodedKeys) > 0 { + emit("WARN", fmt.Sprintf("Unrecognised config key(s) (ignored):")) + tree(configUndecodedKeys) + } + // 8. Data directory store := &Store{} dataDir, err := store.path() diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index 4516bc7..3cbf7ae 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -88,6 +88,36 @@ func TestDoctorIdentityPermissions(t *testing.T) { } } +func TestDoctorUndecodedKeys(t *testing.T) { + dataDir := t.TempDir() + configDir := t.TempDir() + t.Setenv("PDA_DATA", dataDir) + t.Setenv("PDA_CONFIG", configDir) + + // Write a config with an unknown key. + cfgContent := "[store]\nno_such_key = true\n" + if err := os.WriteFile(filepath.Join(configDir, "config.toml"), []byte(cfgContent), 0o644); err != nil { + t.Fatal(err) + } + + savedCfg, savedUndecoded, savedErr := config, configUndecodedKeys, configErr + config, configUndecodedKeys, configErr = loadConfig() + t.Cleanup(func() { + config, configUndecodedKeys, configErr = savedCfg, savedUndecoded, savedErr + }) + + var buf bytes.Buffer + runDoctor(&buf) + out := buf.String() + + if !strings.Contains(out, "Unrecognised config key") { + t.Errorf("expected undecoded key warning, got:\n%s", out) + } + if !strings.Contains(out, "store.no_such_key") { + t.Errorf("expected 'store.no_such_key' in output, got:\n%s", out) + } +} + func TestDoctorGitInitialised(t *testing.T) { dataDir := t.TempDir() configDir := t.TempDir()