github.com/RomiChan/protobuf@v0.1.1-0.20230204044148-2ed269a2e54d/internal/generator/main.go (about)

     1  // protocolbuffers/protobuf-go cmd/protoc-gen-go/internal_gengo/main.go
     2  // https://github.com/protocolbuffers/protobuf-go/blob/master/cmd/protoc-gen-go/internal_gengo/main.go
     3  //
     4  //	Copyright © 2018 The Go Authors. All rights reserved.
     5  //	Portions Copyright © 2021 RomiChan
     6  //
     7  // Redistribution and use in source and binary forms, with or without
     8  // modification, are permitted provided that the following conditions are
     9  // met:
    10  //
    11  // * Redistributions of source code must retain the above copyright
    12  // notice, this list of conditions and the following disclaimer.
    13  // * Redistributions in binary form must reproduce the above
    14  // copyright notice, this list of conditions and the following disclaimer
    15  // in the documentation and/or other materials provided with the
    16  // distribution.
    17  // * Neither the name of Google Inc. nor the names of its
    18  // contributors may be used to endorse or promote products derived from
    19  // this software without specific prior written permission.
    20  //
    21  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    22  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    23  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    24  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    25  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    26  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    27  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    28  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    29  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    30  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    31  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    32  
    33  package generator
    34  
    35  import (
    36  	"fmt"
    37  	"go/ast"
    38  	"go/parser"
    39  	"go/token"
    40  	"strconv"
    41  	"strings"
    42  	"unicode"
    43  	"unicode/utf8"
    44  
    45  	"google.golang.org/protobuf/compiler/protogen"
    46  	"google.golang.org/protobuf/reflect/protoreflect"
    47  	"google.golang.org/protobuf/types/descriptorpb"
    48  )
    49  
    50  var protoPackage = protogen.GoImportPath("github.com/RomiChan/protobuf/proto")
    51  
    52  // GenerateFile generates the contents of a .pb.go file.
    53  func GenerateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile {
    54  	filename := file.GeneratedFilenamePrefix + ".pb.go"
    55  	g := gen.NewGeneratedFile(filename, file.GoImportPath)
    56  	f := newFileInfo(file)
    57  
    58  	genGeneratedHeader(gen, g, f)
    59  
    60  	packageDoc := ""
    61  	g.P(packageDoc, "package ", f.GoPackageName)
    62  	g.P()
    63  
    64  	for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ {
    65  		genImport(gen, g, f, imps.Get(i))
    66  	}
    67  	for _, enum := range f.allEnums {
    68  		genEnum(g, f, enum)
    69  	}
    70  	for _, message := range f.allMessages {
    71  		genMessage(g, f, message)
    72  	}
    73  
    74  	return g
    75  }
    76  
    77  func genGeneratedHeader(_ *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
    78  	g.P("// Code generated by protoc-gen-golite. DO NOT EDIT.")
    79  
    80  	if f.Proto.GetOptions().GetDeprecated() {
    81  		g.P("// ", f.Desc.Path(), " is a deprecated file.")
    82  	} else {
    83  		g.P("// source: ", f.Desc.Path())
    84  	}
    85  	g.P()
    86  }
    87  
    88  func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport) {
    89  	impFile, ok := gen.FilesByPath[imp.Path()]
    90  	if !ok {
    91  		return
    92  	}
    93  	if impFile.GoImportPath == f.GoImportPath {
    94  		// Don't generate imports or aliases for types in the same Go package.
    95  		return
    96  	}
    97  	// Generate imports for all non-weak dependencies, even if they are not
    98  	// referenced, because other code and tools depend on having the
    99  	// full transitive closure of protocol buffer types in the binary.
   100  	if !imp.IsWeak {
   101  		g.Import(impFile.GoImportPath)
   102  	}
   103  	if !imp.IsPublic {
   104  		return
   105  	}
   106  
   107  	// Generate public imports by generating the imported file, parsing it,
   108  	// and extracting every symbol that should receive a forwarding declaration.
   109  	impGen := GenerateFile(gen, impFile)
   110  	impGen.Skip()
   111  	b, err := impGen.Content()
   112  	if err != nil {
   113  		gen.Error(err)
   114  		return
   115  	}
   116  	fset := token.NewFileSet()
   117  	astFile, err := parser.ParseFile(fset, "", b, parser.ParseComments)
   118  	if err != nil {
   119  		gen.Error(err)
   120  		return
   121  	}
   122  	genForward := func(tok token.Token, name string, expr ast.Expr) {
   123  		// Don't import unexported symbols.
   124  		r, _ := utf8.DecodeRuneInString(name)
   125  		if !unicode.IsUpper(r) {
   126  			return
   127  		}
   128  		// Don't import the FileDescriptor.
   129  		if name == impFile.GoDescriptorIdent.GoName {
   130  			return
   131  		}
   132  		// Don't import decls referencing a symbol defined in another package.
   133  		// i.e., don't import decls which are themselves public imports:
   134  		//
   135  		//	type T = somepackage.T
   136  		if _, ok := expr.(*ast.SelectorExpr); ok {
   137  			return
   138  		}
   139  		g.P(tok, " ", name, " = ", impFile.GoImportPath.Ident(name))
   140  	}
   141  	g.P("// Symbols defined in public import of ", imp.Path(), ".")
   142  	g.P()
   143  	for _, decl := range astFile.Decls {
   144  		switch decl := decl.(type) {
   145  		case *ast.GenDecl:
   146  			for _, spec := range decl.Specs {
   147  				switch spec := spec.(type) {
   148  				case *ast.TypeSpec:
   149  					genForward(decl.Tok, spec.Name.Name, spec.Type)
   150  				case *ast.ValueSpec:
   151  					for i, name := range spec.Names {
   152  						var expr ast.Expr
   153  						if i < len(spec.Values) {
   154  							expr = spec.Values[i]
   155  						}
   156  						genForward(decl.Tok, name.Name, expr)
   157  					}
   158  				case *ast.ImportSpec:
   159  				default:
   160  					panic(fmt.Sprintf("can't generate forward for spec type %T", spec))
   161  				}
   162  			}
   163  		}
   164  	}
   165  	g.P()
   166  }
   167  
   168  func genEnum(g *protogen.GeneratedFile, _ *fileInfo, e *enumInfo) {
   169  	// Enum type declaration.
   170  	g.Annotate(e.GoIdent.GoName, e.Location)
   171  	leadingComments := appendDeprecationSuffix(e.Comments.Leading,
   172  		e.Desc.Options().(*descriptorpb.EnumOptions).GetDeprecated())
   173  	g.P(leadingComments,
   174  		"type ", e.GoIdent, "= int32")
   175  
   176  	// Enum value constants.
   177  	g.P("const (")
   178  	for _, value := range e.Values {
   179  		g.Annotate(value.GoIdent.GoName, value.Location)
   180  		leadingComments := appendDeprecationSuffix(value.Comments.Leading,
   181  			value.Desc.Options().(*descriptorpb.EnumValueOptions).GetDeprecated())
   182  		g.P(leadingComments,
   183  			value.GoIdent, " ", e.GoIdent, " = ", value.Desc.Number(),
   184  			trailingComment(value.Comments.Trailing))
   185  	}
   186  	g.P(")")
   187  	g.P()
   188  
   189  	// Enum method.
   190  	//
   191  	// NOTE: A pointer value is needed to represent presence in proto2.
   192  	// Since a proto2 message can reference a proto3 enum, it is useful to
   193  	// always generate this method (even on proto3 enums) to support that case.
   194  	/*
   195  		g.P("func (x ", e.GoIdent, ") Enum() *", e.GoIdent, " {")
   196  		g.P("p := new(", e.GoIdent, ")")
   197  		g.P("*p = x")
   198  		g.P("return p")
   199  		g.P("}")
   200  		g.P()
   201  	*/
   202  
   203  	// String method.
   204  	// todo: gen string method
   205  }
   206  
   207  func genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
   208  	if m.Desc.IsMapEntry() {
   209  		return
   210  	}
   211  
   212  	// Message type declaration.
   213  	g.Annotate(m.GoIdent.GoName, m.Location)
   214  	leadingComments := appendDeprecationSuffix(m.Comments.Leading,
   215  		m.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
   216  	g.P(leadingComments,
   217  		"type ", m.GoIdent, " struct {")
   218  	genMessageFields(g, f, m)
   219  	g.P("}")
   220  	g.P()
   221  
   222  	genMessageMethods(g, f, m)
   223  	genMessageOneofWrapperTypes(g, f, m)
   224  }
   225  
   226  func genMessageFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
   227  	sf := f.allMessageFieldsByPtr[m]
   228  	f.comparable = true
   229  	for _, field := range m.Fields {
   230  		genMessageField(g, f, m, field, sf)
   231  	}
   232  	if f.comparable {
   233  		g.P("_ [0]func()")
   234  	}
   235  }
   236  
   237  func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field, sf *structFields) {
   238  	if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
   239  		// It would be a bit simpler to iterate over the oneofs below,
   240  		// but generating the field here keeps the contents of the Go
   241  		// struct in the same order as the contents of the source
   242  		// .proto file.
   243  		if oneof.Fields[0] != field {
   244  			return // only generate for first appearance
   245  		}
   246  
   247  		tags := structTags{
   248  			{"protobuf_oneof", string(oneof.Desc.Name())},
   249  		}
   250  		if m.isTracked {
   251  			tags = append(tags, gotrackTags...)
   252  		}
   253  
   254  		g.Annotate(m.GoIdent.GoName+"."+oneof.GoName, oneof.Location)
   255  		leadingComments := oneof.Comments.Leading
   256  		if leadingComments != "" {
   257  			leadingComments += "\n"
   258  		}
   259  		ss := []string{fmt.Sprintf(" Types that are assignable to %s:\n", oneof.GoName)}
   260  		for _, field := range oneof.Fields {
   261  			ss = append(ss, "\t*"+field.GoIdent.GoName+"\n")
   262  		}
   263  		leadingComments += protogen.Comments(strings.Join(ss, ""))
   264  		g.P(leadingComments,
   265  			oneof.GoName, " ", oneofInterfaceName(oneof), tags)
   266  		sf.append(oneof.GoName)
   267  		return
   268  	}
   269  	goType, option, comp := fieldGoType(g, f, field)
   270  	f.comparable = f.comparable && comp
   271  	if option {
   272  		goType = g.QualifiedGoIdent(protoPackage.Ident("Option[" + goType + "]"))
   273  	}
   274  	tags := structTags{
   275  		{"protobuf", fieldProtobufTagValue(field)},
   276  	}
   277  	if field.Desc.IsMap() {
   278  		key := field.Message.Fields[0]
   279  		val := field.Message.Fields[1]
   280  		tags = append(tags, structTags{
   281  			{"protobuf_key", fieldProtobufTagValue(key)},
   282  			{"protobuf_val", fieldProtobufTagValue(val)},
   283  		}...)
   284  	}
   285  	if m.isTracked {
   286  		tags = append(tags, gotrackTags...)
   287  	}
   288  
   289  	name := field.GoName
   290  	g.Annotate(m.GoIdent.GoName+"."+name, field.Location)
   291  	leadingComments := appendDeprecationSuffix(field.Comments.Leading,
   292  		field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
   293  	g.P(leadingComments,
   294  		name, " ", goType, tags,
   295  		trailingComment(field.Comments.Trailing))
   296  	sf.append(field.GoName)
   297  }
   298  
   299  func genMessageMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
   300  	genMessageGetterMethods(g, f, m)
   301  }
   302  
   303  func genMessageGetterMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
   304  	for _, field := range m.Fields {
   305  		genNoInterfacePragma(g, m.isTracked)
   306  
   307  		// Getter for parent oneof.
   308  		if oneof := field.Oneof; oneof != nil && oneof.Fields[0] == field && !oneof.Desc.IsSynthetic() {
   309  			g.Annotate(m.GoIdent.GoName+".Get"+oneof.GoName, oneof.Location)
   310  			g.P("func (m *", m.GoIdent.GoName, ") Get", oneof.GoName, "() ", oneofInterfaceName(oneof), " {")
   311  			g.P("if m != nil {")
   312  			g.P("return m.", oneof.GoName)
   313  			g.P("}")
   314  			g.P("return nil")
   315  			g.P("}")
   316  			g.P()
   317  		}
   318  
   319  		// Getter for message field.
   320  		goType, _, _ := fieldGoType(g, f, field)
   321  		defaultValue := fieldDefaultValue(g, f, m, field)
   322  		g.Annotate(m.GoIdent.GoName+".Get"+field.GoName, field.Location)
   323  		leadingComments := appendDeprecationSuffix("",
   324  			field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
   325  		switch {
   326  		case field.Oneof != nil && !field.Oneof.Desc.IsSynthetic():
   327  			g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {")
   328  			g.P("if x, ok := x.Get", field.Oneof.GoName, "().(*", field.GoIdent, "); ok {")
   329  			g.P("return x.", field.GoName)
   330  			g.P("}")
   331  			g.P("return ", defaultValue)
   332  			g.P("}")
   333  		default:
   334  			/*
   335  				if !field.Desc.HasPresence() || defaultValue == "nil" {
   336  					continue
   337  				}
   338  				g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {")
   339  				g.P("if x != nil && x.", field.GoName, " != nil {")
   340  				star := ""
   341  				if pointer {
   342  					star = "*"
   343  				}
   344  				g.P("return ", star, " x.", field.GoName)
   345  				g.P("}")
   346  				g.P("return ", defaultValue)
   347  				g.P("}")'
   348  			*/
   349  		}
   350  		g.P()
   351  	}
   352  }
   353  
   354  // fieldGoType returns the Go type used for a field.
   355  //
   356  // If it returns pointer=true, the struct field is a pointer to the type.
   357  func fieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field) (goType string, pointer bool, comparable bool) {
   358  	if field.Desc.IsWeak() {
   359  		return "struct{}", false, true
   360  	}
   361  
   362  	pointer = field.Desc.HasPresence()
   363  	comparable = true
   364  	switch field.Desc.Kind() {
   365  	case protoreflect.BoolKind:
   366  		goType = "bool"
   367  	case protoreflect.EnumKind:
   368  		goType = g.QualifiedGoIdent(field.Enum.GoIdent)
   369  	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
   370  		goType = "int32"
   371  	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
   372  		goType = "uint32"
   373  	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
   374  		goType = "int64"
   375  	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
   376  		goType = "uint64"
   377  	case protoreflect.FloatKind:
   378  		goType = "float32"
   379  	case protoreflect.DoubleKind:
   380  		goType = "float64"
   381  	case protoreflect.StringKind:
   382  		goType = "string"
   383  	case protoreflect.BytesKind:
   384  		goType = "[]byte"
   385  		comparable = false
   386  		pointer = false // rely on nullability of slices for presence
   387  	case protoreflect.MessageKind, protoreflect.GroupKind:
   388  		goType = "*" + g.QualifiedGoIdent(field.Message.GoIdent)
   389  		pointer = false // pointer captured as part of the type
   390  	}
   391  	switch {
   392  	case field.Desc.IsList():
   393  		return "[]" + goType, false, false
   394  	case field.Desc.IsMap():
   395  		keyType, _, _ := fieldGoType(g, f, field.Message.Fields[0])
   396  		valType, _, _ := fieldGoType(g, f, field.Message.Fields[1])
   397  		return fmt.Sprintf("map[%v]%v", keyType, valType), false, false
   398  	}
   399  	return goType, pointer, comparable
   400  }
   401  
   402  func fieldProtobufTagValue(field *protogen.Field) string {
   403  	fd := field.Desc
   404  	var tag []string
   405  	switch fd.Kind() {
   406  	case protoreflect.BoolKind, protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Uint32Kind, protoreflect.Int64Kind, protoreflect.Uint64Kind:
   407  		tag = append(tag, "varint")
   408  	case protoreflect.Sint32Kind:
   409  		tag = append(tag, "zigzag32")
   410  	case protoreflect.Sint64Kind:
   411  		tag = append(tag, "zigzag64")
   412  	case protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind, protoreflect.FloatKind:
   413  		tag = append(tag, "fixed32")
   414  	case protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind, protoreflect.DoubleKind:
   415  		tag = append(tag, "fixed64")
   416  	case protoreflect.StringKind, protoreflect.BytesKind, protoreflect.MessageKind:
   417  		tag = append(tag, "bytes")
   418  	case protoreflect.GroupKind:
   419  		tag = append(tag, "group")
   420  	}
   421  	tag = append(tag, strconv.Itoa(int(fd.Number())))
   422  	switch fd.Cardinality() {
   423  	case protoreflect.Optional:
   424  		tag = append(tag, "opt")
   425  	case protoreflect.Required:
   426  		tag = append(tag, "req")
   427  	case protoreflect.Repeated:
   428  		tag = append(tag, "rep")
   429  	}
   430  	return strings.Join(tag, ",")
   431  }
   432  
   433  func fieldDefaultValue(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field) string {
   434  	if field.Desc.IsList() {
   435  		return "nil"
   436  	}
   437  	switch field.Desc.Kind() {
   438  	case protoreflect.BoolKind:
   439  		return "false"
   440  	case protoreflect.StringKind:
   441  		return `""`
   442  	case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind:
   443  		return "nil"
   444  	case protoreflect.EnumKind:
   445  		val := field.Enum.Values[0]
   446  		if val.GoIdent.GoImportPath == f.GoImportPath {
   447  			return g.QualifiedGoIdent(val.GoIdent)
   448  		} else {
   449  			// If the enum value is declared in a different Go package,
   450  			// reference it by number since the name may not be correct.
   451  			// See https://github.com/golang/protobuf/issues/513.
   452  			return g.QualifiedGoIdent(field.Enum.GoIdent) + "(" + strconv.FormatInt(int64(val.Desc.Number()), 10) + ")"
   453  		}
   454  	default:
   455  		return "0"
   456  	}
   457  }
   458  
   459  // genMessageOneofWrapperTypes generates the oneof wrapper types and
   460  // associates the types with the parent message type.
   461  func genMessageOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
   462  	for _, oneof := range m.Oneofs {
   463  		if oneof.Desc.IsSynthetic() {
   464  			continue
   465  		}
   466  		ifName := oneofInterfaceName(oneof)
   467  		g.P("type ", ifName, " interface {")
   468  		g.P(ifName, "()")
   469  		g.P("}")
   470  		g.P()
   471  		for _, field := range oneof.Fields {
   472  			g.Annotate(field.GoIdent.GoName, field.Location)
   473  			g.Annotate(field.GoIdent.GoName+"."+field.GoName, field.Location)
   474  			g.P("type ", field.GoIdent, " struct {")
   475  			goType, _, _ := fieldGoType(g, f, field)
   476  			tags := structTags{
   477  				{"protobuf", fieldProtobufTagValue(field)},
   478  			}
   479  			if m.isTracked {
   480  				tags = append(tags, gotrackTags...)
   481  			}
   482  			leadingComments := appendDeprecationSuffix(field.Comments.Leading,
   483  				field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
   484  			g.P(leadingComments,
   485  				field.GoName, " ", goType, tags,
   486  				trailingComment(field.Comments.Trailing))
   487  			g.P("}")
   488  			g.P()
   489  		}
   490  		for _, field := range oneof.Fields {
   491  			g.P("func (*", field.GoIdent, ") ", ifName, "() {}")
   492  			g.P()
   493  		}
   494  	}
   495  }
   496  
   497  // oneofInterfaceName returns the name of the interface type implemented by
   498  // the oneof field value types.
   499  func oneofInterfaceName(oneof *protogen.Oneof) string {
   500  	return "is" + oneof.GoIdent.GoName
   501  }
   502  
   503  // genNoInterfacePragma generates a standalone "nointerface" pragma to
   504  // decorate methods with field-tracking support.
   505  func genNoInterfacePragma(g *protogen.GeneratedFile, tracked bool) {
   506  	if tracked {
   507  		g.P("//go:nointerface")
   508  		g.P()
   509  	}
   510  }
   511  
   512  var gotrackTags = structTags{{"go", "track"}}
   513  
   514  // structTags is a data structure for build idiomatic Go struct tags.
   515  // Each [2]string is a key-value pair, where value is the unescaped string.
   516  //
   517  // Example: structTags{{"key", "value"}}.String() -> `key:"value"`
   518  type structTags [][2]string
   519  
   520  func (tags structTags) String() string {
   521  	if len(tags) == 0 {
   522  		return ""
   523  	}
   524  	var ss []string
   525  	for _, tag := range tags {
   526  		// NOTE: When quoting the value, we need to make sure the backtick
   527  		// character does not appear. Convert all cases to the escaped hex form.
   528  		key := tag[0]
   529  		val := strings.Replace(strconv.Quote(tag[1]), "`", `\x60`, -1)
   530  		ss = append(ss, fmt.Sprintf("%s:%s", key, val))
   531  	}
   532  	return "`" + strings.Join(ss, " ") + "`"
   533  }
   534  
   535  // appendDeprecationSuffix optionally appends a deprecation notice as a suffix.
   536  func appendDeprecationSuffix(protoreflectix protogen.Comments, deprecated bool) protogen.Comments {
   537  	if !deprecated {
   538  		return protoreflectix
   539  	}
   540  	if protoreflectix != "" {
   541  		protoreflectix += "\n"
   542  	}
   543  	return protoreflectix + " Deprecated: Do not use.\n"
   544  }
   545  
   546  // trailingComment is like protogen.Comments, but lacks a trailing newline.
   547  type trailingComment protogen.Comments
   548  
   549  func (c trailingComment) String() string {
   550  	s := strings.TrimSuffix(protogen.Comments(c).String(), "\n")
   551  	if strings.Contains(s, "\n") {
   552  		// We don't support multi-lined trailing comments as it is unclear
   553  		// how to best render them in the generated code.
   554  		return ""
   555  	}
   556  	return s
   557  }