cmd/stringer: add flag to use line comment as str

This can be very helpful if you lay out each value's string
representation like this:

	and    // &
	andAnd // &&
	or     // |
	orOr   // ||

Without the use of comments, it's impossible to use stringer with these
names as the characters & and | cannot form valid identifiers in a Go
program.

Fixes #20483.

Change-Id: I4d36c74059dd48ae3a5e09b70a429a75853ef179
Reviewed-on: https://go-review.googlesource.com/44076
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/cmd/stringer/golden_test.go b/cmd/stringer/golden_test.go
index 12bd538..4ba7788 100644
--- a/cmd/stringer/golden_test.go
+++ b/cmd/stringer/golden_test.go
@@ -16,20 +16,22 @@
 
 // Golden represents a test case.
 type Golden struct {
-	name       string
-	trimPrefix string
-	input      string // input; the package clause is provided when running the test.
-	output     string // exected output.
+	name        string
+	trimPrefix  string
+	lineComment bool
+	input       string // input; the package clause is provided when running the test.
+	output      string // exected output.
 }
 
 var golden = []Golden{
-	{"day", "", day_in, day_out},
-	{"offset", "", offset_in, offset_out},
-	{"gap", "", gap_in, gap_out},
-	{"num", "", num_in, num_out},
-	{"unum", "", unum_in, unum_out},
-	{"prime", "", prime_in, prime_out},
-	{"prefix", "Type", prefix_in, prefix_out},
+	{"day", "", false, day_in, day_out},
+	{"offset", "", false, offset_in, offset_out},
+	{"gap", "", false, gap_in, gap_out},
+	{"num", "", false, num_in, num_out},
+	{"unum", "", false, unum_in, unum_out},
+	{"prime", "", false, prime_in, prime_out},
+	{"prefix", "Type", false, prefix_in, prefix_out},
+	{"tokens", "", true, tokens_in, tokens_out},
 }
 
 // Each example starts with "type XXX [u]int", with a single space separating them.
@@ -264,9 +266,42 @@
 }
 `
 
+const tokens_in = `type Token int
+const (
+	And Token = iota // &
+	Or               // |
+	Add              // +
+	Sub              // -
+	Ident
+	Period // .
+
+	// not to be used
+	SingleBefore
+	// not to be used
+	BeforeAndInline // inline
+	InlineGeneral /* inline general */
+)
+`
+
+const tokens_out = `
+const _Token_name = "&|+-Ident.SingleBeforeinlineinline general"
+
+var _Token_index = [...]uint8{0, 1, 2, 3, 4, 9, 10, 22, 28, 42}
+
+func (i Token) String() string {
+	if i < 0 || i >= Token(len(_Token_index)-1) {
+		return "Token(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _Token_name[_Token_index[i]:_Token_index[i+1]]
+}
+`
+
 func TestGolden(t *testing.T) {
 	for _, test := range golden {
-		g := Generator{trimPrefix: test.trimPrefix}
+		g := Generator{
+			trimPrefix:  test.trimPrefix,
+			lineComment: test.lineComment,
+		}
 		input := "package test\n" + test.input
 		file := test.name + ".go"
 		g.parsePackage(".", []string{file}, input)
diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go
index 90c81bf..4b8d1ba 100644
--- a/cmd/stringer/stringer.go
+++ b/cmd/stringer/stringer.go
@@ -78,9 +78,10 @@
 )
 
 var (
-	typeNames  = flag.String("type", "", "comma-separated list of type names; must be set")
-	output     = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
-	trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
+	typeNames   = flag.String("type", "", "comma-separated list of type names; must be set")
+	output      = flag.String("output", "", "output file name; default srcdir/<type>_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")
 )
 
 // Usage is a replacement usage function for the flags package.
@@ -114,7 +115,10 @@
 
 	// Parse the package once.
 	var dir string
-	g := Generator{trimPrefix: *trimprefix}
+	g := Generator{
+		trimPrefix:  *trimprefix,
+		lineComment: *linecomment,
+	}
 	if len(args) == 1 && isDirectory(args[0]) {
 		dir = args[0]
 		g.parsePackageDir(args[0])
@@ -165,7 +169,8 @@
 	buf bytes.Buffer // Accumulated output.
 	pkg *Package     // Package we are scanning.
 
-	trimPrefix string
+	trimPrefix  string
+	lineComment bool
 }
 
 func (g *Generator) Printf(format string, args ...interface{}) {
@@ -180,7 +185,8 @@
 	typeName string  // Name of the constant type.
 	values   []Value // Accumulator for constant values of that type.
 
-	trimPrefix string
+	trimPrefix  string
+	lineComment bool
 }
 
 type Package struct {
@@ -237,15 +243,16 @@
 		if !strings.HasSuffix(name, ".go") {
 			continue
 		}
-		parsedFile, err := parser.ParseFile(fs, name, text, 0)
+		parsedFile, err := parser.ParseFile(fs, name, text, parser.ParseComments)
 		if err != nil {
 			log.Fatalf("parsing package: %s: %s", name, err)
 		}
 		astFiles = append(astFiles, parsedFile)
 		files = append(files, &File{
-			file:       parsedFile,
-			pkg:        g.pkg,
-			trimPrefix: g.trimPrefix,
+			file:        parsedFile,
+			pkg:         g.pkg,
+			trimPrefix:  g.trimPrefix,
+			lineComment: g.lineComment,
 		})
 	}
 	if len(astFiles) == 0 {
@@ -457,6 +464,9 @@
 				signed: info&types.IsUnsigned == 0,
 				str:    value.String(),
 			}
+			if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
+				v.name = strings.TrimSpace(c.Text())
+			}
 			v.name = strings.TrimPrefix(v.name, f.trimPrefix)
 			f.values = append(f.values, v)
 		}