blob: 0363cf02420d1c182534c6f4311c6f10e75ee3a4 [file] [log] [blame]
package protoprint
import (
"bytes"
"fmt"
"io"
"math"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/internal"
"github.com/jhump/protoreflect/dynamic"
)
// Printer knows how to format file descriptors as proto source code. Its fields
// provide some control over how the resulting source file is constructed and
// formatted.
type Printer struct {
// If true, comments are rendered using "/*" style comments. Otherwise, they
// are printed using "//" style line comments.
PreferMultiLineStyleComments bool
// If true, elements are sorted into a canonical order.
//
// The canonical order for elements in a file follows:
// 1. Syntax
// 2. Package
// 3. Imports (sorted lexically)
// 4. Options (sorted by name, standard options before custom options)
// 5. Messages (sorted by name)
// 6. Enums (sorted by name)
// 7. Services (sorted by name)
// 8. Extensions (grouped by extendee, sorted by extendee+tag)
//
// The canonical order of elements in a message follows:
// 1. Options (sorted by name, standard options before custom options)
// 2. Fields and One-Ofs (sorted by tag; one-ofs interleaved based on the
// minimum tag therein)
// 3. Nested Messages (sorted by name)
// 4. Nested Enums (sorted by name)
// 5. Extension ranges (sorted by starting tag number)
// 6. Nested Extensions (grouped by extendee, sorted by extendee+tag)
// 7. Reserved ranges (sorted by starting tag number)
// 8. Reserved names (sorted lexically)
//
// Methods are sorted within a service by name and appear after any service
// options (which are sorted by name, standard options before custom ones).
// Enum values are sorted within an enum, first by numeric value then by
// name, and also appear after any enum options.
//
// Options for fields, enum values, and extension ranges are sorted by name,
// standard options before custom ones.
SortElements bool
// The indentation used. Any characters other than spaces or tabs will be
// replaced with spaces. If unset/empty, two spaces will be used.
Indent string
// If true, detached comments (between elements) will be ignored.
//
// Deprecated: Use OmitComments bitmask instead.
OmitDetachedComments bool
// A bitmask of comment types to omit. If unset, all comments will be
// included. Use CommentsAll to not print any comments.
OmitComments CommentType
// If true, trailing comments that typically appear on the same line as an
// element (option, field, enum value, method) will be printed on a separate
// line instead.
//
// So, with this set, you'll get output like so:
//
// // leading comment for field
// repeated string names = 1;
// // trailing comment
//
// If left false, the printer will try to emit trailing comments on the same
// line instead:
//
// // leading comment for field
// repeated string names = 1; // trailing comment
//
// If the trailing comment has more than one line, it will automatically be
// forced to the next line. Also, elements that end with "}" instead of ";"
// will have trailing comments rendered on the subsequent line.
TrailingCommentsOnSeparateLine bool
// If true, the printed output will eschew any blank lines, which otherwise
// appear between descriptor elements and comment blocks. Note that if
// detached comments are being printed, this will cause them to be merged
// into the subsequent leading comments. Similarly, any element trailing
// comments will be merged into the subsequent leading comments.
Compact bool
// If true, all references to messages, extensions, and enums (such as in
// options, field types, and method request and response types) will be
// fully-qualified. When left unset, the referenced elements will contain
// only as much qualifier as is required.
//
// For example, if a message is in the same package as the reference, the
// simple name can be used. If a message shares some context with the
// reference, only the unshared context needs to be included. For example:
//
// message Foo {
// message Bar {
// enum Baz {
// ZERO = 0;
// ONE = 1;
// }
// }
//
// // This field shares some context as the enum it references: they are
// // both inside of the namespace Foo:
// // field is "Foo.my_baz"
// // enum is "Foo.Bar.Baz"
// // So we only need to qualify the reference with the context that they
// // do NOT have in common:
// Bar.Baz my_baz = 1;
// }
//
// When printing fully-qualified names, they will be preceded by a dot, to
// avoid any ambiguity that they might be relative vs. fully-qualified.
ForceFullyQualifiedNames bool
}
// CommentType is a kind of comments in a proto source file. This can be used
// as a bitmask.
type CommentType int
const (
// CommentsDetached refers to comments that are not "attached" to any
// source element. They are attributed to the subsequent element in the
// file as "detached" comments.
CommentsDetached CommentType = 1 << iota
// CommentsTrailing refers to a comment block immediately following an
// element in the source file. If another element immediately follows
// the trailing comment, it is instead considered a leading comment for
// that subsequent element.
CommentsTrailing
// CommentsLeading refers to a comment block immediately preceding an
// element in the source file. For high-level elements (those that have
// their own descriptor), these are used as doc comments for that element.
CommentsLeading
// CommentsTokens refers to any comments (leading, trailing, or detached)
// on low-level elements in the file. "High-level" elements have their own
// descriptors, e.g. messages, enums, fields, services, and methods. But
// comments can appear anywhere (such as around identifiers and keywords,
// sprinkled inside the declarations of a high-level element). This class
// of comments are for those extra comments sprinkled into the file.
CommentsTokens
// CommentsNonDoc refers to comments that are *not* doc comments. This is a
// bitwise union of everything other than CommentsLeading. If you configure
// a printer to omit this, only doc comments on descriptor elements will be
// included in the printed output.
CommentsNonDoc = CommentsDetached | CommentsTrailing | CommentsTokens
// CommentsAll indicates all kinds of comments. If you configure a printer
// to omit this, no comments will appear in the printed output, even if the
// input descriptors had source info and comments.
CommentsAll = -1
)
// PrintProtoFiles prints all of the given file descriptors. The given open
// function is given a file name and is responsible for creating the outputs and
// returning the corresponding writer.
func (p *Printer) PrintProtoFiles(fds []*desc.FileDescriptor, open func(name string) (io.WriteCloser, error)) error {
for _, fd := range fds {
w, err := open(fd.GetName())
if err != nil {
return fmt.Errorf("failed to open %s: %v", fd.GetName(), err)
}
err = func() error {
defer w.Close()
return p.PrintProtoFile(fd, w)
}()
if err != nil {
return fmt.Errorf("failed to write %s: %v", fd.GetName(), err)
}
}
return nil
}
// PrintProtosToFileSystem prints all of the given file descriptors to files in
// the given directory. If file names in the given descriptors include path
// information, they will be relative to the given root.
func (p *Printer) PrintProtosToFileSystem(fds []*desc.FileDescriptor, rootDir string) error {
return p.PrintProtoFiles(fds, func(name string) (io.WriteCloser, error) {
fullPath := filepath.Join(rootDir, name)
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, err
}
return os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
})
}
// pkg represents a package name
type pkg string
// imp represents an imported file name
type imp string
// ident represents an identifier
type ident string
// option represents a resolved descriptor option
type option struct {
name string
val interface{}
}
// reservedRange represents a reserved range from a message or enum
type reservedRange struct {
start, end int32
}
// PrintProtoFile prints the given single file descriptor to the given writer.
func (p *Printer) PrintProtoFile(fd *desc.FileDescriptor, out io.Writer) error {
return p.printProto(fd, out)
}
// PrintProto prints the given descriptor and returns the resulting string. This
// can be used to print proto files, but it can also be used to get the proto
// "source form" for any kind of descriptor, which can be a more user-friendly
// way to present descriptors that are intended for human consumption.
func (p *Printer) PrintProtoToString(dsc desc.Descriptor) (string, error) {
var buf bytes.Buffer
if err := p.printProto(dsc, &buf); err != nil {
return "", err
}
return buf.String(), nil
}
func (p *Printer) printProto(dsc desc.Descriptor, out io.Writer) error {
w := newWriter(out)
if p.Indent == "" {
// default indent to two spaces
p.Indent = " "
} else {
// indent must be all spaces or tabs, so convert other chars to spaces
ind := make([]rune, 0, len(p.Indent))
for _, r := range p.Indent {
if r == '\t' {
ind = append(ind, r)
} else {
ind = append(ind, ' ')
}
}
p.Indent = string(ind)
}
if p.OmitDetachedComments {
p.OmitComments |= CommentsDetached
}
er := dynamic.ExtensionRegistry{}
er.AddExtensionsFromFileRecursively(dsc.GetFile())
mf := dynamic.NewMessageFactoryWithExtensionRegistry(&er)
fdp := dsc.GetFile().AsFileDescriptorProto()
sourceInfo := internal.CreateSourceInfoMap(fdp)
extendOptionLocations(sourceInfo, fdp.GetSourceCodeInfo().GetLocation())
path := findElement(dsc)
switch d := dsc.(type) {
case *desc.FileDescriptor:
p.printFile(d, mf, w, sourceInfo)
case *desc.MessageDescriptor:
p.printMessage(d, mf, w, sourceInfo, path, 0)
case *desc.FieldDescriptor:
var scope string
if md, ok := d.GetParent().(*desc.MessageDescriptor); ok {
scope = md.GetFullyQualifiedName()
} else {
scope = d.GetFile().GetPackage()
}
if d.IsExtension() {
fmt.Fprint(w, "extend ")
extNameSi := sourceInfo.Get(append(path, internal.Field_extendeeTag))
p.printElementString(extNameSi, w, 0, p.qualifyName(d.GetFile().GetPackage(), scope, d.GetOwner().GetFullyQualifiedName()))
fmt.Fprintln(w, "{")
p.printField(d, mf, w, sourceInfo, path, scope, 1)
fmt.Fprintln(w, "}")
} else {
p.printField(d, mf, w, sourceInfo, path, scope, 0)
}
case *desc.OneOfDescriptor:
md := d.GetOwner()
elements := elementAddrs{dsc: md}
for i := range md.GetFields() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_fieldsTag, elementIndex: i})
}
p.printOneOf(d, elements, 0, mf, w, sourceInfo, path[:len(path)-1], 0, path[len(path)-1])
case *desc.EnumDescriptor:
p.printEnum(d, mf, w, sourceInfo, path, 0)
case *desc.EnumValueDescriptor:
p.printEnumValue(d, mf, w, sourceInfo, path, 0)
case *desc.ServiceDescriptor:
p.printService(d, mf, w, sourceInfo, path, 0)
case *desc.MethodDescriptor:
p.printMethod(d, mf, w, sourceInfo, path, 0)
}
return w.err
}
func findElement(dsc desc.Descriptor) []int32 {
if dsc.GetParent() == nil {
return nil
}
path := findElement(dsc.GetParent())
switch d := dsc.(type) {
case *desc.MessageDescriptor:
if pm, ok := d.GetParent().(*desc.MessageDescriptor); ok {
return append(path, internal.Message_nestedMessagesTag, getMessageIndex(d, pm.GetNestedMessageTypes()))
}
return append(path, internal.File_messagesTag, getMessageIndex(d, d.GetFile().GetMessageTypes()))
case *desc.FieldDescriptor:
if d.IsExtension() {
if pm, ok := d.GetParent().(*desc.MessageDescriptor); ok {
return append(path, internal.Message_extensionsTag, getFieldIndex(d, pm.GetNestedExtensions()))
}
return append(path, internal.File_extensionsTag, getFieldIndex(d, d.GetFile().GetExtensions()))
}
return append(path, internal.Message_fieldsTag, getFieldIndex(d, d.GetOwner().GetFields()))
case *desc.OneOfDescriptor:
return append(path, internal.Message_oneOfsTag, getOneOfIndex(d, d.GetOwner().GetOneOfs()))
case *desc.EnumDescriptor:
if pm, ok := d.GetParent().(*desc.MessageDescriptor); ok {
return append(path, internal.Message_enumsTag, getEnumIndex(d, pm.GetNestedEnumTypes()))
}
return append(path, internal.File_enumsTag, getEnumIndex(d, d.GetFile().GetEnumTypes()))
case *desc.EnumValueDescriptor:
return append(path, internal.Enum_valuesTag, getEnumValueIndex(d, d.GetEnum().GetValues()))
case *desc.ServiceDescriptor:
return append(path, internal.File_servicesTag, getServiceIndex(d, d.GetFile().GetServices()))
case *desc.MethodDescriptor:
return append(path, internal.Service_methodsTag, getMethodIndex(d, d.GetService().GetMethods()))
default:
panic(fmt.Sprintf("unexpected descriptor type: %T", dsc))
}
}
func getMessageIndex(md *desc.MessageDescriptor, list []*desc.MessageDescriptor) int32 {
for i := range list {
if md == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of message %s", md.GetFullyQualifiedName()))
}
func getFieldIndex(fd *desc.FieldDescriptor, list []*desc.FieldDescriptor) int32 {
for i := range list {
if fd == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of field %s", fd.GetFullyQualifiedName()))
}
func getOneOfIndex(ood *desc.OneOfDescriptor, list []*desc.OneOfDescriptor) int32 {
for i := range list {
if ood == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of oneof %s", ood.GetFullyQualifiedName()))
}
func getEnumIndex(ed *desc.EnumDescriptor, list []*desc.EnumDescriptor) int32 {
for i := range list {
if ed == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of enum %s", ed.GetFullyQualifiedName()))
}
func getEnumValueIndex(evd *desc.EnumValueDescriptor, list []*desc.EnumValueDescriptor) int32 {
for i := range list {
if evd == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of enum value %s", evd.GetFullyQualifiedName()))
}
func getServiceIndex(sd *desc.ServiceDescriptor, list []*desc.ServiceDescriptor) int32 {
for i := range list {
if sd == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of service %s", sd.GetFullyQualifiedName()))
}
func getMethodIndex(mtd *desc.MethodDescriptor, list []*desc.MethodDescriptor) int32 {
for i := range list {
if mtd == list[i] {
return int32(i)
}
}
panic(fmt.Sprintf("unable to determine index of method %s", mtd.GetFullyQualifiedName()))
}
func (p *Printer) newLine(w io.Writer) {
if !p.Compact {
fmt.Fprintln(w)
}
}
func (p *Printer) printFile(fd *desc.FileDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap) {
opts, err := p.extractOptions(fd, fd.GetOptions(), mf)
if err != nil {
return
}
fdp := fd.AsFileDescriptorProto()
path := make([]int32, 1)
path[0] = internal.File_packageTag
sourceInfo.PutIfAbsent(append(path, 0), sourceInfo.Get(path))
path[0] = internal.File_syntaxTag
si := sourceInfo.Get(path)
p.printElement(false, si, w, 0, func(w *writer) {
syn := fdp.GetSyntax()
if syn == "" {
syn = "proto2"
}
fmt.Fprintf(w, "syntax = %q;", syn)
})
p.newLine(w)
skip := map[interface{}]bool{}
elements := elementAddrs{dsc: fd, opts: opts}
if fdp.Package != nil {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_packageTag, elementIndex: 0, order: -3})
}
for i := range fd.AsFileDescriptorProto().GetDependency() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_dependencyTag, elementIndex: i, order: -2})
}
elements.addrs = append(elements.addrs, optionsAsElementAddrs(internal.File_optionsTag, -1, opts)...)
for i := range fd.GetMessageTypes() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_messagesTag, elementIndex: i})
}
for i := range fd.GetEnumTypes() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_enumsTag, elementIndex: i})
}
for i := range fd.GetServices() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_servicesTag, elementIndex: i})
}
exts := p.computeExtensions(sourceInfo, fd.GetExtensions(), []int32{internal.File_extensionsTag})
for i, extd := range fd.GetExtensions() {
if extd.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP {
// we don't emit nested messages for groups since
// they get special treatment
skip[extd.GetMessageType()] = true
}
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.File_extensionsTag, elementIndex: i})
}
p.sort(elements, sourceInfo, nil)
pkgName := fd.GetPackage()
for i, el := range elements.addrs {
d := elements.at(el)
// skip[d] will panic if d is a slice (which it could be for []option),
// so just ignore it since we don't try to skip options
if reflect.TypeOf(d).Kind() != reflect.Slice && skip[d] {
// skip this element
continue
}
if i > 0 {
p.newLine(w)
}
path = []int32{el.elementType, int32(el.elementIndex)}
switch d := d.(type) {
case pkg:
si := sourceInfo.Get(path)
p.printElement(false, si, w, 0, func(w *writer) {
fmt.Fprintf(w, "package %s;", d)
})
case imp:
si := sourceInfo.Get(path)
p.printElement(false, si, w, 0, func(w *writer) {
fmt.Fprintf(w, "import %q;", d)
})
case []option:
p.printOptionsLong(d, w, sourceInfo, path, 0)
case *desc.MessageDescriptor:
p.printMessage(d, mf, w, sourceInfo, path, 0)
case *desc.EnumDescriptor:
p.printEnum(d, mf, w, sourceInfo, path, 0)
case *desc.ServiceDescriptor:
p.printService(d, mf, w, sourceInfo, path, 0)
case *desc.FieldDescriptor:
extDecl := exts[d]
p.printExtensions(extDecl, exts, elements, i, mf, w, sourceInfo, nil, internal.File_extensionsTag, pkgName, pkgName, 0)
// we printed all extensions in the group, so we can skip the others
for _, fld := range extDecl.fields {
skip[fld] = true
}
}
}
}
func findExtSi(fieldSi *descriptor.SourceCodeInfo_Location, extSis []*descriptor.SourceCodeInfo_Location) *descriptor.SourceCodeInfo_Location {
if len(fieldSi.GetSpan()) == 0 {
return nil
}
for _, extSi := range extSis {
if isSpanWithin(fieldSi.Span, extSi.Span) {
return extSi
}
}
return nil
}
func isSpanWithin(span, enclosing []int32) bool {
start := enclosing[0]
var end int32
if len(enclosing) == 3 {
end = enclosing[0]
} else {
end = enclosing[2]
}
if span[0] < start || span[0] > end {
return false
}
if span[0] == start {
return span[1] >= enclosing[1]
} else if span[0] == end {
return span[1] <= enclosing[len(enclosing)-1]
}
return true
}
type extensionDecl struct {
extendee string
sourceInfo *descriptor.SourceCodeInfo_Location
fields []*desc.FieldDescriptor
}
type extensions map[*desc.FieldDescriptor]*extensionDecl
func (p *Printer) computeExtensions(sourceInfo internal.SourceInfoMap, exts []*desc.FieldDescriptor, path []int32) extensions {
extsMap := map[string]map[*descriptor.SourceCodeInfo_Location]*extensionDecl{}
extSis := sourceInfo.GetAll(path)
for _, extd := range exts {
name := extd.GetOwner().GetFullyQualifiedName()
extSi := findExtSi(extd.GetSourceInfo(), extSis)
extsBySi := extsMap[name]
if extsBySi == nil {
extsBySi = map[*descriptor.SourceCodeInfo_Location]*extensionDecl{}
extsMap[name] = extsBySi
}
extDecl := extsBySi[extSi]
if extDecl == nil {
extDecl = &extensionDecl{
sourceInfo: extSi,
extendee: name,
}
extsBySi[extSi] = extDecl
}
extDecl.fields = append(extDecl.fields, extd)
}
ret := extensions{}
for _, extsBySi := range extsMap {
for _, extDecl := range extsBySi {
for _, extd := range extDecl.fields {
ret[extd] = extDecl
}
}
}
return ret
}
func (p *Printer) sort(elements elementAddrs, sourceInfo internal.SourceInfoMap, path []int32) {
if p.SortElements {
// canonical sorted order
sort.Stable(elements)
} else {
// use source order (per location information in SourceCodeInfo); or
// if that isn't present use declaration order, but grouped by type
sort.Stable(elementSrcOrder{
elementAddrs: elements,
sourceInfo: sourceInfo,
prefix: path,
})
}
}
func (p *Printer) qualifyName(pkg, scope string, fqn string) string {
if p.ForceFullyQualifiedNames {
// forcing fully-qualified names; make sure to include preceding dot
if fqn[0] == '.' {
return fqn
}
return fmt.Sprintf(".%s", fqn)
}
// compute relative name (so no leading dot)
if fqn[0] == '.' {
fqn = fqn[1:]
}
if len(scope) > 0 && scope[len(scope)-1] != '.' {
scope = scope + "."
}
for scope != "" {
if strings.HasPrefix(fqn, scope) {
return fqn[len(scope):]
}
if scope == pkg+"." {
break
}
pos := strings.LastIndex(scope[:len(scope)-1], ".")
scope = scope[:pos+1]
}
return fqn
}
func (p *Printer) typeString(fld *desc.FieldDescriptor, scope string) string {
if fld.IsMap() {
return fmt.Sprintf("map<%s, %s>", p.typeString(fld.GetMapKeyType(), scope), p.typeString(fld.GetMapValueType(), scope))
}
switch fld.GetType() {
case descriptor.FieldDescriptorProto_TYPE_INT32:
return "int32"
case descriptor.FieldDescriptorProto_TYPE_INT64:
return "int64"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
return "uint32"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
return "uint64"
case descriptor.FieldDescriptorProto_TYPE_SINT32:
return "sint32"
case descriptor.FieldDescriptorProto_TYPE_SINT64:
return "sint64"
case descriptor.FieldDescriptorProto_TYPE_FIXED32:
return "fixed32"
case descriptor.FieldDescriptorProto_TYPE_FIXED64:
return "fixed64"
case descriptor.FieldDescriptorProto_TYPE_SFIXED32:
return "sfixed32"
case descriptor.FieldDescriptorProto_TYPE_SFIXED64:
return "sfixed64"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
return "float"
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
return "double"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
return "bool"
case descriptor.FieldDescriptorProto_TYPE_STRING:
return "string"
case descriptor.FieldDescriptorProto_TYPE_BYTES:
return "bytes"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return p.qualifyName(fld.GetFile().GetPackage(), scope, fld.GetEnumType().GetFullyQualifiedName())
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
return p.qualifyName(fld.GetFile().GetPackage(), scope, fld.GetMessageType().GetFullyQualifiedName())
case descriptor.FieldDescriptorProto_TYPE_GROUP:
return fld.GetMessageType().GetName()
}
panic(fmt.Sprintf("invalid type: %v", fld.GetType()))
}
func (p *Printer) printMessage(md *desc.MessageDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
si := sourceInfo.Get(path)
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
fmt.Fprint(w, "message ")
nameSi := sourceInfo.Get(append(path, internal.Message_nameTag))
p.printElementString(nameSi, w, indent, md.GetName())
fmt.Fprintln(w, "{")
p.printMessageBody(md, mf, w, sourceInfo, path, indent+1)
p.indent(w, indent)
fmt.Fprintln(w, "}")
})
}
func (p *Printer) printMessageBody(md *desc.MessageDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
opts, err := p.extractOptions(md, md.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
skip := map[interface{}]bool{}
elements := elementAddrs{dsc: md, opts: opts}
elements.addrs = append(elements.addrs, optionsAsElementAddrs(internal.Message_optionsTag, -1, opts)...)
for i := range md.AsDescriptorProto().GetReservedRange() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_reservedRangeTag, elementIndex: i})
}
for i := range md.AsDescriptorProto().GetReservedName() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_reservedNameTag, elementIndex: i})
}
for i := range md.AsDescriptorProto().GetExtensionRange() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_extensionRangeTag, elementIndex: i})
}
for i, fld := range md.GetFields() {
if fld.IsMap() || fld.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP {
// we don't emit nested messages for map types or groups since
// they get special treatment
skip[fld.GetMessageType()] = true
}
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_fieldsTag, elementIndex: i})
}
for i := range md.GetNestedMessageTypes() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_nestedMessagesTag, elementIndex: i})
}
for i := range md.GetNestedEnumTypes() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_enumsTag, elementIndex: i})
}
exts := p.computeExtensions(sourceInfo, md.GetNestedExtensions(), append(path, internal.Message_extensionsTag))
for i, extd := range md.GetNestedExtensions() {
if extd.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP {
// we don't emit nested messages for groups since
// they get special treatment
skip[extd.GetMessageType()] = true
}
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Message_extensionsTag, elementIndex: i})
}
p.sort(elements, sourceInfo, path)
pkg := md.GetFile().GetPackage()
scope := md.GetFullyQualifiedName()
for i, el := range elements.addrs {
d := elements.at(el)
// skip[d] will panic if d is a slice (which it could be for []option),
// so just ignore it since we don't try to skip options
if reflect.TypeOf(d).Kind() != reflect.Slice && skip[d] {
// skip this element
continue
}
if i > 0 {
p.newLine(w)
}
childPath := append(path, el.elementType, int32(el.elementIndex))
switch d := d.(type) {
case []option:
p.printOptionsLong(d, w, sourceInfo, childPath, indent)
case *desc.FieldDescriptor:
if d.IsExtension() {
extDecl := exts[d]
p.printExtensions(extDecl, exts, elements, i, mf, w, sourceInfo, path, internal.Message_extensionsTag, pkg, scope, indent)
// we printed all extensions in the group, so we can skip the others
for _, fld := range extDecl.fields {
skip[fld] = true
}
} else {
ood := d.GetOneOf()
if ood == nil {
p.printField(d, mf, w, sourceInfo, childPath, scope, indent)
} else {
// print the one-of, including all of its fields
p.printOneOf(ood, elements, i, mf, w, sourceInfo, path, indent, d.AsFieldDescriptorProto().GetOneofIndex())
for _, fld := range ood.GetChoices() {
skip[fld] = true
}
}
}
case *desc.MessageDescriptor:
p.printMessage(d, mf, w, sourceInfo, childPath, indent)
case *desc.EnumDescriptor:
p.printEnum(d, mf, w, sourceInfo, childPath, indent)
case *descriptor.DescriptorProto_ExtensionRange:
// collapse ranges into a single "extensions" block
ranges := []*descriptor.DescriptorProto_ExtensionRange{d}
addrs := []elementAddr{el}
for idx := i + 1; idx < len(elements.addrs); idx++ {
elnext := elements.addrs[idx]
if elnext.elementType != el.elementType {
break
}
extr := elements.at(elnext).(*descriptor.DescriptorProto_ExtensionRange)
if !areEqual(d.Options, extr.Options, mf) {
break
}
ranges = append(ranges, extr)
addrs = append(addrs, elnext)
skip[extr] = true
}
p.printExtensionRanges(md, ranges, addrs, mf, w, sourceInfo, path, indent)
case reservedRange:
// collapse reserved ranges into a single "reserved" block
ranges := []reservedRange{d}
addrs := []elementAddr{el}
for idx := i + 1; idx < len(elements.addrs); idx++ {
elnext := elements.addrs[idx]
if elnext.elementType != el.elementType {
break
}
rr := elements.at(elnext).(reservedRange)
ranges = append(ranges, rr)
addrs = append(addrs, elnext)
skip[rr] = true
}
p.printReservedRanges(ranges, false, addrs, w, sourceInfo, path, indent)
case string: // reserved name
// collapse reserved names into a single "reserved" block
names := []string{d}
addrs := []elementAddr{el}
for idx := i + 1; idx < len(elements.addrs); idx++ {
elnext := elements.addrs[idx]
if elnext.elementType != el.elementType {
break
}
rn := elements.at(elnext).(string)
names = append(names, rn)
addrs = append(addrs, elnext)
skip[rn] = true
}
p.printReservedNames(names, addrs, w, sourceInfo, path, indent)
}
}
}
func areEqual(a, b proto.Message, mf *dynamic.MessageFactory) bool {
// proto.Equal doesn't handle unknown extensions very well :(
// so we convert to a dynamic message (which should know about all extensions via
// extension registry) and then compare
return dynamic.MessagesEqual(asDynamicIfPossible(a, mf), asDynamicIfPossible(b, mf))
}
func asDynamicIfPossible(msg proto.Message, mf *dynamic.MessageFactory) proto.Message {
if dm, ok := msg.(*dynamic.Message); ok {
return dm
} else {
md, err := desc.LoadMessageDescriptorForMessage(msg)
if err == nil {
dm := mf.NewDynamicMessage(md)
if dm.ConvertFrom(msg) == nil {
return dm
}
}
}
return msg
}
func (p *Printer) printField(fld *desc.FieldDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, scope string, indent int) {
var groupPath []int32
var si *descriptor.SourceCodeInfo_Location
if isGroup(fld) {
// compute path to group message type
groupPath = make([]int32, len(path)-2)
copy(groupPath, path)
var candidates []*desc.MessageDescriptor
var parentTag int32
switch parent := fld.GetParent().(type) {
case *desc.MessageDescriptor:
// group in a message
candidates = parent.GetNestedMessageTypes()
parentTag = internal.Message_nestedMessagesTag
case *desc.FileDescriptor:
// group that is a top-level extension
candidates = parent.GetMessageTypes()
parentTag = internal.File_messagesTag
}
var groupMsgIndex int32
for i, nmd := range candidates {
if nmd == fld.GetMessageType() {
// found it
groupMsgIndex = int32(i)
break
}
}
groupPath = append(groupPath, parentTag, groupMsgIndex)
// the group message is where the field's comments and position are stored
si = sourceInfo.Get(groupPath)
} else {
si = sourceInfo.Get(path)
}
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
if shouldEmitLabel(fld) {
locSi := sourceInfo.Get(append(path, internal.Field_labelTag))
p.printElementString(locSi, w, indent, labelString(fld.GetLabel()))
}
if isGroup(fld) {
fmt.Fprint(w, "group ")
typeSi := sourceInfo.Get(append(path, internal.Field_typeTag))
p.printElementString(typeSi, w, indent, p.typeString(fld, scope))
fmt.Fprint(w, "= ")
numSi := sourceInfo.Get(append(path, internal.Field_numberTag))
p.printElementString(numSi, w, indent, fmt.Sprintf("%d", fld.GetNumber()))
fmt.Fprintln(w, "{")
p.printMessageBody(fld.GetMessageType(), mf, w, sourceInfo, groupPath, indent+1)
p.indent(w, indent)
fmt.Fprintln(w, "}")
} else {
typeSi := sourceInfo.Get(append(path, internal.Field_typeTag))
p.printElementString(typeSi, w, indent, p.typeString(fld, scope))
nameSi := sourceInfo.Get(append(path, internal.Field_nameTag))
p.printElementString(nameSi, w, indent, fld.GetName())
fmt.Fprint(w, "= ")
numSi := sourceInfo.Get(append(path, internal.Field_numberTag))
p.printElementString(numSi, w, indent, fmt.Sprintf("%d", fld.GetNumber()))
opts, err := p.extractOptions(fld, fld.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
// we use negative values for "extras" keys so they can't collide
// with legit option tags
if !fld.GetFile().IsProto3() && fld.AsFieldDescriptorProto().DefaultValue != nil {
defVal := fld.GetDefaultValue()
if fld.GetEnumType() != nil {
defVal = fld.GetEnumType().FindValueByNumber(defVal.(int32))
}
opts[-internal.Field_defaultTag] = []option{{name: "default", val: defVal}}
}
jsn := fld.AsFieldDescriptorProto().GetJsonName()
if jsn != "" && jsn != internal.JsonName(fld.GetName()) {
opts[-internal.Field_jsonNameTag] = []option{{name: "json_name", val: jsn}}
}
elements := elementAddrs{dsc: fld, opts: opts}
elements.addrs = optionsAsElementAddrs(internal.Field_optionsTag, 0, opts)
p.sort(elements, sourceInfo, path)
p.printOptionElementsShort(elements, w, sourceInfo, path, indent)
fmt.Fprint(w, ";")
}
})
}
func shouldEmitLabel(fld *desc.FieldDescriptor) bool {
return !fld.IsMap() && fld.GetOneOf() == nil && (fld.GetLabel() != descriptor.FieldDescriptorProto_LABEL_OPTIONAL || !fld.GetFile().IsProto3())
}
func labelString(lbl descriptor.FieldDescriptorProto_Label) string {
switch lbl {
case descriptor.FieldDescriptorProto_LABEL_OPTIONAL:
return "optional"
case descriptor.FieldDescriptorProto_LABEL_REQUIRED:
return "required"
case descriptor.FieldDescriptorProto_LABEL_REPEATED:
return "repeated"
}
panic(fmt.Sprintf("invalid label: %v", lbl))
}
func isGroup(fld *desc.FieldDescriptor) bool {
return fld.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP
}
func (p *Printer) printOneOf(ood *desc.OneOfDescriptor, parentElements elementAddrs, startFieldIndex int, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int, ooIndex int32) {
oopath := append(parentPath, internal.Message_oneOfsTag, ooIndex)
oosi := sourceInfo.Get(oopath)
p.printElement(true, oosi, w, indent, func(w *writer) {
p.indent(w, indent)
fmt.Fprint(w, "oneof ")
extNameSi := sourceInfo.Get(append(oopath, internal.OneOf_nameTag))
p.printElementString(extNameSi, w, indent, ood.GetName())
fmt.Fprintln(w, "{")
indent++
opts, err := p.extractOptions(ood, ood.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
elements := elementAddrs{dsc: ood, opts: opts}
elements.addrs = append(elements.addrs, optionsAsElementAddrs(internal.OneOf_optionsTag, -1, opts)...)
count := len(ood.GetChoices())
for idx := startFieldIndex; count > 0 && idx < len(parentElements.addrs); idx++ {
el := parentElements.addrs[idx]
if el.elementType != internal.Message_fieldsTag {
continue
}
if parentElements.at(el).(*desc.FieldDescriptor).GetOneOf() == ood {
// negative tag indicates that this element is actually a sibling, not a child
elements.addrs = append(elements.addrs, elementAddr{elementType: -internal.Message_fieldsTag, elementIndex: el.elementIndex})
count--
}
}
// the fields are already sorted, but we have to re-sort in order to
// interleave the options (in the event that we are using file location
// order and the option locations are interleaved with the fields)
p.sort(elements, sourceInfo, oopath)
scope := ood.GetOwner().GetFullyQualifiedName()
for i, el := range elements.addrs {
if i > 0 {
p.newLine(w)
}
switch d := elements.at(el).(type) {
case []option:
childPath := append(oopath, el.elementType, int32(el.elementIndex))
p.printOptionsLong(d, w, sourceInfo, childPath, indent)
case *desc.FieldDescriptor:
childPath := append(parentPath, -el.elementType, int32(el.elementIndex))
p.printField(d, mf, w, sourceInfo, childPath, scope, indent)
}
}
p.indent(w, indent-1)
fmt.Fprintln(w, "}")
})
}
func (p *Printer) printExtensions(exts *extensionDecl, allExts extensions, parentElements elementAddrs, startFieldIndex int, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, extTag int32, pkg, scope string, indent int) {
path := append(parentPath, extTag)
p.printLeadingComments(exts.sourceInfo, w, indent)
p.indent(w, indent)
fmt.Fprint(w, "extend ")
extNameSi := sourceInfo.Get(append(path, 0, internal.Field_extendeeTag))
p.printElementString(extNameSi, w, indent, p.qualifyName(pkg, scope, exts.extendee))
fmt.Fprintln(w, "{")
count := len(exts.fields)
first := true
for idx := startFieldIndex; count > 0 && idx < len(parentElements.addrs); idx++ {
el := parentElements.addrs[idx]
if el.elementType != extTag {
continue
}
fld := parentElements.at(el).(*desc.FieldDescriptor)
if allExts[fld] == exts {
if first {
first = false
} else {
p.newLine(w)
}
childPath := append(path, int32(el.elementIndex))
p.printField(fld, mf, w, sourceInfo, childPath, scope, indent+1)
count--
}
}
p.indent(w, indent)
fmt.Fprintln(w, "}")
p.printTrailingComments(exts.sourceInfo, w, indent)
if indent >= 0 && !w.newline {
// if we're not printing inline but element did not have trailing newline, add one now
fmt.Fprintln(w)
}
}
func (p *Printer) printExtensionRanges(parent *desc.MessageDescriptor, ranges []*descriptor.DescriptorProto_ExtensionRange, addrs []elementAddr, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int) {
p.indent(w, indent)
fmt.Fprint(w, "extensions ")
var opts *descriptor.ExtensionRangeOptions
var elPath []int32
first := true
for i, extr := range ranges {
if first {
first = false
} else {
fmt.Fprint(w, ", ")
}
opts = extr.Options
el := addrs[i]
elPath = append(parentPath, el.elementType, int32(el.elementIndex))
si := sourceInfo.Get(elPath)
p.printElement(true, si, w, inline(indent), func(w *writer) {
if extr.GetStart() == extr.GetEnd()-1 {
fmt.Fprintf(w, "%d ", extr.GetStart())
} else if extr.GetEnd()-1 == internal.MaxTag {
fmt.Fprintf(w, "%d to max ", extr.GetStart())
} else {
fmt.Fprintf(w, "%d to %d ", extr.GetStart(), extr.GetEnd()-1)
}
})
}
dsc := extensionRange{owner: parent, extRange: ranges[0]}
p.printOptionsShort(dsc, opts, mf, internal.ExtensionRange_optionsTag, w, sourceInfo, elPath, indent)
fmt.Fprintln(w, ";")
}
func (p *Printer) printReservedRanges(ranges []reservedRange, isEnum bool, addrs []elementAddr, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int) {
p.indent(w, indent)
fmt.Fprint(w, "reserved ")
first := true
for i, rr := range ranges {
if first {
first = false
} else {
fmt.Fprint(w, ", ")
}
el := addrs[i]
si := sourceInfo.Get(append(parentPath, el.elementType, int32(el.elementIndex)))
p.printElement(false, si, w, inline(indent), func(w *writer) {
if rr.start == rr.end {
fmt.Fprintf(w, "%d ", rr.start)
} else if (rr.end == internal.MaxTag && !isEnum) ||
(rr.end == math.MaxInt32 && isEnum) {
fmt.Fprintf(w, "%d to max ", rr.start)
} else {
fmt.Fprintf(w, "%d to %d ", rr.start, rr.end)
}
})
}
fmt.Fprintln(w, ";")
}
func (p *Printer) printReservedNames(names []string, addrs []elementAddr, w *writer, sourceInfo internal.SourceInfoMap, parentPath []int32, indent int) {
p.indent(w, indent)
fmt.Fprint(w, "reserved ")
first := true
for i, name := range names {
if first {
first = false
} else {
fmt.Fprint(w, ", ")
}
el := addrs[i]
si := sourceInfo.Get(append(parentPath, el.elementType, int32(el.elementIndex)))
p.printElementString(si, w, indent, quotedString(name))
}
fmt.Fprintln(w, ";")
}
func (p *Printer) printEnum(ed *desc.EnumDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
si := sourceInfo.Get(path)
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
fmt.Fprint(w, "enum ")
nameSi := sourceInfo.Get(append(path, internal.Enum_nameTag))
p.printElementString(nameSi, w, indent, ed.GetName())
fmt.Fprintln(w, "{")
indent++
opts, err := p.extractOptions(ed, ed.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
skip := map[interface{}]bool{}
elements := elementAddrs{dsc: ed, opts: opts}
elements.addrs = append(elements.addrs, optionsAsElementAddrs(internal.Enum_optionsTag, -1, opts)...)
for i := range ed.GetValues() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Enum_valuesTag, elementIndex: i})
}
for i := range ed.AsEnumDescriptorProto().GetReservedRange() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Enum_reservedRangeTag, elementIndex: i})
}
for i := range ed.AsEnumDescriptorProto().GetReservedName() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Enum_reservedNameTag, elementIndex: i})
}
p.sort(elements, sourceInfo, path)
for i, el := range elements.addrs {
d := elements.at(el)
// skip[d] will panic if d is a slice (which it could be for []option),
// so just ignore it since we don't try to skip options
if reflect.TypeOf(d).Kind() != reflect.Slice && skip[d] {
// skip this element
continue
}
if i > 0 {
p.newLine(w)
}
childPath := append(path, el.elementType, int32(el.elementIndex))
switch d := d.(type) {
case []option:
p.printOptionsLong(d, w, sourceInfo, childPath, indent)
case *desc.EnumValueDescriptor:
p.printEnumValue(d, mf, w, sourceInfo, childPath, indent)
case reservedRange:
// collapse reserved ranges into a single "reserved" block
ranges := []reservedRange{d}
addrs := []elementAddr{el}
for idx := i + 1; idx < len(elements.addrs); idx++ {
elnext := elements.addrs[idx]
if elnext.elementType != el.elementType {
break
}
rr := elements.at(elnext).(reservedRange)
ranges = append(ranges, rr)
addrs = append(addrs, elnext)
skip[rr] = true
}
p.printReservedRanges(ranges, true, addrs, w, sourceInfo, path, indent)
case string: // reserved name
// collapse reserved names into a single "reserved" block
names := []string{d}
addrs := []elementAddr{el}
for idx := i + 1; idx < len(elements.addrs); idx++ {
elnext := elements.addrs[idx]
if elnext.elementType != el.elementType {
break
}
rn := elements.at(elnext).(string)
names = append(names, rn)
addrs = append(addrs, elnext)
skip[rn] = true
}
p.printReservedNames(names, addrs, w, sourceInfo, path, indent)
}
}
p.indent(w, indent-1)
fmt.Fprintln(w, "}")
})
}
func (p *Printer) printEnumValue(evd *desc.EnumValueDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
si := sourceInfo.Get(path)
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
nameSi := sourceInfo.Get(append(path, internal.EnumVal_nameTag))
p.printElementString(nameSi, w, indent, evd.GetName())
fmt.Fprint(w, "= ")
numSi := sourceInfo.Get(append(path, internal.EnumVal_numberTag))
p.printElementString(numSi, w, indent, fmt.Sprintf("%d", evd.GetNumber()))
p.printOptionsShort(evd, evd.GetOptions(), mf, internal.EnumVal_optionsTag, w, sourceInfo, path, indent)
fmt.Fprint(w, ";")
})
}
func (p *Printer) printService(sd *desc.ServiceDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
si := sourceInfo.Get(path)
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
fmt.Fprint(w, "service ")
nameSi := sourceInfo.Get(append(path, internal.Service_nameTag))
p.printElementString(nameSi, w, indent, sd.GetName())
fmt.Fprintln(w, "{")
indent++
opts, err := p.extractOptions(sd, sd.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
elements := elementAddrs{dsc: sd, opts: opts}
elements.addrs = append(elements.addrs, optionsAsElementAddrs(internal.Service_optionsTag, -1, opts)...)
for i := range sd.GetMethods() {
elements.addrs = append(elements.addrs, elementAddr{elementType: internal.Service_methodsTag, elementIndex: i})
}
p.sort(elements, sourceInfo, path)
for i, el := range elements.addrs {
if i > 0 {
p.newLine(w)
}
childPath := append(path, el.elementType, int32(el.elementIndex))
switch d := elements.at(el).(type) {
case []option:
p.printOptionsLong(d, w, sourceInfo, childPath, indent)
case *desc.MethodDescriptor:
p.printMethod(d, mf, w, sourceInfo, childPath, indent)
}
}
p.indent(w, indent-1)
fmt.Fprintln(w, "}")
})
}
func (p *Printer) printMethod(mtd *desc.MethodDescriptor, mf *dynamic.MessageFactory, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
si := sourceInfo.Get(path)
pkg := mtd.GetFile().GetPackage()
p.printElement(true, si, w, indent, func(w *writer) {
p.indent(w, indent)
fmt.Fprint(w, "rpc ")
nameSi := sourceInfo.Get(append(path, internal.Method_nameTag))
p.printElementString(nameSi, w, indent, mtd.GetName())
fmt.Fprint(w, "( ")
inSi := sourceInfo.Get(append(path, internal.Method_inputTag))
inName := p.qualifyName(pkg, pkg, mtd.GetInputType().GetFullyQualifiedName())
if mtd.IsClientStreaming() {
inName = "stream " + inName
}
p.printElementString(inSi, w, indent, inName)
fmt.Fprint(w, ") returns ( ")
outSi := sourceInfo.Get(append(path, internal.Method_outputTag))
outName := p.qualifyName(pkg, pkg, mtd.GetOutputType().GetFullyQualifiedName())
if mtd.IsServerStreaming() {
outName = "stream " + outName
}
p.printElementString(outSi, w, indent, outName)
fmt.Fprint(w, ") ")
opts, err := p.extractOptions(mtd, mtd.GetOptions(), mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
if len(opts) > 0 {
fmt.Fprintln(w, "{")
indent++
elements := elementAddrs{dsc: mtd, opts: opts}
elements.addrs = optionsAsElementAddrs(internal.Method_optionsTag, 0, opts)
p.sort(elements, sourceInfo, path)
path = append(path, internal.Method_optionsTag)
for i, addr := range elements.addrs {
if i > 0 {
p.newLine(w)
}
o := elements.at(addr).([]option)
p.printOptionsLong(o, w, sourceInfo, path, indent)
}
p.indent(w, indent-1)
fmt.Fprintln(w, "}")
} else {
fmt.Fprint(w, ";")
}
})
}
func (p *Printer) printOptionsLong(opts []option, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
p.printOptions(opts, w, indent,
func(i int32) *descriptor.SourceCodeInfo_Location {
return sourceInfo.Get(append(path, i))
},
func(w *writer, indent int, opt option) {
p.indent(w, indent)
fmt.Fprint(w, "option ")
p.printOption(opt.name, opt.val, w, indent)
fmt.Fprint(w, ";")
})
}
func (p *Printer) printOptionsShort(dsc interface{}, optsMsg proto.Message, mf *dynamic.MessageFactory, optsTag int32, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
d, ok := dsc.(desc.Descriptor)
if !ok {
d = dsc.(extensionRange).owner
}
opts, err := p.extractOptions(d, optsMsg, mf)
if err != nil {
if w.err == nil {
w.err = err
}
return
}
elements := elementAddrs{dsc: dsc, opts: opts}
elements.addrs = optionsAsElementAddrs(optsTag, 0, opts)
p.sort(elements, sourceInfo, path)
p.printOptionElementsShort(elements, w, sourceInfo, path, indent)
}
func (p *Printer) printOptionElementsShort(addrs elementAddrs, w *writer, sourceInfo internal.SourceInfoMap, path []int32, indent int) {
if len(addrs.addrs) == 0 {
return
}
first := true
fmt.Fprint(w, "[")
for _, addr := range addrs.addrs {
opts := addrs.at(addr).([]option)
var childPath []int32
if addr.elementIndex < 0 {
// pseudo-option
childPath = append(path, int32(-addr.elementIndex))
} else {
childPath = append(path, addr.elementType, int32(addr.elementIndex))
}
p.printOptions(opts, w, inline(indent),
func(i int32) *descriptor.SourceCodeInfo_Location {
p := childPath
if addr.elementIndex >= 0 {
p = append(p, i)
}
return sourceInfo.Get(p)
},
func(w *writer, indent int, opt option) {
if first {
first = false
} else {
fmt.Fprint(w, ", ")
}
p.printOption(opt.name, opt.val, w, indent)
fmt.Fprint(w, " ") // trailing space
})
}
fmt.Fprint(w, "]")
}
func (p *Printer) printOptions(opts []option, w *writer, indent int, siFetch func(i int32) *descriptor.SourceCodeInfo_Location, fn func(w *writer, indent int, opt option)) {
for i, opt := range opts {
si := siFetch(int32(i))
p.printElement(false, si, w, indent, func(w *writer) {
fn(w, indent, opt)
})
}
}
func inline(indent int) int {
if indent < 0 {
// already inlined
return indent
}
// negative indent means inline; indent 2 stops further in case value wraps
return -indent - 2
}
func sortKeys(m map[interface{}]interface{}) []interface{} {
res := make(sortedKeys, len(m))
i := 0
for k := range m {
res[i] = k
i++
}
sort.Sort(res)
return ([]interface{})(res)
}
type sortedKeys []interface{}
func (k sortedKeys) Len() int {
return len(k)
}
func (k sortedKeys) Swap(i, j int) {
k[i], k[j] = k[j], k[i]
}
func (k sortedKeys) Less(i, j int) bool {
switch i := k[i].(type) {
case int32:
return i < k[j].(int32)
case uint32:
return i < k[j].(uint32)
case int64:
return i < k[j].(int64)
case uint64:
return i < k[j].(uint64)
case string:
return i < k[j].(string)
case bool:
return !i && k[j].(bool)
default:
panic(fmt.Sprintf("invalid type for map key: %T", i))
}
}
func (p *Printer) printOption(name string, optVal interface{}, w *writer, indent int) {
fmt.Fprintf(w, "%s = ", name)
switch optVal := optVal.(type) {
case int32, uint32, int64, uint64:
fmt.Fprintf(w, "%d", optVal)
case float32, float64:
fmt.Fprintf(w, "%f", optVal)
case string:
fmt.Fprintf(w, "%s", quotedString(optVal))
case []byte:
fmt.Fprintf(w, "%s", quotedString(string(optVal)))
case bool:
fmt.Fprintf(w, "%v", optVal)
case ident:
fmt.Fprintf(w, "%s", optVal)
case *desc.EnumValueDescriptor:
fmt.Fprintf(w, "%s", optVal.GetName())
case proto.Message:
// TODO: if value is too long, marshal to text format with indentation to
// make output prettier (also requires correctly indenting subsequent lines)
// TODO: alternate approach so we can apply p.ForceFullyQualifiedNames
// inside the resulting value?
fmt.Fprintf(w, "{ %s }", proto.CompactTextString(optVal))
default:
panic(fmt.Sprintf("unknown type of value %T for field %s", optVal, name))
}
}
type edgeKind int
const (
edgeKindOption edgeKind = iota
edgeKindFile
edgeKindMessage
edgeKindField
edgeKindOneOf
edgeKindExtensionRange
edgeKindEnum
edgeKindEnumVal
edgeKindService
edgeKindMethod
)
// edges in simple state machine for matching options paths
// whose prefix should be included in source info to handle
// the way options are printed (which cannot always include
// the full path from original source)
var edges = map[edgeKind]map[int32]edgeKind{
edgeKindFile: {
internal.File_optionsTag: edgeKindOption,
internal.File_messagesTag: edgeKindMessage,
internal.File_enumsTag: edgeKindEnum,
internal.File_extensionsTag: edgeKindField,
internal.File_servicesTag: edgeKindService,
},
edgeKindMessage: {
internal.Message_optionsTag: edgeKindOption,
internal.Message_fieldsTag: edgeKindField,
internal.Message_oneOfsTag: edgeKindOneOf,
internal.Message_nestedMessagesTag: edgeKindMessage,
internal.Message_enumsTag: edgeKindEnum,
internal.Message_extensionsTag: edgeKindField,
internal.Message_extensionRangeTag: edgeKindExtensionRange,
// TODO: reserved range tag
},
edgeKindField: {
internal.Field_optionsTag: edgeKindOption,
},
edgeKindOneOf: {
internal.OneOf_optionsTag: edgeKindOption,
},
edgeKindExtensionRange: {
internal.ExtensionRange_optionsTag: edgeKindOption,
},
edgeKindEnum: {
internal.Enum_optionsTag: edgeKindOption,
internal.Enum_valuesTag: edgeKindEnumVal,
},
edgeKindEnumVal: {
internal.EnumVal_optionsTag: edgeKindOption,
},
edgeKindService: {
internal.Service_optionsTag: edgeKindOption,
internal.Service_methodsTag: edgeKindMethod,
},
edgeKindMethod: {
internal.Method_optionsTag: edgeKindOption,
},
}
func extendOptionLocations(sc internal.SourceInfoMap, locs []*descriptor.SourceCodeInfo_Location) {
// we iterate in the order that locations appear in descriptor
// for determinism (if we ranged over the map, order and thus
// potentially results are non-deterministic)
for _, loc := range locs {
allowed := edges[edgeKindFile]
for i := 0; i+1 < len(loc.Path); i += 2 {
nextKind, ok := allowed[loc.Path[i]]
if !ok {
break
}
if nextKind == edgeKindOption {
// We've found an option entry. This could be arbitrarily
// deep (for options that nested messages) or it could end
// abruptly (for non-repeated fields). But we need a path
// that is exactly the path-so-far plus two: the option tag
// and an optional index for repeated option fields (zero
// for non-repeated option fields). This is used for
// querying source info when printing options.
// for sorting elements
newPath := make([]int32, i+3)
copy(newPath, loc.Path)
sc.PutIfAbsent(newPath, loc)
// we do another path of path-so-far plus two, but with
// explicit zero index -- just in case this actual path has
// an extra path element, but it's not an index (e.g the
// option field is not repeated, but the source info we are
// looking at indicates a tag of a nested field)
newPath[len(newPath)-1] = 0
sc.PutIfAbsent(newPath, loc)
// finally, we need the path-so-far plus one, just the option
// tag, for sorting option groups
newPath = newPath[:len(newPath)-1]
sc.PutIfAbsent(newPath, loc)
break
} else {
allowed = edges[nextKind]
}
}
}
}
func (p *Printer) extractOptions(dsc desc.Descriptor, opts proto.Message, mf *dynamic.MessageFactory) (map[int32][]option, error) {
md, err := desc.LoadMessageDescriptorForMessage(opts)
if err != nil {
return nil, err
}
dm := mf.NewDynamicMessage(md)
if err = dm.ConvertFrom(opts); err != nil {
return nil, fmt.Errorf("failed convert %s to dynamic message: %v", md.GetFullyQualifiedName(), err)
}
pkg := dsc.GetFile().GetPackage()
var scope string
if _, ok := dsc.(*desc.FileDescriptor); ok {
scope = pkg
} else {
scope = dsc.GetFullyQualifiedName()
}
options := map[int32][]option{}
var uninterpreted []interface{}
for _, fldset := range [][]*desc.FieldDescriptor{md.GetFields(), mf.GetExtensionRegistry().AllExtensionsForType(md.GetFullyQualifiedName())} {
for _, fld := range fldset {
if dm.HasField(fld) {
val := dm.GetField(fld)
var opts []option
var name string
if fld.IsExtension() {
name = fmt.Sprintf("(%s)", p.qualifyName(pkg, scope, fld.GetFullyQualifiedName()))
} else {
name = fld.GetName()
}
switch val := val.(type) {
case []interface{}:
if fld.GetNumber() == internal.UninterpretedOptionsTag {
// we handle uninterpreted options differently
uninterpreted = val
continue
}
for _, e := range val {
if fld.GetType() == descriptor.FieldDescriptorProto_TYPE_ENUM {
ev := fld.GetEnumType().FindValueByNumber(e.(int32))
if ev == nil {
// have to skip unknown enum values :(
continue
}
e = ev
}
var name string
if fld.IsExtension() {
name = fmt.Sprintf("(%s)", p.qualifyName(pkg, scope, fld.GetFullyQualifiedName()))
} else {
name = fld.GetName()
}
opts = append(opts, option{name: name, val: e})
}
case map[interface{}]interface{}:
for k := range sortKeys(val) {
v := val[k]
vf := fld.GetMapValueType()
if vf.GetType() == descriptor.FieldDescriptorProto_TYPE_ENUM {
ev := vf.GetEnumType().FindValueByNumber(v.(int32))
if ev == nil {
// have to skip unknown enum values :(
continue
}
v = ev
}
entry := mf.NewDynamicMessage(fld.GetMessageType())
entry.SetFieldByNumber(1, k)
entry.SetFieldByNumber(2, v)
opts = append(opts, option{name: name, val: entry})
}
default:
if fld.GetType() == descriptor.FieldDescriptorProto_TYPE_ENUM {
ev := fld.GetEnumType().FindValueByNumber(val.(int32))
if ev == nil {
// have to skip unknown enum values :(
continue
}
val = ev
}
opts = append(opts, option{name: name, val: val})
}
if len(opts) > 0 {
options[fld.GetNumber()] = opts
}
}
}
}
// if there are uninterpreted options, add those too
if len(uninterpreted) > 0 {
opts := make([]option, len(uninterpreted))
for i, u := range uninterpreted {
var unint *descriptor.UninterpretedOption
if un, ok := u.(*descriptor.UninterpretedOption); ok {
unint = un
} else {
dm := u.(*dynamic.Message)
unint = &descriptor.UninterpretedOption{}
if err := dm.ConvertTo(unint); err != nil {
return nil, err
}
}
var buf bytes.Buffer
for ni, n := range unint.Name {
if ni > 0 {
buf.WriteByte('.')
}
if n.GetIsExtension() {
fmt.Fprintf(&buf, "(%s)", n.GetNamePart())
} else {
buf.WriteString(n.GetNamePart())
}
}
var v interface{}
switch {
case unint.IdentifierValue != nil:
v = ident(unint.GetIdentifierValue())
case unint.StringValue != nil:
v = string(unint.GetStringValue())
case unint.DoubleValue != nil:
v = unint.GetDoubleValue()
case unint.PositiveIntValue != nil:
v = unint.GetPositiveIntValue()
case unint.NegativeIntValue != nil:
v = unint.GetNegativeIntValue()
case unint.AggregateValue != nil:
v = ident(unint.GetAggregateValue())
}
opts[i] = option{name: buf.String(), val: v}
}
options[internal.UninterpretedOptionsTag] = opts
}
return options, nil
}
func optionsAsElementAddrs(optionsTag int32, order int, opts map[int32][]option) []elementAddr {
var optAddrs []elementAddr
for tag := range opts {
optAddrs = append(optAddrs, elementAddr{elementType: optionsTag, elementIndex: int(tag), order: order})
}
sort.Sort(optionsByName{addrs: optAddrs, opts: opts})
return optAddrs
}
// quotedString implements the text format for string literals for protocol
// buffers. This form is also acceptable for string literals in option values
// by the protocol buffer compiler, protoc.
func quotedString(s string) string {
var b bytes.Buffer
// use WriteByte here to get any needed indent
b.WriteByte('"')
// Loop over the bytes, not the runes.
for i := 0; i < len(s); i++ {
// Divergence from C++: we don't escape apostrophes.
// There's no need to escape them, and the C++ parser
// copes with a naked apostrophe.
switch c := s[i]; c {
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
case '"':
b.WriteString("\\")
case '\\':
b.WriteString("\\\\")
default:
if c >= 0x20 && c < 0x7f {
b.WriteByte(c)
} else {
fmt.Fprintf(&b, "\\%03o", c)
}
}
}
b.WriteByte('"')
return b.String()
}
type elementAddr struct {
elementType int32
elementIndex int
order int
}
type elementAddrs struct {
addrs []elementAddr
dsc interface{}
opts map[int32][]option
}
func (a elementAddrs) Len() int {
return len(a.addrs)
}
func (a elementAddrs) Less(i, j int) bool {
// explicit order is considered first
if a.addrs[i].order < a.addrs[j].order {
return true
} else if a.addrs[i].order > a.addrs[j].order {
return false
}
// if order is equal, sort by element type
if a.addrs[i].elementType < a.addrs[j].elementType {
return true
} else if a.addrs[i].elementType > a.addrs[j].elementType {
return false
}
di := a.at(a.addrs[i])
dj := a.at(a.addrs[j])
switch vi := di.(type) {
case *desc.FieldDescriptor:
// fields are ordered by tag number
vj := dj.(*desc.FieldDescriptor)
// regular fields before extensions; extensions grouped by extendee
if !vi.IsExtension() && vj.IsExtension() {
return true
} else if vi.IsExtension() && !vj.IsExtension() {
return false
} else if vi.IsExtension() && vj.IsExtension() {
if vi.GetOwner() != vj.GetOwner() {
return vi.GetOwner().GetFullyQualifiedName() < vj.GetOwner().GetFullyQualifiedName()
}
}
return vi.GetNumber() < vj.GetNumber()
case *desc.EnumValueDescriptor:
// enum values ordered by number then name
vj := dj.(*desc.EnumValueDescriptor)
if vi.GetNumber() == vj.GetNumber() {
return vi.GetName() < vj.GetName()
}
return vi.GetNumber() < vj.GetNumber()
case *descriptor.DescriptorProto_ExtensionRange:
// extension ranges ordered by tag
return vi.GetStart() < dj.(*descriptor.DescriptorProto_ExtensionRange).GetStart()
case reservedRange:
// reserved ranges ordered by tag, too
return vi.start < dj.(reservedRange).start
case string:
// reserved names lexically sorted
return vi < dj.(string)
case pkg:
// reserved names lexically sorted
return vi < dj.(pkg)
case imp:
// reserved names lexically sorted
return vi < dj.(imp)
case []option:
// options sorted by name, extensions last
return optionLess(vi, dj.([]option))
default:
// all other descriptors ordered by name
return di.(desc.Descriptor).GetName() < dj.(desc.Descriptor).GetName()
}
}
func (a elementAddrs) Swap(i, j int) {
a.addrs[i], a.addrs[j] = a.addrs[j], a.addrs[i]
}
func (a elementAddrs) at(addr elementAddr) interface{} {
switch dsc := a.dsc.(type) {
case *desc.FileDescriptor:
switch addr.elementType {
case internal.File_packageTag:
return pkg(dsc.GetPackage())
case internal.File_dependencyTag:
return imp(dsc.AsFileDescriptorProto().GetDependency()[addr.elementIndex])
case internal.File_optionsTag:
return a.opts[int32(addr.elementIndex)]
case internal.File_messagesTag:
return dsc.GetMessageTypes()[addr.elementIndex]
case internal.File_enumsTag:
return dsc.GetEnumTypes()[addr.elementIndex]
case internal.File_servicesTag:
return dsc.GetServices()[addr.elementIndex]
case internal.File_extensionsTag:
return dsc.GetExtensions()[addr.elementIndex]
}
case *desc.MessageDescriptor:
switch addr.elementType {
case internal.Message_optionsTag:
return a.opts[int32(addr.elementIndex)]
case internal.Message_fieldsTag:
return dsc.GetFields()[addr.elementIndex]
case internal.Message_nestedMessagesTag:
return dsc.GetNestedMessageTypes()[addr.elementIndex]
case internal.Message_enumsTag:
return dsc.GetNestedEnumTypes()[addr.elementIndex]
case internal.Message_extensionsTag:
return dsc.GetNestedExtensions()[addr.elementIndex]
case internal.Message_extensionRangeTag:
return dsc.AsDescriptorProto().GetExtensionRange()[addr.elementIndex]
case internal.Message_reservedRangeTag:
rng := dsc.AsDescriptorProto().GetReservedRange()[addr.elementIndex]
return reservedRange{start: rng.GetStart(), end: rng.GetEnd() - 1}
case internal.Message_reservedNameTag:
return dsc.AsDescriptorProto().GetReservedName()[addr.elementIndex]
}
case *desc.FieldDescriptor:
if addr.elementType == internal.Field_optionsTag {
return a.opts[int32(addr.elementIndex)]
}
case *desc.OneOfDescriptor:
switch addr.elementType {
case internal.OneOf_optionsTag:
return a.opts[int32(addr.elementIndex)]
case -internal.Message_fieldsTag:
return dsc.GetOwner().GetFields()[addr.elementIndex]
}
case *desc.EnumDescriptor:
switch addr.elementType {
case internal.Enum_optionsTag:
return a.opts[int32(addr.elementIndex)]
case internal.Enum_valuesTag:
return dsc.GetValues()[addr.elementIndex]
case internal.Enum_reservedRangeTag:
rng := dsc.AsEnumDescriptorProto().GetReservedRange()[addr.elementIndex]
return reservedRange{start: rng.GetStart(), end: rng.GetEnd()}
case internal.Enum_reservedNameTag:
return dsc.AsEnumDescriptorProto().GetReservedName()[addr.elementIndex]
}
case *desc.EnumValueDescriptor:
if addr.elementType == internal.EnumVal_optionsTag {
return a.opts[int32(addr.elementIndex)]
}
case *desc.ServiceDescriptor:
switch addr.elementType {
case internal.Service_optionsTag:
return a.opts[int32(addr.elementIndex)]
case internal.Service_methodsTag:
return dsc.GetMethods()[addr.elementIndex]
}
case *desc.MethodDescriptor:
if addr.elementType == internal.Method_optionsTag {
return a.opts[int32(addr.elementIndex)]
}
case extensionRange:
if addr.elementType == internal.ExtensionRange_optionsTag {
return a.opts[int32(addr.elementIndex)]
}
}
panic(fmt.Sprintf("location for unknown field %d of %T", addr.elementType, a.dsc))
}
type extensionRange struct {
owner *desc.MessageDescriptor
extRange *descriptor.DescriptorProto_ExtensionRange
}
type elementSrcOrder struct {
elementAddrs
sourceInfo internal.SourceInfoMap
prefix []int32
}
func (a elementSrcOrder) Less(i, j int) bool {
ti := a.addrs[i].elementType
ei := a.addrs[i].elementIndex
tj := a.addrs[j].elementType
ej := a.addrs[j].elementIndex
var si, sj *descriptor.SourceCodeInfo_Location
if ei < 0 {
si = a.sourceInfo.Get(append(a.prefix, -int32(ei)))
} else if ti < 0 {
p := make([]int32, len(a.prefix)-2)
copy(p, a.prefix)
si = a.sourceInfo.Get(append(p, ti, int32(ei)))
} else {
si = a.sourceInfo.Get(append(a.prefix, ti, int32(ei)))
}
if ej < 0 {
sj = a.sourceInfo.Get(append(a.prefix, -int32(ej)))
} else if tj < 0 {
p := make([]int32, len(a.prefix)-2)
copy(p, a.prefix)
sj = a.sourceInfo.Get(append(p, tj, int32(ej)))
} else {
sj = a.sourceInfo.Get(append(a.prefix, tj, int32(ej)))
}
if (si == nil) != (sj == nil) {
// generally, we put unknown elements after known ones;
// except package, imports, and option elements go first
// i will be unknown and j will be known
swapped := false
if si != nil {
si, sj = sj, si
ti, tj = tj, ti
swapped = true
}
switch a.dsc.(type) {
case *desc.FileDescriptor:
// NB: These comparisons are *trying* to get things ordered so that
// 1) If the package element has no source info, it appears _first_.
// 2) If any import element has no source info, it appears _after_
// the package element but _before_ any other element.
// 3) If any option element has no source info, it appears _after_
// the package and import elements but _before_ any other element.
// If the package, imports, and options are all missing source info,
// this will sort them all to the top in expected order. But if they
// are mixed (some _do_ have source info, some do not), and elements
// with source info have spans that positions them _after_ other
// elements in the file, then this Less function will be unstable
// since the above dual objectives for imports and options ("before
// this but after that") may be in conflict with one another. This
// should not cause any problems, other than elements being possibly
// sorted in a confusing order.
//
// Well-formed descriptors should instead have consistent source
// info: either all elements have source info or none do. So this
// should not be an issue in practice.
if ti == internal.File_packageTag {
return !swapped
}
if ti == internal.File_dependencyTag {
if tj == internal.File_packageTag {
// imports will come *after* package
return swapped
}
return !swapped
}
if ti == internal.File_optionsTag {
if tj == internal.File_packageTag || tj == internal.File_dependencyTag {
// options will come *after* package and imports
return swapped
}
return !swapped
}
case *desc.MessageDescriptor:
if ti == internal.Message_optionsTag {
return !swapped
}
case *desc.EnumDescriptor:
if ti == internal.Enum_optionsTag {
return !swapped
}
case *desc.ServiceDescriptor:
if ti == internal.Service_optionsTag {
return !swapped
}
}
return swapped
} else if si == nil || sj == nil {
// let stable sort keep unknown elements in same relative order
return false
}
for idx := 0; idx < len(sj.Span); idx++ {
if idx >= len(si.Span) {
return true
}
if si.Span[idx] < sj.Span[idx] {
return true
}
if si.Span[idx] > sj.Span[idx] {
return false
}
}
return false
}
type optionsByName struct {
addrs []elementAddr
opts map[int32][]option
}
func (o optionsByName) Len() int {
return len(o.addrs)
}
func (o optionsByName) Less(i, j int) bool {
oi := o.opts[int32(o.addrs[i].elementIndex)]
oj := o.opts[int32(o.addrs[j].elementIndex)]
return optionLess(oi, oj)
}
func optionLess(i, j []option) bool {
ni := i[0].name
nj := j[0].name
if ni[0] != '(' && nj[0] == '(' {
return true
} else if ni[0] == '(' && nj[0] != '(' {
return false
}
return ni < nj
}
func (o optionsByName) Swap(i, j int) {
o.addrs[i], o.addrs[j] = o.addrs[j], o.addrs[i]
}
func (p *Printer) printElement(isDecriptor bool, si *descriptor.SourceCodeInfo_Location, w *writer, indent int, el func(*writer)) {
includeComments := isDecriptor || p.includeCommentType(CommentsTokens)
if includeComments && si != nil {
p.printLeadingComments(si, w, indent)
}
el(w)
if includeComments && si != nil {
p.printTrailingComments(si, w, indent)
}
if indent >= 0 && !w.newline {
// if we're not printing inline but element did not have trailing newline, add one now
fmt.Fprintln(w)
}
}
func (p *Printer) printElementString(si *descriptor.SourceCodeInfo_Location, w *writer, indent int, str string) {
p.printElement(false, si, w, inline(indent), func(w *writer) {
fmt.Fprintf(w, "%s ", str)
})
}
func (p *Printer) includeCommentType(c CommentType) bool {
return (p.OmitComments & c) == 0
}
func (p *Printer) printLeadingComments(si *descriptor.SourceCodeInfo_Location, w *writer, indent int) bool {
endsInNewLine := false
if p.includeCommentType(CommentsDetached) {
for _, c := range si.GetLeadingDetachedComments() {
if p.printComment(c, w, indent, true) {
// if comment ended in newline, add another newline to separate
// this comment from the next
p.newLine(w)
endsInNewLine = true
} else if indent < 0 {
// comment did not end in newline and we are trying to inline?
// just add a space to separate this comment from what follows
fmt.Fprint(w, " ")
endsInNewLine = false
} else {
// comment did not end in newline and we are *not* trying to inline?
// add newline to end of comment and add another to separate this
// comment from what follows
fmt.Fprintln(w) // needed to end comment, regardless of p.Compact
p.newLine(w)
endsInNewLine = true
}
}
}
if p.includeCommentType(CommentsLeading) && si.GetLeadingComments() != "" {
endsInNewLine = p.printComment(si.GetLeadingComments(), w, indent, true)
if !endsInNewLine {
if indent >= 0 {
// leading comment didn't end with newline but needs one
// (because we're *not* inlining)
fmt.Fprintln(w) // needed to end comment, regardless of p.Compact
endsInNewLine = true
} else {
// space between comment and following element when inlined
fmt.Fprint(w, " ")
}
}
}
return endsInNewLine
}
func (p *Printer) printTrailingComments(si *descriptor.SourceCodeInfo_Location, w *writer, indent int) {
if p.includeCommentType(CommentsTrailing) && si.GetTrailingComments() != "" {
if !p.printComment(si.GetTrailingComments(), w, indent, p.TrailingCommentsOnSeparateLine) && indent >= 0 {
// trailing comment didn't end with newline but needs one
// (because we're *not* inlining)
fmt.Fprintln(w) // needed to end comment, regardless of p.Compact
} else if indent < 0 {
fmt.Fprint(w, " ")
}
}
}
func (p *Printer) printComment(comments string, w *writer, indent int, forceNextLine bool) bool {
if comments == "" {
return false
}
var multiLine bool
if indent < 0 {
// use multi-line style when inlining
multiLine = true
} else {
multiLine = p.PreferMultiLineStyleComments
}
if multiLine && strings.Contains(comments, "*/") {
// can't emit '*/' in a multi-line style comment
multiLine = false
}
lines := strings.Split(comments, "\n")
// first, remove leading and trailing blank lines
if lines[0] == "" {
lines = lines[1:]
}
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
if len(lines) == 0 {
return false
}
if indent >= 0 && !w.newline {
// last element did not have trailing newline, so we
// either need to tack on newline or, if comment is
// just one line, inline it on the end
if forceNextLine || len(lines) > 1 {
fmt.Fprintln(w)
} else {
if !w.space {
fmt.Fprint(w, " ")
}
indent = inline(indent)
}
}
if len(lines) == 1 && multiLine {
p.indent(w, indent)
line := lines[0]
if line[0] == ' ' && line[len(line)-1] != ' ' {
// add trailing space for symmetry
line += " "
}
fmt.Fprintf(w, "/*%s*/", line)
if indent >= 0 {
fmt.Fprintln(w)
return true
}
return false
}
if multiLine {
// multi-line style comments that actually span multiple lines
// get a blank line before and after so that comment renders nicely
lines = append(lines, "", "")
copy(lines[1:], lines)
lines[0] = ""
}
for i, l := range lines {
p.maybeIndent(w, indent, i > 0)
if multiLine {
if i == 0 {
// first line
fmt.Fprintf(w, "/*%s\n", strings.TrimRight(l, " \t"))
} else if i == len(lines)-1 {
// last line
if l == "" {
fmt.Fprint(w, " */")
} else {
fmt.Fprintf(w, " *%s*/", l)
}
if indent >= 0 {
fmt.Fprintln(w)
}
} else {
fmt.Fprintf(w, " *%s\n", strings.TrimRight(l, " \t"))
}
} else {
fmt.Fprintf(w, "//%s\n", strings.TrimRight(l, " \t"))
}
}
// single-line comments always end in newline; multi-line comments only
// end in newline for non-negative (e.g. non-inlined) indentation
return !multiLine || indent >= 0
}
func (p *Printer) indent(w io.Writer, indent int) {
for i := 0; i < indent; i++ {
fmt.Fprint(w, p.Indent)
}
}
func (p *Printer) maybeIndent(w io.Writer, indent int, requireIndent bool) {
if indent < 0 && requireIndent {
p.indent(w, -indent)
} else {
p.indent(w, indent)
}
}
type writer struct {
io.Writer
err error
space bool
newline bool
}
func newWriter(w io.Writer) *writer {
return &writer{Writer: w, newline: true}
}
func (w *writer) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
w.newline = false
if w.space {
// skip any trailing space if the following
// character is semicolon, comma, or close bracket
if p[0] != ';' && p[0] != ',' && p[0] != ']' {
_, err := w.Writer.Write([]byte{' '})
if err != nil {
w.err = err
return 0, err
}
}
w.space = false
}
if p[len(p)-1] == ' ' {
w.space = true
p = p[:len(p)-1]
}
if len(p) > 0 && p[len(p)-1] == '\n' {
w.newline = true
}
num, err := w.Writer.Write(p)
if err != nil {
w.err = err
} else if w.space {
// pretend space was written
num++
}
return num, err
}