golang.org/x/tools@v0.21.0/cmd/stringer/stringer.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer 6 // interface. Given the name of a (signed or unsigned) integer type T that has constants 7 // defined, stringer will create a new self-contained Go source file implementing 8 // 9 // func (t T) String() string 10 // 11 // The file is created in the same package and directory as the package that defines T. 12 // It has helpful defaults designed for use with go generate. 13 // 14 // Stringer works best with constants that are consecutive values such as created using iota, 15 // but creates good code regardless. In the future it might also provide custom support for 16 // constant sets that are bit patterns. 17 // 18 // For example, given this snippet, 19 // 20 // package painkiller 21 // 22 // type Pill int 23 // 24 // const ( 25 // Placebo Pill = iota 26 // Aspirin 27 // Ibuprofen 28 // Paracetamol 29 // Acetaminophen = Paracetamol 30 // ) 31 // 32 // running this command 33 // 34 // stringer -type=Pill 35 // 36 // in the same directory will create the file pill_string.go, in package painkiller, 37 // containing a definition of 38 // 39 // func (Pill) String() string 40 // 41 // That method will translate the value of a Pill constant to the string representation 42 // of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will 43 // print the string "Aspirin". 44 // 45 // Typically this process would be run using go generate, like this: 46 // 47 // //go:generate stringer -type=Pill 48 // 49 // If multiple constants have the same value, the lexically first matching name will 50 // be used (in the example, Acetaminophen will print as "Paracetamol"). 51 // 52 // With no arguments, it processes the package in the current directory. 53 // Otherwise, the arguments must name a single directory holding a Go package 54 // or a set of Go source files that represent a single Go package. 55 // 56 // The -type flag accepts a comma-separated list of types so a single run can 57 // generate methods for multiple types. The default output file is t_string.go, 58 // where t is the lower-cased name of the first type listed. It can be overridden 59 // with the -output flag. 60 // 61 // The -linecomment flag tells stringer to generate the text of any line comment, trimmed 62 // of leading spaces, instead of the constant name. For instance, if the constants above had a 63 // Pill prefix, one could write 64 // 65 // PillAspirin // Aspirin 66 // 67 // to suppress it in the output. 68 package main // import "golang.org/x/tools/cmd/stringer" 69 70 import ( 71 "bytes" 72 "flag" 73 "fmt" 74 "go/ast" 75 "go/constant" 76 "go/format" 77 "go/token" 78 "go/types" 79 "log" 80 "os" 81 "path/filepath" 82 "sort" 83 "strings" 84 85 "golang.org/x/tools/go/packages" 86 ) 87 88 var ( 89 typeNames = flag.String("type", "", "comma-separated list of type names; must be set") 90 output = flag.String("output", "", "output file name; default srcdir/<type>_string.go") 91 trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") 92 linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") 93 buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") 94 ) 95 96 // Usage is a replacement usage function for the flags package. 97 func Usage() { 98 fmt.Fprintf(os.Stderr, "Usage of stringer:\n") 99 fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n") 100 fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n") 101 fmt.Fprintf(os.Stderr, "For more information, see:\n") 102 fmt.Fprintf(os.Stderr, "\thttps://pkg.go.dev/golang.org/x/tools/cmd/stringer\n") 103 fmt.Fprintf(os.Stderr, "Flags:\n") 104 flag.PrintDefaults() 105 } 106 107 func main() { 108 log.SetFlags(0) 109 log.SetPrefix("stringer: ") 110 flag.Usage = Usage 111 flag.Parse() 112 if len(*typeNames) == 0 { 113 flag.Usage() 114 os.Exit(2) 115 } 116 types := strings.Split(*typeNames, ",") 117 var tags []string 118 if len(*buildTags) > 0 { 119 tags = strings.Split(*buildTags, ",") 120 } 121 122 // We accept either one directory or a list of files. Which do we have? 123 args := flag.Args() 124 if len(args) == 0 { 125 // Default: process whole package in current directory. 126 args = []string{"."} 127 } 128 129 // Parse the package once. 130 var dir string 131 g := Generator{ 132 trimPrefix: *trimprefix, 133 lineComment: *linecomment, 134 } 135 // TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc). 136 if len(args) == 1 && isDirectory(args[0]) { 137 dir = args[0] 138 } else { 139 if len(tags) != 0 { 140 log.Fatal("-tags option applies only to directories, not when files are specified") 141 } 142 dir = filepath.Dir(args[0]) 143 } 144 145 g.parsePackage(args, tags) 146 147 // Print the header and package clause. 148 g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) 149 g.Printf("\n") 150 g.Printf("package %s", g.pkg.name) 151 g.Printf("\n") 152 g.Printf("import \"strconv\"\n") // Used by all methods. 153 154 // Run generate for each type. 155 for _, typeName := range types { 156 g.generate(typeName) 157 } 158 159 // Format the output. 160 src := g.format() 161 162 // Write to file. 163 outputName := *output 164 if outputName == "" { 165 baseName := fmt.Sprintf("%s_string.go", types[0]) 166 outputName = filepath.Join(dir, strings.ToLower(baseName)) 167 } 168 err := os.WriteFile(outputName, src, 0644) 169 if err != nil { 170 log.Fatalf("writing output: %s", err) 171 } 172 } 173 174 // isDirectory reports whether the named file is a directory. 175 func isDirectory(name string) bool { 176 info, err := os.Stat(name) 177 if err != nil { 178 log.Fatal(err) 179 } 180 return info.IsDir() 181 } 182 183 // Generator holds the state of the analysis. Primarily used to buffer 184 // the output for format.Source. 185 type Generator struct { 186 buf bytes.Buffer // Accumulated output. 187 pkg *Package // Package we are scanning. 188 189 trimPrefix string 190 lineComment bool 191 192 logf func(format string, args ...interface{}) // test logging hook; nil when not testing 193 } 194 195 func (g *Generator) Printf(format string, args ...interface{}) { 196 fmt.Fprintf(&g.buf, format, args...) 197 } 198 199 // File holds a single parsed file and associated data. 200 type File struct { 201 pkg *Package // Package to which this file belongs. 202 file *ast.File // Parsed AST. 203 // These fields are reset for each type being generated. 204 typeName string // Name of the constant type. 205 values []Value // Accumulator for constant values of that type. 206 207 trimPrefix string 208 lineComment bool 209 } 210 211 type Package struct { 212 name string 213 defs map[*ast.Ident]types.Object 214 files []*File 215 } 216 217 // parsePackage analyzes the single package constructed from the patterns and tags. 218 // parsePackage exits if there is an error. 219 func (g *Generator) parsePackage(patterns []string, tags []string) { 220 cfg := &packages.Config{ 221 Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax, 222 // TODO: Need to think about constants in test files. Maybe write type_string_test.go 223 // in a separate pass? For later. 224 Tests: false, 225 BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, 226 Logf: g.logf, 227 } 228 pkgs, err := packages.Load(cfg, patterns...) 229 if err != nil { 230 log.Fatal(err) 231 } 232 if len(pkgs) != 1 { 233 log.Fatalf("error: %d packages matching %v", len(pkgs), strings.Join(patterns, " ")) 234 } 235 g.addPackage(pkgs[0]) 236 } 237 238 // addPackage adds a type checked Package and its syntax files to the generator. 239 func (g *Generator) addPackage(pkg *packages.Package) { 240 g.pkg = &Package{ 241 name: pkg.Name, 242 defs: pkg.TypesInfo.Defs, 243 files: make([]*File, len(pkg.Syntax)), 244 } 245 246 for i, file := range pkg.Syntax { 247 g.pkg.files[i] = &File{ 248 file: file, 249 pkg: g.pkg, 250 trimPrefix: g.trimPrefix, 251 lineComment: g.lineComment, 252 } 253 } 254 } 255 256 // generate produces the String method for the named type. 257 func (g *Generator) generate(typeName string) { 258 values := make([]Value, 0, 100) 259 for _, file := range g.pkg.files { 260 // Set the state for this run of the walker. 261 file.typeName = typeName 262 file.values = nil 263 if file.file != nil { 264 ast.Inspect(file.file, file.genDecl) 265 values = append(values, file.values...) 266 } 267 } 268 269 if len(values) == 0 { 270 log.Fatalf("no values defined for type %s", typeName) 271 } 272 // Generate code that will fail if the constants change value. 273 g.Printf("func _() {\n") 274 g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n") 275 g.Printf("\t// Re-run the stringer command to generate them again.\n") 276 g.Printf("\tvar x [1]struct{}\n") 277 for _, v := range values { 278 g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str) 279 } 280 g.Printf("}\n") 281 runs := splitIntoRuns(values) 282 // The decision of which pattern to use depends on the number of 283 // runs in the numbers. If there's only one, it's easy. For more than 284 // one, there's a tradeoff between complexity and size of the data 285 // and code vs. the simplicity of a map. A map takes more space, 286 // but so does the code. The decision here (crossover at 10) is 287 // arbitrary, but considers that for large numbers of runs the cost 288 // of the linear scan in the switch might become important, and 289 // rather than use yet another algorithm such as binary search, 290 // we punt and use a map. In any case, the likelihood of a map 291 // being necessary for any realistic example other than bitmasks 292 // is very low. And bitmasks probably deserve their own analysis, 293 // to be done some other day. 294 switch { 295 case len(runs) == 1: 296 g.buildOneRun(runs, typeName) 297 case len(runs) <= 10: 298 g.buildMultipleRuns(runs, typeName) 299 default: 300 g.buildMap(runs, typeName) 301 } 302 } 303 304 // splitIntoRuns breaks the values into runs of contiguous sequences. 305 // For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}. 306 // The input slice is known to be non-empty. 307 func splitIntoRuns(values []Value) [][]Value { 308 // We use stable sort so the lexically first name is chosen for equal elements. 309 sort.Stable(byValue(values)) 310 // Remove duplicates. Stable sort has put the one we want to print first, 311 // so use that one. The String method won't care about which named constant 312 // was the argument, so the first name for the given value is the only one to keep. 313 // We need to do this because identical values would cause the switch or map 314 // to fail to compile. 315 j := 1 316 for i := 1; i < len(values); i++ { 317 if values[i].value != values[i-1].value { 318 values[j] = values[i] 319 j++ 320 } 321 } 322 values = values[:j] 323 runs := make([][]Value, 0, 10) 324 for len(values) > 0 { 325 // One contiguous sequence per outer loop. 326 i := 1 327 for i < len(values) && values[i].value == values[i-1].value+1 { 328 i++ 329 } 330 runs = append(runs, values[:i]) 331 values = values[i:] 332 } 333 return runs 334 } 335 336 // format returns the gofmt-ed contents of the Generator's buffer. 337 func (g *Generator) format() []byte { 338 src, err := format.Source(g.buf.Bytes()) 339 if err != nil { 340 // Should never happen, but can arise when developing this code. 341 // The user can compile the output to see the error. 342 log.Printf("warning: internal error: invalid Go generated: %s", err) 343 log.Printf("warning: compile the package to analyze the error") 344 return g.buf.Bytes() 345 } 346 return src 347 } 348 349 // Value represents a declared constant. 350 type Value struct { 351 originalName string // The name of the constant. 352 name string // The name with trimmed prefix. 353 // The value is stored as a bit pattern alone. The boolean tells us 354 // whether to interpret it as an int64 or a uint64; the only place 355 // this matters is when sorting. 356 // Much of the time the str field is all we need; it is printed 357 // by Value.String. 358 value uint64 // Will be converted to int64 when needed. 359 signed bool // Whether the constant is a signed type. 360 str string // The string representation given by the "go/constant" package. 361 } 362 363 func (v *Value) String() string { 364 return v.str 365 } 366 367 // byValue lets us sort the constants into increasing order. 368 // We take care in the Less method to sort in signed or unsigned order, 369 // as appropriate. 370 type byValue []Value 371 372 func (b byValue) Len() int { return len(b) } 373 func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 374 func (b byValue) Less(i, j int) bool { 375 if b[i].signed { 376 return int64(b[i].value) < int64(b[j].value) 377 } 378 return b[i].value < b[j].value 379 } 380 381 // genDecl processes one declaration clause. 382 func (f *File) genDecl(node ast.Node) bool { 383 decl, ok := node.(*ast.GenDecl) 384 if !ok || decl.Tok != token.CONST { 385 // We only care about const declarations. 386 return true 387 } 388 // The name of the type of the constants we are declaring. 389 // Can change if this is a multi-element declaration. 390 typ := "" 391 // Loop over the elements of the declaration. Each element is a ValueSpec: 392 // a list of names possibly followed by a type, possibly followed by values. 393 // If the type and value are both missing, we carry down the type (and value, 394 // but the "go/types" package takes care of that). 395 for _, spec := range decl.Specs { 396 vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. 397 if vspec.Type == nil && len(vspec.Values) > 0 { 398 // "X = 1". With no type but a value. If the constant is untyped, 399 // skip this vspec and reset the remembered type. 400 typ = "" 401 402 // If this is a simple type conversion, remember the type. 403 // We don't mind if this is actually a call; a qualified call won't 404 // be matched (that will be SelectorExpr, not Ident), and only unusual 405 // situations will result in a function call that appears to be 406 // a type conversion. 407 ce, ok := vspec.Values[0].(*ast.CallExpr) 408 if !ok { 409 continue 410 } 411 id, ok := ce.Fun.(*ast.Ident) 412 if !ok { 413 continue 414 } 415 typ = id.Name 416 } 417 if vspec.Type != nil { 418 // "X T". We have a type. Remember it. 419 ident, ok := vspec.Type.(*ast.Ident) 420 if !ok { 421 continue 422 } 423 typ = ident.Name 424 } 425 if typ != f.typeName { 426 // This is not the type we're looking for. 427 continue 428 } 429 // We now have a list of names (from one line of source code) all being 430 // declared with the desired type. 431 // Grab their names and actual values and store them in f.values. 432 for _, name := range vspec.Names { 433 if name.Name == "_" { 434 continue 435 } 436 // This dance lets the type checker find the values for us. It's a 437 // bit tricky: look up the object declared by the name, find its 438 // types.Const, and extract its value. 439 obj, ok := f.pkg.defs[name] 440 if !ok { 441 log.Fatalf("no value for constant %s", name) 442 } 443 info := obj.Type().Underlying().(*types.Basic).Info() 444 if info&types.IsInteger == 0 { 445 log.Fatalf("can't handle non-integer constant type %s", typ) 446 } 447 value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. 448 if value.Kind() != constant.Int { 449 log.Fatalf("can't happen: constant is not an integer %s", name) 450 } 451 i64, isInt := constant.Int64Val(value) 452 u64, isUint := constant.Uint64Val(value) 453 if !isInt && !isUint { 454 log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) 455 } 456 if !isInt { 457 u64 = uint64(i64) 458 } 459 v := Value{ 460 originalName: name.Name, 461 value: u64, 462 signed: info&types.IsUnsigned == 0, 463 str: value.String(), 464 } 465 if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { 466 v.name = strings.TrimSpace(c.Text()) 467 } else { 468 v.name = strings.TrimPrefix(v.originalName, f.trimPrefix) 469 } 470 f.values = append(f.values, v) 471 } 472 } 473 return false 474 } 475 476 // Helpers 477 478 // usize returns the number of bits of the smallest unsigned integer 479 // type that will hold n. Used to create the smallest possible slice of 480 // integers to use as indexes into the concatenated strings. 481 func usize(n int) int { 482 switch { 483 case n < 1<<8: 484 return 8 485 case n < 1<<16: 486 return 16 487 default: 488 // 2^32 is enough constants for anyone. 489 return 32 490 } 491 } 492 493 // declareIndexAndNameVars declares the index slices and concatenated names 494 // strings representing the runs of values. 495 func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) { 496 var indexes, names []string 497 for i, run := range runs { 498 index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i)) 499 if len(run) != 1 { 500 indexes = append(indexes, index) 501 } 502 names = append(names, name) 503 } 504 g.Printf("const (\n") 505 for _, name := range names { 506 g.Printf("\t%s\n", name) 507 } 508 g.Printf(")\n\n") 509 510 if len(indexes) > 0 { 511 g.Printf("var (") 512 for _, index := range indexes { 513 g.Printf("\t%s\n", index) 514 } 515 g.Printf(")\n\n") 516 } 517 } 518 519 // declareIndexAndNameVar is the single-run version of declareIndexAndNameVars 520 func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) { 521 index, name := g.createIndexAndNameDecl(run, typeName, "") 522 g.Printf("const %s\n", name) 523 g.Printf("var %s\n", index) 524 } 525 526 // createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var". 527 func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) { 528 b := new(bytes.Buffer) 529 indexes := make([]int, len(run)) 530 for i := range run { 531 b.WriteString(run[i].name) 532 indexes[i] = b.Len() 533 } 534 nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String()) 535 nameLen := b.Len() 536 b.Reset() 537 fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen)) 538 for i, v := range indexes { 539 if i > 0 { 540 fmt.Fprintf(b, ", ") 541 } 542 fmt.Fprintf(b, "%d", v) 543 } 544 fmt.Fprintf(b, "}") 545 return b.String(), nameConst 546 } 547 548 // declareNameVars declares the concatenated names string representing all the values in the runs. 549 func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) { 550 g.Printf("const _%s_name%s = \"", typeName, suffix) 551 for _, run := range runs { 552 for i := range run { 553 g.Printf("%s", run[i].name) 554 } 555 } 556 g.Printf("\"\n") 557 } 558 559 // buildOneRun generates the variables and String method for a single run of contiguous values. 560 func (g *Generator) buildOneRun(runs [][]Value, typeName string) { 561 values := runs[0] 562 g.Printf("\n") 563 g.declareIndexAndNameVar(values, typeName) 564 // The generated code is simple enough to write as a Printf format. 565 lessThanZero := "" 566 if values[0].signed { 567 lessThanZero = "i < 0 || " 568 } 569 if values[0].value == 0 { // Signed or unsigned, 0 is still 0. 570 g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero) 571 } else { 572 g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero) 573 } 574 } 575 576 // Arguments to format are: 577 // 578 // [1]: type name 579 // [2]: size of index element (8 for uint8 etc.) 580 // [3]: less than zero check (for signed types) 581 const stringOneRun = `func (i %[1]s) String() string { 582 if %[3]si >= %[1]s(len(_%[1]s_index)-1) { 583 return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" 584 } 585 return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]] 586 } 587 ` 588 589 // Arguments to format are: 590 // [1]: type name 591 // [2]: lowest defined value for type, as a string 592 // [3]: size of index element (8 for uint8 etc.) 593 // [4]: less than zero check (for signed types) 594 /* 595 */ 596 const stringOneRunWithOffset = `func (i %[1]s) String() string { 597 i -= %[2]s 598 if %[4]si >= %[1]s(len(_%[1]s_index)-1) { 599 return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")" 600 } 601 return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]] 602 } 603 ` 604 605 // buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. 606 // For this pattern, a single Printf format won't do. 607 func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) { 608 g.Printf("\n") 609 g.declareIndexAndNameVars(runs, typeName) 610 g.Printf("func (i %s) String() string {\n", typeName) 611 g.Printf("\tswitch {\n") 612 for i, values := range runs { 613 if len(values) == 1 { 614 g.Printf("\tcase i == %s:\n", &values[0]) 615 g.Printf("\t\treturn _%s_name_%d\n", typeName, i) 616 continue 617 } 618 if values[0].value == 0 && !values[0].signed { 619 // For an unsigned lower bound of 0, "0 <= i" would be redundant. 620 g.Printf("\tcase i <= %s:\n", &values[len(values)-1]) 621 } else { 622 g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1]) 623 } 624 if values[0].value != 0 { 625 g.Printf("\t\ti -= %s\n", &values[0]) 626 } 627 g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n", 628 typeName, i, typeName, i, typeName, i) 629 } 630 g.Printf("\tdefault:\n") 631 g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName) 632 g.Printf("\t}\n") 633 g.Printf("}\n") 634 } 635 636 // buildMap handles the case where the space is so sparse a map is a reasonable fallback. 637 // It's a rare situation but has simple code. 638 func (g *Generator) buildMap(runs [][]Value, typeName string) { 639 g.Printf("\n") 640 g.declareNameVars(runs, typeName, "") 641 g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName) 642 n := 0 643 for _, values := range runs { 644 for _, value := range values { 645 g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name)) 646 n += len(value.name) 647 } 648 } 649 g.Printf("}\n\n") 650 g.Printf(stringMap, typeName) 651 } 652 653 // Argument to format is the type name. 654 const stringMap = `func (i %[1]s) String() string { 655 if str, ok := _%[1]s_map[i]; ok { 656 return str 657 } 658 return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" 659 } 660 `