diff --git a/cmd/stringer/endtoend_test.go b/cmd/stringer/endtoend_test.go index 273f29bd..f8530a6c 100644 --- a/cmd/stringer/endtoend_test.go +++ b/cmd/stringer/endtoend_test.go @@ -9,6 +9,7 @@ package main import ( + "bytes" "fmt" "go/build" "io" @@ -26,17 +27,8 @@ import ( // binary panics if the String method for X is not correct, including for error cases. func TestEndToEnd(t *testing.T) { - dir, err := ioutil.TempDir("", "stringer") - if err != nil { - t.Fatal(err) - } + dir, stringer := buildStringer(t) defer os.RemoveAll(dir) - // Create stringer in temporary directory. - stringer := filepath.Join(dir, "stringer.exe") - err = run("go", "build", "-o", stringer) - if err != nil { - t.Fatalf("building stringer: %s", err) - } // Read the testdata directory. fd, err := os.Open("testdata") if err != nil { @@ -53,6 +45,10 @@ func TestEndToEnd(t *testing.T) { t.Errorf("%s is not a Go file", name) continue } + if strings.HasPrefix(name, "tag_") { + // This file is used for tag processing in TestTags, below. + continue + } if name == "cgo.go" && !build.Default.CgoEnabled { t.Logf("cgo is not enabled for %s", name) continue @@ -63,9 +59,69 @@ func TestEndToEnd(t *testing.T) { } } +// TestTags verifies that the -tags flag works as advertised. +func TestTags(t *testing.T) { + dir, stringer := buildStringer(t) + defer os.RemoveAll(dir) + var ( + protectedConst = []byte("TagProtected") + output = filepath.Join(dir, "const_string.go") + ) + for _, file := range []string{"tag_main.go", "tag_tag.go"} { + + err := copy(filepath.Join(dir, file), filepath.Join("testdata", file)) + if err != nil { + t.Fatal(err) + } + + } + err := run(stringer, "-type", "Const", dir) + if err != nil { + t.Fatal(err) + } + result, err := ioutil.ReadFile(output) + if err != nil { + t.Fatal(err) + } + if bytes.Contains(result, protectedConst) { + t.Fatal("tagged variable appears in untagged run") + } + err = os.Remove(output) + if err != nil { + t.Fatal(err) + } + err = run(stringer, "-type", "Const", "-tags", "tag", dir) + if err != nil { + t.Fatal(err) + } + result, err = ioutil.ReadFile(output) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(result, protectedConst) { + t.Fatal("tagged variable does not appear in tagged run") + } +} + +// buildStringer creates a temporary directory and installs stringer there. +func buildStringer(t *testing.T) (dir string, stringer string) { + t.Helper() + dir, err := ioutil.TempDir("", "stringer") + if err != nil { + t.Fatal(err) + } + stringer = filepath.Join(dir, "stringer.exe") + err = run("go", "build", "-o", stringer) + if err != nil { + t.Fatalf("building stringer: %s", err) + } + return dir, stringer +} + // stringerCompileAndRun runs stringer for the named file and compiles and // runs the target binary in directory dir. That binary will panic if the String method is incorrect. func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) { + t.Helper() t.Logf("run: %s %s\n", fileName, typeName) source := filepath.Join(dir, fileName) err := copy(source, filepath.Join("testdata", fileName)) diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go index 4b8d1ba4..5e660fdf 100644 --- a/cmd/stringer/stringer.go +++ b/cmd/stringer/stringer.go @@ -82,6 +82,7 @@ var ( output = flag.String("output", "", "output file name; default srcdir/_string.go") trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") + buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") ) // Usage is a replacement usage function for the flags package. @@ -105,6 +106,10 @@ func main() { os.Exit(2) } types := strings.Split(*typeNames, ",") + var tags []string + if len(*buildTags) > 0 { + tags = strings.Split(*buildTags, ",") + } // We accept either one directory or a list of files. Which do we have? args := flag.Args() @@ -121,8 +126,11 @@ func main() { } if len(args) == 1 && isDirectory(args[0]) { dir = args[0] - g.parsePackageDir(args[0]) + g.parsePackageDir(args[0], tags) } else { + if len(tags) != 0 { + log.Fatal("-tags option applies only to directories, not when files are specified") + } dir = filepath.Dir(args[0]) g.parsePackageFiles(args) } @@ -197,9 +205,15 @@ type Package struct { typesPkg *types.Package } +func buildContext(tags []string) *build.Context { + ctx := build.Default + ctx.BuildTags = tags + return &ctx +} + // parsePackageDir parses the package residing in the directory. -func (g *Generator) parsePackageDir(directory string) { - pkg, err := build.Default.ImportDir(directory, 0) +func (g *Generator) parsePackageDir(directory string, tags []string) { + pkg, err := buildContext(tags).ImportDir(directory, 0) if err != nil { log.Fatalf("cannot process directory %s: %s", directory, err) } @@ -261,14 +275,17 @@ func (g *Generator) parsePackage(directory string, names []string, text interfac g.pkg.name = astFiles[0].Name.Name g.pkg.files = files g.pkg.dir = directory - // Type check the package. - g.pkg.check(fs, astFiles) + g.pkg.typeCheck(fs, astFiles) } -// check type-checks the package. The package must be OK to proceed. -func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) { +// check type-checks the package so we can evaluate contants whose values we are printing. +func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) { pkg.defs = make(map[*ast.Ident]types.Object) - config := types.Config{Importer: defaultImporter(), FakeImportC: true} + config := types.Config{ + IgnoreFuncBodies: true, // We only need to evaluate constants. + Importer: defaultImporter(), + FakeImportC: true, + } info := &types.Info{ Defs: pkg.defs, } diff --git a/cmd/stringer/testdata/tag_main.go b/cmd/stringer/testdata/tag_main.go new file mode 100644 index 00000000..449ea0d4 --- /dev/null +++ b/cmd/stringer/testdata/tag_main.go @@ -0,0 +1,11 @@ +// No build tag in this file. + +package main + +type Const int + +const ( + A Const = iota + B + C +) diff --git a/cmd/stringer/testdata/tag_tag.go b/cmd/stringer/testdata/tag_tag.go new file mode 100644 index 00000000..6844cb00 --- /dev/null +++ b/cmd/stringer/testdata/tag_tag.go @@ -0,0 +1,7 @@ +// This file has a build tag "tag" + +// +build tag + +package main + +const TagProtected Const = C + 1