blob: 87e643cf764f33799017fbb904bd2dc477169ebd [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//
7// Plugins for protoc, the Protocol Buffers Compiler, are programs which read
8// a CodeGeneratorRequest protocol buffer from standard input and write a
9// CodeGeneratorResponse protocol buffer to standard output. This package
10// provides support for writing plugins which generate Go code.
11package protogen
12
13import (
Damien Neilc7d07d92018-08-22 13:46:02 -070014 "bufio"
Damien Neil220c2022018-08-15 11:24:18 -070015 "bytes"
16 "fmt"
Damien Neil1ec33152018-09-13 13:12:36 -070017 "go/ast"
Damien Neilc7d07d92018-08-22 13:46:02 -070018 "go/parser"
19 "go/printer"
20 "go/token"
Damien Neil220c2022018-08-15 11:24:18 -070021 "io/ioutil"
22 "os"
Damien Neil082ce922018-09-06 10:23:53 -070023 "path"
Damien Neil220c2022018-08-15 11:24:18 -070024 "path/filepath"
Damien Neild9016772018-08-23 14:39:30 -070025 "sort"
26 "strconv"
Damien Neil220c2022018-08-15 11:24:18 -070027 "strings"
28
29 "github.com/golang/protobuf/proto"
30 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
31 pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
Joe Tsai01ab2962018-09-21 17:44:00 -070032 "github.com/golang/protobuf/v2/reflect/protoreflect"
33 "github.com/golang/protobuf/v2/reflect/protoregistry"
34 "github.com/golang/protobuf/v2/reflect/prototype"
Damien Neild9016772018-08-23 14:39:30 -070035 "golang.org/x/tools/go/ast/astutil"
Damien Neil220c2022018-08-15 11:24:18 -070036)
37
38// Run executes a function as a protoc plugin.
39//
40// It reads a CodeGeneratorRequest message from os.Stdin, invokes the plugin
41// function, and writes a CodeGeneratorResponse message to os.Stdout.
42//
43// If a failure occurs while reading or writing, Run prints an error to
44// os.Stderr and calls os.Exit(1).
Damien Neil3cf6e622018-09-11 13:53:14 -070045//
46// Passing a nil options is equivalent to passing a zero-valued one.
47func Run(opts *Options, f func(*Plugin) error) {
48 if err := run(opts, f); err != nil {
Damien Neil220c2022018-08-15 11:24:18 -070049 fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err)
50 os.Exit(1)
51 }
52}
53
Damien Neil3cf6e622018-09-11 13:53:14 -070054func run(opts *Options, f func(*Plugin) error) error {
Damien Neil220c2022018-08-15 11:24:18 -070055 in, err := ioutil.ReadAll(os.Stdin)
56 if err != nil {
57 return err
58 }
59 req := &pluginpb.CodeGeneratorRequest{}
60 if err := proto.Unmarshal(in, req); err != nil {
61 return err
62 }
Damien Neil3cf6e622018-09-11 13:53:14 -070063 gen, err := New(req, opts)
Damien Neil220c2022018-08-15 11:24:18 -070064 if err != nil {
65 return err
66 }
67 if err := f(gen); err != nil {
68 // Errors from the plugin function are reported by setting the
69 // error field in the CodeGeneratorResponse.
70 //
71 // In contrast, errors that indicate a problem in protoc
72 // itself (unparsable input, I/O errors, etc.) are reported
73 // to stderr.
74 gen.Error(err)
75 }
76 resp := gen.Response()
77 out, err := proto.Marshal(resp)
78 if err != nil {
79 return err
80 }
81 if _, err := os.Stdout.Write(out); err != nil {
82 return err
83 }
84 return nil
85}
86
87// A Plugin is a protoc plugin invocation.
88type Plugin struct {
89 // Request is the CodeGeneratorRequest provided by protoc.
90 Request *pluginpb.CodeGeneratorRequest
91
92 // Files is the set of files to generate and everything they import.
93 // Files appear in topological order, so each file appears before any
94 // file that imports it.
95 Files []*File
96 filesByName map[string]*File
97
Damien Neil658051b2018-09-10 12:26:21 -070098 fileReg *protoregistry.Files
99 messagesByName map[protoreflect.FullName]*Message
100 enumsByName map[protoreflect.FullName]*Enum
101 pathType pathType
102 genFiles []*GeneratedFile
103 err error
Damien Neil220c2022018-08-15 11:24:18 -0700104}
105
Damien Neil3cf6e622018-09-11 13:53:14 -0700106// Options are optional parameters to New.
107type Options struct {
108 // If ParamFunc is non-nil, it will be called with each unknown
109 // generator parameter.
110 //
111 // Plugins for protoc can accept parameters from the command line,
112 // passed in the --<lang>_out protoc, separated from the output
113 // directory with a colon; e.g.,
114 //
115 // --go_out=<param1>=<value1>,<param2>=<value2>:<output_directory>
116 //
117 // Parameters passed in this fashion as a comma-separated list of
118 // key=value pairs will be passed to the ParamFunc.
119 //
120 // The (flag.FlagSet).Set method matches this function signature,
121 // so parameters can be converted into flags as in the following:
122 //
123 // var flags flag.FlagSet
124 // value := flags.Bool("param", false, "")
125 // opts := &protogen.Options{
126 // ParamFunc: flags.Set,
127 // }
128 // protogen.Run(opts, func(p *protogen.Plugin) error {
129 // if *value { ... }
130 // })
131 ParamFunc func(name, value string) error
132}
133
Damien Neil220c2022018-08-15 11:24:18 -0700134// New returns a new Plugin.
Damien Neil3cf6e622018-09-11 13:53:14 -0700135//
136// Passing a nil Options is equivalent to passing a zero-valued one.
137func New(req *pluginpb.CodeGeneratorRequest, opts *Options) (*Plugin, error) {
138 if opts == nil {
139 opts = &Options{}
140 }
Damien Neil220c2022018-08-15 11:24:18 -0700141 gen := &Plugin{
Damien Neil658051b2018-09-10 12:26:21 -0700142 Request: req,
143 filesByName: make(map[string]*File),
144 fileReg: protoregistry.NewFiles(),
145 messagesByName: make(map[protoreflect.FullName]*Message),
146 enumsByName: make(map[protoreflect.FullName]*Enum),
Damien Neil220c2022018-08-15 11:24:18 -0700147 }
148
Damien Neil082ce922018-09-06 10:23:53 -0700149 packageNames := make(map[string]GoPackageName) // filename -> package name
150 importPaths := make(map[string]GoImportPath) // filename -> import path
151 var packageImportPath GoImportPath
Damien Neil220c2022018-08-15 11:24:18 -0700152 for _, param := range strings.Split(req.GetParameter(), ",") {
153 var value string
154 if i := strings.Index(param, "="); i >= 0 {
155 value = param[i+1:]
156 param = param[0:i]
157 }
158 switch param {
159 case "":
160 // Ignore.
161 case "import_prefix":
162 // TODO
163 case "import_path":
Damien Neil082ce922018-09-06 10:23:53 -0700164 packageImportPath = GoImportPath(value)
Damien Neil220c2022018-08-15 11:24:18 -0700165 case "paths":
Damien Neil082ce922018-09-06 10:23:53 -0700166 switch value {
167 case "import":
168 gen.pathType = pathTypeImport
169 case "source_relative":
170 gen.pathType = pathTypeSourceRelative
171 default:
172 return nil, fmt.Errorf(`unknown path type %q: want "import" or "source_relative"`, value)
173 }
Damien Neil220c2022018-08-15 11:24:18 -0700174 case "annotate_code":
175 // TODO
176 default:
Damien Neil3cf6e622018-09-11 13:53:14 -0700177 if param[0] == 'M' {
178 importPaths[param[1:]] = GoImportPath(value)
179 continue
Damien Neil220c2022018-08-15 11:24:18 -0700180 }
Damien Neil3cf6e622018-09-11 13:53:14 -0700181 if opts.ParamFunc != nil {
182 if err := opts.ParamFunc(param, value); err != nil {
183 return nil, err
184 }
185 }
Damien Neil082ce922018-09-06 10:23:53 -0700186 }
187 }
188
189 // Figure out the import path and package name for each file.
190 //
191 // The rules here are complicated and have grown organically over time.
192 // Interactions between different ways of specifying package information
193 // may be surprising.
194 //
195 // The recommended approach is to include a go_package option in every
196 // .proto source file specifying the full import path of the Go package
197 // associated with this file.
198 //
199 // option go_package = "github.com/golang/protobuf/ptypes/any";
200 //
201 // Build systems which want to exert full control over import paths may
202 // specify M<filename>=<import_path> flags.
203 //
204 // Other approaches are not recommend.
205 generatedFileNames := make(map[string]bool)
206 for _, name := range gen.Request.FileToGenerate {
207 generatedFileNames[name] = true
208 }
209 // We need to determine the import paths before the package names,
210 // because the Go package name for a file is sometimes derived from
211 // different file in the same package.
212 packageNameForImportPath := make(map[GoImportPath]GoPackageName)
213 for _, fdesc := range gen.Request.ProtoFile {
214 filename := fdesc.GetName()
215 packageName, importPath := goPackageOption(fdesc)
216 switch {
217 case importPaths[filename] != "":
218 // Command line: M=foo.proto=quux/bar
219 //
220 // Explicit mapping of source file to import path.
221 case generatedFileNames[filename] && packageImportPath != "":
222 // Command line: import_path=quux/bar
223 //
224 // The import_path flag sets the import path for every file that
225 // we generate code for.
226 importPaths[filename] = packageImportPath
227 case importPath != "":
228 // Source file: option go_package = "quux/bar";
229 //
230 // The go_package option sets the import path. Most users should use this.
231 importPaths[filename] = importPath
232 default:
233 // Source filename.
234 //
235 // Last resort when nothing else is available.
236 importPaths[filename] = GoImportPath(path.Dir(filename))
237 }
238 if packageName != "" {
239 packageNameForImportPath[importPaths[filename]] = packageName
240 }
241 }
242 for _, fdesc := range gen.Request.ProtoFile {
243 filename := fdesc.GetName()
244 packageName, _ := goPackageOption(fdesc)
245 defaultPackageName := packageNameForImportPath[importPaths[filename]]
246 switch {
247 case packageName != "":
248 // Source file: option go_package = "quux/bar";
249 packageNames[filename] = packageName
250 case defaultPackageName != "":
251 // A go_package option in another file in the same package.
252 //
253 // This is a poor choice in general, since every source file should
254 // contain a go_package option. Supported mainly for historical
255 // compatibility.
256 packageNames[filename] = defaultPackageName
257 case generatedFileNames[filename] && packageImportPath != "":
258 // Command line: import_path=quux/bar
259 packageNames[filename] = cleanPackageName(path.Base(string(packageImportPath)))
260 case fdesc.GetPackage() != "":
261 // Source file: package quux.bar;
262 packageNames[filename] = cleanPackageName(fdesc.GetPackage())
263 default:
264 // Source filename.
265 packageNames[filename] = cleanPackageName(baseName(filename))
266 }
267 }
268
269 // Consistency check: Every file with the same Go import path should have
270 // the same Go package name.
271 packageFiles := make(map[GoImportPath][]string)
272 for filename, importPath := range importPaths {
273 packageFiles[importPath] = append(packageFiles[importPath], filename)
274 }
275 for importPath, filenames := range packageFiles {
276 for i := 1; i < len(filenames); i++ {
277 if a, b := packageNames[filenames[0]], packageNames[filenames[i]]; a != b {
278 return nil, fmt.Errorf("Go package %v has inconsistent names %v (%v) and %v (%v)",
279 importPath, a, filenames[0], b, filenames[i])
280 }
Damien Neil220c2022018-08-15 11:24:18 -0700281 }
282 }
283
284 for _, fdesc := range gen.Request.ProtoFile {
Damien Neil082ce922018-09-06 10:23:53 -0700285 filename := fdesc.GetName()
286 if gen.filesByName[filename] != nil {
287 return nil, fmt.Errorf("duplicate file name: %q", filename)
288 }
289 f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename])
Damien Neilabc6fc12018-08-23 14:39:30 -0700290 if err != nil {
291 return nil, err
292 }
Damien Neil220c2022018-08-15 11:24:18 -0700293 gen.Files = append(gen.Files, f)
Damien Neil082ce922018-09-06 10:23:53 -0700294 gen.filesByName[filename] = f
Damien Neil220c2022018-08-15 11:24:18 -0700295 }
Damien Neil082ce922018-09-06 10:23:53 -0700296 for _, filename := range gen.Request.FileToGenerate {
297 f, ok := gen.FileByName(filename)
Damien Neil220c2022018-08-15 11:24:18 -0700298 if !ok {
Damien Neil082ce922018-09-06 10:23:53 -0700299 return nil, fmt.Errorf("no descriptor for generated file: %v", filename)
Damien Neil220c2022018-08-15 11:24:18 -0700300 }
301 f.Generate = true
302 }
303 return gen, nil
304}
305
306// Error records an error in code generation. The generator will report the
307// error back to protoc and will not produce output.
308func (gen *Plugin) Error(err error) {
309 if gen.err == nil {
310 gen.err = err
311 }
312}
313
314// Response returns the generator output.
315func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse {
316 resp := &pluginpb.CodeGeneratorResponse{}
317 if gen.err != nil {
318 resp.Error = proto.String(gen.err.Error())
319 return resp
320 }
321 for _, gf := range gen.genFiles {
Damien Neilc7d07d92018-08-22 13:46:02 -0700322 content, err := gf.Content()
323 if err != nil {
324 return &pluginpb.CodeGeneratorResponse{
325 Error: proto.String(err.Error()),
326 }
327 }
Damien Neil220c2022018-08-15 11:24:18 -0700328 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
Damien Neild9016772018-08-23 14:39:30 -0700329 Name: proto.String(gf.filename),
Damien Neilc7d07d92018-08-22 13:46:02 -0700330 Content: proto.String(string(content)),
Damien Neil220c2022018-08-15 11:24:18 -0700331 })
332 }
333 return resp
334}
335
336// FileByName returns the file with the given name.
337func (gen *Plugin) FileByName(name string) (f *File, ok bool) {
338 f, ok = gen.filesByName[name]
339 return f, ok
340}
341
Damien Neilc7d07d92018-08-22 13:46:02 -0700342// A File describes a .proto source file.
Damien Neil220c2022018-08-15 11:24:18 -0700343type File struct {
Damien Neil7779e052018-09-07 14:14:06 -0700344 Desc protoreflect.FileDescriptor
345 Proto *descpb.FileDescriptorProto
Damien Neil220c2022018-08-15 11:24:18 -0700346
Damien Neil082ce922018-09-06 10:23:53 -0700347 GoPackageName GoPackageName // name of this file's Go package
348 GoImportPath GoImportPath // import path of this file's Go package
349 Messages []*Message // top-level message declarations
Damien Neil46abb572018-09-07 12:45:37 -0700350 Enums []*Enum // top-level enum declarations
Damien Neil993c04d2018-09-14 15:41:11 -0700351 Extensions []*Extension // top-level extension declarations
Damien Neil2dc67182018-09-21 15:03:34 -0700352 Services []*Service // top-level service declarations
Damien Neil082ce922018-09-06 10:23:53 -0700353 Generate bool // true if we should generate code for this file
354
355 // GeneratedFilenamePrefix is used to construct filenames for generated
356 // files associated with this source file.
357 //
358 // For example, the source file "dir/foo.proto" might have a filename prefix
359 // of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go".
360 GeneratedFilenamePrefix string
Damien Neil220c2022018-08-15 11:24:18 -0700361}
362
Damien Neil082ce922018-09-06 10:23:53 -0700363func newFile(gen *Plugin, p *descpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) {
Damien Neilabc6fc12018-08-23 14:39:30 -0700364 desc, err := prototype.NewFileFromDescriptorProto(p, gen.fileReg)
365 if err != nil {
366 return nil, fmt.Errorf("invalid FileDescriptorProto %q: %v", p.GetName(), err)
367 }
368 if err := gen.fileReg.Register(desc); err != nil {
369 return nil, fmt.Errorf("cannot register descriptor %q: %v", p.GetName(), err)
370 }
Damien Neilc7d07d92018-08-22 13:46:02 -0700371 f := &File{
Damien Neil082ce922018-09-06 10:23:53 -0700372 Desc: desc,
Damien Neil7779e052018-09-07 14:14:06 -0700373 Proto: p,
Damien Neil082ce922018-09-06 10:23:53 -0700374 GoPackageName: packageName,
375 GoImportPath: importPath,
Damien Neil220c2022018-08-15 11:24:18 -0700376 }
Damien Neil082ce922018-09-06 10:23:53 -0700377
378 // Determine the prefix for generated Go files.
379 prefix := p.GetName()
380 if ext := path.Ext(prefix); ext == ".proto" || ext == ".protodevel" {
381 prefix = prefix[:len(prefix)-len(ext)]
382 }
383 if gen.pathType == pathTypeImport {
384 // If paths=import (the default) and the file contains a go_package option
385 // with a full import path, the output filename is derived from the Go import
386 // path.
387 //
388 // Pass the paths=source_relative flag to always derive the output filename
389 // from the input filename instead.
390 if _, importPath := goPackageOption(p); importPath != "" {
391 prefix = path.Join(string(importPath), path.Base(prefix))
392 }
393 }
394 f.GeneratedFilenamePrefix = prefix
395
Damien Neilabc6fc12018-08-23 14:39:30 -0700396 for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
Damien Neil1fa78d82018-09-13 13:12:36 -0700397 f.Messages = append(f.Messages, newMessage(gen, f, nil, mdescs.Get(i)))
Damien Neilc7d07d92018-08-22 13:46:02 -0700398 }
Damien Neil46abb572018-09-07 12:45:37 -0700399 for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
400 f.Enums = append(f.Enums, newEnum(gen, f, nil, edescs.Get(i)))
401 }
Damien Neil993c04d2018-09-14 15:41:11 -0700402 for i, extdescs := 0, desc.Extensions(); i < extdescs.Len(); i++ {
403 f.Extensions = append(f.Extensions, newField(gen, f, nil, extdescs.Get(i)))
404 }
Damien Neil2dc67182018-09-21 15:03:34 -0700405 for i, sdescs := 0, desc.Services(); i < sdescs.Len(); i++ {
406 f.Services = append(f.Services, newService(gen, f, sdescs.Get(i)))
407 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700408 for _, message := range f.Messages {
Damien Neil993c04d2018-09-14 15:41:11 -0700409 if err := message.init(gen); err != nil {
410 return nil, err
411 }
412 }
413 for _, extension := range f.Extensions {
414 if err := extension.init(gen); err != nil {
415 return nil, err
416 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700417 }
Damien Neil2dc67182018-09-21 15:03:34 -0700418 for _, service := range f.Services {
419 for _, method := range service.Methods {
420 if err := method.init(gen); err != nil {
421 return nil, err
422 }
423 }
424 }
Damien Neilabc6fc12018-08-23 14:39:30 -0700425 return f, nil
Damien Neilc7d07d92018-08-22 13:46:02 -0700426}
427
Damien Neil082ce922018-09-06 10:23:53 -0700428// goPackageOption interprets a file's go_package option.
429// If there is no go_package, it returns ("", "").
430// If there's a simple name, it returns (pkg, "").
431// If the option implies an import path, it returns (pkg, impPath).
432func goPackageOption(d *descpb.FileDescriptorProto) (pkg GoPackageName, impPath GoImportPath) {
433 opt := d.GetOptions().GetGoPackage()
434 if opt == "" {
435 return "", ""
436 }
437 // A semicolon-delimited suffix delimits the import path and package name.
438 if i := strings.Index(opt, ";"); i >= 0 {
439 return cleanPackageName(opt[i+1:]), GoImportPath(opt[:i])
440 }
441 // The presence of a slash implies there's an import path.
442 if i := strings.LastIndex(opt, "/"); i >= 0 {
443 return cleanPackageName(opt[i+1:]), GoImportPath(opt)
444 }
445 return cleanPackageName(opt), ""
446}
447
Damien Neilc7d07d92018-08-22 13:46:02 -0700448// A Message describes a message.
449type Message struct {
Damien Neilabc6fc12018-08-23 14:39:30 -0700450 Desc protoreflect.MessageDescriptor
Damien Neilc7d07d92018-08-22 13:46:02 -0700451
Damien Neil993c04d2018-09-14 15:41:11 -0700452 GoIdent GoIdent // name of the generated Go type
453 Fields []*Field // message field declarations
454 Oneofs []*Oneof // oneof declarations
455 Messages []*Message // nested message declarations
456 Enums []*Enum // nested enum declarations
457 Extensions []*Extension // nested extension declarations
458 Path []int32 // location path of this message
Damien Neilc7d07d92018-08-22 13:46:02 -0700459}
460
Damien Neil1fa78d82018-09-13 13:12:36 -0700461func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
Damien Neilcab8dfe2018-09-06 14:51:28 -0700462 var path []int32
463 if parent != nil {
464 path = pathAppend(parent.Path, messageMessageField, int32(desc.Index()))
465 } else {
466 path = []int32{fileMessageField, int32(desc.Index())}
467 }
Damien Neil46abb572018-09-07 12:45:37 -0700468 message := &Message{
Damien Neilabc6fc12018-08-23 14:39:30 -0700469 Desc: desc,
470 GoIdent: newGoIdent(f, desc),
Damien Neilcab8dfe2018-09-06 14:51:28 -0700471 Path: path,
Damien Neilc7d07d92018-08-22 13:46:02 -0700472 }
Damien Neil658051b2018-09-10 12:26:21 -0700473 gen.messagesByName[desc.FullName()] = message
Damien Neilabc6fc12018-08-23 14:39:30 -0700474 for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
Damien Neil1fa78d82018-09-13 13:12:36 -0700475 message.Messages = append(message.Messages, newMessage(gen, f, message, mdescs.Get(i)))
Damien Neilc7d07d92018-08-22 13:46:02 -0700476 }
Damien Neil46abb572018-09-07 12:45:37 -0700477 for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
478 message.Enums = append(message.Enums, newEnum(gen, f, message, edescs.Get(i)))
479 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700480 for i, odescs := 0, desc.Oneofs(); i < odescs.Len(); i++ {
481 message.Oneofs = append(message.Oneofs, newOneof(gen, f, message, odescs.Get(i)))
482 }
Damien Neil658051b2018-09-10 12:26:21 -0700483 for i, fdescs := 0, desc.Fields(); i < fdescs.Len(); i++ {
Damien Neil1fa78d82018-09-13 13:12:36 -0700484 message.Fields = append(message.Fields, newField(gen, f, message, fdescs.Get(i)))
Damien Neil658051b2018-09-10 12:26:21 -0700485 }
Damien Neil993c04d2018-09-14 15:41:11 -0700486 for i, extdescs := 0, desc.Extensions(); i < extdescs.Len(); i++ {
487 message.Extensions = append(message.Extensions, newField(gen, f, message, extdescs.Get(i)))
488 }
Damien Neil658051b2018-09-10 12:26:21 -0700489
490 // Field name conflict resolution.
491 //
492 // We assume well-known method names that may be attached to a generated
493 // message type, as well as a 'Get*' method for each field. For each
494 // field in turn, we add _s to its name until there are no conflicts.
495 //
496 // Any change to the following set of method names is a potential
497 // incompatible API change because it may change generated field names.
498 //
499 // TODO: If we ever support a 'go_name' option to set the Go name of a
500 // field, we should consider dropping this entirely. The conflict
501 // resolution algorithm is subtle and surprising (changing the order
502 // in which fields appear in the .proto source file can change the
503 // names of fields in generated code), and does not adapt well to
504 // adding new per-field methods such as setters.
505 usedNames := map[string]bool{
506 "Reset": true,
507 "String": true,
508 "ProtoMessage": true,
509 "Marshal": true,
510 "Unmarshal": true,
511 "ExtensionRangeArray": true,
512 "ExtensionMap": true,
513 "Descriptor": true,
514 }
515 makeNameUnique := func(name string) string {
516 for usedNames[name] || usedNames["Get"+name] {
517 name += "_"
518 }
519 usedNames[name] = true
520 usedNames["Get"+name] = true
521 return name
522 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700523 seenOneofs := make(map[int]bool)
Damien Neil658051b2018-09-10 12:26:21 -0700524 for _, field := range message.Fields {
Damien Neil1fa78d82018-09-13 13:12:36 -0700525 field.GoName = makeNameUnique(field.GoName)
526 if field.OneofType != nil {
527 if !seenOneofs[field.OneofType.Desc.Index()] {
528 // If this is a field in a oneof that we haven't seen before,
529 // make the name for that oneof unique as well.
530 field.OneofType.GoName = makeNameUnique(field.OneofType.GoName)
531 seenOneofs[field.OneofType.Desc.Index()] = true
532 }
533 }
Damien Neil658051b2018-09-10 12:26:21 -0700534 }
535
Damien Neil1fa78d82018-09-13 13:12:36 -0700536 return message
Damien Neil658051b2018-09-10 12:26:21 -0700537}
538
Damien Neil0bd5a382018-09-13 15:07:10 -0700539func (message *Message) init(gen *Plugin) error {
540 for _, child := range message.Messages {
541 if err := child.init(gen); err != nil {
542 return err
543 }
544 }
545 for _, field := range message.Fields {
546 if err := field.init(gen); err != nil {
547 return err
548 }
549 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700550 for _, oneof := range message.Oneofs {
551 oneof.init(gen, message)
552 }
Damien Neil993c04d2018-09-14 15:41:11 -0700553 for _, extension := range message.Extensions {
554 if err := extension.init(gen); err != nil {
555 return err
556 }
557 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700558 return nil
559}
560
Damien Neil658051b2018-09-10 12:26:21 -0700561// A Field describes a message field.
562type Field struct {
563 Desc protoreflect.FieldDescriptor
564
Damien Neil1fa78d82018-09-13 13:12:36 -0700565 // GoName is the base name of this field's Go field and methods.
Damien Neil658051b2018-09-10 12:26:21 -0700566 // For code generated by protoc-gen-go, this means a field named
Damien Neil1fa78d82018-09-13 13:12:36 -0700567 // '{{GoName}}' and a getter method named 'Get{{GoName}}'.
568 GoName string
Damien Neil658051b2018-09-10 12:26:21 -0700569
Damien Neil993c04d2018-09-14 15:41:11 -0700570 ParentMessage *Message // message in which this field is defined; nil if top-level extension
571 ExtendedType *Message // extended message for extension fields; nil otherwise
572 MessageType *Message // type for message or group fields; nil otherwise
573 EnumType *Enum // type for enum fields; nil otherwise
574 OneofType *Oneof // containing oneof; nil if not part of a oneof
575 Path []int32 // location path of this field
Damien Neil658051b2018-09-10 12:26:21 -0700576}
577
Damien Neil1fa78d82018-09-13 13:12:36 -0700578func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
Damien Neil993c04d2018-09-14 15:41:11 -0700579 var path []int32
580 switch {
581 case desc.ExtendedType() != nil && message == nil:
582 path = []int32{fileExtensionField, int32(desc.Index())}
583 case desc.ExtendedType() != nil && message != nil:
584 path = pathAppend(message.Path, messageExtensionField, int32(desc.Index()))
585 default:
586 path = pathAppend(message.Path, messageFieldField, int32(desc.Index()))
587 }
Damien Neil658051b2018-09-10 12:26:21 -0700588 field := &Field{
Damien Neil993c04d2018-09-14 15:41:11 -0700589 Desc: desc,
590 GoName: camelCase(string(desc.Name())),
591 ParentMessage: message,
592 Path: path,
Damien Neil658051b2018-09-10 12:26:21 -0700593 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700594 if desc.OneofType() != nil {
595 field.OneofType = message.Oneofs[desc.OneofType().Index()]
596 }
597 return field
Damien Neil0bd5a382018-09-13 15:07:10 -0700598}
599
Damien Neil993c04d2018-09-14 15:41:11 -0700600// Extension is an alias of Field for documentation.
601type Extension = Field
602
Damien Neil0bd5a382018-09-13 15:07:10 -0700603func (field *Field) init(gen *Plugin) error {
604 desc := field.Desc
Damien Neil658051b2018-09-10 12:26:21 -0700605 switch desc.Kind() {
606 case protoreflect.MessageKind, protoreflect.GroupKind:
607 mname := desc.MessageType().FullName()
608 message, ok := gen.messagesByName[mname]
609 if !ok {
Damien Neil0bd5a382018-09-13 15:07:10 -0700610 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), mname)
Damien Neil658051b2018-09-10 12:26:21 -0700611 }
612 field.MessageType = message
613 case protoreflect.EnumKind:
Damien Neil0bd5a382018-09-13 15:07:10 -0700614 ename := field.Desc.EnumType().FullName()
Damien Neil658051b2018-09-10 12:26:21 -0700615 enum, ok := gen.enumsByName[ename]
616 if !ok {
Damien Neil0bd5a382018-09-13 15:07:10 -0700617 return fmt.Errorf("field %v: no descriptor for enum %v", desc.FullName(), ename)
Damien Neil658051b2018-09-10 12:26:21 -0700618 }
619 field.EnumType = enum
620 }
Damien Neil993c04d2018-09-14 15:41:11 -0700621 if desc.ExtendedType() != nil {
622 mname := desc.ExtendedType().FullName()
623 message, ok := gen.messagesByName[mname]
624 if !ok {
625 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), mname)
626 }
627 field.ExtendedType = message
628 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700629 return nil
Damien Neil46abb572018-09-07 12:45:37 -0700630}
631
Damien Neil1fa78d82018-09-13 13:12:36 -0700632// A Oneof describes a oneof field.
633type Oneof struct {
634 Desc protoreflect.OneofDescriptor
635
Damien Neil993c04d2018-09-14 15:41:11 -0700636 GoName string // Go field name of this oneof
637 ParentMessage *Message // message in which this oneof occurs
638 Fields []*Field // fields that are part of this oneof
639 Path []int32 // location path of this oneof
Damien Neil1fa78d82018-09-13 13:12:36 -0700640}
641
642func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof {
643 return &Oneof{
Damien Neil993c04d2018-09-14 15:41:11 -0700644 Desc: desc,
645 ParentMessage: message,
646 GoName: camelCase(string(desc.Name())),
647 Path: pathAppend(message.Path, messageOneofField, int32(desc.Index())),
Damien Neil1fa78d82018-09-13 13:12:36 -0700648 }
649}
650
651func (oneof *Oneof) init(gen *Plugin, parent *Message) {
652 for i, fdescs := 0, oneof.Desc.Fields(); i < fdescs.Len(); i++ {
653 oneof.Fields = append(oneof.Fields, parent.Fields[fdescs.Get(i).Index()])
654 }
655}
656
Damien Neil46abb572018-09-07 12:45:37 -0700657// An Enum describes an enum.
658type Enum struct {
659 Desc protoreflect.EnumDescriptor
660
661 GoIdent GoIdent // name of the generated Go type
662 Values []*EnumValue // enum values
663 Path []int32 // location path of this enum
664}
665
666func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum {
667 var path []int32
668 if parent != nil {
669 path = pathAppend(parent.Path, messageEnumField, int32(desc.Index()))
670 } else {
671 path = []int32{fileEnumField, int32(desc.Index())}
672 }
673 enum := &Enum{
674 Desc: desc,
675 GoIdent: newGoIdent(f, desc),
676 Path: path,
677 }
Damien Neil658051b2018-09-10 12:26:21 -0700678 gen.enumsByName[desc.FullName()] = enum
Damien Neil46abb572018-09-07 12:45:37 -0700679 for i, evdescs := 0, enum.Desc.Values(); i < evdescs.Len(); i++ {
680 enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, evdescs.Get(i)))
681 }
682 return enum
683}
684
685// An EnumValue describes an enum value.
686type EnumValue struct {
687 Desc protoreflect.EnumValueDescriptor
688
689 GoIdent GoIdent // name of the generated Go type
690 Path []int32 // location path of this enum value
691}
692
693func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue {
694 // A top-level enum value's name is: EnumName_ValueName
695 // An enum value contained in a message is: MessageName_ValueName
696 //
697 // Enum value names are not camelcased.
698 parentIdent := enum.GoIdent
699 if message != nil {
700 parentIdent = message.GoIdent
701 }
702 name := parentIdent.GoName + "_" + string(desc.Name())
703 return &EnumValue{
704 Desc: desc,
705 GoIdent: GoIdent{
706 GoName: name,
707 GoImportPath: f.GoImportPath,
708 },
709 Path: pathAppend(enum.Path, enumValueField, int32(desc.Index())),
710 }
Damien Neil220c2022018-08-15 11:24:18 -0700711}
712
713// A GeneratedFile is a generated file.
714type GeneratedFile struct {
Damien Neild9016772018-08-23 14:39:30 -0700715 filename string
716 goImportPath GoImportPath
717 buf bytes.Buffer
718 packageNames map[GoImportPath]GoPackageName
719 usedPackageNames map[GoPackageName]bool
Damien Neil2e0c3da2018-09-19 12:51:36 -0700720 manualImports map[GoImportPath]bool
Damien Neil220c2022018-08-15 11:24:18 -0700721}
722
Damien Neild9016772018-08-23 14:39:30 -0700723// NewGeneratedFile creates a new generated file with the given filename
724// and import path.
725func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath) *GeneratedFile {
Damien Neil220c2022018-08-15 11:24:18 -0700726 g := &GeneratedFile{
Damien Neild9016772018-08-23 14:39:30 -0700727 filename: filename,
728 goImportPath: goImportPath,
729 packageNames: make(map[GoImportPath]GoPackageName),
730 usedPackageNames: make(map[GoPackageName]bool),
Damien Neil2e0c3da2018-09-19 12:51:36 -0700731 manualImports: make(map[GoImportPath]bool),
Damien Neil220c2022018-08-15 11:24:18 -0700732 }
733 gen.genFiles = append(gen.genFiles, g)
734 return g
735}
736
Damien Neil2dc67182018-09-21 15:03:34 -0700737// A Service describes a service.
738type Service struct {
739 Desc protoreflect.ServiceDescriptor
740
741 GoName string
742 Path []int32 // location path of this service
743 Methods []*Method // service method definitions
744}
745
746func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
747 service := &Service{
748 Desc: desc,
749 GoName: camelCase(string(desc.Name())),
750 Path: []int32{fileServiceField, int32(desc.Index())},
751 }
752 for i, mdescs := 0, desc.Methods(); i < mdescs.Len(); i++ {
753 service.Methods = append(service.Methods, newMethod(gen, f, service, mdescs.Get(i)))
754 }
755 return service
756}
757
758// A Method describes a method in a service.
759type Method struct {
760 Desc protoreflect.MethodDescriptor
761
762 GoName string
763 ParentService *Service
764 Path []int32 // location path of this method
765 InputType *Message
766 OutputType *Message
767}
768
769func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method {
770 method := &Method{
771 Desc: desc,
772 GoName: camelCase(string(desc.Name())),
773 ParentService: service,
774 Path: pathAppend(service.Path, serviceMethodField, int32(desc.Index())),
775 }
776 return method
777}
778
779func (method *Method) init(gen *Plugin) error {
780 desc := method.Desc
781
782 inName := desc.InputType().FullName()
783 in, ok := gen.messagesByName[inName]
784 if !ok {
785 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName)
786 }
787 method.InputType = in
788
789 outName := desc.OutputType().FullName()
790 out, ok := gen.messagesByName[outName]
791 if !ok {
792 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName)
793 }
794 method.OutputType = out
795
796 return nil
797}
798
Damien Neil220c2022018-08-15 11:24:18 -0700799// P prints a line to the generated output. It converts each parameter to a
800// string following the same rules as fmt.Print. It never inserts spaces
801// between parameters.
802//
803// TODO: .meta file annotations.
804func (g *GeneratedFile) P(v ...interface{}) {
805 for _, x := range v {
Damien Neild9016772018-08-23 14:39:30 -0700806 switch x := x.(type) {
807 case GoIdent:
Damien Neil46abb572018-09-07 12:45:37 -0700808 fmt.Fprint(&g.buf, g.QualifiedGoIdent(x))
Damien Neild9016772018-08-23 14:39:30 -0700809 default:
810 fmt.Fprint(&g.buf, x)
811 }
Damien Neil220c2022018-08-15 11:24:18 -0700812 }
813 fmt.Fprintln(&g.buf)
814}
815
Damien Neil46abb572018-09-07 12:45:37 -0700816// QualifiedGoIdent returns the string to use for a Go identifier.
817//
818// If the identifier is from a different Go package than the generated file,
819// the returned name will be qualified (package.name) and an import statement
820// for the identifier's package will be included in the file.
821func (g *GeneratedFile) QualifiedGoIdent(ident GoIdent) string {
822 if ident.GoImportPath == g.goImportPath {
823 return ident.GoName
824 }
825 if packageName, ok := g.packageNames[ident.GoImportPath]; ok {
826 return string(packageName) + "." + ident.GoName
827 }
828 packageName := cleanPackageName(baseName(string(ident.GoImportPath)))
829 for i, orig := 1, packageName; g.usedPackageNames[packageName]; i++ {
830 packageName = orig + GoPackageName(strconv.Itoa(i))
831 }
832 g.packageNames[ident.GoImportPath] = packageName
833 g.usedPackageNames[packageName] = true
834 return string(packageName) + "." + ident.GoName
835}
836
Damien Neil2e0c3da2018-09-19 12:51:36 -0700837// Import ensures a package is imported by the generated file.
838//
839// Packages referenced by QualifiedGoIdent are automatically imported.
840// Explicitly importing a package with Import is generally only necessary
841// when the import will be blank (import _ "package").
842func (g *GeneratedFile) Import(importPath GoImportPath) {
843 g.manualImports[importPath] = true
844}
845
Damien Neil220c2022018-08-15 11:24:18 -0700846// Write implements io.Writer.
847func (g *GeneratedFile) Write(p []byte) (n int, err error) {
848 return g.buf.Write(p)
849}
850
851// Content returns the contents of the generated file.
Damien Neilc7d07d92018-08-22 13:46:02 -0700852func (g *GeneratedFile) Content() ([]byte, error) {
Damien Neild9016772018-08-23 14:39:30 -0700853 if !strings.HasSuffix(g.filename, ".go") {
Damien Neilc7d07d92018-08-22 13:46:02 -0700854 return g.buf.Bytes(), nil
855 }
856
857 // Reformat generated code.
858 original := g.buf.Bytes()
859 fset := token.NewFileSet()
Damien Neil1ec33152018-09-13 13:12:36 -0700860 file, err := parser.ParseFile(fset, "", original, parser.ParseComments)
Damien Neilc7d07d92018-08-22 13:46:02 -0700861 if err != nil {
862 // Print out the bad code with line numbers.
863 // This should never happen in practice, but it can while changing generated code
864 // so consider this a debugging aid.
865 var src bytes.Buffer
866 s := bufio.NewScanner(bytes.NewReader(original))
867 for line := 1; s.Scan(); line++ {
868 fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes())
869 }
Damien Neild9016772018-08-23 14:39:30 -0700870 return nil, fmt.Errorf("%v: unparsable Go source: %v\n%v", g.filename, err, src.String())
Damien Neilc7d07d92018-08-22 13:46:02 -0700871 }
Damien Neild9016772018-08-23 14:39:30 -0700872
873 // Add imports.
874 var importPaths []string
875 for importPath := range g.packageNames {
876 importPaths = append(importPaths, string(importPath))
877 }
878 sort.Strings(importPaths)
879 for _, importPath := range importPaths {
Damien Neil1ec33152018-09-13 13:12:36 -0700880 astutil.AddNamedImport(fset, file, string(g.packageNames[GoImportPath(importPath)]), importPath)
Damien Neild9016772018-08-23 14:39:30 -0700881 }
Damien Neil2e0c3da2018-09-19 12:51:36 -0700882 for importPath := range g.manualImports {
883 if _, ok := g.packageNames[importPath]; ok {
884 continue
885 }
886 astutil.AddNamedImport(fset, file, "_", string(importPath))
887 }
Damien Neil1ec33152018-09-13 13:12:36 -0700888 ast.SortImports(fset, file)
Damien Neild9016772018-08-23 14:39:30 -0700889
Damien Neilc7d07d92018-08-22 13:46:02 -0700890 var out bytes.Buffer
Damien Neil1ec33152018-09-13 13:12:36 -0700891 if err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(&out, fset, file); err != nil {
Damien Neild9016772018-08-23 14:39:30 -0700892 return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err)
Damien Neilc7d07d92018-08-22 13:46:02 -0700893 }
Damien Neild9016772018-08-23 14:39:30 -0700894 // TODO: Annotations.
Damien Neilc7d07d92018-08-22 13:46:02 -0700895 return out.Bytes(), nil
896
Damien Neil220c2022018-08-15 11:24:18 -0700897}
Damien Neil082ce922018-09-06 10:23:53 -0700898
899type pathType int
900
901const (
902 pathTypeImport pathType = iota
903 pathTypeSourceRelative
904)
Damien Neilcab8dfe2018-09-06 14:51:28 -0700905
906// The SourceCodeInfo message describes the location of elements of a parsed
907// .proto file by way of a "path", which is a sequence of integers that
908// describe the route from a FileDescriptorProto to the relevant submessage.
909// The path alternates between a field number of a repeated field, and an index
910// into that repeated field. The constants below define the field numbers that
911// are used.
912//
913// See descriptor.proto for more information about this.
914const (
915 // field numbers in FileDescriptorProto
Damien Neil993c04d2018-09-14 15:41:11 -0700916 filePackageField = 2 // package
917 fileMessageField = 4 // message_type
918 fileEnumField = 5 // enum_type
Damien Neil2dc67182018-09-21 15:03:34 -0700919 fileServiceField = 6 // service
Damien Neil993c04d2018-09-14 15:41:11 -0700920 fileExtensionField = 7 // extension
Damien Neilcab8dfe2018-09-06 14:51:28 -0700921 // field numbers in DescriptorProto
Damien Neil993c04d2018-09-14 15:41:11 -0700922 messageFieldField = 2 // field
923 messageMessageField = 3 // nested_type
924 messageEnumField = 4 // enum_type
925 messageExtensionField = 6 // extension
926 messageOneofField = 8 // oneof_decl
Damien Neilcab8dfe2018-09-06 14:51:28 -0700927 // field numbers in EnumDescriptorProto
928 enumValueField = 2 // value
Damien Neil2dc67182018-09-21 15:03:34 -0700929 // field numbers in ServiceDescriptorProto
930 serviceMethodField = 2 // method
931 serviceStreamField = 4 // stream
Damien Neilcab8dfe2018-09-06 14:51:28 -0700932)
933
934// pathAppend appends elements to a location path.
935// It does not alias the original path.
936func pathAppend(path []int32, a ...int32) []int32 {
937 var n []int32
938 n = append(n, path...)
939 n = append(n, a...)
940 return n
941}