Skip to content

Commit e42003f

Browse files
author
Randall C. O'Reilly
committed
updated to latest go stringer which has been fixed to be much faster: golang/go#25650
1 parent d5655e7 commit e42003f

File tree

4 files changed

+95
-130
lines changed

4 files changed

+95
-130
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# stringer
2-
my fork of stringer that also generates a conversion BACK from a string to the const type
2+
3+
fork of stringer that also generates a conversion BACK from a string to the const type
4+
in the `FromString` method
5+

importer18.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

importer19.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

stringer.go

Lines changed: 91 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,21 @@
5656
// where t is the lower-cased name of the first type listed. It can be overridden
5757
// with the -output flag.
5858
//
59+
// The -linecomment flag tells stringer to generate the text of any line comment, trimmed
60+
// of leading spaces, instead of the constant name. For instance, if the constants above had a
61+
// Pill prefix, one could write
62+
// PillAspirin // Aspirin
63+
// to suppress it in the output.
64+
5965
package main
6066

6167
import (
6268
"bytes"
6369
"flag"
6470
"fmt"
6571
"go/ast"
66-
"go/build"
67-
exact "go/constant"
72+
"go/constant"
6873
"go/format"
69-
"go/parser"
7074
"go/token"
7175
"go/types"
7276
"io/ioutil"
@@ -75,18 +79,21 @@ import (
7579
"path/filepath"
7680
"sort"
7781
"strings"
82+
83+
"golang.org/x/tools/go/packages"
7884
)
7985

8086
var (
8187
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
8288
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
8389
trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
8490
linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
91+
buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
8592
)
8693

8794
// Usage is a replacement usage function for the flags package.
8895
func Usage() {
89-
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
96+
fmt.Fprintf(os.Stderr, "Usage of stringer:\n")
9097
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
9198
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n")
9299
fmt.Fprintf(os.Stderr, "For more information, see:\n")
@@ -105,6 +112,10 @@ func main() {
105112
os.Exit(2)
106113
}
107114
types := strings.Split(*typeNames, ",")
115+
var tags []string
116+
if len(*buildTags) > 0 {
117+
tags = strings.Split(*buildTags, ",")
118+
}
108119

109120
// We accept either one directory or a list of files. Which do we have?
110121
args := flag.Args()
@@ -119,14 +130,18 @@ func main() {
119130
trimPrefix: *trimprefix,
120131
lineComment: *linecomment,
121132
}
133+
// TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc).
122134
if len(args) == 1 && isDirectory(args[0]) {
123135
dir = args[0]
124-
g.parsePackageDir(args[0])
125136
} else {
137+
if len(tags) != 0 {
138+
log.Fatal("-tags option applies only to directories, not when files are specified")
139+
}
126140
dir = filepath.Dir(args[0])
127-
g.parsePackageFiles(args)
128141
}
129142

143+
g.parsePackage(args, tags)
144+
130145
// Print the header and package clause.
131146
g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
132147
g.Printf("\n")
@@ -191,93 +206,47 @@ type File struct {
191206
}
192207

193208
type Package struct {
194-
dir string
195-
name string
196-
defs map[*ast.Ident]types.Object
197-
files []*File
198-
typesPkg *types.Package
199-
}
200-
201-
// parsePackageDir parses the package residing in the directory.
202-
func (g *Generator) parsePackageDir(directory string) {
203-
pkg, err := build.Default.ImportDir(directory, 0)
209+
name string
210+
defs map[*ast.Ident]types.Object
211+
files []*File
212+
}
213+
214+
// parsePackage analyzes the single package constructed from the patterns and tags.
215+
// parsePackage exits if there is an error.
216+
func (g *Generator) parsePackage(patterns []string, tags []string) {
217+
cfg := &packages.Config{
218+
Mode: packages.LoadSyntax,
219+
// TODO: Need to think about constants in test files. Maybe write type_string_test.go
220+
// in a separate pass? For later.
221+
Tests: false,
222+
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
223+
}
224+
pkgs, err := packages.Load(cfg, patterns...)
204225
if err != nil {
205-
log.Fatalf("cannot process directory %s: %s", directory, err)
206-
}
207-
var names []string
208-
names = append(names, pkg.GoFiles...)
209-
names = append(names, pkg.CgoFiles...)
210-
// TODO: Need to think about constants in test files. Maybe write type_string_test.go
211-
// in a separate pass? For later.
212-
// names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
213-
names = append(names, pkg.SFiles...)
214-
names = prefixDirectory(directory, names)
215-
g.parsePackage(directory, names, nil)
216-
}
217-
218-
// parsePackageFiles parses the package occupying the named files.
219-
func (g *Generator) parsePackageFiles(names []string) {
220-
g.parsePackage(".", names, nil)
221-
}
222-
223-
// prefixDirectory places the directory name on the beginning of each name in the list.
224-
func prefixDirectory(directory string, names []string) []string {
225-
if directory == "." {
226-
return names
226+
log.Fatal(err)
227227
}
228-
ret := make([]string, len(names))
229-
for i, name := range names {
230-
ret[i] = filepath.Join(directory, name)
228+
if len(pkgs) != 1 {
229+
log.Fatalf("error: %d packages found", len(pkgs))
231230
}
232-
return ret
231+
g.addPackage(pkgs[0])
233232
}
234233

235-
// parsePackage analyzes the single package constructed from the named files.
236-
// If text is non-nil, it is a string to be used instead of the content of the file,
237-
// to be used for testing. parsePackage exits if there is an error.
238-
func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
239-
var files []*File
240-
var astFiles []*ast.File
241-
g.pkg = new(Package)
242-
fs := token.NewFileSet()
243-
for _, name := range names {
244-
if !strings.HasSuffix(name, ".go") {
245-
continue
246-
}
247-
parsedFile, err := parser.ParseFile(fs, name, text, parser.ParseComments)
248-
if err != nil {
249-
log.Fatalf("parsing package: %s: %s", name, err)
250-
}
251-
astFiles = append(astFiles, parsedFile)
252-
files = append(files, &File{
253-
file: parsedFile,
234+
// addPackage adds a type checked Package and its syntax files to the generator.
235+
func (g *Generator) addPackage(pkg *packages.Package) {
236+
g.pkg = &Package{
237+
name: pkg.Name,
238+
defs: pkg.TypesInfo.Defs,
239+
files: make([]*File, len(pkg.Syntax)),
240+
}
241+
242+
for i, file := range pkg.Syntax {
243+
g.pkg.files[i] = &File{
244+
file: file,
254245
pkg: g.pkg,
255246
trimPrefix: g.trimPrefix,
256247
lineComment: g.lineComment,
257-
})
258-
}
259-
if len(astFiles) == 0 {
260-
log.Fatalf("%s: no buildable Go files", directory)
261-
}
262-
g.pkg.name = astFiles[0].Name.Name
263-
g.pkg.files = files
264-
g.pkg.dir = directory
265-
// Type check the package.
266-
g.pkg.check(fs, astFiles)
267-
}
268-
269-
// check type-checks the package. The package must be OK to proceed.
270-
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
271-
pkg.defs = make(map[*ast.Ident]types.Object)
272-
config := types.Config{Importer: defaultImporter(), FakeImportC: true}
273-
info := &types.Info{
274-
Defs: pkg.defs,
275-
}
276-
typesPkg, err := config.Check(pkg.dir, fs, astFiles, info)
277-
if err != nil {
278-
log.Fatalf("checking package: %s", err)
248+
}
279249
}
280-
pkg.typesPkg = typesPkg
281250
}
282251

283252
// generate produces the String method for the named type.
@@ -296,6 +265,15 @@ func (g *Generator) generate(typeName string) {
296265
if len(values) == 0 {
297266
log.Fatalf("no values defined for type %s", typeName)
298267
}
268+
// Generate code that will fail if the constants change value.
269+
g.Printf("func _() {\n")
270+
g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")
271+
g.Printf("\t// Re-run the stringer command to generate them again.\n")
272+
g.Printf("\tvar x [1]struct{}\n")
273+
for _, v := range values {
274+
g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str)
275+
}
276+
g.Printf("}\n")
299277
runs := splitIntoRuns(values)
300278
// The decision of which pattern to use depends on the number of
301279
// runs in the numbers. If there's only one, it's easy. For more than
@@ -366,15 +344,16 @@ func (g *Generator) format() []byte {
366344

367345
// Value represents a declared constant.
368346
type Value struct {
369-
name string // The name of the constant.
347+
originalName string // The name of the constant.
348+
name string // The name with trimmed prefix.
370349
// The value is stored as a bit pattern alone. The boolean tells us
371350
// whether to interpret it as an int64 or a uint64; the only place
372351
// this matters is when sorting.
373352
// Much of the time the str field is all we need; it is printed
374353
// by Value.String.
375354
value uint64 // Will be converted to int64 when needed.
376355
signed bool // Whether the constant is a signed type.
377-
str string // The string representation given by the "go/exact" package.
356+
str string // The string representation given by the "go/constant" package.
378357
}
379358

380359
func (v *Value) String() string {
@@ -412,10 +391,24 @@ func (f *File) genDecl(node ast.Node) bool {
412391
for _, spec := range decl.Specs {
413392
vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
414393
if vspec.Type == nil && len(vspec.Values) > 0 {
415-
// "X = 1". With no type but a value, the constant is untyped.
416-
// Skip this vspec and reset the remembered type.
394+
// "X = 1". With no type but a value. If the constant is untyped,
395+
// skip this vspec and reset the remembered type.
417396
typ = ""
418-
continue
397+
398+
// If this is a simple type conversion, remember the type.
399+
// We don't mind if this is actually a call; a qualified call won't
400+
// be matched (that will be SelectorExpr, not Ident), and only unusual
401+
// situations will result in a function call that appears to be
402+
// a type conversion.
403+
ce, ok := vspec.Values[0].(*ast.CallExpr)
404+
if !ok {
405+
continue
406+
}
407+
id, ok := ce.Fun.(*ast.Ident)
408+
if !ok {
409+
continue
410+
}
411+
typ = id.Name
419412
}
420413
if vspec.Type != nil {
421414
// "X T". We have a type. Remember it.
@@ -448,27 +441,28 @@ func (f *File) genDecl(node ast.Node) bool {
448441
log.Fatalf("can't handle non-integer constant type %s", typ)
449442
}
450443
value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
451-
if value.Kind() != exact.Int {
444+
if value.Kind() != constant.Int {
452445
log.Fatalf("can't happen: constant is not an integer %s", name)
453446
}
454-
i64, isInt := exact.Int64Val(value)
455-
u64, isUint := exact.Uint64Val(value)
447+
i64, isInt := constant.Int64Val(value)
448+
u64, isUint := constant.Uint64Val(value)
456449
if !isInt && !isUint {
457450
log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String())
458451
}
459452
if !isInt {
460453
u64 = uint64(i64)
461454
}
462455
v := Value{
463-
name: name.Name,
464-
value: u64,
465-
signed: info&types.IsUnsigned == 0,
466-
str: value.String(),
456+
originalName: name.Name,
457+
value: u64,
458+
signed: info&types.IsUnsigned == 0,
459+
str: value.String(),
467460
}
468461
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
469462
v.name = strings.TrimSpace(c.Text())
463+
} else {
464+
v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
470465
}
471-
v.name = strings.TrimPrefix(v.name, f.trimPrefix)
472466
f.values = append(f.values, v)
473467
}
474468
}

0 commit comments

Comments
 (0)