1// 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 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. 11package protogen 12 13import ( 14 "bufio" 15 "bytes" 16 "encoding/binary" 17 "fmt" 18 "go/ast" 19 "go/parser" 20 "go/printer" 21 "go/token" 22 "go/types" 23 "io/ioutil" 24 "log" 25 "os" 26 "path" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 32 "google.golang.org/protobuf/encoding/prototext" 33 "google.golang.org/protobuf/internal/fieldnum" 34 "google.golang.org/protobuf/internal/strs" 35 "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" 39 40 "google.golang.org/protobuf/types/descriptorpb" 41 "google.golang.org/protobuf/types/pluginpb" 42) 43 44const goPackageDocURL = "https://developers.google.com/protocol-buffers/docs/reference/go-generated#package" 45 46// 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). 53func (opts Options) Run(f func(*Plugin) error) { 54 if err := run(opts, f); err != nil { 55 fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err) 56 os.Exit(1) 57 } 58} 59 60func run(opts Options, f func(*Plugin) error) error { 61 if len(os.Args) > 1 { 62 return fmt.Errorf("unknown argument %q (this program should be run by protoc, not directly)", os.Args[1]) 63 } 64 in, err := ioutil.ReadAll(os.Stdin) 65 if err != nil { 66 return err 67 } 68 req := &pluginpb.CodeGeneratorRequest{} 69 if err := proto.Unmarshal(in, req); err != nil { 70 return err 71 } 72 gen, err := opts.New(req) 73 if err != nil { 74 return err 75 } 76 if err := f(gen); err != nil { 77 // Errors from the plugin function are reported by setting the 78 // error field in the CodeGeneratorResponse. 79 // 80 // In contrast, errors that indicate a problem in protoc 81 // itself (unparsable input, I/O errors, etc.) are reported 82 // to stderr. 83 gen.Error(err) 84 } 85 resp := gen.Response() 86 out, err := proto.Marshal(resp) 87 if err != nil { 88 return err 89 } 90 if _, err := os.Stdout.Write(out); err != nil { 91 return err 92 } 93 return nil 94} 95 96// A Plugin is a protoc plugin invocation. 97type Plugin struct { 98 // Request is the CodeGeneratorRequest provided by protoc. 99 Request *pluginpb.CodeGeneratorRequest 100 101 // Files is the set of files to generate and everything they import. 102 // Files appear in topological order, so each file appears before any 103 // file that imports it. 104 Files []*File 105 FilesByPath map[string]*File 106 107 // SupportedFeatures is the set of protobuf language features supported by 108 // this generator plugin. See the documentation for 109 // google.protobuf.CodeGeneratorResponse.supported_features for details. 110 SupportedFeatures uint64 111 112 fileReg *protoregistry.Files 113 enumsByName map[protoreflect.FullName]*Enum 114 messagesByName map[protoreflect.FullName]*Message 115 annotateCode bool 116 pathType pathType 117 module string 118 genFiles []*GeneratedFile 119 opts Options 120 err error 121} 122 123type Options struct { 124 // If ParamFunc is non-nil, it will be called with each unknown 125 // generator parameter. 126 // 127 // Plugins for protoc can accept parameters from the command line, 128 // passed in the --<lang>_out protoc, separated from the output 129 // directory with a colon; e.g., 130 // 131 // --go_out=<param1>=<value1>,<param2>=<value2>:<output_directory> 132 // 133 // Parameters passed in this fashion as a comma-separated list of 134 // key=value pairs will be passed to the ParamFunc. 135 // 136 // The (flag.FlagSet).Set method matches this function signature, 137 // so parameters can be converted into flags as in the following: 138 // 139 // var flags flag.FlagSet 140 // value := flags.Bool("param", false, "") 141 // opts := &protogen.Options{ 142 // ParamFunc: flags.Set, 143 // } 144 // protogen.Run(opts, func(p *protogen.Plugin) error { 145 // if *value { ... } 146 // }) 147 ParamFunc func(name, value string) error 148 149 // ImportRewriteFunc is called with the import path of each package 150 // imported by a generated file. It returns the import path to use 151 // for this package. 152 ImportRewriteFunc func(GoImportPath) GoImportPath 153} 154 155// New returns a new Plugin. 156func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) { 157 gen := &Plugin{ 158 Request: req, 159 FilesByPath: make(map[string]*File), 160 fileReg: new(protoregistry.Files), 161 enumsByName: make(map[protoreflect.FullName]*Enum), 162 messagesByName: make(map[protoreflect.FullName]*Message), 163 opts: opts, 164 } 165 166 packageNames := make(map[string]GoPackageName) // filename -> package name 167 importPaths := make(map[string]GoImportPath) // filename -> import path 168 mfiles := make(map[string]bool) // filename set 169 var packageImportPath GoImportPath 170 for _, param := range strings.Split(req.GetParameter(), ",") { 171 var value string 172 if i := strings.Index(param, "="); i >= 0 { 173 value = param[i+1:] 174 param = param[0:i] 175 } 176 switch param { 177 case "": 178 // Ignore. 179 case "import_path": 180 packageImportPath = GoImportPath(value) 181 case "module": 182 gen.module = value 183 case "paths": 184 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 } 192 case "annotate_code": 193 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 } 200 default: 201 if param[0] == 'M' { 202 if i := strings.Index(value, ";"); i >= 0 { 203 pkgName := GoPackageName(value[i+1:]) 204 if otherName, ok := packageNames[param[1:]]; ok && pkgName != otherName { 205 return nil, fmt.Errorf("inconsistent package names for %q: %q != %q", value[:i], pkgName, otherName) 206 } 207 packageNames[param[1:]] = pkgName 208 value = value[:i] 209 } 210 importPaths[param[1:]] = GoImportPath(value) 211 mfiles[param[1:]] = true 212 continue 213 } 214 if opts.ParamFunc != nil { 215 if err := opts.ParamFunc(param, value); err != nil { 216 return nil, err 217 } 218 } 219 } 220 } 221 if gen.module != "" { 222 // When the module= option is provided, we strip the module name 223 // prefix from generated files. This only makes sense if generated 224 // filenames are based on the import path, so default to paths=import 225 // and complain if source_relative was selected manually. 226 switch gen.pathType { 227 case pathTypeLegacy: 228 gen.pathType = pathTypeImport 229 case pathTypeSourceRelative: 230 return nil, fmt.Errorf("cannot use module= with paths=source_relative") 231 } 232 } 233 234 // Figure out the import path and package name for each file. 235 // 236 // The rules here are complicated and have grown organically over time. 237 // Interactions between different ways of specifying package information 238 // may be surprising. 239 // 240 // The recommended approach is to include a go_package option in every 241 // .proto source file specifying the full import path of the Go package 242 // associated with this file. 243 // 244 // option go_package = "google.golang.org/protobuf/types/known/anypb"; 245 // 246 // Build systems which want to exert full control over import paths may 247 // specify M<filename>=<import_path> flags. 248 // 249 // Other approaches are not recommend. 250 generatedFileNames := make(map[string]bool) 251 for _, name := range gen.Request.FileToGenerate { 252 generatedFileNames[name] = true 253 } 254 // We need to determine the import paths before the package names, 255 // because the Go package name for a file is sometimes derived from 256 // different file in the same package. 257 packageNameForImportPath := make(map[GoImportPath]GoPackageName) 258 for _, fdesc := range gen.Request.ProtoFile { 259 filename := fdesc.GetName() 260 packageName, importPath := goPackageOption(fdesc) 261 switch { 262 case importPaths[filename] != "": 263 // Command line: Mfoo.proto=quux/bar 264 // 265 // Explicit mapping of source file to import path. 266 case generatedFileNames[filename] && packageImportPath != "": 267 // Command line: import_path=quux/bar 268 // 269 // The import_path flag sets the import path for every file that 270 // we generate code for. 271 importPaths[filename] = packageImportPath 272 case importPath != "": 273 // Source file: option go_package = "quux/bar"; 274 // 275 // The go_package option sets the import path. Most users should use this. 276 importPaths[filename] = importPath 277 default: 278 // Source filename. 279 // 280 // Last resort when nothing else is available. 281 importPaths[filename] = GoImportPath(path.Dir(filename)) 282 } 283 if packageName != "" { 284 packageNameForImportPath[importPaths[filename]] = packageName 285 } 286 } 287 for _, fdesc := range gen.Request.ProtoFile { 288 filename := fdesc.GetName() 289 packageName, importPath := goPackageOption(fdesc) 290 defaultPackageName := packageNameForImportPath[importPaths[filename]] 291 switch { 292 case packageNames[filename] != "": 293 // A package name specified by the "M" command-line argument. 294 case packageName != "": 295 // TODO: For the "M" command-line argument, this means that the 296 // package name can be derived from the go_package option. 297 // Go package information should either consistently come from the 298 // command-line or the .proto source file, but not both. 299 // See how to make this consistent. 300 301 // Source file: option go_package = "quux/bar"; 302 packageNames[filename] = packageName 303 case defaultPackageName != "": 304 // A go_package option in another file in the same package. 305 // 306 // This is a poor choice in general, since every source file should 307 // contain a go_package option. Supported mainly for historical 308 // compatibility. 309 packageNames[filename] = defaultPackageName 310 case generatedFileNames[filename] && packageImportPath != "": 311 // Command line: import_path=quux/bar 312 packageNames[filename] = cleanPackageName(path.Base(string(packageImportPath))) 313 case fdesc.GetPackage() != "": 314 // Source file: package quux.bar; 315 packageNames[filename] = cleanPackageName(fdesc.GetPackage()) 316 default: 317 // Source filename. 318 packageNames[filename] = cleanPackageName(baseName(filename)) 319 } 320 321 goPkgOpt := string(importPaths[filename]) 322 if path.Base(string(goPkgOpt)) != string(packageNames[filename]) { 323 goPkgOpt += ";" + string(packageNames[filename]) 324 } 325 switch { 326 case packageImportPath != "": 327 // Command line: import_path=quux/bar 328 warn("Deprecated use of the 'import_path' command-line argument. In %q, please specify:\n"+ 329 "\toption go_package = %q;\n"+ 330 "A future release of protoc-gen-go will no longer support the 'import_path' argument.\n"+ 331 "See "+goPackageDocURL+" for more information.\n"+ 332 "\n", fdesc.GetName(), goPkgOpt) 333 case mfiles[filename]: 334 // Command line: M=foo.proto=quux/bar 335 case packageName != "" && importPath == "": 336 // Source file: option go_package = "quux"; 337 warn("Deprecated use of 'go_package' option without a full import path in %q, please specify:\n"+ 338 "\toption go_package = %q;\n"+ 339 "A future release of protoc-gen-go will require the import path be specified.\n"+ 340 "See "+goPackageDocURL+" for more information.\n"+ 341 "\n", fdesc.GetName(), goPkgOpt) 342 case packageName == "" && importPath == "": 343 // No Go package information provided. 344 dotIdx := strings.Index(goPkgOpt, ".") // heuristic for top-level domain 345 slashIdx := strings.Index(goPkgOpt, "/") // heuristic for multi-segment path 346 if isFull := 0 <= dotIdx && dotIdx <= slashIdx; isFull { 347 warn("Missing 'go_package' option in %q, please specify:\n"+ 348 "\toption go_package = %q;\n"+ 349 "A future release of protoc-gen-go will require this be specified.\n"+ 350 "See "+goPackageDocURL+" for more information.\n"+ 351 "\n", fdesc.GetName(), goPkgOpt) 352 } else { 353 warn("Missing 'go_package' option in %q,\n"+ 354 "please specify it with the full Go package path as\n"+ 355 "a future release of protoc-gen-go will require this be specified.\n"+ 356 "See "+goPackageDocURL+" for more information.\n"+ 357 "\n", fdesc.GetName()) 358 } 359 } 360 } 361 362 // Consistency check: Every file with the same Go import path should have 363 // the same Go package name. 364 packageFiles := make(map[GoImportPath][]string) 365 for filename, importPath := range importPaths { 366 if _, ok := packageNames[filename]; !ok { 367 // Skip files mentioned in a M<file>=<import_path> parameter 368 // but which do not appear in the CodeGeneratorRequest. 369 continue 370 } 371 packageFiles[importPath] = append(packageFiles[importPath], filename) 372 } 373 for importPath, filenames := range packageFiles { 374 for i := 1; i < len(filenames); i++ { 375 if a, b := packageNames[filenames[0]], packageNames[filenames[i]]; a != b { 376 return nil, fmt.Errorf("Go package %v has inconsistent names %v (%v) and %v (%v)", 377 importPath, a, filenames[0], b, filenames[i]) 378 } 379 } 380 } 381 382 for _, fdesc := range gen.Request.ProtoFile { 383 filename := fdesc.GetName() 384 if gen.FilesByPath[filename] != nil { 385 return nil, fmt.Errorf("duplicate file name: %q", filename) 386 } 387 f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename]) 388 if err != nil { 389 return nil, err 390 } 391 gen.Files = append(gen.Files, f) 392 gen.FilesByPath[filename] = f 393 } 394 for _, filename := range gen.Request.FileToGenerate { 395 f, ok := gen.FilesByPath[filename] 396 if !ok { 397 return nil, fmt.Errorf("no descriptor for generated file: %v", filename) 398 } 399 f.Generate = true 400 } 401 return gen, nil 402} 403 404// Error records an error in code generation. The generator will report the 405// error back to protoc and will not produce output. 406func (gen *Plugin) Error(err error) { 407 if gen.err == nil { 408 gen.err = err 409 } 410} 411 412// Response returns the generator output. 413func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse { 414 resp := &pluginpb.CodeGeneratorResponse{} 415 if gen.err != nil { 416 resp.Error = proto.String(gen.err.Error()) 417 return resp 418 } 419 for _, g := range gen.genFiles { 420 if g.skip { 421 continue 422 } 423 content, err := g.Content() 424 if err != nil { 425 return &pluginpb.CodeGeneratorResponse{ 426 Error: proto.String(err.Error()), 427 } 428 } 429 filename := g.filename 430 if gen.module != "" { 431 trim := gen.module + "/" 432 if !strings.HasPrefix(filename, trim) { 433 return &pluginpb.CodeGeneratorResponse{ 434 Error: proto.String(fmt.Sprintf("%v: generated file does not match prefix %q", filename, gen.module)), 435 } 436 } 437 filename = strings.TrimPrefix(filename, trim) 438 } 439 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{ 440 Name: proto.String(filename), 441 Content: proto.String(string(content)), 442 }) 443 if gen.annotateCode && strings.HasSuffix(g.filename, ".go") { 444 meta, err := g.metaFile(content) 445 if err != nil { 446 return &pluginpb.CodeGeneratorResponse{ 447 Error: proto.String(err.Error()), 448 } 449 } 450 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{ 451 Name: proto.String(filename + ".meta"), 452 Content: proto.String(meta), 453 }) 454 } 455 } 456 if gen.SupportedFeatures > 0 { 457 resp.SupportedFeatures = proto.Uint64(gen.SupportedFeatures) 458 } 459 return resp 460} 461 462// A File describes a .proto source file. 463type File struct { 464 Desc protoreflect.FileDescriptor 465 Proto *descriptorpb.FileDescriptorProto 466 467 GoDescriptorIdent GoIdent // name of Go variable for the file descriptor 468 GoPackageName GoPackageName // name of this file's Go package 469 GoImportPath GoImportPath // import path of this file's Go package 470 471 Enums []*Enum // top-level enum declarations 472 Messages []*Message // top-level message declarations 473 Extensions []*Extension // top-level extension declarations 474 Services []*Service // top-level service declarations 475 476 Generate bool // true if we should generate code for this file 477 478 // GeneratedFilenamePrefix is used to construct filenames for generated 479 // files associated with this source file. 480 // 481 // For example, the source file "dir/foo.proto" might have a filename prefix 482 // of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go". 483 GeneratedFilenamePrefix string 484 485 comments map[pathKey]CommentSet 486} 487 488func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) { 489 desc, err := protodesc.NewFile(p, gen.fileReg) 490 if err != nil { 491 return nil, fmt.Errorf("invalid FileDescriptorProto %q: %v", p.GetName(), err) 492 } 493 if err := gen.fileReg.RegisterFile(desc); err != nil { 494 return nil, fmt.Errorf("cannot register descriptor %q: %v", p.GetName(), err) 495 } 496 f := &File{ 497 Desc: desc, 498 Proto: p, 499 GoPackageName: packageName, 500 GoImportPath: importPath, 501 comments: make(map[pathKey]CommentSet), 502 } 503 504 // Determine the prefix for generated Go files. 505 prefix := p.GetName() 506 if ext := path.Ext(prefix); ext == ".proto" || ext == ".protodevel" { 507 prefix = prefix[:len(prefix)-len(ext)] 508 } 509 switch gen.pathType { 510 case pathTypeLegacy: 511 // The default is to derive the output filename from the Go import path 512 // if the file contains a go_package option,or from the input filename instead. 513 if _, importPath := goPackageOption(p); importPath != "" { 514 prefix = path.Join(string(importPath), path.Base(prefix)) 515 } 516 case pathTypeImport: 517 // If paths=import, the output filename is derived from the Go import path. 518 prefix = path.Join(string(f.GoImportPath), path.Base(prefix)) 519 case pathTypeSourceRelative: 520 // If paths=source_relative, the output filename is derived from 521 // the input filename. 522 } 523 f.GoDescriptorIdent = GoIdent{ 524 GoName: "File_" + strs.GoSanitized(p.GetName()), 525 GoImportPath: f.GoImportPath, 526 } 527 f.GeneratedFilenamePrefix = prefix 528 529 for _, loc := range p.GetSourceCodeInfo().GetLocation() { 530 // Descriptors declarations are guaranteed to have unique comment sets. 531 // Other locations may not be unique, but we don't use them. 532 var leadingDetached []Comments 533 for _, s := range loc.GetLeadingDetachedComments() { 534 leadingDetached = append(leadingDetached, Comments(s)) 535 } 536 f.comments[newPathKey(loc.Path)] = CommentSet{ 537 LeadingDetached: leadingDetached, 538 Leading: Comments(loc.GetLeadingComments()), 539 Trailing: Comments(loc.GetTrailingComments()), 540 } 541 } 542 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { 543 f.Enums = append(f.Enums, newEnum(gen, f, nil, eds.Get(i))) 544 } 545 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ { 546 f.Messages = append(f.Messages, newMessage(gen, f, nil, mds.Get(i))) 547 } 548 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ { 549 f.Extensions = append(f.Extensions, newField(gen, f, nil, xds.Get(i))) 550 } 551 for i, sds := 0, desc.Services(); i < sds.Len(); i++ { 552 f.Services = append(f.Services, newService(gen, f, sds.Get(i))) 553 } 554 for _, message := range f.Messages { 555 if err := message.resolveDependencies(gen); err != nil { 556 return nil, err 557 } 558 } 559 for _, extension := range f.Extensions { 560 if err := extension.resolveDependencies(gen); err != nil { 561 return nil, err 562 } 563 } 564 for _, service := range f.Services { 565 for _, method := range service.Methods { 566 if err := method.resolveDependencies(gen); err != nil { 567 return nil, err 568 } 569 } 570 } 571 return f, nil 572} 573 574func (f *File) location(idxPath ...int32) Location { 575 return Location{ 576 SourceFile: f.Desc.Path(), 577 Path: idxPath, 578 } 579} 580 581// goPackageOption interprets a file's go_package option. 582// If there is no go_package, it returns ("", ""). 583// If there's a simple name, it returns (pkg, ""). 584// If the option implies an import path, it returns (pkg, impPath). 585func goPackageOption(d *descriptorpb.FileDescriptorProto) (pkg GoPackageName, impPath GoImportPath) { 586 opt := d.GetOptions().GetGoPackage() 587 if opt == "" { 588 return "", "" 589 } 590 rawPkg, impPath := goPackageOptionRaw(opt) 591 pkg = cleanPackageName(rawPkg) 592 if string(pkg) != rawPkg && impPath != "" { 593 warn("Malformed 'go_package' option in %q, please specify:\n"+ 594 "\toption go_package = %q;\n"+ 595 "A future release of protoc-gen-go will reject this.\n"+ 596 "See "+goPackageDocURL+" for more information.\n"+ 597 "\n", d.GetName(), string(impPath)+";"+string(pkg)) 598 } 599 return pkg, impPath 600} 601func goPackageOptionRaw(opt string) (rawPkg string, impPath GoImportPath) { 602 // A semicolon-delimited suffix delimits the import path and package name. 603 if i := strings.Index(opt, ";"); i >= 0 { 604 return opt[i+1:], GoImportPath(opt[:i]) 605 } 606 // The presence of a slash implies there's an import path. 607 if i := strings.LastIndex(opt, "/"); i >= 0 { 608 return opt[i+1:], GoImportPath(opt) 609 } 610 return opt, "" 611} 612 613// An Enum describes an enum. 614type Enum struct { 615 Desc protoreflect.EnumDescriptor 616 617 GoIdent GoIdent // name of the generated Go type 618 619 Values []*EnumValue // enum value declarations 620 621 Location Location // location of this enum 622 Comments CommentSet // comments associated with this enum 623} 624 625func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum { 626 var loc Location 627 if parent != nil { 628 loc = parent.Location.appendPath(fieldnum.DescriptorProto_EnumType, int32(desc.Index())) 629 } else { 630 loc = f.location(fieldnum.FileDescriptorProto_EnumType, int32(desc.Index())) 631 } 632 enum := &Enum{ 633 Desc: desc, 634 GoIdent: newGoIdent(f, desc), 635 Location: loc, 636 Comments: f.comments[newPathKey(loc.Path)], 637 } 638 gen.enumsByName[desc.FullName()] = enum 639 for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ { 640 enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, vds.Get(i))) 641 } 642 return enum 643} 644 645// An EnumValue describes an enum value. 646type EnumValue struct { 647 Desc protoreflect.EnumValueDescriptor 648 649 GoIdent GoIdent // name of the generated Go declaration 650 651 Parent *Enum // enum in which this value is declared 652 653 Location Location // location of this enum value 654 Comments CommentSet // comments associated with this enum value 655} 656 657func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue { 658 // A top-level enum value's name is: EnumName_ValueName 659 // An enum value contained in a message is: MessageName_ValueName 660 // 661 // For historical reasons, enum value names are not camel-cased. 662 parentIdent := enum.GoIdent 663 if message != nil { 664 parentIdent = message.GoIdent 665 } 666 name := parentIdent.GoName + "_" + string(desc.Name()) 667 loc := enum.Location.appendPath(fieldnum.EnumDescriptorProto_Value, int32(desc.Index())) 668 return &EnumValue{ 669 Desc: desc, 670 GoIdent: f.GoImportPath.Ident(name), 671 Parent: enum, 672 Location: loc, 673 Comments: f.comments[newPathKey(loc.Path)], 674 } 675} 676 677// A Message describes a message. 678type Message struct { 679 Desc protoreflect.MessageDescriptor 680 681 GoIdent GoIdent // name of the generated Go type 682 683 Fields []*Field // message field declarations 684 Oneofs []*Oneof // message oneof declarations 685 686 Enums []*Enum // nested enum declarations 687 Messages []*Message // nested message declarations 688 Extensions []*Extension // nested extension declarations 689 690 Location Location // location of this message 691 Comments CommentSet // comments associated with this message 692} 693 694func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message { 695 var loc Location 696 if parent != nil { 697 loc = parent.Location.appendPath(fieldnum.DescriptorProto_NestedType, int32(desc.Index())) 698 } else { 699 loc = f.location(fieldnum.FileDescriptorProto_MessageType, int32(desc.Index())) 700 } 701 message := &Message{ 702 Desc: desc, 703 GoIdent: newGoIdent(f, desc), 704 Location: loc, 705 Comments: f.comments[newPathKey(loc.Path)], 706 } 707 gen.messagesByName[desc.FullName()] = message 708 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { 709 message.Enums = append(message.Enums, newEnum(gen, f, message, eds.Get(i))) 710 } 711 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ { 712 message.Messages = append(message.Messages, newMessage(gen, f, message, mds.Get(i))) 713 } 714 for i, fds := 0, desc.Fields(); i < fds.Len(); i++ { 715 message.Fields = append(message.Fields, newField(gen, f, message, fds.Get(i))) 716 } 717 for i, ods := 0, desc.Oneofs(); i < ods.Len(); i++ { 718 message.Oneofs = append(message.Oneofs, newOneof(gen, f, message, ods.Get(i))) 719 } 720 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ { 721 message.Extensions = append(message.Extensions, newField(gen, f, message, xds.Get(i))) 722 } 723 724 // Resolve local references between fields and oneofs. 725 for _, field := range message.Fields { 726 if od := field.Desc.ContainingOneof(); od != nil { 727 oneof := message.Oneofs[od.Index()] 728 field.Oneof = oneof 729 oneof.Fields = append(oneof.Fields, field) 730 } 731 } 732 733 // Field name conflict resolution. 734 // 735 // We assume well-known method names that may be attached to a generated 736 // message type, as well as a 'Get*' method for each field. For each 737 // field in turn, we add _s to its name until there are no conflicts. 738 // 739 // Any change to the following set of method names is a potential 740 // incompatible API change because it may change generated field names. 741 // 742 // TODO: If we ever support a 'go_name' option to set the Go name of a 743 // field, we should consider dropping this entirely. The conflict 744 // resolution algorithm is subtle and surprising (changing the order 745 // in which fields appear in the .proto source file can change the 746 // names of fields in generated code), and does not adapt well to 747 // adding new per-field methods such as setters. 748 usedNames := map[string]bool{ 749 "Reset": true, 750 "String": true, 751 "ProtoMessage": true, 752 "Marshal": true, 753 "Unmarshal": true, 754 "ExtensionRangeArray": true, 755 "ExtensionMap": true, 756 "Descriptor": true, 757 } 758 makeNameUnique := func(name string, hasGetter bool) string { 759 for usedNames[name] || (hasGetter && usedNames["Get"+name]) { 760 name += "_" 761 } 762 usedNames[name] = true 763 usedNames["Get"+name] = hasGetter 764 return name 765 } 766 for _, field := range message.Fields { 767 field.GoName = makeNameUnique(field.GoName, true) 768 field.GoIdent.GoName = message.GoIdent.GoName + "_" + field.GoName 769 if field.Oneof != nil && field.Oneof.Fields[0] == field { 770 // Make the name for a oneof unique as well. For historical reasons, 771 // this assumes that a getter method is not generated for oneofs. 772 // This is incorrect, but fixing it breaks existing code. 773 field.Oneof.GoName = makeNameUnique(field.Oneof.GoName, false) 774 field.Oneof.GoIdent.GoName = message.GoIdent.GoName + "_" + field.Oneof.GoName 775 } 776 } 777 778 // Oneof field name conflict resolution. 779 // 780 // This conflict resolution is incomplete as it does not consider collisions 781 // with other oneof field types, but fixing it breaks existing code. 782 for _, field := range message.Fields { 783 if field.Oneof != nil { 784 Loop: 785 for { 786 for _, nestedMessage := range message.Messages { 787 if nestedMessage.GoIdent == field.GoIdent { 788 field.GoIdent.GoName += "_" 789 continue Loop 790 } 791 } 792 for _, nestedEnum := range message.Enums { 793 if nestedEnum.GoIdent == field.GoIdent { 794 field.GoIdent.GoName += "_" 795 continue Loop 796 } 797 } 798 break Loop 799 } 800 } 801 } 802 803 return message 804} 805 806func (message *Message) resolveDependencies(gen *Plugin) error { 807 for _, field := range message.Fields { 808 if err := field.resolveDependencies(gen); err != nil { 809 return err 810 } 811 } 812 for _, message := range message.Messages { 813 if err := message.resolveDependencies(gen); err != nil { 814 return err 815 } 816 } 817 for _, extension := range message.Extensions { 818 if err := extension.resolveDependencies(gen); err != nil { 819 return err 820 } 821 } 822 return nil 823} 824 825// A Field describes a message field. 826type Field struct { 827 Desc protoreflect.FieldDescriptor 828 829 // GoName is the base name of this field's Go field and methods. 830 // For code generated by protoc-gen-go, this means a field named 831 // '{{GoName}}' and a getter method named 'Get{{GoName}}'. 832 GoName string // e.g., "FieldName" 833 834 // GoIdent is the base name of a top-level declaration for this field. 835 // For code generated by protoc-gen-go, this means a wrapper type named 836 // '{{GoIdent}}' for members fields of a oneof, and a variable named 837 // 'E_{{GoIdent}}' for extension fields. 838 GoIdent GoIdent // e.g., "MessageName_FieldName" 839 840 Parent *Message // message in which this field is declared; nil if top-level extension 841 Oneof *Oneof // containing oneof; nil if not part of a oneof 842 Extendee *Message // extended message for extension fields; nil otherwise 843 844 Enum *Enum // type for enum fields; nil otherwise 845 Message *Message // type for message or group fields; nil otherwise 846 847 Location Location // location of this field 848 Comments CommentSet // comments associated with this field 849} 850 851func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field { 852 var loc Location 853 switch { 854 case desc.IsExtension() && message == nil: 855 loc = f.location(fieldnum.FileDescriptorProto_Extension, int32(desc.Index())) 856 case desc.IsExtension() && message != nil: 857 loc = message.Location.appendPath(fieldnum.DescriptorProto_Extension, int32(desc.Index())) 858 default: 859 loc = message.Location.appendPath(fieldnum.DescriptorProto_Field, int32(desc.Index())) 860 } 861 camelCased := strs.GoCamelCase(string(desc.Name())) 862 var parentPrefix string 863 if message != nil { 864 parentPrefix = message.GoIdent.GoName + "_" 865 } 866 field := &Field{ 867 Desc: desc, 868 GoName: camelCased, 869 GoIdent: GoIdent{ 870 GoImportPath: f.GoImportPath, 871 GoName: parentPrefix + camelCased, 872 }, 873 Parent: message, 874 Location: loc, 875 Comments: f.comments[newPathKey(loc.Path)], 876 } 877 return field 878} 879 880func (field *Field) resolveDependencies(gen *Plugin) error { 881 desc := field.Desc 882 switch desc.Kind() { 883 case protoreflect.EnumKind: 884 name := field.Desc.Enum().FullName() 885 enum, ok := gen.enumsByName[name] 886 if !ok { 887 return fmt.Errorf("field %v: no descriptor for enum %v", desc.FullName(), name) 888 } 889 field.Enum = enum 890 case protoreflect.MessageKind, protoreflect.GroupKind: 891 name := desc.Message().FullName() 892 message, ok := gen.messagesByName[name] 893 if !ok { 894 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name) 895 } 896 field.Message = message 897 } 898 if desc.IsExtension() { 899 name := desc.ContainingMessage().FullName() 900 message, ok := gen.messagesByName[name] 901 if !ok { 902 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name) 903 } 904 field.Extendee = message 905 } 906 return nil 907} 908 909// A Oneof describes a message oneof. 910type Oneof struct { 911 Desc protoreflect.OneofDescriptor 912 913 // GoName is the base name of this oneof's Go field and methods. 914 // For code generated by protoc-gen-go, this means a field named 915 // '{{GoName}}' and a getter method named 'Get{{GoName}}'. 916 GoName string // e.g., "OneofName" 917 918 // GoIdent is the base name of a top-level declaration for this oneof. 919 GoIdent GoIdent // e.g., "MessageName_OneofName" 920 921 Parent *Message // message in which this oneof is declared 922 923 Fields []*Field // fields that are part of this oneof 924 925 Location Location // location of this oneof 926 Comments CommentSet // comments associated with this oneof 927} 928 929func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof { 930 loc := message.Location.appendPath(fieldnum.DescriptorProto_OneofDecl, int32(desc.Index())) 931 camelCased := strs.GoCamelCase(string(desc.Name())) 932 parentPrefix := message.GoIdent.GoName + "_" 933 return &Oneof{ 934 Desc: desc, 935 Parent: message, 936 GoName: camelCased, 937 GoIdent: GoIdent{ 938 GoImportPath: f.GoImportPath, 939 GoName: parentPrefix + camelCased, 940 }, 941 Location: loc, 942 Comments: f.comments[newPathKey(loc.Path)], 943 } 944} 945 946// Extension is an alias of Field for documentation. 947type Extension = Field 948 949// A Service describes a service. 950type Service struct { 951 Desc protoreflect.ServiceDescriptor 952 953 GoName string 954 955 Methods []*Method // service method declarations 956 957 Location Location // location of this service 958 Comments CommentSet // comments associated with this service 959} 960 961func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service { 962 loc := f.location(fieldnum.FileDescriptorProto_Service, int32(desc.Index())) 963 service := &Service{ 964 Desc: desc, 965 GoName: strs.GoCamelCase(string(desc.Name())), 966 Location: loc, 967 Comments: f.comments[newPathKey(loc.Path)], 968 } 969 for i, mds := 0, desc.Methods(); i < mds.Len(); i++ { 970 service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i))) 971 } 972 return service 973} 974 975// A Method describes a method in a service. 976type Method struct { 977 Desc protoreflect.MethodDescriptor 978 979 GoName string 980 981 Parent *Service // service in which this method is declared 982 983 Input *Message 984 Output *Message 985 986 Location Location // location of this method 987 Comments CommentSet // comments associated with this method 988} 989 990func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method { 991 loc := service.Location.appendPath(fieldnum.ServiceDescriptorProto_Method, int32(desc.Index())) 992 method := &Method{ 993 Desc: desc, 994 GoName: strs.GoCamelCase(string(desc.Name())), 995 Parent: service, 996 Location: loc, 997 Comments: f.comments[newPathKey(loc.Path)], 998 } 999 return method 1000} 1001 1002func (method *Method) resolveDependencies(gen *Plugin) error { 1003 desc := method.Desc 1004 1005 inName := desc.Input().FullName() 1006 in, ok := gen.messagesByName[inName] 1007 if !ok { 1008 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName) 1009 } 1010 method.Input = in 1011 1012 outName := desc.Output().FullName() 1013 out, ok := gen.messagesByName[outName] 1014 if !ok { 1015 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName) 1016 } 1017 method.Output = out 1018 1019 return nil 1020} 1021 1022// A GeneratedFile is a generated file. 1023type GeneratedFile struct { 1024 gen *Plugin 1025 skip bool 1026 filename string 1027 goImportPath GoImportPath 1028 buf bytes.Buffer 1029 packageNames map[GoImportPath]GoPackageName 1030 usedPackageNames map[GoPackageName]bool 1031 manualImports map[GoImportPath]bool 1032 annotations map[string][]Location 1033} 1034 1035// NewGeneratedFile creates a new generated file with the given filename 1036// and import path. 1037func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath) *GeneratedFile { 1038 g := &GeneratedFile{ 1039 gen: gen, 1040 filename: filename, 1041 goImportPath: goImportPath, 1042 packageNames: make(map[GoImportPath]GoPackageName), 1043 usedPackageNames: make(map[GoPackageName]bool), 1044 manualImports: make(map[GoImportPath]bool), 1045 annotations: make(map[string][]Location), 1046 } 1047 1048 // All predeclared identifiers in Go are already used. 1049 for _, s := range types.Universe.Names() { 1050 g.usedPackageNames[GoPackageName(s)] = true 1051 } 1052 1053 gen.genFiles = append(gen.genFiles, g) 1054 return g 1055} 1056 1057// P prints a line to the generated output. It converts each parameter to a 1058// string following the same rules as fmt.Print. It never inserts spaces 1059// between parameters. 1060func (g *GeneratedFile) P(v ...interface{}) { 1061 for _, x := range v { 1062 switch x := x.(type) { 1063 case GoIdent: 1064 fmt.Fprint(&g.buf, g.QualifiedGoIdent(x)) 1065 default: 1066 fmt.Fprint(&g.buf, x) 1067 } 1068 } 1069 fmt.Fprintln(&g.buf) 1070} 1071 1072// QualifiedGoIdent returns the string to use for a Go identifier. 1073// 1074// If the identifier is from a different Go package than the generated file, 1075// the returned name will be qualified (package.name) and an import statement 1076// for the identifier's package will be included in the file. 1077func (g *GeneratedFile) QualifiedGoIdent(ident GoIdent) string { 1078 if ident.GoImportPath == g.goImportPath { 1079 return ident.GoName 1080 } 1081 if packageName, ok := g.packageNames[ident.GoImportPath]; ok { 1082 return string(packageName) + "." + ident.GoName 1083 } 1084 packageName := cleanPackageName(baseName(string(ident.GoImportPath))) 1085 for i, orig := 1, packageName; g.usedPackageNames[packageName]; i++ { 1086 packageName = orig + GoPackageName(strconv.Itoa(i)) 1087 } 1088 g.packageNames[ident.GoImportPath] = packageName 1089 g.usedPackageNames[packageName] = true 1090 return string(packageName) + "." + ident.GoName 1091} 1092 1093// Import ensures a package is imported by the generated file. 1094// 1095// Packages referenced by QualifiedGoIdent are automatically imported. 1096// Explicitly importing a package with Import is generally only necessary 1097// when the import will be blank (import _ "package"). 1098func (g *GeneratedFile) Import(importPath GoImportPath) { 1099 g.manualImports[importPath] = true 1100} 1101 1102// Write implements io.Writer. 1103func (g *GeneratedFile) Write(p []byte) (n int, err error) { 1104 return g.buf.Write(p) 1105} 1106 1107// Skip removes the generated file from the plugin output. 1108func (g *GeneratedFile) Skip() { 1109 g.skip = true 1110} 1111 1112// Unskip reverts a previous call to Skip, re-including the generated file in 1113// the plugin output. 1114func (g *GeneratedFile) Unskip() { 1115 g.skip = false 1116} 1117 1118// Annotate associates a symbol in a generated Go file with a location in a 1119// source .proto file. 1120// 1121// The symbol may refer to a type, constant, variable, function, method, or 1122// struct field. The "T.sel" syntax is used to identify the method or field 1123// 'sel' on type 'T'. 1124func (g *GeneratedFile) Annotate(symbol string, loc Location) { 1125 g.annotations[symbol] = append(g.annotations[symbol], loc) 1126} 1127 1128// Content returns the contents of the generated file. 1129func (g *GeneratedFile) Content() ([]byte, error) { 1130 if !strings.HasSuffix(g.filename, ".go") { 1131 return g.buf.Bytes(), nil 1132 } 1133 1134 // Reformat generated code. 1135 original := g.buf.Bytes() 1136 fset := token.NewFileSet() 1137 file, err := parser.ParseFile(fset, "", original, parser.ParseComments) 1138 if err != nil { 1139 // Print out the bad code with line numbers. 1140 // This should never happen in practice, but it can while changing generated code 1141 // so consider this a debugging aid. 1142 var src bytes.Buffer 1143 s := bufio.NewScanner(bytes.NewReader(original)) 1144 for line := 1; s.Scan(); line++ { 1145 fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes()) 1146 } 1147 return nil, fmt.Errorf("%v: unparsable Go source: %v\n%v", g.filename, err, src.String()) 1148 } 1149 1150 // Collect a sorted list of all imports. 1151 var importPaths [][2]string 1152 rewriteImport := func(importPath string) string { 1153 if f := g.gen.opts.ImportRewriteFunc; f != nil { 1154 return string(f(GoImportPath(importPath))) 1155 } 1156 return importPath 1157 } 1158 for importPath := range g.packageNames { 1159 pkgName := string(g.packageNames[GoImportPath(importPath)]) 1160 pkgPath := rewriteImport(string(importPath)) 1161 importPaths = append(importPaths, [2]string{pkgName, pkgPath}) 1162 } 1163 for importPath := range g.manualImports { 1164 if _, ok := g.packageNames[importPath]; !ok { 1165 pkgPath := rewriteImport(string(importPath)) 1166 importPaths = append(importPaths, [2]string{"_", pkgPath}) 1167 } 1168 } 1169 sort.Slice(importPaths, func(i, j int) bool { 1170 return importPaths[i][1] < importPaths[j][1] 1171 }) 1172 1173 // Modify the AST to include a new import block. 1174 if len(importPaths) > 0 { 1175 // Insert block after package statement or 1176 // possible comment attached to the end of the package statement. 1177 pos := file.Package 1178 tokFile := fset.File(file.Package) 1179 pkgLine := tokFile.Line(file.Package) 1180 for _, c := range file.Comments { 1181 if tokFile.Line(c.Pos()) > pkgLine { 1182 break 1183 } 1184 pos = c.End() 1185 } 1186 1187 // Construct the import block. 1188 impDecl := &ast.GenDecl{ 1189 Tok: token.IMPORT, 1190 TokPos: pos, 1191 Lparen: pos, 1192 Rparen: pos, 1193 } 1194 for _, importPath := range importPaths { 1195 impDecl.Specs = append(impDecl.Specs, &ast.ImportSpec{ 1196 Name: &ast.Ident{ 1197 Name: importPath[0], 1198 NamePos: pos, 1199 }, 1200 Path: &ast.BasicLit{ 1201 Kind: token.STRING, 1202 Value: strconv.Quote(importPath[1]), 1203 ValuePos: pos, 1204 }, 1205 EndPos: pos, 1206 }) 1207 } 1208 file.Decls = append([]ast.Decl{impDecl}, file.Decls...) 1209 } 1210 1211 var out bytes.Buffer 1212 if err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(&out, fset, file); err != nil { 1213 return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err) 1214 } 1215 return out.Bytes(), nil 1216} 1217 1218// metaFile returns the contents of the file's metadata file, which is a 1219// text formatted string of the google.protobuf.GeneratedCodeInfo. 1220func (g *GeneratedFile) metaFile(content []byte) (string, error) { 1221 fset := token.NewFileSet() 1222 astFile, err := parser.ParseFile(fset, "", content, 0) 1223 if err != nil { 1224 return "", err 1225 } 1226 info := &descriptorpb.GeneratedCodeInfo{} 1227 1228 seenAnnotations := make(map[string]bool) 1229 annotate := func(s string, ident *ast.Ident) { 1230 seenAnnotations[s] = true 1231 for _, loc := range g.annotations[s] { 1232 info.Annotation = append(info.Annotation, &descriptorpb.GeneratedCodeInfo_Annotation{ 1233 SourceFile: proto.String(loc.SourceFile), 1234 Path: loc.Path, 1235 Begin: proto.Int32(int32(fset.Position(ident.Pos()).Offset)), 1236 End: proto.Int32(int32(fset.Position(ident.End()).Offset)), 1237 }) 1238 } 1239 } 1240 for _, decl := range astFile.Decls { 1241 switch decl := decl.(type) { 1242 case *ast.GenDecl: 1243 for _, spec := range decl.Specs { 1244 switch spec := spec.(type) { 1245 case *ast.TypeSpec: 1246 annotate(spec.Name.Name, spec.Name) 1247 switch st := spec.Type.(type) { 1248 case *ast.StructType: 1249 for _, field := range st.Fields.List { 1250 for _, name := range field.Names { 1251 annotate(spec.Name.Name+"."+name.Name, name) 1252 } 1253 } 1254 case *ast.InterfaceType: 1255 for _, field := range st.Methods.List { 1256 for _, name := range field.Names { 1257 annotate(spec.Name.Name+"."+name.Name, name) 1258 } 1259 } 1260 } 1261 case *ast.ValueSpec: 1262 for _, name := range spec.Names { 1263 annotate(name.Name, name) 1264 } 1265 } 1266 } 1267 case *ast.FuncDecl: 1268 if decl.Recv == nil { 1269 annotate(decl.Name.Name, decl.Name) 1270 } else { 1271 recv := decl.Recv.List[0].Type 1272 if s, ok := recv.(*ast.StarExpr); ok { 1273 recv = s.X 1274 } 1275 if id, ok := recv.(*ast.Ident); ok { 1276 annotate(id.Name+"."+decl.Name.Name, decl.Name) 1277 } 1278 } 1279 } 1280 } 1281 for a := range g.annotations { 1282 if !seenAnnotations[a] { 1283 return "", fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a) 1284 } 1285 } 1286 1287 b, err := prototext.Marshal(info) 1288 if err != nil { 1289 return "", err 1290 } 1291 return string(b), nil 1292} 1293 1294// A GoIdent is a Go identifier, consisting of a name and import path. 1295// The name is a single identifier and may not be a dot-qualified selector. 1296type GoIdent struct { 1297 GoName string 1298 GoImportPath GoImportPath 1299} 1300 1301func (id GoIdent) String() string { return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName) } 1302 1303// newGoIdent returns the Go identifier for a descriptor. 1304func newGoIdent(f *File, d protoreflect.Descriptor) GoIdent { 1305 name := strings.TrimPrefix(string(d.FullName()), string(f.Desc.Package())+".") 1306 return GoIdent{ 1307 GoName: strs.GoCamelCase(name), 1308 GoImportPath: f.GoImportPath, 1309 } 1310} 1311 1312// A GoImportPath is the import path of a Go package. 1313// For example: "google.golang.org/protobuf/compiler/protogen" 1314type GoImportPath string 1315 1316func (p GoImportPath) String() string { return strconv.Quote(string(p)) } 1317 1318// Ident returns a GoIdent with s as the GoName and p as the GoImportPath. 1319func (p GoImportPath) Ident(s string) GoIdent { 1320 return GoIdent{GoName: s, GoImportPath: p} 1321} 1322 1323// A GoPackageName is the name of a Go package. e.g., "protobuf". 1324type GoPackageName string 1325 1326// cleanPackageName converts a string to a valid Go package name. 1327func cleanPackageName(name string) GoPackageName { 1328 return GoPackageName(strs.GoSanitized(name)) 1329} 1330 1331// baseName returns the last path element of the name, with the last dotted suffix removed. 1332func baseName(name string) string { 1333 // First, find the last element 1334 if i := strings.LastIndex(name, "/"); i >= 0 { 1335 name = name[i+1:] 1336 } 1337 // Now drop the suffix 1338 if i := strings.LastIndex(name, "."); i >= 0 { 1339 name = name[:i] 1340 } 1341 return name 1342} 1343 1344type pathType int 1345 1346const ( 1347 pathTypeLegacy pathType = iota 1348 pathTypeImport 1349 pathTypeSourceRelative 1350) 1351 1352// A Location is a location in a .proto source file. 1353// 1354// See the google.protobuf.SourceCodeInfo documentation in descriptor.proto 1355// for details. 1356type Location struct { 1357 SourceFile string 1358 Path protoreflect.SourcePath 1359} 1360 1361// appendPath add elements to a Location's path, returning a new Location. 1362func (loc Location) appendPath(a ...int32) Location { 1363 var n protoreflect.SourcePath 1364 n = append(n, loc.Path...) 1365 n = append(n, a...) 1366 return Location{ 1367 SourceFile: loc.SourceFile, 1368 Path: n, 1369 } 1370} 1371 1372// A pathKey is a representation of a location path suitable for use as a map key. 1373type pathKey struct { 1374 s string 1375} 1376 1377// newPathKey converts a location path to a pathKey. 1378func newPathKey(idxPath []int32) pathKey { 1379 buf := make([]byte, 4*len(idxPath)) 1380 for i, x := range idxPath { 1381 binary.LittleEndian.PutUint32(buf[i*4:], uint32(x)) 1382 } 1383 return pathKey{string(buf)} 1384} 1385 1386// CommentSet is a set of leading and trailing comments associated 1387// with a .proto descriptor declaration. 1388type CommentSet struct { 1389 LeadingDetached []Comments 1390 Leading Comments 1391 Trailing Comments 1392} 1393 1394// Comments is a comments string as provided by protoc. 1395type Comments string 1396 1397// String formats the comments by inserting // to the start of each line, 1398// ensuring that there is a trailing newline. 1399// An empty comment is formatted as an empty string. 1400func (c Comments) String() string { 1401 if c == "" { 1402 return "" 1403 } 1404 var b []byte 1405 for _, line := range strings.Split(strings.TrimSuffix(string(c), "\n"), "\n") { 1406 b = append(b, "//"...) 1407 b = append(b, line...) 1408 b = append(b, "\n"...) 1409 } 1410 return string(b) 1411} 1412 1413var warnings = true 1414 1415func warn(format string, a ...interface{}) { 1416 if warnings { 1417 log.Printf("WARNING: "+format, a...) 1418 } 1419} 1420