From 754f362da077b06420ab8350dfb62bd2d8d89d75 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 20:13:11 +0200 Subject: plan 8: enum values read from source must all be handled --- internal/enumtest/enumtest.go | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 internal/enumtest/enumtest.go (limited to 'internal/enumtest/enumtest.go') diff --git a/internal/enumtest/enumtest.go b/internal/enumtest/enumtest.go new file mode 100644 index 0000000..cccb09c --- /dev/null +++ b/internal/enumtest/enumtest.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// Package enumtest reads the constants of an enum type from Go source, so +// a test can check that every value is handled - and fails when a value is +// added without the code that needs it. Only tests import it. +package enumtest + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" +) + +// Names returns, in declaration order, the constants declared with type typ +// in the Go file at path, including those an iota block types implicitly. +// A type with no constants there is an error, so a renamed file or type +// cannot make a test pass by finding nothing. +func Names(path, typ string) ([]string, error) { + f, err := parser.ParseFile(token.NewFileSet(), path, nil, 0) + if err != nil { + return nil, err + } + var names []string + for _, decl := range f.Decls { + g, ok := decl.(*ast.GenDecl) + if !ok || g.Tok != token.CONST { + continue + } + inType := false + for _, spec := range g.Specs { + vs, ok := spec.(*ast.ValueSpec) + if !ok { + continue + } + switch { + case vs.Type != nil: + id, ok := vs.Type.(*ast.Ident) + inType = ok && id.Name == typ + case len(vs.Values) > 0: + inType = false + } + if inType { + for _, n := range vs.Names { + names = append(names, n.Name) + } + } + } + } + if len(names) == 0 { + return nil, fmt.Errorf("%s: no constants of type %s", path, typ) + } + return names, nil +} -- cgit v1.3