cmd/stringer: support types declared in test files
Adds additional passes over the test variants of the given
package. Picks the most "narrow" (least amount of files)
package where each type can be found. The generated code
will be placed in the same package and test-or-not-test file
as the original type declaration.
Closes golang/go#66557.
Change-Id: I2354cd44e357a9ce0b45f5e2fadc1c141455a88d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/604535
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Findley <rfindley@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
diff --git a/cmd/stringer/endtoend_test.go b/cmd/stringer/endtoend_test.go
index 2b9afa3..2b7d6a7 100644
--- a/cmd/stringer/endtoend_test.go
+++ b/cmd/stringer/endtoend_test.go
@@ -17,6 +17,8 @@
"os"
"path"
"path/filepath"
+ "reflect"
+ "slices"
"strings"
"sync"
"testing"
@@ -197,6 +199,127 @@
}
}
+var testfileSrcs = map[string]string{
+ "go.mod": "module foo",
+
+ // Normal file in the package.
+ "main.go": `package foo
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)
+`,
+
+ // Test file in the package.
+ "main_test.go": `package foo
+
+type Bar int
+
+const (
+ barX Bar = iota
+ barY
+ barZ
+)
+`,
+
+ // Test file in the test package.
+ "main_pkg_test.go": `package foo_test
+
+type Baz int
+
+const (
+ bazX Baz = iota
+ bazY
+ bazZ
+)
+`,
+}
+
+// Test stringer on types defined in different kinds of tests.
+// The generated code should not interfere between itself.
+func TestTestFiles(t *testing.T) {
+ testenv.NeedsTool(t, "go")
+ stringer := stringerPath(t)
+
+ dir := t.TempDir()
+ t.Logf("TestTestFiles in: %s \n", dir)
+ for name, src := range testfileSrcs {
+ source := filepath.Join(dir, name)
+ err := os.WriteFile(source, []byte(src), 0666)
+ if err != nil {
+ t.Fatalf("write file: %s", err)
+ }
+ }
+
+ // Must run stringer in the temp directory, see TestTags.
+ err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", dir)
+ if err != nil {
+ t.Fatalf("run stringer: %s", err)
+ }
+
+ // Check that stringer has created the expected files.
+ content, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatalf("read dir: %s", err)
+ }
+ gotFiles := []string{}
+ for _, f := range content {
+ if !f.IsDir() {
+ gotFiles = append(gotFiles, f.Name())
+ }
+ }
+ wantFiles := []string{
+ // Original.
+ "go.mod",
+ "main.go",
+ "main_test.go",
+ "main_pkg_test.go",
+ // Generated.
+ "foo_string.go",
+ "bar_string_test.go",
+ "baz_string_test.go",
+ }
+ slices.Sort(gotFiles)
+ slices.Sort(wantFiles)
+ if !reflect.DeepEqual(gotFiles, wantFiles) {
+ t.Errorf("stringer generated files:\n%s\n\nbut want:\n%s",
+ strings.Join(gotFiles, "\n"),
+ strings.Join(wantFiles, "\n"),
+ )
+ }
+
+ // Run go test as a smoke test.
+ err = runInDir(t, dir, "go", "test", "-count=1", ".")
+ if err != nil {
+ t.Fatalf("go test: %s", err)
+ }
+}
+
+// The -output flag cannot be used in combiation with matching types across multiple packages.
+func TestCollidingOutput(t *testing.T) {
+ testenv.NeedsTool(t, "go")
+ stringer := stringerPath(t)
+
+ dir := t.TempDir()
+ for name, src := range testfileSrcs {
+ source := filepath.Join(dir, name)
+ err := os.WriteFile(source, []byte(src), 0666)
+ if err != nil {
+ t.Fatalf("write file: %s", err)
+ }
+ }
+
+ // Must run stringer in the temp directory, see TestTags.
+ err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", "-output=somefile.go", dir)
+ if err == nil {
+ t.Fatal("unexpected stringer success")
+ }
+}
+
var exe struct {
path string
err error
diff --git a/cmd/stringer/golden_test.go b/cmd/stringer/golden_test.go
index a26eef3..2a81c08 100644
--- a/cmd/stringer/golden_test.go
+++ b/cmd/stringer/golden_test.go
@@ -455,11 +455,6 @@
for _, test := range golden {
test := test
t.Run(test.name, func(t *testing.T) {
- g := Generator{
- trimPrefix: test.trimPrefix,
- lineComment: test.lineComment,
- logf: t.Logf,
- }
input := "package test\n" + test.input
file := test.name + ".go"
absFile := filepath.Join(dir, file)
@@ -468,16 +463,24 @@
t.Fatal(err)
}
- g.parsePackage([]string{absFile}, nil)
+ pkgs := loadPackages([]string{absFile}, nil, test.trimPrefix, test.lineComment, t.Logf)
+ if len(pkgs) != 1 {
+ t.Fatalf("got %d parsed packages but expected 1", len(pkgs))
+ }
// Extract the name and type of the constant from the first line.
tokens := strings.SplitN(test.input, " ", 3)
if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name)
}
- g.generate(tokens[1])
+
+ g := Generator{
+ pkg: pkgs[0],
+ logf: t.Logf,
+ }
+ g.generate(tokens[1], findValues(tokens[1], pkgs[0]))
got := string(g.format())
if got != test.output {
- t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====%q", test.name, len(got), got, len(test.output), test.output)
+ t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====\n%q", test.name, len(got), got, len(test.output), test.output)
}
})
}
diff --git a/cmd/stringer/multifile_test.go b/cmd/stringer/multifile_test.go
new file mode 100644
index 0000000..7a7ae66
--- /dev/null
+++ b/cmd/stringer/multifile_test.go
@@ -0,0 +1,464 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// For os.CopyFS
+//go:build go1.23
+
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/tools/internal/diffp"
+ "golang.org/x/tools/internal/testenv"
+ "golang.org/x/tools/txtar"
+)
+
+// This file contains a test that checks the output files existence
+// and content when stringer has types from multiple different input
+// files to choose from.
+//
+// Input is specified in a txtar string.
+
+// Several tests expect the type Foo generated in some package.
+func expectFooString(pkg string) []byte {
+ return []byte(fmt.Sprintf(`
+// Header comment ignored.
+
+package %s
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[fooX-0]
+ _ = x[fooY-1]
+ _ = x[fooZ-2]
+}
+
+const _Foo_name = "fooXfooYfooZ"
+
+var _Foo_index = [...]uint8{0, 4, 8, 12}
+
+func (i Foo) String() string {
+ if i < 0 || i >= Foo(len(_Foo_index)-1) {
+ return "Foo(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Foo_name[_Foo_index[i]:_Foo_index[i+1]]
+}`, pkg))
+}
+
+func TestMultifileStringer(t *testing.T) {
+ testenv.NeedsTool(t, "go")
+ stringer := stringerPath(t)
+
+ tests := []struct {
+ name string
+ args []string
+ archive []byte
+ expectFiles map[string][]byte
+ }{
+ {
+ name: "package only",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)`),
+ expectFiles: map[string][]byte{
+ "foo_string.go": expectFooString("main"),
+ },
+ },
+ {
+ name: "test package only",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+func main() {}
+
+-- main_test.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)`),
+ expectFiles: map[string][]byte{
+ "foo_string_test.go": expectFooString("main"),
+ },
+ },
+ {
+ name: "x_test package only",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+func main() {}
+
+-- main_test.go --
+package main_test
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)`),
+ expectFiles: map[string][]byte{
+ "foo_string_test.go": expectFooString("main_test"),
+ },
+ },
+ {
+ // Re-declaring the type in a less prioritized package does not change our output.
+ name: "package over test package",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)
+
+-- main_test.go --
+package main
+
+type Foo int
+
+const (
+ otherX Foo = iota
+ otherY
+ otherZ
+)
+`),
+ expectFiles: map[string][]byte{
+ "foo_string.go": expectFooString("main"),
+ },
+ },
+ {
+ // Re-declaring the type in a less prioritized package does not change our output.
+ name: "package over x_test package",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)
+
+-- main_test.go --
+package main_test
+
+type Foo int
+
+const (
+ otherX Foo = iota
+ otherY
+ otherZ
+)
+`),
+ expectFiles: map[string][]byte{
+ "foo_string.go": expectFooString("main"),
+ },
+ },
+ {
+ // Re-declaring the type in a less prioritized package does not change our output.
+ name: "test package over x_test package",
+ args: []string{"-type=Foo"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+-- main_test.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)
+
+-- main_pkg_test.go --
+package main_test
+
+type Foo int
+
+const (
+ otherX Foo = iota
+ otherY
+ otherZ
+)`),
+ expectFiles: map[string][]byte{
+ "foo_string_test.go": expectFooString("main"),
+ },
+ },
+ {
+ name: "unique type in each package variant",
+ args: []string{"-type=Foo,Bar,Baz"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+type Foo int
+
+const fooX Foo = 1
+
+-- main_test.go --
+package main
+
+type Bar int
+
+const barX Bar = 1
+
+-- main_pkg_test.go --
+package main_test
+
+type Baz int
+
+const bazX Baz = 1
+`),
+ expectFiles: map[string][]byte{
+ "foo_string.go": []byte(`
+// Header comment ignored.
+
+package main
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[fooX-1]
+}
+
+const _Foo_name = "fooX"
+
+var _Foo_index = [...]uint8{0, 4}
+
+func (i Foo) String() string {
+ i -= 1
+ if i < 0 || i >= Foo(len(_Foo_index)-1) {
+ return "Foo(" + strconv.FormatInt(int64(i+1), 10) + ")"
+ }
+ return _Foo_name[_Foo_index[i]:_Foo_index[i+1]]
+}`),
+
+ "bar_string_test.go": []byte(`
+// Header comment ignored.
+
+package main
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[barX-1]
+}
+
+const _Bar_name = "barX"
+
+var _Bar_index = [...]uint8{0, 4}
+
+func (i Bar) String() string {
+ i -= 1
+ if i < 0 || i >= Bar(len(_Bar_index)-1) {
+ return "Bar(" + strconv.FormatInt(int64(i+1), 10) + ")"
+ }
+ return _Bar_name[_Bar_index[i]:_Bar_index[i+1]]
+}`),
+
+ "baz_string_test.go": []byte(`
+// Header comment ignored.
+
+package main_test
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[bazX-1]
+}
+
+const _Baz_name = "bazX"
+
+var _Baz_index = [...]uint8{0, 4}
+
+func (i Baz) String() string {
+ i -= 1
+ if i < 0 || i >= Baz(len(_Baz_index)-1) {
+ return "Baz(" + strconv.FormatInt(int64(i+1), 10) + ")"
+ }
+ return _Baz_name[_Baz_index[i]:_Baz_index[i+1]]
+}`),
+ },
+ },
+
+ {
+ name: "package over test package with custom output",
+ args: []string{"-type=Foo", "-output=custom_output.go"},
+ archive: []byte(`
+-- go.mod --
+module foo
+
+-- main.go --
+package main
+
+type Foo int
+
+const (
+ fooX Foo = iota
+ fooY
+ fooZ
+)
+
+-- main_test.go --
+package main
+
+type Foo int
+
+const (
+ otherX Foo = iota
+ otherY
+ otherZ
+)`),
+ expectFiles: map[string][]byte{
+ "custom_output.go": expectFooString("main"),
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tmpDir := t.TempDir()
+
+ arFS, err := txtar.FS(txtar.Parse(tt.archive))
+ if err != nil {
+ t.Fatalf("txtar.FS: %s", err)
+ }
+ err = os.CopyFS(tmpDir, arFS)
+ if err != nil {
+ t.Fatalf("copy fs: %s", err)
+ }
+ before := dirContent(t, tmpDir)
+
+ // Must run stringer in the temp directory, see TestTags.
+ args := append(tt.args, tmpDir)
+ err = runInDir(t, tmpDir, stringer, args...)
+ if err != nil {
+ t.Fatalf("run stringer: %s", err)
+ }
+
+ // Check that all !path files have been created with the expected content.
+ for f, want := range tt.expectFiles {
+ got, err := os.ReadFile(filepath.Join(tmpDir, f))
+ if errors.Is(err, os.ErrNotExist) {
+ t.Errorf("expected file not written during test: %s", f)
+ continue
+ }
+ if err != nil {
+ t.Fatalf("read file %q: %s", f, err)
+ }
+ // Trim data for more robust comparison.
+ got = trimHeader(bytes.TrimSpace(got))
+ want = trimHeader(bytes.TrimSpace(want))
+ if !bytes.Equal(want, got) {
+ t.Errorf("file %s does not have the expected content:\n%s", f, diffp.Diff("want", want, "got", got))
+ }
+ }
+
+ // Check that nothing else has been created.
+ after := dirContent(t, tmpDir)
+ for f := range after {
+ if _, expected := tt.expectFiles[f]; !expected && !before[f] {
+ t.Errorf("found %q in output directory, it is neither input or expected output", f)
+ }
+ }
+
+ })
+ }
+}
+
+func dirContent(t *testing.T, dir string) map[string]bool {
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatalf("read dir: %s", err)
+ }
+
+ out := map[string]bool{}
+ for _, e := range entries {
+ out[e.Name()] = true
+ }
+ return out
+}
+
+// trimHeader that stringer puts in file.
+// It depends on location and interferes with comparing file content.
+func trimHeader(s []byte) []byte {
+ if !bytes.HasPrefix(s, []byte("//")) {
+ return s
+ }
+ _, after, ok := bytes.Cut(s, []byte{'\n'})
+ if ok {
+ return after
+ }
+ return s
+}
diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go
index 2b19c93..09be11c 100644
--- a/cmd/stringer/stringer.go
+++ b/cmd/stringer/stringer.go
@@ -58,6 +58,11 @@
// where t is the lower-cased name of the first type listed. It can be overridden
// with the -output flag.
//
+// Types can also be declared in tests, in which case type declarations in the
+// non-test package or its test variant are preferred over types defined in the
+// package with suffix "_test".
+// The default output file for type declarations in tests is t_string_test.go with t picked as above.
+//
// The -linecomment flag tells stringer to generate the text of any line comment, trimmed
// of leading spaces, instead of the constant name. For instance, if the constants above had a
// Pill prefix, one could write
@@ -128,10 +133,6 @@
// Parse the package once.
var dir string
- g := Generator{
- trimPrefix: *trimprefix,
- lineComment: *linecomment,
- }
// TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc).
if len(args) == 1 && isDirectory(args[0]) {
dir = args[0]
@@ -142,33 +143,90 @@
dir = filepath.Dir(args[0])
}
- g.parsePackage(args, tags)
+ // For each type, generate code in the first package where the type is declared.
+ // The order of packages is as follows:
+ // package x
+ // package x compiled for tests
+ // package x_test
+ //
+ // Each package pass could result in a separate generated file.
+ // These files must have the same package and test/not-test nature as the types
+ // from which they were generated.
+ //
+ // Types will be excluded when generated, to avoid repetitions.
+ pkgs := loadPackages(args, tags, *trimprefix, *linecomment, nil /* logf */)
+ sort.Slice(pkgs, func(i, j int) bool {
+ // Put x_test packages last.
+ iTest := strings.HasSuffix(pkgs[i].name, "_test")
+ jTest := strings.HasSuffix(pkgs[j].name, "_test")
+ if iTest != jTest {
+ return !iTest
+ }
- // Print the header and package clause.
- g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
- g.Printf("\n")
- g.Printf("package %s", g.pkg.name)
- g.Printf("\n")
- g.Printf("import \"strconv\"\n") // Used by all methods.
+ return len(pkgs[i].files) < len(pkgs[j].files)
+ })
+ for _, pkg := range pkgs {
+ g := Generator{
+ pkg: pkg,
+ }
- // Run generate for each type.
- for _, typeName := range types {
- g.generate(typeName)
+ // Print the header and package clause.
+ g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
+ g.Printf("\n")
+ g.Printf("package %s", g.pkg.name)
+ g.Printf("\n")
+ g.Printf("import \"strconv\"\n") // Used by all methods.
+
+ // Run generate for types that can be found. Keep the rest for the remainingTypes iteration.
+ var foundTypes, remainingTypes []string
+ for _, typeName := range types {
+ values := findValues(typeName, pkg)
+ if len(values) > 0 {
+ g.generate(typeName, values)
+ foundTypes = append(foundTypes, typeName)
+ } else {
+ remainingTypes = append(remainingTypes, typeName)
+ }
+ }
+ if len(foundTypes) == 0 {
+ // This package didn't have any of the relevant types, skip writing a file.
+ continue
+ }
+ if len(remainingTypes) > 0 && output != nil && *output != "" {
+ log.Fatalf("cannot write to single file (-output=%q) when matching types are found in multiple packages", *output)
+ }
+ types = remainingTypes
+
+ // Format the output.
+ src := g.format()
+
+ // Write to file.
+ outputName := *output
+ if outputName == "" {
+ // Type names will be unique across packages since only the first
+ // match is picked.
+ // So there won't be collisions between a package compiled for tests
+ // and the separate package of tests (package foo_test).
+ outputName = filepath.Join(dir, baseName(pkg, foundTypes[0]))
+ }
+ err := os.WriteFile(outputName, src, 0644)
+ if err != nil {
+ log.Fatalf("writing output: %s", err)
+ }
}
- // Format the output.
- src := g.format()
+ if len(types) > 0 {
+ log.Fatalf("no values defined for types: %s", strings.Join(types, ","))
+ }
+}
- // Write to file.
- outputName := *output
- if outputName == "" {
- baseName := fmt.Sprintf("%s_string.go", types[0])
- outputName = filepath.Join(dir, strings.ToLower(baseName))
+// baseName that will put the generated code together with pkg.
+func baseName(pkg *Package, typename string) string {
+ suffix := "string.go"
+ if pkg.hasTestFiles {
+ suffix = "string_test.go"
}
- err := os.WriteFile(outputName, src, 0644)
- if err != nil {
- log.Fatalf("writing output: %s", err)
- }
+ return fmt.Sprintf("%s_%s", strings.ToLower(typename), suffix)
}
// isDirectory reports whether the named file is a directory.
@@ -186,9 +244,6 @@
buf bytes.Buffer // Accumulated output.
pkg *Package // Package we are scanning.
- trimPrefix string
- lineComment bool
-
logf func(format string, args ...interface{}) // test logging hook; nil when not testing
}
@@ -209,54 +264,74 @@
}
type Package struct {
- name string
- defs map[*ast.Ident]types.Object
- files []*File
+ name string
+ defs map[*ast.Ident]types.Object
+ files []*File
+ hasTestFiles bool
}
-// parsePackage analyzes the single package constructed from the patterns and tags.
-// parsePackage exits if there is an error.
-func (g *Generator) parsePackage(patterns []string, tags []string) {
+// loadPackages analyzes the single package constructed from the patterns and tags.
+// loadPackages exits if there is an error.
+//
+// Returns all variants (such as tests) of the package.
+//
+// logf is a test logging hook. It can be nil when not testing.
+func loadPackages(
+ patterns, tags []string,
+ trimPrefix string, lineComment bool,
+ logf func(format string, args ...interface{}),
+) []*Package {
cfg := &packages.Config{
- Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax,
- // TODO: Need to think about constants in test files. Maybe write type_string_test.go
- // in a separate pass? For later.
- Tests: false,
+ Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedFiles,
+ // Tests are included, let the caller decide how to fold them in.
+ Tests: true,
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
- Logf: g.logf,
+ Logf: logf,
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
log.Fatal(err)
}
- if len(pkgs) != 1 {
- log.Fatalf("error: %d packages matching %v", len(pkgs), strings.Join(patterns, " "))
- }
- g.addPackage(pkgs[0])
-}
-
-// addPackage adds a type checked Package and its syntax files to the generator.
-func (g *Generator) addPackage(pkg *packages.Package) {
- g.pkg = &Package{
- name: pkg.Name,
- defs: pkg.TypesInfo.Defs,
- files: make([]*File, len(pkg.Syntax)),
+ if len(pkgs) == 0 {
+ log.Fatalf("error: no packages matching %v", strings.Join(patterns, " "))
}
- for i, file := range pkg.Syntax {
- g.pkg.files[i] = &File{
- file: file,
- pkg: g.pkg,
- trimPrefix: g.trimPrefix,
- lineComment: g.lineComment,
+ out := make([]*Package, len(pkgs))
+ for i, pkg := range pkgs {
+ p := &Package{
+ name: pkg.Name,
+ defs: pkg.TypesInfo.Defs,
+ files: make([]*File, len(pkg.Syntax)),
}
+
+ for j, file := range pkg.Syntax {
+ p.files[j] = &File{
+ file: file,
+ pkg: p,
+
+ trimPrefix: trimPrefix,
+ lineComment: lineComment,
+ }
+ }
+
+ // Keep track of test files, since we might want to generated
+ // code that ends up in that kind of package.
+ // Can be replaced once https://go.dev/issue/38445 lands.
+ for _, f := range pkg.GoFiles {
+ if strings.HasSuffix(f, "_test.go") {
+ p.hasTestFiles = true
+ break
+ }
+ }
+
+ out[i] = p
}
+ return out
}
-// generate produces the String method for the named type.
-func (g *Generator) generate(typeName string) {
+func findValues(typeName string, pkg *Package) []Value {
values := make([]Value, 0, 100)
- for _, file := range g.pkg.files {
+ for _, file := range pkg.files {
// Set the state for this run of the walker.
file.typeName = typeName
file.values = nil
@@ -265,10 +340,11 @@
values = append(values, file.values...)
}
}
+ return values
+}
- if len(values) == 0 {
- log.Fatalf("no values defined for type %s", typeName)
- }
+// generate produces the String method for the named type.
+func (g *Generator) generate(typeName string, values []Value) {
// Generate code that will fail if the constants change value.
g.Printf("func _() {\n")
g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")