From c4913dbbcb357381237125000543ff6c2c7bb15b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 21 Oct 2026 09:26:11 +0200 Subject: [PATCH] Declare scanner.scan's apart argument, so the console lets it through to the tool (issue 1528) --- modules/brother/cmd/brother/declared_test.go | 95 ++++++++++++++++++++ modules/brother/cmd/brother/main.go | 1 + 2 files changed, 96 insertions(+) create mode 100644 modules/brother/cmd/brother/declared_test.go diff --git a/modules/brother/cmd/brother/declared_test.go b/modules/brother/cmd/brother/declared_test.go new file mode 100644 index 00000000..938c9f60 --- /dev/null +++ b/modules/brother/cmd/brother/declared_test.go @@ -0,0 +1,95 @@ +package main + +import ( + "go/ast" + "go/parser" + "go/token" + "sort" + "strconv" + "testing" +) + +// 1528: a tool reads only arguments its input schema declares; the console refuses an undeclared +// one before it reaches the tool (scanner.scan read apart, which its schema never named). +func TestEveryArgumentAToolReadsIsDeclared(t *testing.T) { + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, "main.go", nil, 0) + if err != nil { + t.Fatal(err) + } + checked := 0 + ast.Inspect(f, func(n ast.Node) bool { + lit, ok := n.(*ast.CompositeLit) + if !ok { + return true + } + var name string + var input, run ast.Expr + for _, e := range lit.Elts { + kv, ok := e.(*ast.KeyValueExpr) + if !ok { + continue + } + switch k, _ := kv.Key.(*ast.Ident); { + case k == nil: + case k.Name == "Name": + if b, ok := kv.Value.(*ast.BasicLit); ok { + name, _ = strconv.Unquote(b.Value) + } + case k.Name == "Input": + input = kv.Value + case k.Name == "Run": + run = kv.Value + } + } + if name == "" || input == nil || run == nil { + return true + } + declared := map[string]bool{} + ast.Inspect(input, func(m ast.Node) bool { + kv, ok := m.(*ast.KeyValueExpr) + if !ok { + return true + } + if k, ok := kv.Key.(*ast.BasicLit); ok && k.Value == `"properties"` { + if props, ok := kv.Value.(*ast.CompositeLit); ok { + for _, p := range props.Elts { + if pkv, ok := p.(*ast.KeyValueExpr); ok { + if b, ok := pkv.Key.(*ast.BasicLit); ok { + s, _ := strconv.Unquote(b.Value) + declared[s] = true + } + } + } + } + return false + } + return true + }) + var missing []string + ast.Inspect(run, func(m ast.Node) bool { + ix, ok := m.(*ast.IndexExpr) + if !ok { + return true + } + if id, ok := ix.X.(*ast.Ident); ok && id.Name == "args" { + if b, ok := ix.Index.(*ast.BasicLit); ok && b.Kind == token.STRING { + s, _ := strconv.Unquote(b.Value) + if !declared[s] { + missing = append(missing, s) + } + } + } + return true + }) + checked++ + if len(missing) > 0 { + sort.Strings(missing) + t.Errorf("%s reads %v, which its input schema does not declare", name, missing) + } + return false + }) + if checked < 5 { + t.Fatalf("checked %d tools; the test no longer finds the tool literals", checked) + } +} diff --git a/modules/brother/cmd/brother/main.go b/modules/brother/cmd/brother/main.go index d33a82ad..fe676e4f 100644 --- a/modules/brother/cmd/brother/main.go +++ b/modules/brother/cmd/brother/main.go @@ -277,6 +277,7 @@ func tools(settingsFile string, now func() time.Time) []stdio.Tool { "document": map[string]any{"type": "string", "description": "glass only, a document of several pages in one PDF: start opens one and scans its first page; the id of an open document scans the next page into it"}, "finish": map[string]any{"type": "boolean", "description": "with document: the id of an open document: close it into one PDF, filed once (nothing is scanned)"}, "duplex": map[string]any{"type": "boolean", "description": "feeder only: scan both sides of each sheet (default false)"}, + "apart": map[string]any{"type": "boolean", "description": "glass only: several documents lying apart on the glass are each filed as their own PDF (default false: one scan of the glass)"}, }}, Run: func(args map[string]any) (any, error) { s, err := service()