56
56
// where t is the lower-cased name of the first type listed. It can be overridden
57
57
// with the -output flag.
58
58
//
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
+
59
65
package main
60
66
61
67
import (
62
68
"bytes"
63
69
"flag"
64
70
"fmt"
65
71
"go/ast"
66
- "go/build"
67
- exact "go/constant"
72
+ "go/constant"
68
73
"go/format"
69
- "go/parser"
70
74
"go/token"
71
75
"go/types"
72
76
"io/ioutil"
@@ -75,18 +79,21 @@ import (
75
79
"path/filepath"
76
80
"sort"
77
81
"strings"
82
+
83
+ "golang.org/x/tools/go/packages"
78
84
)
79
85
80
86
var (
81
87
typeNames = flag .String ("type" , "" , "comma-separated list of type names; must be set" )
82
88
output = flag .String ("output" , "" , "output file name; default srcdir/<type>_string.go" )
83
89
trimprefix = flag .String ("trimprefix" , "" , "trim the `prefix` from the generated constant names" )
84
90
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" )
85
92
)
86
93
87
94
// Usage is a replacement usage function for the flags package.
88
95
func Usage () {
89
- fmt .Fprintf (os .Stderr , "Usage of %s :\n " , os . Args [ 0 ] )
96
+ fmt .Fprintf (os .Stderr , "Usage of stringer :\n " )
90
97
fmt .Fprintf (os .Stderr , "\t stringer [flags] -type T [directory]\n " )
91
98
fmt .Fprintf (os .Stderr , "\t stringer [flags] -type T files... # Must be a single package\n " )
92
99
fmt .Fprintf (os .Stderr , "For more information, see:\n " )
@@ -105,6 +112,10 @@ func main() {
105
112
os .Exit (2 )
106
113
}
107
114
types := strings .Split (* typeNames , "," )
115
+ var tags []string
116
+ if len (* buildTags ) > 0 {
117
+ tags = strings .Split (* buildTags , "," )
118
+ }
108
119
109
120
// We accept either one directory or a list of files. Which do we have?
110
121
args := flag .Args ()
@@ -119,14 +130,18 @@ func main() {
119
130
trimPrefix : * trimprefix ,
120
131
lineComment : * linecomment ,
121
132
}
133
+ // TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc).
122
134
if len (args ) == 1 && isDirectory (args [0 ]) {
123
135
dir = args [0 ]
124
- g .parsePackageDir (args [0 ])
125
136
} else {
137
+ if len (tags ) != 0 {
138
+ log .Fatal ("-tags option applies only to directories, not when files are specified" )
139
+ }
126
140
dir = filepath .Dir (args [0 ])
127
- g .parsePackageFiles (args )
128
141
}
129
142
143
+ g .parsePackage (args , tags )
144
+
130
145
// Print the header and package clause.
131
146
g .Printf ("// Code generated by \" stringer %s\" ; DO NOT EDIT.\n " , strings .Join (os .Args [1 :], " " ))
132
147
g .Printf ("\n " )
@@ -191,93 +206,47 @@ type File struct {
191
206
}
192
207
193
208
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 ... )
204
225
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 )
227
227
}
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 ))
231
230
}
232
- return ret
231
+ g . addPackage ( pkgs [ 0 ])
233
232
}
234
233
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 ,
254
245
pkg : g .pkg ,
255
246
trimPrefix : g .trimPrefix ,
256
247
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
+ }
279
249
}
280
- pkg .typesPkg = typesPkg
281
250
}
282
251
283
252
// generate produces the String method for the named type.
@@ -296,6 +265,15 @@ func (g *Generator) generate(typeName string) {
296
265
if len (values ) == 0 {
297
266
log .Fatalf ("no values defined for type %s" , typeName )
298
267
}
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 ("\t var 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 " )
299
277
runs := splitIntoRuns (values )
300
278
// The decision of which pattern to use depends on the number of
301
279
// runs in the numbers. If there's only one, it's easy. For more than
@@ -366,15 +344,16 @@ func (g *Generator) format() []byte {
366
344
367
345
// Value represents a declared constant.
368
346
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.
370
349
// The value is stored as a bit pattern alone. The boolean tells us
371
350
// whether to interpret it as an int64 or a uint64; the only place
372
351
// this matters is when sorting.
373
352
// Much of the time the str field is all we need; it is printed
374
353
// by Value.String.
375
354
value uint64 // Will be converted to int64 when needed.
376
355
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.
378
357
}
379
358
380
359
func (v * Value ) String () string {
@@ -412,10 +391,24 @@ func (f *File) genDecl(node ast.Node) bool {
412
391
for _ , spec := range decl .Specs {
413
392
vspec := spec .(* ast.ValueSpec ) // Guaranteed to succeed as this is CONST.
414
393
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.
417
396
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
419
412
}
420
413
if vspec .Type != nil {
421
414
// "X T". We have a type. Remember it.
@@ -448,27 +441,28 @@ func (f *File) genDecl(node ast.Node) bool {
448
441
log .Fatalf ("can't handle non-integer constant type %s" , typ )
449
442
}
450
443
value := obj .(* types.Const ).Val () // Guaranteed to succeed as this is CONST.
451
- if value .Kind () != exact .Int {
444
+ if value .Kind () != constant .Int {
452
445
log .Fatalf ("can't happen: constant is not an integer %s" , name )
453
446
}
454
- i64 , isInt := exact .Int64Val (value )
455
- u64 , isUint := exact .Uint64Val (value )
447
+ i64 , isInt := constant .Int64Val (value )
448
+ u64 , isUint := constant .Uint64Val (value )
456
449
if ! isInt && ! isUint {
457
450
log .Fatalf ("internal error: value of %s is not an integer: %s" , name , value .String ())
458
451
}
459
452
if ! isInt {
460
453
u64 = uint64 (i64 )
461
454
}
462
455
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 (),
467
460
}
468
461
if c := vspec .Comment ; f .lineComment && c != nil && len (c .List ) == 1 {
469
462
v .name = strings .TrimSpace (c .Text ())
463
+ } else {
464
+ v .name = strings .TrimPrefix (v .originalName , f .trimPrefix )
470
465
}
471
- v .name = strings .TrimPrefix (v .name , f .trimPrefix )
472
466
f .values = append (f .values , v )
473
467
}
474
468
}
0 commit comments