protogen: include source comments in generated code

Add initial handling of location paths (arrays of integers identifying
an entity in a .proto source file).

Expose path info in protogen; each descriptor has a Path field containing
its location path.

Format comments in protoc-gen-go. This contains one change from
github.com/golang/protobuf: Package comments are now included before the
package statement (but not attached to it) and use // comment syntax
instead of /* */. e.g.,

Before:

	package test

	/*
	This package contains interesting messages.
	*/

After:

	// This package contains interesting messages.

	package test

Change-Id: Ieee13ae77b3584f7562183100554d3df732348aa
Reviewed-on: https://go-review.googlesource.com/133915
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index b9223b0..d330941 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -13,6 +13,7 @@
 	"encoding/hex"
 	"fmt"
 	"strconv"
+	"strings"
 
 	"github.com/golang/protobuf/proto"
 	descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
@@ -31,22 +32,39 @@
 	})
 }
 
-func genFile(gen *protogen.Plugin, f *protogen.File) {
+type File struct {
+	*protogen.File
+	locationMap map[string][]*descpb.SourceCodeInfo_Location
+}
+
+func genFile(gen *protogen.Plugin, file *protogen.File) {
+	f := &File{
+		File:        file,
+		locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
+	}
+	for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() {
+		key := pathKey(loc.Path)
+		f.locationMap[key] = append(f.locationMap[key], loc)
+	}
+
 	g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
 	g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
 	g.P("// source: ", f.Desc.Path())
 	g.P()
+	const filePackageField = 2 // FileDescriptorProto.package
+	genComment(g, f, []int32{filePackageField})
+	g.P()
 	g.P("package ", f.GoPackageName)
 	g.P()
 
-	for _, m := range f.Messages {
-		genMessage(gen, g, m)
+	for _, message := range f.Messages {
+		genMessage(gen, g, f, message)
 	}
 
 	genFileDescriptor(gen, g, f)
 }
 
-func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *protogen.File) {
+func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File) {
 	// Determine the name of the var holding the file descriptor:
 	//
 	//     fileDescriptor_<hash of filename>
@@ -90,12 +108,37 @@
 	g.P()
 }
 
-func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, m *protogen.Message) {
-	g.P("type ", m.GoIdent, " struct {")
+func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
+	genComment(g, f, message.Path)
+	g.P("type ", message.GoIdent, " struct {")
 	g.P("}")
 	g.P()
 
-	for _, nested := range m.Messages {
-		genMessage(gen, g, nested)
+	for _, nested := range message.Messages {
+		genMessage(gen, g, f, nested)
 	}
 }
+
+func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
+	for _, loc := range f.locationMap[pathKey(path)] {
+		if loc.LeadingComments == nil {
+			continue
+		}
+		for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
+			g.P("//", line)
+		}
+		return
+	}
+}
+
+// pathKey converts a location path to a string suitable for use as a map key.
+func pathKey(path []int32) string {
+	var buf []byte
+	for i, x := range path {
+		if i != 0 {
+			buf = append(buf, ',')
+		}
+		buf = strconv.AppendInt(buf, int64(x), 10)
+	}
+	return string(buf)
+}