Skip to content

Commit e4c2d82

Browse files
committed
feat: warn about vars.tsv rows that can never apply
A fixed-value row whose name column matches no slot of the template, or whose command is not template-derived, failed without a trace: the value stopped applying and the slot went back to prompting at run time. Both cases now surface in the startup warnings and baton check, closing the last silent failure mode of hand-edited names.
1 parent 1900936 commit e4c2d82

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ Unlike command renames, name changes inside `vars.tsv` are not auto-repaired —
352352

353353
- **Renaming a global** (`*` row): every `{$oldname}` reference stays literal, and the startup warning names each command and list still using it. Update the references yourself, or create the new name via **Manage vars** and let the rebase offer rewrite the values.
354354
- **Changing the command column** of a fixed-value row detaches the value: that slot is prompted at run time again, the row is flagged as belonging to an unknown command, and the next TUI save removes it.
355-
- **Changing the name column** of a fixed-value row to something that isn't a slot of the template: the value silently stops applying and the slot is prompted at run time.
355+
- **Changing the name column** of a fixed-value row to something that isn't a slot of the template: the value stops applying, the slot is prompted at run time again, and the startup warning flags the row.
356356

357357
In short: rename commands in `commands.tsv`, rename variables through their references, and treat the first two columns of `vars.tsv` as addresses rather than free text.
358358

internal/config/loader_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,40 @@ func TestExampleProjectsLoad(t *testing.T) {
240240

241241
// TestLoadProject_Warnings checks the diagnostics behind `baton check`:
242242
// a project wired with known mistakes reports each of them.
243+
// TestDiagnose_DeadVarRows checks fixed-value rows that can never apply
244+
// are warned about: a name column matching no slot of the template, and
245+
// a command column naming a command that is not template-derived. Valid
246+
// rows and globals stay quiet.
247+
func TestDiagnose_DeadVarRows(t *testing.T) {
248+
cfg := model.Config{
249+
Base: []model.Command{
250+
{Name: "build", Cmd: "make {mode}", Dir: "{workdir}"},
251+
{Name: "plain", Cmd: "echo hi"},
252+
},
253+
Commands: []model.Command{{Name: "as", Template: "build"}},
254+
}
255+
vars := map[string]string{
256+
"as.workdir": "./src", // applies — must stay quiet
257+
"as.workdirx": "./typo", // matches no slot
258+
"plain.x": "v", // command is not template-derived
259+
"root": `C:\x`, // global — never scoped
260+
}
261+
262+
warnings := Diagnose(cfg, nil, nil, vars)
263+
joined := strings.Join(warnings, "; ")
264+
for _, want := range []string{
265+
`"as.workdirx" is not a slot of template "build"`,
266+
`"plain.x" has no effect`,
267+
} {
268+
if !strings.Contains(joined, want) {
269+
t.Errorf("warnings missing %q, got %q", want, joined)
270+
}
271+
}
272+
if len(warnings) != 2 {
273+
t.Fatalf("warnings = %q, want exactly the two dead rows flagged", warnings)
274+
}
275+
}
276+
243277
func TestLoadProject_Warnings(t *testing.T) {
244278
dir := t.TempDir()
245279
tsv := "name\tgroup\tworkdir\tcmd\tshell\tslots\n" +

internal/config/project.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,42 @@ func Diagnose(cfg model.Config, workflows []model.Workflow, lists map[string][]m
147147
for _, name := range orphans {
148148
warnings = append(warnings, "vars.tsv: values for unknown command \""+name+"\" (removed on next save)")
149149
}
150+
// Fixed-value rows whose command exists but that can never apply —
151+
// the command is not template-derived, or the name column matches no
152+
// slot of the template. Without a warning these fail silently: the
153+
// value stops applying and the slot is prompted at run time.
154+
var scoped []string
155+
for k := range vars {
156+
if strings.Contains(k, ".") {
157+
scoped = append(scoped, k)
158+
}
159+
}
160+
sort.Strings(scoped)
161+
for _, k := range scoped {
162+
i := strings.LastIndex(k, ".")
163+
cmdName, slotName := k[:i], k[i+1:]
164+
cmd, ok := cfg.FindCommand(cmdName)
165+
if !ok {
166+
continue // already reported as an orphan
167+
}
168+
if cmd.Template == "" {
169+
warnings = append(warnings, "vars.tsv: \""+k+"\" has no effect (\""+cmdName+"\" is not template-derived)")
170+
continue
171+
}
172+
tpl, ok := cfg.FindCommand(cmd.Template)
173+
if !ok || tpl.Template != "" {
174+
continue // the missing-template warning covers this command
175+
}
176+
found := false
177+
for _, def := range slot.GetSlots(tpl) {
178+
if def.Name == slotName {
179+
found = true
180+
break
181+
}
182+
}
183+
if !found {
184+
warnings = append(warnings, "vars.tsv: \""+k+"\" is not a slot of template \""+tpl.Name+"\"")
185+
}
186+
}
150187
return warnings
151188
}

0 commit comments

Comments
 (0)