blob: 35b72954d6947cd3cd4590b4c2cce7627f9d41f4 [file] [log] [blame]
Damien Neil220c2022018-08-15 11:24:18 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package protogen provides support for writing protoc plugins.
6//
Joe Tsai04f03cb2020-02-14 12:40:48 -08007// Plugins for protoc, the Protocol Buffer compiler,
8// are programs which read a CodeGeneratorRequest message from standard input
9// and write a CodeGeneratorResponse message to standard output.
10// This package provides support for writing plugins which generate Go code.
Damien Neil220c2022018-08-15 11:24:18 -070011package protogen
12
13import (
Damien Neilc7d07d92018-08-22 13:46:02 -070014 "bufio"
Damien Neil220c2022018-08-15 11:24:18 -070015 "bytes"
Damien Neilba1159f2018-10-17 12:53:18 -070016 "encoding/binary"
Damien Neil220c2022018-08-15 11:24:18 -070017 "fmt"
Damien Neil1ec33152018-09-13 13:12:36 -070018 "go/ast"
Damien Neilc7d07d92018-08-22 13:46:02 -070019 "go/parser"
20 "go/printer"
21 "go/token"
Joe Tsai124c8122019-01-14 11:48:43 -080022 "go/types"
Damien Neil220c2022018-08-15 11:24:18 -070023 "io/ioutil"
Joe Tsai3e802492019-09-07 13:06:27 -070024 "log"
Damien Neil220c2022018-08-15 11:24:18 -070025 "os"
Damien Neil082ce922018-09-06 10:23:53 -070026 "path"
Damien Neil220c2022018-08-15 11:24:18 -070027 "path/filepath"
Damien Neild9016772018-08-23 14:39:30 -070028 "sort"
29 "strconv"
Damien Neil220c2022018-08-15 11:24:18 -070030 "strings"
31
Damien Neil5c5b5312019-05-14 12:44:37 -070032 "google.golang.org/protobuf/encoding/prototext"
Damien Neile89e6242019-05-13 23:55:40 -070033 "google.golang.org/protobuf/internal/fieldnum"
Joe Tsai2e7817f2019-08-23 12:18:57 -070034 "google.golang.org/protobuf/internal/strs"
Damien Neile89e6242019-05-13 23:55:40 -070035 "google.golang.org/protobuf/proto"
36 "google.golang.org/protobuf/reflect/protodesc"
37 "google.golang.org/protobuf/reflect/protoreflect"
38 "google.golang.org/protobuf/reflect/protoregistry"
Joe Tsaie1f8d502018-11-26 18:55:29 -080039
Joe Tsaia95b29f2019-05-16 12:47:20 -070040 "google.golang.org/protobuf/types/descriptorpb"
41 "google.golang.org/protobuf/types/pluginpb"
Damien Neil220c2022018-08-15 11:24:18 -070042)
43
Joe Tsai222a0002020-02-24 11:21:30 -080044const goPackageDocURL = "https://developers.google.com/protocol-buffers/docs/reference/go-generated#package"
45
Damien Neil220c2022018-08-15 11:24:18 -070046// Run executes a function as a protoc plugin.
47//
48// It reads a CodeGeneratorRequest message from os.Stdin, invokes the plugin
49// function, and writes a CodeGeneratorResponse message to os.Stdout.
50//
51// If a failure occurs while reading or writing, Run prints an error to
52// os.Stderr and calls os.Exit(1).
Damien Neil3cf6e622018-09-11 13:53:14 -070053//
54// Passing a nil options is equivalent to passing a zero-valued one.
55func Run(opts *Options, f func(*Plugin) error) {
56 if err := run(opts, f); err != nil {
Damien Neil220c2022018-08-15 11:24:18 -070057 fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err)
58 os.Exit(1)
59 }
60}
61
Damien Neil3cf6e622018-09-11 13:53:14 -070062func run(opts *Options, f func(*Plugin) error) error {
Damien Neild277b522018-10-04 15:30:51 -070063 if len(os.Args) > 1 {
64 return fmt.Errorf("unknown argument %q (this program should be run by protoc, not directly)", os.Args[1])
65 }
Damien Neil220c2022018-08-15 11:24:18 -070066 in, err := ioutil.ReadAll(os.Stdin)
67 if err != nil {
68 return err
69 }
70 req := &pluginpb.CodeGeneratorRequest{}
71 if err := proto.Unmarshal(in, req); err != nil {
72 return err
73 }
Damien Neil3cf6e622018-09-11 13:53:14 -070074 gen, err := New(req, opts)
Damien Neil220c2022018-08-15 11:24:18 -070075 if err != nil {
76 return err
77 }
78 if err := f(gen); err != nil {
79 // Errors from the plugin function are reported by setting the
80 // error field in the CodeGeneratorResponse.
81 //
82 // In contrast, errors that indicate a problem in protoc
83 // itself (unparsable input, I/O errors, etc.) are reported
84 // to stderr.
85 gen.Error(err)
86 }
87 resp := gen.Response()
88 out, err := proto.Marshal(resp)
89 if err != nil {
90 return err
91 }
92 if _, err := os.Stdout.Write(out); err != nil {
93 return err
94 }
95 return nil
96}
97
98// A Plugin is a protoc plugin invocation.
99type Plugin struct {
100 // Request is the CodeGeneratorRequest provided by protoc.
101 Request *pluginpb.CodeGeneratorRequest
102
103 // Files is the set of files to generate and everything they import.
104 // Files appear in topological order, so each file appears before any
105 // file that imports it.
106 Files []*File
Joe Tsai2cec4842019-08-20 20:14:19 -0700107 FilesByPath map[string]*File
Damien Neil220c2022018-08-15 11:24:18 -0700108
Damien Neil658051b2018-09-10 12:26:21 -0700109 fileReg *protoregistry.Files
Damien Neil658051b2018-09-10 12:26:21 -0700110 enumsByName map[protoreflect.FullName]*Enum
Joe Tsai7762ec22019-08-20 20:10:23 -0700111 messagesByName map[protoreflect.FullName]*Message
Damien Neil162c1272018-10-04 12:42:37 -0700112 annotateCode bool
Damien Neil658051b2018-09-10 12:26:21 -0700113 pathType pathType
114 genFiles []*GeneratedFile
Damien Neil1fa8ab02018-09-27 15:51:05 -0700115 opts *Options
Damien Neil658051b2018-09-10 12:26:21 -0700116 err error
Damien Neil220c2022018-08-15 11:24:18 -0700117}
118
Damien Neil3cf6e622018-09-11 13:53:14 -0700119// Options are optional parameters to New.
120type Options struct {
121 // If ParamFunc is non-nil, it will be called with each unknown
122 // generator parameter.
123 //
124 // Plugins for protoc can accept parameters from the command line,
125 // passed in the --<lang>_out protoc, separated from the output
126 // directory with a colon; e.g.,
127 //
128 // --go_out=<param1>=<value1>,<param2>=<value2>:<output_directory>
129 //
130 // Parameters passed in this fashion as a comma-separated list of
131 // key=value pairs will be passed to the ParamFunc.
132 //
133 // The (flag.FlagSet).Set method matches this function signature,
134 // so parameters can be converted into flags as in the following:
135 //
136 // var flags flag.FlagSet
137 // value := flags.Bool("param", false, "")
138 // opts := &protogen.Options{
139 // ParamFunc: flags.Set,
140 // }
141 // protogen.Run(opts, func(p *protogen.Plugin) error {
142 // if *value { ... }
143 // })
144 ParamFunc func(name, value string) error
Damien Neil1fa8ab02018-09-27 15:51:05 -0700145
146 // ImportRewriteFunc is called with the import path of each package
147 // imported by a generated file. It returns the import path to use
148 // for this package.
149 ImportRewriteFunc func(GoImportPath) GoImportPath
Damien Neil3cf6e622018-09-11 13:53:14 -0700150}
151
Damien Neil220c2022018-08-15 11:24:18 -0700152// New returns a new Plugin.
Damien Neil3cf6e622018-09-11 13:53:14 -0700153//
154// Passing a nil Options is equivalent to passing a zero-valued one.
155func New(req *pluginpb.CodeGeneratorRequest, opts *Options) (*Plugin, error) {
156 if opts == nil {
157 opts = &Options{}
158 }
Damien Neil220c2022018-08-15 11:24:18 -0700159 gen := &Plugin{
Damien Neil658051b2018-09-10 12:26:21 -0700160 Request: req,
Joe Tsai2cec4842019-08-20 20:14:19 -0700161 FilesByPath: make(map[string]*File),
Damien Neilc8268852019-10-08 13:28:53 -0700162 fileReg: new(protoregistry.Files),
Damien Neil658051b2018-09-10 12:26:21 -0700163 enumsByName: make(map[protoreflect.FullName]*Enum),
Joe Tsai7762ec22019-08-20 20:10:23 -0700164 messagesByName: make(map[protoreflect.FullName]*Message),
Damien Neil1fa8ab02018-09-27 15:51:05 -0700165 opts: opts,
Damien Neil220c2022018-08-15 11:24:18 -0700166 }
167
Damien Neil082ce922018-09-06 10:23:53 -0700168 packageNames := make(map[string]GoPackageName) // filename -> package name
169 importPaths := make(map[string]GoImportPath) // filename -> import path
Joe Tsai3e802492019-09-07 13:06:27 -0700170 mfiles := make(map[string]bool) // filename set
Damien Neil082ce922018-09-06 10:23:53 -0700171 var packageImportPath GoImportPath
Damien Neil220c2022018-08-15 11:24:18 -0700172 for _, param := range strings.Split(req.GetParameter(), ",") {
173 var value string
174 if i := strings.Index(param, "="); i >= 0 {
175 value = param[i+1:]
176 param = param[0:i]
177 }
178 switch param {
179 case "":
180 // Ignore.
Damien Neil220c2022018-08-15 11:24:18 -0700181 case "import_path":
Damien Neil082ce922018-09-06 10:23:53 -0700182 packageImportPath = GoImportPath(value)
Damien Neil220c2022018-08-15 11:24:18 -0700183 case "paths":
Damien Neil082ce922018-09-06 10:23:53 -0700184 switch value {
185 case "import":
186 gen.pathType = pathTypeImport
187 case "source_relative":
188 gen.pathType = pathTypeSourceRelative
189 default:
190 return nil, fmt.Errorf(`unknown path type %q: want "import" or "source_relative"`, value)
191 }
Damien Neil220c2022018-08-15 11:24:18 -0700192 case "annotate_code":
Damien Neil162c1272018-10-04 12:42:37 -0700193 switch value {
194 case "true", "":
195 gen.annotateCode = true
196 case "false":
197 default:
198 return nil, fmt.Errorf(`bad value for parameter %q: want "true" or "false"`, param)
199 }
Damien Neil220c2022018-08-15 11:24:18 -0700200 default:
Damien Neil3cf6e622018-09-11 13:53:14 -0700201 if param[0] == 'M' {
202 importPaths[param[1:]] = GoImportPath(value)
Joe Tsai3e802492019-09-07 13:06:27 -0700203 mfiles[param[1:]] = true
Damien Neil3cf6e622018-09-11 13:53:14 -0700204 continue
Damien Neil220c2022018-08-15 11:24:18 -0700205 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700206 if opts.ParamFunc != nil {
207 if err := opts.ParamFunc(param, value); err != nil {
208 return nil, err
209 }
210 }
Damien Neil082ce922018-09-06 10:23:53 -0700211 }
212 }
213
214 // Figure out the import path and package name for each file.
215 //
216 // The rules here are complicated and have grown organically over time.
217 // Interactions between different ways of specifying package information
218 // may be surprising.
219 //
220 // The recommended approach is to include a go_package option in every
221 // .proto source file specifying the full import path of the Go package
222 // associated with this file.
223 //
Joe Tsai8d30bbe2019-05-16 15:53:25 -0700224 // option go_package = "google.golang.org/protobuf/types/known/anypb";
Damien Neil082ce922018-09-06 10:23:53 -0700225 //
226 // Build systems which want to exert full control over import paths may
227 // specify M<filename>=<import_path> flags.
228 //
229 // Other approaches are not recommend.
230 generatedFileNames := make(map[string]bool)
231 for _, name := range gen.Request.FileToGenerate {
232 generatedFileNames[name] = true
233 }
234 // We need to determine the import paths before the package names,
235 // because the Go package name for a file is sometimes derived from
236 // different file in the same package.
237 packageNameForImportPath := make(map[GoImportPath]GoPackageName)
238 for _, fdesc := range gen.Request.ProtoFile {
239 filename := fdesc.GetName()
240 packageName, importPath := goPackageOption(fdesc)
241 switch {
242 case importPaths[filename] != "":
243 // Command line: M=foo.proto=quux/bar
244 //
245 // Explicit mapping of source file to import path.
246 case generatedFileNames[filename] && packageImportPath != "":
247 // Command line: import_path=quux/bar
248 //
249 // The import_path flag sets the import path for every file that
250 // we generate code for.
251 importPaths[filename] = packageImportPath
252 case importPath != "":
253 // Source file: option go_package = "quux/bar";
254 //
255 // The go_package option sets the import path. Most users should use this.
256 importPaths[filename] = importPath
257 default:
258 // Source filename.
259 //
260 // Last resort when nothing else is available.
261 importPaths[filename] = GoImportPath(path.Dir(filename))
262 }
263 if packageName != "" {
264 packageNameForImportPath[importPaths[filename]] = packageName
265 }
266 }
267 for _, fdesc := range gen.Request.ProtoFile {
268 filename := fdesc.GetName()
Joe Tsai3e802492019-09-07 13:06:27 -0700269 packageName, importPath := goPackageOption(fdesc)
Damien Neil082ce922018-09-06 10:23:53 -0700270 defaultPackageName := packageNameForImportPath[importPaths[filename]]
271 switch {
272 case packageName != "":
Joe Tsai3e802492019-09-07 13:06:27 -0700273 // TODO: For the "M" command-line argument, this means that the
274 // package name can be derived from the go_package option.
275 // Go package information should either consistently come from the
276 // command-line or the .proto source file, but not both.
277 // See how to make this consistent.
278
Damien Neil082ce922018-09-06 10:23:53 -0700279 // Source file: option go_package = "quux/bar";
280 packageNames[filename] = packageName
281 case defaultPackageName != "":
282 // A go_package option in another file in the same package.
283 //
284 // This is a poor choice in general, since every source file should
285 // contain a go_package option. Supported mainly for historical
286 // compatibility.
287 packageNames[filename] = defaultPackageName
288 case generatedFileNames[filename] && packageImportPath != "":
289 // Command line: import_path=quux/bar
290 packageNames[filename] = cleanPackageName(path.Base(string(packageImportPath)))
291 case fdesc.GetPackage() != "":
292 // Source file: package quux.bar;
293 packageNames[filename] = cleanPackageName(fdesc.GetPackage())
294 default:
295 // Source filename.
296 packageNames[filename] = cleanPackageName(baseName(filename))
297 }
Joe Tsai3e802492019-09-07 13:06:27 -0700298
299 goPkgOpt := string(importPaths[filename])
300 if path.Base(string(goPkgOpt)) != string(packageNames[filename]) {
301 goPkgOpt += ";" + string(packageNames[filename])
302 }
303 switch {
304 case packageImportPath != "":
305 // Command line: import_path=quux/bar
306 log.Printf("WARNING: Deprecated use of the 'import_path' command-line argument. In %q, please specify:\n"+
307 "\toption go_package = %q;\n"+
308 "A future release of protoc-gen-go will no longer support the 'import_path' argument.\n"+
Joe Tsai222a0002020-02-24 11:21:30 -0800309 "See "+goPackageDocURL+" for more information.\n"+
Joe Tsai3e802492019-09-07 13:06:27 -0700310 "\n", fdesc.GetName(), goPkgOpt)
311 case mfiles[filename]:
312 // Command line: M=foo.proto=quux/bar
313 case packageName != "" && importPath == "":
314 // Source file: option go_package = "quux";
315 log.Printf("WARNING: Deprecated use of 'go_package' option without a full import path in %q, please specify:\n"+
316 "\toption go_package = %q;\n"+
317 "A future release of protoc-gen-go will require the import path be specified.\n"+
Joe Tsai222a0002020-02-24 11:21:30 -0800318 "See "+goPackageDocURL+" for more information.\n"+
Joe Tsai3e802492019-09-07 13:06:27 -0700319 "\n", fdesc.GetName(), goPkgOpt)
320 case packageName == "" && importPath == "":
321 // No Go package information provided.
322 log.Printf("WARNING: Missing 'go_package' option in %q, please specify:\n"+
323 "\toption go_package = %q;\n"+
324 "A future release of protoc-gen-go will require this be specified.\n"+
Joe Tsai222a0002020-02-24 11:21:30 -0800325 "See "+goPackageDocURL+" for more information.\n"+
Joe Tsai3e802492019-09-07 13:06:27 -0700326 "\n", fdesc.GetName(), goPkgOpt)
327 }
Damien Neil082ce922018-09-06 10:23:53 -0700328 }
329
330 // Consistency check: Every file with the same Go import path should have
331 // the same Go package name.
332 packageFiles := make(map[GoImportPath][]string)
333 for filename, importPath := range importPaths {
Damien Neilbbbd38f2018-10-08 16:36:49 -0700334 if _, ok := packageNames[filename]; !ok {
335 // Skip files mentioned in a M<file>=<import_path> parameter
336 // but which do not appear in the CodeGeneratorRequest.
337 continue
338 }
Damien Neil082ce922018-09-06 10:23:53 -0700339 packageFiles[importPath] = append(packageFiles[importPath], filename)
340 }
341 for importPath, filenames := range packageFiles {
342 for i := 1; i < len(filenames); i++ {
343 if a, b := packageNames[filenames[0]], packageNames[filenames[i]]; a != b {
344 return nil, fmt.Errorf("Go package %v has inconsistent names %v (%v) and %v (%v)",
345 importPath, a, filenames[0], b, filenames[i])
346 }
Damien Neil220c2022018-08-15 11:24:18 -0700347 }
348 }
349
350 for _, fdesc := range gen.Request.ProtoFile {
Damien Neil082ce922018-09-06 10:23:53 -0700351 filename := fdesc.GetName()
Joe Tsai2cec4842019-08-20 20:14:19 -0700352 if gen.FilesByPath[filename] != nil {
Damien Neil082ce922018-09-06 10:23:53 -0700353 return nil, fmt.Errorf("duplicate file name: %q", filename)
354 }
355 f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename])
Damien Neilabc6fc12018-08-23 14:39:30 -0700356 if err != nil {
357 return nil, err
358 }
Damien Neil220c2022018-08-15 11:24:18 -0700359 gen.Files = append(gen.Files, f)
Joe Tsai2cec4842019-08-20 20:14:19 -0700360 gen.FilesByPath[filename] = f
Damien Neil220c2022018-08-15 11:24:18 -0700361 }
Damien Neil082ce922018-09-06 10:23:53 -0700362 for _, filename := range gen.Request.FileToGenerate {
Joe Tsai2cec4842019-08-20 20:14:19 -0700363 f, ok := gen.FilesByPath[filename]
Damien Neil220c2022018-08-15 11:24:18 -0700364 if !ok {
Damien Neil082ce922018-09-06 10:23:53 -0700365 return nil, fmt.Errorf("no descriptor for generated file: %v", filename)
Damien Neil220c2022018-08-15 11:24:18 -0700366 }
367 f.Generate = true
368 }
369 return gen, nil
370}
371
372// Error records an error in code generation. The generator will report the
373// error back to protoc and will not produce output.
374func (gen *Plugin) Error(err error) {
375 if gen.err == nil {
376 gen.err = err
377 }
378}
379
380// Response returns the generator output.
381func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse {
382 resp := &pluginpb.CodeGeneratorResponse{}
383 if gen.err != nil {
Damien Neila8a2cea2019-07-10 16:17:16 -0700384 resp.Error = proto.String(gen.err.Error())
Damien Neil220c2022018-08-15 11:24:18 -0700385 return resp
386 }
Damien Neil162c1272018-10-04 12:42:37 -0700387 for _, g := range gen.genFiles {
Damien Neil7bf3ce22018-12-21 15:54:06 -0800388 if g.skip {
389 continue
390 }
391 content, err := g.Content()
Damien Neilc7d07d92018-08-22 13:46:02 -0700392 if err != nil {
393 return &pluginpb.CodeGeneratorResponse{
Damien Neila8a2cea2019-07-10 16:17:16 -0700394 Error: proto.String(err.Error()),
Damien Neilc7d07d92018-08-22 13:46:02 -0700395 }
396 }
Damien Neil220c2022018-08-15 11:24:18 -0700397 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
Damien Neila8a2cea2019-07-10 16:17:16 -0700398 Name: proto.String(g.filename),
399 Content: proto.String(string(content)),
Damien Neil220c2022018-08-15 11:24:18 -0700400 })
Damien Neil162c1272018-10-04 12:42:37 -0700401 if gen.annotateCode && strings.HasSuffix(g.filename, ".go") {
402 meta, err := g.metaFile(content)
403 if err != nil {
404 return &pluginpb.CodeGeneratorResponse{
Damien Neila8a2cea2019-07-10 16:17:16 -0700405 Error: proto.String(err.Error()),
Damien Neil162c1272018-10-04 12:42:37 -0700406 }
407 }
408 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
Damien Neila8a2cea2019-07-10 16:17:16 -0700409 Name: proto.String(g.filename + ".meta"),
410 Content: proto.String(meta),
Damien Neil162c1272018-10-04 12:42:37 -0700411 })
412 }
Damien Neil220c2022018-08-15 11:24:18 -0700413 }
414 return resp
415}
416
Damien Neilc7d07d92018-08-22 13:46:02 -0700417// A File describes a .proto source file.
Damien Neil220c2022018-08-15 11:24:18 -0700418type File struct {
Damien Neil7779e052018-09-07 14:14:06 -0700419 Desc protoreflect.FileDescriptor
Joe Tsaie1f8d502018-11-26 18:55:29 -0800420 Proto *descriptorpb.FileDescriptorProto
Damien Neil220c2022018-08-15 11:24:18 -0700421
Joe Tsaib6405bd2018-11-15 14:44:37 -0800422 GoDescriptorIdent GoIdent // name of Go variable for the file descriptor
423 GoPackageName GoPackageName // name of this file's Go package
424 GoImportPath GoImportPath // import path of this file's Go package
Joe Tsai7762ec22019-08-20 20:10:23 -0700425
426 Enums []*Enum // top-level enum declarations
427 Messages []*Message // top-level message declarations
428 Extensions []*Extension // top-level extension declarations
429 Services []*Service // top-level service declarations
430
431 Generate bool // true if we should generate code for this file
Damien Neil082ce922018-09-06 10:23:53 -0700432
433 // GeneratedFilenamePrefix is used to construct filenames for generated
434 // files associated with this source file.
435 //
436 // For example, the source file "dir/foo.proto" might have a filename prefix
437 // of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go".
438 GeneratedFilenamePrefix string
Damien Neilba1159f2018-10-17 12:53:18 -0700439
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700440 comments map[pathKey]CommentSet
Damien Neil220c2022018-08-15 11:24:18 -0700441}
442
Joe Tsaie1f8d502018-11-26 18:55:29 -0800443func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) {
444 desc, err := protodesc.NewFile(p, gen.fileReg)
Damien Neilabc6fc12018-08-23 14:39:30 -0700445 if err != nil {
446 return nil, fmt.Errorf("invalid FileDescriptorProto %q: %v", p.GetName(), err)
447 }
Damien Neilc8268852019-10-08 13:28:53 -0700448 if err := gen.fileReg.RegisterFile(desc); err != nil {
Damien Neilabc6fc12018-08-23 14:39:30 -0700449 return nil, fmt.Errorf("cannot register descriptor %q: %v", p.GetName(), err)
450 }
Damien Neilc7d07d92018-08-22 13:46:02 -0700451 f := &File{
Damien Neil082ce922018-09-06 10:23:53 -0700452 Desc: desc,
Damien Neil7779e052018-09-07 14:14:06 -0700453 Proto: p,
Damien Neil082ce922018-09-06 10:23:53 -0700454 GoPackageName: packageName,
455 GoImportPath: importPath,
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700456 comments: make(map[pathKey]CommentSet),
Damien Neil220c2022018-08-15 11:24:18 -0700457 }
Damien Neil082ce922018-09-06 10:23:53 -0700458
459 // Determine the prefix for generated Go files.
460 prefix := p.GetName()
461 if ext := path.Ext(prefix); ext == ".proto" || ext == ".protodevel" {
462 prefix = prefix[:len(prefix)-len(ext)]
463 }
464 if gen.pathType == pathTypeImport {
465 // If paths=import (the default) and the file contains a go_package option
466 // with a full import path, the output filename is derived from the Go import
467 // path.
468 //
469 // Pass the paths=source_relative flag to always derive the output filename
470 // from the input filename instead.
471 if _, importPath := goPackageOption(p); importPath != "" {
472 prefix = path.Join(string(importPath), path.Base(prefix))
473 }
474 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800475 f.GoDescriptorIdent = GoIdent{
Joe Tsai2e7817f2019-08-23 12:18:57 -0700476 GoName: "File_" + strs.GoSanitized(p.GetName()),
Joe Tsaib6405bd2018-11-15 14:44:37 -0800477 GoImportPath: f.GoImportPath,
478 }
Damien Neil082ce922018-09-06 10:23:53 -0700479 f.GeneratedFilenamePrefix = prefix
480
Damien Neilba1159f2018-10-17 12:53:18 -0700481 for _, loc := range p.GetSourceCodeInfo().GetLocation() {
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700482 // Descriptors declarations are guaranteed to have unique comment sets.
483 // Other locations may not be unique, but we don't use them.
484 var leadingDetached []Comments
485 for _, s := range loc.GetLeadingDetachedComments() {
486 leadingDetached = append(leadingDetached, Comments(s))
487 }
488 f.comments[newPathKey(loc.Path)] = CommentSet{
489 LeadingDetached: leadingDetached,
490 Leading: Comments(loc.GetLeadingComments()),
491 Trailing: Comments(loc.GetTrailingComments()),
492 }
Damien Neilba1159f2018-10-17 12:53:18 -0700493 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700494 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ {
495 f.Enums = append(f.Enums, newEnum(gen, f, nil, eds.Get(i)))
Damien Neilc7d07d92018-08-22 13:46:02 -0700496 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700497 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ {
498 f.Messages = append(f.Messages, newMessage(gen, f, nil, mds.Get(i)))
Damien Neil46abb572018-09-07 12:45:37 -0700499 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700500 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ {
501 f.Extensions = append(f.Extensions, newField(gen, f, nil, xds.Get(i)))
Damien Neil993c04d2018-09-14 15:41:11 -0700502 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700503 for i, sds := 0, desc.Services(); i < sds.Len(); i++ {
504 f.Services = append(f.Services, newService(gen, f, sds.Get(i)))
Damien Neil2dc67182018-09-21 15:03:34 -0700505 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700506 for _, message := range f.Messages {
Joe Tsai7762ec22019-08-20 20:10:23 -0700507 if err := message.resolveDependencies(gen); err != nil {
Damien Neil993c04d2018-09-14 15:41:11 -0700508 return nil, err
509 }
510 }
511 for _, extension := range f.Extensions {
Joe Tsai7762ec22019-08-20 20:10:23 -0700512 if err := extension.resolveDependencies(gen); err != nil {
Damien Neil993c04d2018-09-14 15:41:11 -0700513 return nil, err
514 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700515 }
Damien Neil2dc67182018-09-21 15:03:34 -0700516 for _, service := range f.Services {
517 for _, method := range service.Methods {
Joe Tsai7762ec22019-08-20 20:10:23 -0700518 if err := method.resolveDependencies(gen); err != nil {
Damien Neil2dc67182018-09-21 15:03:34 -0700519 return nil, err
520 }
521 }
522 }
Damien Neilabc6fc12018-08-23 14:39:30 -0700523 return f, nil
Damien Neilc7d07d92018-08-22 13:46:02 -0700524}
525
Koichi Shiraishiea2076d2019-05-24 18:24:29 +0900526func (f *File) location(idxPath ...int32) Location {
Damien Neil162c1272018-10-04 12:42:37 -0700527 return Location{
528 SourceFile: f.Desc.Path(),
Koichi Shiraishiea2076d2019-05-24 18:24:29 +0900529 Path: idxPath,
Damien Neil162c1272018-10-04 12:42:37 -0700530 }
531}
532
Damien Neil082ce922018-09-06 10:23:53 -0700533// goPackageOption interprets a file's go_package option.
534// If there is no go_package, it returns ("", "").
535// If there's a simple name, it returns (pkg, "").
536// If the option implies an import path, it returns (pkg, impPath).
Joe Tsaie1f8d502018-11-26 18:55:29 -0800537func goPackageOption(d *descriptorpb.FileDescriptorProto) (pkg GoPackageName, impPath GoImportPath) {
Damien Neil082ce922018-09-06 10:23:53 -0700538 opt := d.GetOptions().GetGoPackage()
539 if opt == "" {
540 return "", ""
541 }
Joe Tsai3e802492019-09-07 13:06:27 -0700542 rawPkg, impPath := goPackageOptionRaw(opt)
543 pkg = cleanPackageName(rawPkg)
544 if string(pkg) != rawPkg && impPath != "" {
545 log.Printf("WARNING: Malformed 'go_package' option in %q, please specify:\n"+
546 "\toption go_package = %q;\n"+
547 "A future release of protoc-gen-go will reject this.\n"+
Joe Tsai222a0002020-02-24 11:21:30 -0800548 "See "+goPackageDocURL+" for more information.\n"+
Joe Tsai3e802492019-09-07 13:06:27 -0700549 "\n", d.GetName(), string(impPath)+";"+string(pkg))
550 }
551 return pkg, impPath
552}
553func goPackageOptionRaw(opt string) (rawPkg string, impPath GoImportPath) {
Damien Neil082ce922018-09-06 10:23:53 -0700554 // A semicolon-delimited suffix delimits the import path and package name.
555 if i := strings.Index(opt, ";"); i >= 0 {
Joe Tsai3e802492019-09-07 13:06:27 -0700556 return opt[i+1:], GoImportPath(opt[:i])
Damien Neil082ce922018-09-06 10:23:53 -0700557 }
558 // The presence of a slash implies there's an import path.
559 if i := strings.LastIndex(opt, "/"); i >= 0 {
Joe Tsai3e802492019-09-07 13:06:27 -0700560 return opt[i+1:], GoImportPath(opt)
Damien Neil082ce922018-09-06 10:23:53 -0700561 }
Joe Tsai3e802492019-09-07 13:06:27 -0700562 return opt, ""
Damien Neil082ce922018-09-06 10:23:53 -0700563}
564
Joe Tsai7762ec22019-08-20 20:10:23 -0700565// An Enum describes an enum.
566type Enum struct {
567 Desc protoreflect.EnumDescriptor
568
569 GoIdent GoIdent // name of the generated Go type
570
571 Values []*EnumValue // enum value declarations
572
573 Location Location // location of this enum
574 Comments CommentSet // comments associated with this enum
575}
576
577func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum {
578 var loc Location
579 if parent != nil {
580 loc = parent.Location.appendPath(fieldnum.DescriptorProto_EnumType, int32(desc.Index()))
581 } else {
582 loc = f.location(fieldnum.FileDescriptorProto_EnumType, int32(desc.Index()))
583 }
584 enum := &Enum{
585 Desc: desc,
586 GoIdent: newGoIdent(f, desc),
587 Location: loc,
588 Comments: f.comments[newPathKey(loc.Path)],
589 }
590 gen.enumsByName[desc.FullName()] = enum
591 for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ {
592 enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, vds.Get(i)))
593 }
594 return enum
595}
596
597// An EnumValue describes an enum value.
598type EnumValue struct {
599 Desc protoreflect.EnumValueDescriptor
600
601 GoIdent GoIdent // name of the generated Go declaration
602
Joe Tsai4df99fd2019-08-20 22:26:16 -0700603 Parent *Enum // enum in which this value is declared
604
Joe Tsai7762ec22019-08-20 20:10:23 -0700605 Location Location // location of this enum value
606 Comments CommentSet // comments associated with this enum value
607}
608
609func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue {
610 // A top-level enum value's name is: EnumName_ValueName
611 // An enum value contained in a message is: MessageName_ValueName
612 //
Joe Tsaief6e5242019-08-21 00:55:36 -0700613 // For historical reasons, enum value names are not camel-cased.
Joe Tsai7762ec22019-08-20 20:10:23 -0700614 parentIdent := enum.GoIdent
615 if message != nil {
616 parentIdent = message.GoIdent
617 }
618 name := parentIdent.GoName + "_" + string(desc.Name())
619 loc := enum.Location.appendPath(fieldnum.EnumDescriptorProto_Value, int32(desc.Index()))
620 return &EnumValue{
621 Desc: desc,
622 GoIdent: f.GoImportPath.Ident(name),
Joe Tsai4df99fd2019-08-20 22:26:16 -0700623 Parent: enum,
Joe Tsai7762ec22019-08-20 20:10:23 -0700624 Location: loc,
625 Comments: f.comments[newPathKey(loc.Path)],
626 }
627}
628
Damien Neilc7d07d92018-08-22 13:46:02 -0700629// A Message describes a message.
630type Message struct {
Damien Neilabc6fc12018-08-23 14:39:30 -0700631 Desc protoreflect.MessageDescriptor
Damien Neilc7d07d92018-08-22 13:46:02 -0700632
Joe Tsai7762ec22019-08-20 20:10:23 -0700633 GoIdent GoIdent // name of the generated Go type
634
635 Fields []*Field // message field declarations
636 Oneofs []*Oneof // message oneof declarations
637
Damien Neil993c04d2018-09-14 15:41:11 -0700638 Enums []*Enum // nested enum declarations
Joe Tsai7762ec22019-08-20 20:10:23 -0700639 Messages []*Message // nested message declarations
Damien Neil993c04d2018-09-14 15:41:11 -0700640 Extensions []*Extension // nested extension declarations
Joe Tsai7762ec22019-08-20 20:10:23 -0700641
642 Location Location // location of this message
643 Comments CommentSet // comments associated with this message
Damien Neilc7d07d92018-08-22 13:46:02 -0700644}
645
Damien Neil1fa78d82018-09-13 13:12:36 -0700646func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
Damien Neil162c1272018-10-04 12:42:37 -0700647 var loc Location
Damien Neilcab8dfe2018-09-06 14:51:28 -0700648 if parent != nil {
Joe Tsaica46d8c2019-03-20 16:51:09 -0700649 loc = parent.Location.appendPath(fieldnum.DescriptorProto_NestedType, int32(desc.Index()))
Damien Neilcab8dfe2018-09-06 14:51:28 -0700650 } else {
Joe Tsaica46d8c2019-03-20 16:51:09 -0700651 loc = f.location(fieldnum.FileDescriptorProto_MessageType, int32(desc.Index()))
Damien Neilcab8dfe2018-09-06 14:51:28 -0700652 }
Damien Neil46abb572018-09-07 12:45:37 -0700653 message := &Message{
Damien Neil162c1272018-10-04 12:42:37 -0700654 Desc: desc,
655 GoIdent: newGoIdent(f, desc),
656 Location: loc,
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700657 Comments: f.comments[newPathKey(loc.Path)],
Damien Neilc7d07d92018-08-22 13:46:02 -0700658 }
Damien Neil658051b2018-09-10 12:26:21 -0700659 gen.messagesByName[desc.FullName()] = message
Joe Tsai7762ec22019-08-20 20:10:23 -0700660 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ {
661 message.Enums = append(message.Enums, newEnum(gen, f, message, eds.Get(i)))
Damien Neilc7d07d92018-08-22 13:46:02 -0700662 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700663 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ {
664 message.Messages = append(message.Messages, newMessage(gen, f, message, mds.Get(i)))
Damien Neil46abb572018-09-07 12:45:37 -0700665 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700666 for i, fds := 0, desc.Fields(); i < fds.Len(); i++ {
667 message.Fields = append(message.Fields, newField(gen, f, message, fds.Get(i)))
Damien Neil1fa78d82018-09-13 13:12:36 -0700668 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700669 for i, ods := 0, desc.Oneofs(); i < ods.Len(); i++ {
670 message.Oneofs = append(message.Oneofs, newOneof(gen, f, message, ods.Get(i)))
Damien Neil658051b2018-09-10 12:26:21 -0700671 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700672 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ {
673 message.Extensions = append(message.Extensions, newField(gen, f, message, xds.Get(i)))
674 }
675
676 // Resolve local references between fields and oneofs.
677 for _, field := range message.Fields {
678 if od := field.Desc.ContainingOneof(); od != nil {
679 oneof := message.Oneofs[od.Index()]
680 field.Oneof = oneof
681 oneof.Fields = append(oneof.Fields, field)
682 }
Damien Neil993c04d2018-09-14 15:41:11 -0700683 }
Damien Neil658051b2018-09-10 12:26:21 -0700684
685 // Field name conflict resolution.
686 //
687 // We assume well-known method names that may be attached to a generated
688 // message type, as well as a 'Get*' method for each field. For each
689 // field in turn, we add _s to its name until there are no conflicts.
690 //
691 // Any change to the following set of method names is a potential
692 // incompatible API change because it may change generated field names.
693 //
694 // TODO: If we ever support a 'go_name' option to set the Go name of a
695 // field, we should consider dropping this entirely. The conflict
696 // resolution algorithm is subtle and surprising (changing the order
697 // in which fields appear in the .proto source file can change the
698 // names of fields in generated code), and does not adapt well to
699 // adding new per-field methods such as setters.
700 usedNames := map[string]bool{
701 "Reset": true,
702 "String": true,
703 "ProtoMessage": true,
704 "Marshal": true,
705 "Unmarshal": true,
706 "ExtensionRangeArray": true,
707 "ExtensionMap": true,
708 "Descriptor": true,
709 }
Joe Tsaid6966a42019-01-08 10:59:34 -0800710 makeNameUnique := func(name string, hasGetter bool) string {
711 for usedNames[name] || (hasGetter && usedNames["Get"+name]) {
Damien Neil658051b2018-09-10 12:26:21 -0700712 name += "_"
713 }
714 usedNames[name] = true
Joe Tsaid6966a42019-01-08 10:59:34 -0800715 usedNames["Get"+name] = hasGetter
Damien Neil658051b2018-09-10 12:26:21 -0700716 return name
717 }
718 for _, field := range message.Fields {
Joe Tsaid6966a42019-01-08 10:59:34 -0800719 field.GoName = makeNameUnique(field.GoName, true)
Joe Tsaief6e5242019-08-21 00:55:36 -0700720 field.GoIdent.GoName = message.GoIdent.GoName + "_" + field.GoName
721 if field.Oneof != nil && field.Oneof.Fields[0] == field {
722 // Make the name for a oneof unique as well. For historical reasons,
723 // this assumes that a getter method is not generated for oneofs.
724 // This is incorrect, but fixing it breaks existing code.
725 field.Oneof.GoName = makeNameUnique(field.Oneof.GoName, false)
726 field.Oneof.GoIdent.GoName = message.GoIdent.GoName + "_" + field.Oneof.GoName
727 }
728 }
729
730 // Oneof field name conflict resolution.
731 //
732 // This conflict resolution is incomplete as it does not consider collisions
733 // with other oneof field types, but fixing it breaks existing code.
734 for _, field := range message.Fields {
Joe Tsaid24bc722019-04-15 23:39:09 -0700735 if field.Oneof != nil {
Joe Tsaief6e5242019-08-21 00:55:36 -0700736 Loop:
737 for {
738 for _, nestedMessage := range message.Messages {
739 if nestedMessage.GoIdent == field.GoIdent {
740 field.GoIdent.GoName += "_"
741 continue Loop
742 }
743 }
744 for _, nestedEnum := range message.Enums {
745 if nestedEnum.GoIdent == field.GoIdent {
746 field.GoIdent.GoName += "_"
747 continue Loop
748 }
749 }
750 break Loop
Damien Neil1fa78d82018-09-13 13:12:36 -0700751 }
752 }
Damien Neil658051b2018-09-10 12:26:21 -0700753 }
754
Damien Neil1fa78d82018-09-13 13:12:36 -0700755 return message
Damien Neil658051b2018-09-10 12:26:21 -0700756}
757
Joe Tsai7762ec22019-08-20 20:10:23 -0700758func (message *Message) resolveDependencies(gen *Plugin) error {
Damien Neil0bd5a382018-09-13 15:07:10 -0700759 for _, field := range message.Fields {
Joe Tsai7762ec22019-08-20 20:10:23 -0700760 if err := field.resolveDependencies(gen); err != nil {
Damien Neil0bd5a382018-09-13 15:07:10 -0700761 return err
762 }
763 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700764 for _, message := range message.Messages {
765 if err := message.resolveDependencies(gen); err != nil {
766 return err
767 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700768 }
Damien Neil993c04d2018-09-14 15:41:11 -0700769 for _, extension := range message.Extensions {
Joe Tsai7762ec22019-08-20 20:10:23 -0700770 if err := extension.resolveDependencies(gen); err != nil {
Damien Neil993c04d2018-09-14 15:41:11 -0700771 return err
772 }
773 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700774 return nil
775}
776
Damien Neil658051b2018-09-10 12:26:21 -0700777// A Field describes a message field.
778type Field struct {
779 Desc protoreflect.FieldDescriptor
780
Damien Neil1fa78d82018-09-13 13:12:36 -0700781 // GoName is the base name of this field's Go field and methods.
Damien Neil658051b2018-09-10 12:26:21 -0700782 // For code generated by protoc-gen-go, this means a field named
Damien Neil1fa78d82018-09-13 13:12:36 -0700783 // '{{GoName}}' and a getter method named 'Get{{GoName}}'.
Joe Tsaief6e5242019-08-21 00:55:36 -0700784 GoName string // e.g., "FieldName"
785
786 // GoIdent is the base name of a top-level declaration for this field.
787 // For code generated by protoc-gen-go, this means a wrapper type named
788 // '{{GoIdent}}' for members fields of a oneof, and a variable named
789 // 'E_{{GoIdent}}' for extension fields.
790 GoIdent GoIdent // e.g., "MessageName_FieldName"
Damien Neil658051b2018-09-10 12:26:21 -0700791
Joe Tsai7762ec22019-08-20 20:10:23 -0700792 Parent *Message // message in which this field is declared; nil if top-level extension
793 Oneof *Oneof // containing oneof; nil if not part of a oneof
794 Extendee *Message // extended message for extension fields; nil otherwise
795
796 Enum *Enum // type for enum fields; nil otherwise
797 Message *Message // type for message or group fields; nil otherwise
798
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700799 Location Location // location of this field
800 Comments CommentSet // comments associated with this field
Damien Neil658051b2018-09-10 12:26:21 -0700801}
802
Damien Neil1fa78d82018-09-13 13:12:36 -0700803func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
Damien Neil162c1272018-10-04 12:42:37 -0700804 var loc Location
Damien Neil993c04d2018-09-14 15:41:11 -0700805 switch {
Joe Tsaiac31a352019-05-13 14:32:56 -0700806 case desc.IsExtension() && message == nil:
Joe Tsaica46d8c2019-03-20 16:51:09 -0700807 loc = f.location(fieldnum.FileDescriptorProto_Extension, int32(desc.Index()))
Joe Tsaiac31a352019-05-13 14:32:56 -0700808 case desc.IsExtension() && message != nil:
Joe Tsaica46d8c2019-03-20 16:51:09 -0700809 loc = message.Location.appendPath(fieldnum.DescriptorProto_Extension, int32(desc.Index()))
Damien Neil993c04d2018-09-14 15:41:11 -0700810 default:
Joe Tsaica46d8c2019-03-20 16:51:09 -0700811 loc = message.Location.appendPath(fieldnum.DescriptorProto_Field, int32(desc.Index()))
Damien Neil993c04d2018-09-14 15:41:11 -0700812 }
Joe Tsai2e7817f2019-08-23 12:18:57 -0700813 camelCased := strs.GoCamelCase(string(desc.Name()))
Joe Tsaief6e5242019-08-21 00:55:36 -0700814 var parentPrefix string
815 if message != nil {
816 parentPrefix = message.GoIdent.GoName + "_"
817 }
Damien Neil658051b2018-09-10 12:26:21 -0700818 field := &Field{
Joe Tsaief6e5242019-08-21 00:55:36 -0700819 Desc: desc,
820 GoName: camelCased,
821 GoIdent: GoIdent{
822 GoImportPath: f.GoImportPath,
823 GoName: parentPrefix + camelCased,
824 },
Joe Tsaid24bc722019-04-15 23:39:09 -0700825 Parent: message,
826 Location: loc,
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700827 Comments: f.comments[newPathKey(loc.Path)],
Damien Neil658051b2018-09-10 12:26:21 -0700828 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700829 return field
Damien Neil0bd5a382018-09-13 15:07:10 -0700830}
831
Joe Tsai7762ec22019-08-20 20:10:23 -0700832func (field *Field) resolveDependencies(gen *Plugin) error {
Damien Neil0bd5a382018-09-13 15:07:10 -0700833 desc := field.Desc
Damien Neil658051b2018-09-10 12:26:21 -0700834 switch desc.Kind() {
Damien Neil658051b2018-09-10 12:26:21 -0700835 case protoreflect.EnumKind:
Joe Tsai7762ec22019-08-20 20:10:23 -0700836 name := field.Desc.Enum().FullName()
837 enum, ok := gen.enumsByName[name]
Damien Neil658051b2018-09-10 12:26:21 -0700838 if !ok {
Joe Tsai7762ec22019-08-20 20:10:23 -0700839 return fmt.Errorf("field %v: no descriptor for enum %v", desc.FullName(), name)
Damien Neil658051b2018-09-10 12:26:21 -0700840 }
Joe Tsaid24bc722019-04-15 23:39:09 -0700841 field.Enum = enum
Joe Tsai7762ec22019-08-20 20:10:23 -0700842 case protoreflect.MessageKind, protoreflect.GroupKind:
843 name := desc.Message().FullName()
844 message, ok := gen.messagesByName[name]
845 if !ok {
846 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name)
847 }
848 field.Message = message
Damien Neil658051b2018-09-10 12:26:21 -0700849 }
Joe Tsaiac31a352019-05-13 14:32:56 -0700850 if desc.IsExtension() {
Joe Tsai7762ec22019-08-20 20:10:23 -0700851 name := desc.ContainingMessage().FullName()
852 message, ok := gen.messagesByName[name]
Damien Neil993c04d2018-09-14 15:41:11 -0700853 if !ok {
Joe Tsai7762ec22019-08-20 20:10:23 -0700854 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name)
Damien Neil993c04d2018-09-14 15:41:11 -0700855 }
Joe Tsaid24bc722019-04-15 23:39:09 -0700856 field.Extendee = message
Damien Neil993c04d2018-09-14 15:41:11 -0700857 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700858 return nil
Damien Neil46abb572018-09-07 12:45:37 -0700859}
860
Joe Tsai7762ec22019-08-20 20:10:23 -0700861// A Oneof describes a message oneof.
Damien Neil1fa78d82018-09-13 13:12:36 -0700862type Oneof struct {
863 Desc protoreflect.OneofDescriptor
864
Joe Tsaief6e5242019-08-21 00:55:36 -0700865 // GoName is the base name of this oneof's Go field and methods.
866 // For code generated by protoc-gen-go, this means a field named
867 // '{{GoName}}' and a getter method named 'Get{{GoName}}'.
868 GoName string // e.g., "OneofName"
869
870 // GoIdent is the base name of a top-level declaration for this oneof.
871 GoIdent GoIdent // e.g., "MessageName_OneofName"
Joe Tsai7762ec22019-08-20 20:10:23 -0700872
873 Parent *Message // message in which this oneof is declared
874
Joe Tsaid24bc722019-04-15 23:39:09 -0700875 Fields []*Field // fields that are part of this oneof
876
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700877 Location Location // location of this oneof
878 Comments CommentSet // comments associated with this oneof
Damien Neil1fa78d82018-09-13 13:12:36 -0700879}
880
881func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof {
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700882 loc := message.Location.appendPath(fieldnum.DescriptorProto_OneofDecl, int32(desc.Index()))
Joe Tsai2e7817f2019-08-23 12:18:57 -0700883 camelCased := strs.GoCamelCase(string(desc.Name()))
Joe Tsaief6e5242019-08-21 00:55:36 -0700884 parentPrefix := message.GoIdent.GoName + "_"
Damien Neil1fa78d82018-09-13 13:12:36 -0700885 return &Oneof{
Joe Tsaief6e5242019-08-21 00:55:36 -0700886 Desc: desc,
887 Parent: message,
888 GoName: camelCased,
889 GoIdent: GoIdent{
890 GoImportPath: f.GoImportPath,
891 GoName: parentPrefix + camelCased,
892 },
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700893 Location: loc,
894 Comments: f.comments[newPathKey(loc.Path)],
Damien Neil1fa78d82018-09-13 13:12:36 -0700895 }
896}
897
Joe Tsai7762ec22019-08-20 20:10:23 -0700898// Extension is an alias of Field for documentation.
899type Extension = Field
Damien Neil220c2022018-08-15 11:24:18 -0700900
Damien Neil2dc67182018-09-21 15:03:34 -0700901// A Service describes a service.
902type Service struct {
903 Desc protoreflect.ServiceDescriptor
904
Joe Tsai7762ec22019-08-20 20:10:23 -0700905 GoName string
906
907 Methods []*Method // service method declarations
Joe Tsaid24bc722019-04-15 23:39:09 -0700908
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700909 Location Location // location of this service
910 Comments CommentSet // comments associated with this service
Damien Neil2dc67182018-09-21 15:03:34 -0700911}
912
913func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700914 loc := f.location(fieldnum.FileDescriptorProto_Service, int32(desc.Index()))
Damien Neil2dc67182018-09-21 15:03:34 -0700915 service := &Service{
Damien Neil162c1272018-10-04 12:42:37 -0700916 Desc: desc,
Joe Tsai2e7817f2019-08-23 12:18:57 -0700917 GoName: strs.GoCamelCase(string(desc.Name())),
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700918 Location: loc,
919 Comments: f.comments[newPathKey(loc.Path)],
Damien Neil2dc67182018-09-21 15:03:34 -0700920 }
Joe Tsai7762ec22019-08-20 20:10:23 -0700921 for i, mds := 0, desc.Methods(); i < mds.Len(); i++ {
922 service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i)))
Damien Neil2dc67182018-09-21 15:03:34 -0700923 }
924 return service
925}
926
927// A Method describes a method in a service.
928type Method struct {
929 Desc protoreflect.MethodDescriptor
930
Joe Tsaid24bc722019-04-15 23:39:09 -0700931 GoName string
Joe Tsai7762ec22019-08-20 20:10:23 -0700932
933 Parent *Service // service in which this method is declared
934
Joe Tsaid24bc722019-04-15 23:39:09 -0700935 Input *Message
936 Output *Message
937
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700938 Location Location // location of this method
939 Comments CommentSet // comments associated with this method
Damien Neil2dc67182018-09-21 15:03:34 -0700940}
941
942func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method {
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700943 loc := service.Location.appendPath(fieldnum.ServiceDescriptorProto_Method, int32(desc.Index()))
Damien Neil2dc67182018-09-21 15:03:34 -0700944 method := &Method{
Joe Tsaid24bc722019-04-15 23:39:09 -0700945 Desc: desc,
Joe Tsai2e7817f2019-08-23 12:18:57 -0700946 GoName: strs.GoCamelCase(string(desc.Name())),
Joe Tsaid24bc722019-04-15 23:39:09 -0700947 Parent: service,
Joe Tsai70fdd5d2019-08-06 01:15:18 -0700948 Location: loc,
949 Comments: f.comments[newPathKey(loc.Path)],
Damien Neil2dc67182018-09-21 15:03:34 -0700950 }
951 return method
952}
953
Joe Tsai7762ec22019-08-20 20:10:23 -0700954func (method *Method) resolveDependencies(gen *Plugin) error {
Damien Neil2dc67182018-09-21 15:03:34 -0700955 desc := method.Desc
956
Joe Tsaid24bc722019-04-15 23:39:09 -0700957 inName := desc.Input().FullName()
Damien Neil2dc67182018-09-21 15:03:34 -0700958 in, ok := gen.messagesByName[inName]
959 if !ok {
960 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName)
961 }
Joe Tsaid24bc722019-04-15 23:39:09 -0700962 method.Input = in
Damien Neil2dc67182018-09-21 15:03:34 -0700963
Joe Tsaid24bc722019-04-15 23:39:09 -0700964 outName := desc.Output().FullName()
Damien Neil2dc67182018-09-21 15:03:34 -0700965 out, ok := gen.messagesByName[outName]
966 if !ok {
967 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName)
968 }
Joe Tsaid24bc722019-04-15 23:39:09 -0700969 method.Output = out
Damien Neil2dc67182018-09-21 15:03:34 -0700970
971 return nil
972}
973
Damien Neil7bf3ce22018-12-21 15:54:06 -0800974// A GeneratedFile is a generated file.
975type GeneratedFile struct {
976 gen *Plugin
977 skip bool
978 filename string
979 goImportPath GoImportPath
980 buf bytes.Buffer
981 packageNames map[GoImportPath]GoPackageName
982 usedPackageNames map[GoPackageName]bool
983 manualImports map[GoImportPath]bool
984 annotations map[string][]Location
985}
986
987// NewGeneratedFile creates a new generated file with the given filename
988// and import path.
989func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath) *GeneratedFile {
990 g := &GeneratedFile{
991 gen: gen,
992 filename: filename,
993 goImportPath: goImportPath,
994 packageNames: make(map[GoImportPath]GoPackageName),
995 usedPackageNames: make(map[GoPackageName]bool),
996 manualImports: make(map[GoImportPath]bool),
997 annotations: make(map[string][]Location),
998 }
Joe Tsai124c8122019-01-14 11:48:43 -0800999
1000 // All predeclared identifiers in Go are already used.
1001 for _, s := range types.Universe.Names() {
1002 g.usedPackageNames[GoPackageName(s)] = true
1003 }
1004
Damien Neil7bf3ce22018-12-21 15:54:06 -08001005 gen.genFiles = append(gen.genFiles, g)
1006 return g
1007}
1008
Damien Neil220c2022018-08-15 11:24:18 -07001009// P prints a line to the generated output. It converts each parameter to a
1010// string following the same rules as fmt.Print. It never inserts spaces
1011// between parameters.
Damien Neil220c2022018-08-15 11:24:18 -07001012func (g *GeneratedFile) P(v ...interface{}) {
1013 for _, x := range v {
Damien Neild9016772018-08-23 14:39:30 -07001014 switch x := x.(type) {
1015 case GoIdent:
Damien Neil46abb572018-09-07 12:45:37 -07001016 fmt.Fprint(&g.buf, g.QualifiedGoIdent(x))
Damien Neild9016772018-08-23 14:39:30 -07001017 default:
1018 fmt.Fprint(&g.buf, x)
1019 }
Damien Neil220c2022018-08-15 11:24:18 -07001020 }
1021 fmt.Fprintln(&g.buf)
1022}
1023
Damien Neil46abb572018-09-07 12:45:37 -07001024// QualifiedGoIdent returns the string to use for a Go identifier.
1025//
1026// If the identifier is from a different Go package than the generated file,
1027// the returned name will be qualified (package.name) and an import statement
1028// for the identifier's package will be included in the file.
1029func (g *GeneratedFile) QualifiedGoIdent(ident GoIdent) string {
1030 if ident.GoImportPath == g.goImportPath {
1031 return ident.GoName
1032 }
1033 if packageName, ok := g.packageNames[ident.GoImportPath]; ok {
1034 return string(packageName) + "." + ident.GoName
1035 }
1036 packageName := cleanPackageName(baseName(string(ident.GoImportPath)))
Joe Tsai124c8122019-01-14 11:48:43 -08001037 for i, orig := 1, packageName; g.usedPackageNames[packageName]; i++ {
Damien Neil46abb572018-09-07 12:45:37 -07001038 packageName = orig + GoPackageName(strconv.Itoa(i))
1039 }
1040 g.packageNames[ident.GoImportPath] = packageName
1041 g.usedPackageNames[packageName] = true
1042 return string(packageName) + "." + ident.GoName
1043}
1044
Damien Neil2e0c3da2018-09-19 12:51:36 -07001045// Import ensures a package is imported by the generated file.
1046//
1047// Packages referenced by QualifiedGoIdent are automatically imported.
1048// Explicitly importing a package with Import is generally only necessary
1049// when the import will be blank (import _ "package").
1050func (g *GeneratedFile) Import(importPath GoImportPath) {
1051 g.manualImports[importPath] = true
1052}
1053
Damien Neil220c2022018-08-15 11:24:18 -07001054// Write implements io.Writer.
1055func (g *GeneratedFile) Write(p []byte) (n int, err error) {
1056 return g.buf.Write(p)
1057}
1058
Damien Neil7bf3ce22018-12-21 15:54:06 -08001059// Skip removes the generated file from the plugin output.
1060func (g *GeneratedFile) Skip() {
1061 g.skip = true
1062}
1063
Damien Neil162c1272018-10-04 12:42:37 -07001064// Annotate associates a symbol in a generated Go file with a location in a
1065// source .proto file.
1066//
1067// The symbol may refer to a type, constant, variable, function, method, or
1068// struct field. The "T.sel" syntax is used to identify the method or field
1069// 'sel' on type 'T'.
1070func (g *GeneratedFile) Annotate(symbol string, loc Location) {
1071 g.annotations[symbol] = append(g.annotations[symbol], loc)
1072}
1073
Damien Neil7bf3ce22018-12-21 15:54:06 -08001074// Content returns the contents of the generated file.
1075func (g *GeneratedFile) Content() ([]byte, error) {
Damien Neild9016772018-08-23 14:39:30 -07001076 if !strings.HasSuffix(g.filename, ".go") {
Damien Neilc7d07d92018-08-22 13:46:02 -07001077 return g.buf.Bytes(), nil
1078 }
1079
1080 // Reformat generated code.
1081 original := g.buf.Bytes()
1082 fset := token.NewFileSet()
Damien Neil1ec33152018-09-13 13:12:36 -07001083 file, err := parser.ParseFile(fset, "", original, parser.ParseComments)
Damien Neilc7d07d92018-08-22 13:46:02 -07001084 if err != nil {
1085 // Print out the bad code with line numbers.
1086 // This should never happen in practice, but it can while changing generated code
1087 // so consider this a debugging aid.
1088 var src bytes.Buffer
1089 s := bufio.NewScanner(bytes.NewReader(original))
1090 for line := 1; s.Scan(); line++ {
1091 fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes())
1092 }
Damien Neild9016772018-08-23 14:39:30 -07001093 return nil, fmt.Errorf("%v: unparsable Go source: %v\n%v", g.filename, err, src.String())
Damien Neilc7d07d92018-08-22 13:46:02 -07001094 }
Damien Neild9016772018-08-23 14:39:30 -07001095
Joe Tsaibeda4042019-03-10 16:40:48 -07001096 // Collect a sorted list of all imports.
1097 var importPaths [][2]string
Damien Neil1fa8ab02018-09-27 15:51:05 -07001098 rewriteImport := func(importPath string) string {
1099 if f := g.gen.opts.ImportRewriteFunc; f != nil {
1100 return string(f(GoImportPath(importPath)))
1101 }
1102 return importPath
1103 }
Joe Tsaibeda4042019-03-10 16:40:48 -07001104 for importPath := range g.packageNames {
1105 pkgName := string(g.packageNames[GoImportPath(importPath)])
1106 pkgPath := rewriteImport(string(importPath))
1107 importPaths = append(importPaths, [2]string{pkgName, pkgPath})
Damien Neild9016772018-08-23 14:39:30 -07001108 }
Damien Neil2e0c3da2018-09-19 12:51:36 -07001109 for importPath := range g.manualImports {
Joe Tsaibeda4042019-03-10 16:40:48 -07001110 if _, ok := g.packageNames[importPath]; !ok {
1111 pkgPath := rewriteImport(string(importPath))
1112 importPaths = append(importPaths, [2]string{"_", pkgPath})
Damien Neil2e0c3da2018-09-19 12:51:36 -07001113 }
Damien Neil2e0c3da2018-09-19 12:51:36 -07001114 }
Joe Tsaibeda4042019-03-10 16:40:48 -07001115 sort.Slice(importPaths, func(i, j int) bool {
1116 return importPaths[i][1] < importPaths[j][1]
1117 })
1118
1119 // Modify the AST to include a new import block.
1120 if len(importPaths) > 0 {
1121 // Insert block after package statement or
1122 // possible comment attached to the end of the package statement.
1123 pos := file.Package
1124 tokFile := fset.File(file.Package)
1125 pkgLine := tokFile.Line(file.Package)
1126 for _, c := range file.Comments {
1127 if tokFile.Line(c.Pos()) > pkgLine {
1128 break
1129 }
1130 pos = c.End()
1131 }
1132
1133 // Construct the import block.
1134 impDecl := &ast.GenDecl{
1135 Tok: token.IMPORT,
1136 TokPos: pos,
1137 Lparen: pos,
1138 Rparen: pos,
1139 }
1140 for _, importPath := range importPaths {
1141 impDecl.Specs = append(impDecl.Specs, &ast.ImportSpec{
1142 Name: &ast.Ident{
1143 Name: importPath[0],
1144 NamePos: pos,
1145 },
1146 Path: &ast.BasicLit{
1147 Kind: token.STRING,
1148 Value: strconv.Quote(importPath[1]),
1149 ValuePos: pos,
1150 },
1151 EndPos: pos,
1152 })
1153 }
1154 file.Decls = append([]ast.Decl{impDecl}, file.Decls...)
1155 }
Damien Neild9016772018-08-23 14:39:30 -07001156
Damien Neilc7d07d92018-08-22 13:46:02 -07001157 var out bytes.Buffer
Damien Neil1ec33152018-09-13 13:12:36 -07001158 if err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(&out, fset, file); err != nil {
Damien Neild9016772018-08-23 14:39:30 -07001159 return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err)
Damien Neilc7d07d92018-08-22 13:46:02 -07001160 }
Damien Neilc7d07d92018-08-22 13:46:02 -07001161 return out.Bytes(), nil
Damien Neil162c1272018-10-04 12:42:37 -07001162}
Damien Neilc7d07d92018-08-22 13:46:02 -07001163
Damien Neil162c1272018-10-04 12:42:37 -07001164// metaFile returns the contents of the file's metadata file, which is a
1165// text formatted string of the google.protobuf.GeneratedCodeInfo.
1166func (g *GeneratedFile) metaFile(content []byte) (string, error) {
1167 fset := token.NewFileSet()
1168 astFile, err := parser.ParseFile(fset, "", content, 0)
1169 if err != nil {
1170 return "", err
1171 }
Joe Tsaie1f8d502018-11-26 18:55:29 -08001172 info := &descriptorpb.GeneratedCodeInfo{}
Damien Neil162c1272018-10-04 12:42:37 -07001173
1174 seenAnnotations := make(map[string]bool)
1175 annotate := func(s string, ident *ast.Ident) {
1176 seenAnnotations[s] = true
1177 for _, loc := range g.annotations[s] {
Joe Tsaie1f8d502018-11-26 18:55:29 -08001178 info.Annotation = append(info.Annotation, &descriptorpb.GeneratedCodeInfo_Annotation{
Damien Neila8a2cea2019-07-10 16:17:16 -07001179 SourceFile: proto.String(loc.SourceFile),
Damien Neil162c1272018-10-04 12:42:37 -07001180 Path: loc.Path,
Damien Neila8a2cea2019-07-10 16:17:16 -07001181 Begin: proto.Int32(int32(fset.Position(ident.Pos()).Offset)),
1182 End: proto.Int32(int32(fset.Position(ident.End()).Offset)),
Damien Neil162c1272018-10-04 12:42:37 -07001183 })
1184 }
1185 }
1186 for _, decl := range astFile.Decls {
1187 switch decl := decl.(type) {
1188 case *ast.GenDecl:
1189 for _, spec := range decl.Specs {
1190 switch spec := spec.(type) {
1191 case *ast.TypeSpec:
1192 annotate(spec.Name.Name, spec.Name)
Damien Neilae2a5612018-12-12 08:54:57 -08001193 switch st := spec.Type.(type) {
1194 case *ast.StructType:
Damien Neil162c1272018-10-04 12:42:37 -07001195 for _, field := range st.Fields.List {
1196 for _, name := range field.Names {
1197 annotate(spec.Name.Name+"."+name.Name, name)
1198 }
1199 }
Damien Neilae2a5612018-12-12 08:54:57 -08001200 case *ast.InterfaceType:
1201 for _, field := range st.Methods.List {
1202 for _, name := range field.Names {
1203 annotate(spec.Name.Name+"."+name.Name, name)
1204 }
1205 }
Damien Neil162c1272018-10-04 12:42:37 -07001206 }
1207 case *ast.ValueSpec:
1208 for _, name := range spec.Names {
1209 annotate(name.Name, name)
1210 }
1211 }
1212 }
1213 case *ast.FuncDecl:
1214 if decl.Recv == nil {
1215 annotate(decl.Name.Name, decl.Name)
1216 } else {
1217 recv := decl.Recv.List[0].Type
1218 if s, ok := recv.(*ast.StarExpr); ok {
1219 recv = s.X
1220 }
1221 if id, ok := recv.(*ast.Ident); ok {
1222 annotate(id.Name+"."+decl.Name.Name, decl.Name)
1223 }
1224 }
1225 }
1226 }
1227 for a := range g.annotations {
1228 if !seenAnnotations[a] {
1229 return "", fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a)
1230 }
1231 }
1232
Damien Neil5c5b5312019-05-14 12:44:37 -07001233 b, err := prototext.Marshal(info)
Joe Tsaif31bf262019-03-18 14:54:34 -07001234 if err != nil {
1235 return "", err
1236 }
1237 return string(b), nil
Damien Neil220c2022018-08-15 11:24:18 -07001238}
Damien Neil082ce922018-09-06 10:23:53 -07001239
Joe Tsai2e7817f2019-08-23 12:18:57 -07001240// A GoIdent is a Go identifier, consisting of a name and import path.
1241// The name is a single identifier and may not be a dot-qualified selector.
1242type GoIdent struct {
1243 GoName string
1244 GoImportPath GoImportPath
1245}
1246
1247func (id GoIdent) String() string { return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName) }
1248
1249// newGoIdent returns the Go identifier for a descriptor.
1250func newGoIdent(f *File, d protoreflect.Descriptor) GoIdent {
1251 name := strings.TrimPrefix(string(d.FullName()), string(f.Desc.Package())+".")
1252 return GoIdent{
1253 GoName: strs.GoCamelCase(name),
1254 GoImportPath: f.GoImportPath,
1255 }
1256}
1257
1258// A GoImportPath is the import path of a Go package.
1259// For example: "google.golang.org/protobuf/compiler/protogen"
1260type GoImportPath string
1261
1262func (p GoImportPath) String() string { return strconv.Quote(string(p)) }
1263
1264// Ident returns a GoIdent with s as the GoName and p as the GoImportPath.
1265func (p GoImportPath) Ident(s string) GoIdent {
1266 return GoIdent{GoName: s, GoImportPath: p}
1267}
1268
1269// A GoPackageName is the name of a Go package. e.g., "protobuf".
1270type GoPackageName string
1271
1272// cleanPackageName converts a string to a valid Go package name.
1273func cleanPackageName(name string) GoPackageName {
1274 return GoPackageName(strs.GoSanitized(name))
1275}
1276
1277// baseName returns the last path element of the name, with the last dotted suffix removed.
1278func baseName(name string) string {
1279 // First, find the last element
1280 if i := strings.LastIndex(name, "/"); i >= 0 {
1281 name = name[i+1:]
1282 }
1283 // Now drop the suffix
1284 if i := strings.LastIndex(name, "."); i >= 0 {
1285 name = name[:i]
1286 }
1287 return name
1288}
1289
Damien Neil082ce922018-09-06 10:23:53 -07001290type pathType int
1291
1292const (
1293 pathTypeImport pathType = iota
1294 pathTypeSourceRelative
1295)
Damien Neilcab8dfe2018-09-06 14:51:28 -07001296
Damien Neil162c1272018-10-04 12:42:37 -07001297// A Location is a location in a .proto source file.
1298//
1299// See the google.protobuf.SourceCodeInfo documentation in descriptor.proto
1300// for details.
1301type Location struct {
1302 SourceFile string
Joe Tsai691d8562019-07-12 17:16:36 -07001303 Path protoreflect.SourcePath
Damien Neil162c1272018-10-04 12:42:37 -07001304}
1305
1306// appendPath add elements to a Location's path, returning a new Location.
1307func (loc Location) appendPath(a ...int32) Location {
Joe Tsai691d8562019-07-12 17:16:36 -07001308 var n protoreflect.SourcePath
Damien Neil162c1272018-10-04 12:42:37 -07001309 n = append(n, loc.Path...)
Damien Neilcab8dfe2018-09-06 14:51:28 -07001310 n = append(n, a...)
Damien Neil162c1272018-10-04 12:42:37 -07001311 return Location{
1312 SourceFile: loc.SourceFile,
1313 Path: n,
1314 }
Damien Neilcab8dfe2018-09-06 14:51:28 -07001315}
Damien Neilba1159f2018-10-17 12:53:18 -07001316
1317// A pathKey is a representation of a location path suitable for use as a map key.
1318type pathKey struct {
1319 s string
1320}
1321
1322// newPathKey converts a location path to a pathKey.
Koichi Shiraishiea2076d2019-05-24 18:24:29 +09001323func newPathKey(idxPath []int32) pathKey {
1324 buf := make([]byte, 4*len(idxPath))
1325 for i, x := range idxPath {
Damien Neilba1159f2018-10-17 12:53:18 -07001326 binary.LittleEndian.PutUint32(buf[i*4:], uint32(x))
1327 }
1328 return pathKey{string(buf)}
1329}
Joe Tsai70fdd5d2019-08-06 01:15:18 -07001330
1331// CommentSet is a set of leading and trailing comments associated
1332// with a .proto descriptor declaration.
1333type CommentSet struct {
1334 LeadingDetached []Comments
1335 Leading Comments
1336 Trailing Comments
1337}
1338
1339// Comments is a comments string as provided by protoc.
1340type Comments string
1341
1342// String formats the comments by inserting // to the start of each line,
1343// ensuring that there is a trailing newline.
1344// An empty comment is formatted as an empty string.
1345func (c Comments) String() string {
1346 if c == "" {
1347 return ""
1348 }
1349 var b []byte
1350 for _, line := range strings.Split(strings.TrimSuffix(string(c), "\n"), "\n") {
1351 b = append(b, "//"...)
1352 b = append(b, line...)
1353 b = append(b, "\n"...)
1354 }
1355 return string(b)
1356}