github.com/jhump/protoreflect@v1.16.0/desc/builder/file.go (about)

     1  package builder
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"sync/atomic"
     8  
     9  	"google.golang.org/protobuf/proto"
    10  	"google.golang.org/protobuf/types/descriptorpb"
    11  
    12  	"github.com/jhump/protoreflect/desc"
    13  	"github.com/jhump/protoreflect/desc/internal"
    14  	"github.com/jhump/protoreflect/dynamic"
    15  )
    16  
    17  var uniqueFileCounter uint64
    18  
    19  func uniqueFileName() string {
    20  	i := atomic.AddUint64(&uniqueFileCounter, 1)
    21  	return fmt.Sprintf("{generated-file-%04x}.proto", i)
    22  }
    23  
    24  func makeUnique(name string, existingNames map[string]struct{}) string {
    25  	i := 1
    26  	n := name
    27  	for {
    28  		if _, ok := existingNames[n]; !ok {
    29  			return n
    30  		}
    31  		n = fmt.Sprintf("%s(%d)", name, i)
    32  		i++
    33  	}
    34  }
    35  
    36  // FileBuilder is a builder used to construct a desc.FileDescriptor. This is the
    37  // root of the hierarchy. All other descriptors belong to a file, and thus all
    38  // other builders also belong to a file.
    39  //
    40  // If a builder is *not* associated with a file, the resulting descriptor will
    41  // be associated with a synthesized file that contains only the built descriptor
    42  // and its ancestors. This means that such descriptors will have no associated
    43  // package name.
    44  //
    45  // To create a new FileBuilder, use NewFile.
    46  type FileBuilder struct {
    47  	name string
    48  
    49  	// IsProto3 indicates that the file's syntax is "proto3". If this is
    50  	// false the file either uses syntax "proto2" (when Edition is zero)
    51  	// or uses editions (when Edition is non-zero).
    52  	IsProto3 bool
    53  
    54  	// Edition indicates which edition this file uses. It may only be
    55  	// set when IsProto3 is false.
    56  	Edition descriptorpb.Edition
    57  
    58  	Package string
    59  	Options *descriptorpb.FileOptions
    60  
    61  	comments        Comments
    62  	SyntaxComments  Comments
    63  	PackageComments Comments
    64  
    65  	messages   []*MessageBuilder
    66  	extensions []*FieldBuilder
    67  	enums      []*EnumBuilder
    68  	services   []*ServiceBuilder
    69  	symbols    map[string]Builder
    70  
    71  	origExts        *dynamic.ExtensionRegistry
    72  	explicitDeps    map[*FileBuilder]struct{}
    73  	explicitImports map[*desc.FileDescriptor]struct{}
    74  }
    75  
    76  // NewFile creates a new FileBuilder for a file with the given name. The
    77  // name can be blank, which indicates a unique name should be generated for it.
    78  func NewFile(name string) *FileBuilder {
    79  	return &FileBuilder{
    80  		name:    name,
    81  		symbols: map[string]Builder{},
    82  	}
    83  }
    84  
    85  // FromFile returns a FileBuilder that is effectively a copy of the given
    86  // descriptor. Note that builders do not retain full source code info, even if
    87  // the given descriptor included it. Instead, comments are extracted from the
    88  // given descriptor's source info (if present) and, when built, the resulting
    89  // descriptor will have just the comment info (no location information).
    90  func FromFile(fd *desc.FileDescriptor) (*FileBuilder, error) {
    91  	fb := NewFile(fd.GetName())
    92  	fb.IsProto3 = fd.IsProto3()
    93  	fb.Edition = fd.Edition()
    94  	fb.Package = fd.GetPackage()
    95  	fb.Options = fd.GetFileOptions()
    96  	setComments(&fb.comments, fd.GetSourceInfo())
    97  
    98  	// find syntax and package comments, too
    99  	for _, loc := range fd.AsFileDescriptorProto().GetSourceCodeInfo().GetLocation() {
   100  		if len(loc.Path) == 1 {
   101  			if loc.Path[0] == internal.File_syntaxTag || loc.Path[0] == internal.File_editionTag {
   102  				setComments(&fb.SyntaxComments, loc)
   103  			} else if loc.Path[0] == internal.File_packageTag {
   104  				setComments(&fb.PackageComments, loc)
   105  			}
   106  		}
   107  	}
   108  
   109  	// add imports explicitly
   110  	for _, dep := range fd.GetDependencies() {
   111  		fb.AddImportedDependency(dep)
   112  		fb.addExtensionsFromImport(dep)
   113  	}
   114  
   115  	localMessages := map[*desc.MessageDescriptor]*MessageBuilder{}
   116  	localEnums := map[*desc.EnumDescriptor]*EnumBuilder{}
   117  
   118  	for _, md := range fd.GetMessageTypes() {
   119  		if mb, err := fromMessage(md, localMessages, localEnums); err != nil {
   120  			return nil, err
   121  		} else if err := fb.TryAddMessage(mb); err != nil {
   122  			return nil, err
   123  		}
   124  	}
   125  	for _, ed := range fd.GetEnumTypes() {
   126  		if eb, err := fromEnum(ed, localEnums); err != nil {
   127  			return nil, err
   128  		} else if err := fb.TryAddEnum(eb); err != nil {
   129  			return nil, err
   130  		}
   131  	}
   132  	for _, exd := range fd.GetExtensions() {
   133  		if exb, err := fromField(exd); err != nil {
   134  			return nil, err
   135  		} else if err := fb.TryAddExtension(exb); err != nil {
   136  			return nil, err
   137  		}
   138  	}
   139  	for _, sd := range fd.GetServices() {
   140  		if sb, err := fromService(sd); err != nil {
   141  			return nil, err
   142  		} else if err := fb.TryAddService(sb); err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  
   147  	// we've converted everything, so now we update all foreign type references
   148  	// to be local type references if possible
   149  	for _, mb := range fb.messages {
   150  		updateLocalRefsInMessage(mb, localMessages, localEnums)
   151  	}
   152  	for _, exb := range fb.extensions {
   153  		updateLocalRefsInField(exb, localMessages, localEnums)
   154  	}
   155  	for _, sb := range fb.services {
   156  		for _, mtb := range sb.methods {
   157  			updateLocalRefsInRpcType(mtb.ReqType, localMessages)
   158  			updateLocalRefsInRpcType(mtb.RespType, localMessages)
   159  		}
   160  	}
   161  
   162  	return fb, nil
   163  }
   164  
   165  func updateLocalRefsInMessage(mb *MessageBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) {
   166  	for _, b := range mb.fieldsAndOneOfs {
   167  		if flb, ok := b.(*FieldBuilder); ok {
   168  			updateLocalRefsInField(flb, localMessages, localEnums)
   169  		} else {
   170  			oob := b.(*OneOfBuilder)
   171  			for _, flb := range oob.choices {
   172  				updateLocalRefsInField(flb, localMessages, localEnums)
   173  			}
   174  		}
   175  	}
   176  	for _, nmb := range mb.nestedMessages {
   177  		updateLocalRefsInMessage(nmb, localMessages, localEnums)
   178  	}
   179  	for _, exb := range mb.nestedExtensions {
   180  		updateLocalRefsInField(exb, localMessages, localEnums)
   181  	}
   182  }
   183  
   184  func updateLocalRefsInField(flb *FieldBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) {
   185  	if flb.fieldType.foreignMsgType != nil {
   186  		if mb, ok := localMessages[flb.fieldType.foreignMsgType]; ok {
   187  			flb.fieldType.foreignMsgType = nil
   188  			flb.fieldType.localMsgType = mb
   189  		}
   190  	}
   191  	if flb.fieldType.foreignEnumType != nil {
   192  		if eb, ok := localEnums[flb.fieldType.foreignEnumType]; ok {
   193  			flb.fieldType.foreignEnumType = nil
   194  			flb.fieldType.localEnumType = eb
   195  		}
   196  	}
   197  	if flb.foreignExtendee != nil {
   198  		if mb, ok := localMessages[flb.foreignExtendee]; ok {
   199  			flb.foreignExtendee = nil
   200  			flb.localExtendee = mb
   201  		}
   202  	}
   203  	if flb.msgType != nil {
   204  		updateLocalRefsInMessage(flb.msgType, localMessages, localEnums)
   205  	}
   206  }
   207  
   208  func updateLocalRefsInRpcType(rpcType *RpcType, localMessages map[*desc.MessageDescriptor]*MessageBuilder) {
   209  	if rpcType.foreignType != nil {
   210  		if mb, ok := localMessages[rpcType.foreignType]; ok {
   211  			rpcType.foreignType = nil
   212  			rpcType.localType = mb
   213  		}
   214  	}
   215  }
   216  
   217  // GetName returns the name of the file. It may include relative path
   218  // information, too.
   219  func (fb *FileBuilder) GetName() string {
   220  	return fb.name
   221  }
   222  
   223  // SetName changes this file's name, returning the file builder for method
   224  // chaining.
   225  func (fb *FileBuilder) SetName(newName string) *FileBuilder {
   226  	fb.name = newName
   227  	return fb
   228  }
   229  
   230  // TrySetName changes this file's name. It always returns nil since renaming
   231  // a file cannot fail. (It is specified to return error to satisfy the Builder
   232  // interface.)
   233  func (fb *FileBuilder) TrySetName(newName string) error {
   234  	fb.name = newName
   235  	return nil
   236  }
   237  
   238  // GetParent always returns nil since files are the roots of builder
   239  // hierarchies.
   240  func (fb *FileBuilder) GetParent() Builder {
   241  	return nil
   242  }
   243  
   244  func (fb *FileBuilder) setParent(parent Builder) {
   245  	if parent != nil {
   246  		panic("files cannot have parent elements")
   247  	}
   248  }
   249  
   250  // GetComments returns comments associated with the file itself and not any
   251  // particular element therein. (Note that such a comment will not be rendered by
   252  // the protoprint package.)
   253  func (fb *FileBuilder) GetComments() *Comments {
   254  	return &fb.comments
   255  }
   256  
   257  // SetComments sets the comments associated with the file itself, not any
   258  // particular element therein. (Note that such a comment will not be rendered by
   259  // the protoprint package.) This method returns the file, for method chaining.
   260  func (fb *FileBuilder) SetComments(c Comments) *FileBuilder {
   261  	fb.comments = c
   262  	return fb
   263  }
   264  
   265  // SetSyntaxComments sets the comments associated with the syntax declaration
   266  // element (which, if present, is required to be the first element in a proto
   267  // file). This method returns the file, for method chaining.
   268  func (fb *FileBuilder) SetSyntaxComments(c Comments) *FileBuilder {
   269  	fb.SyntaxComments = c
   270  	return fb
   271  }
   272  
   273  // SetPackageComments sets the comments associated with the package declaration
   274  // element. (This comment will not be rendered if the file's declared package is
   275  // empty.) This method returns the file, for method chaining.
   276  func (fb *FileBuilder) SetPackageComments(c Comments) *FileBuilder {
   277  	fb.PackageComments = c
   278  	return fb
   279  }
   280  
   281  // GetFile implements the Builder interface and always returns this file.
   282  func (fb *FileBuilder) GetFile() *FileBuilder {
   283  	return fb
   284  }
   285  
   286  // GetChildren returns builders for all nested elements, including all top-level
   287  // messages, enums, extensions, and services.
   288  func (fb *FileBuilder) GetChildren() []Builder {
   289  	var ch []Builder
   290  	for _, mb := range fb.messages {
   291  		ch = append(ch, mb)
   292  	}
   293  	for _, exb := range fb.extensions {
   294  		ch = append(ch, exb)
   295  	}
   296  	for _, eb := range fb.enums {
   297  		ch = append(ch, eb)
   298  	}
   299  	for _, sb := range fb.services {
   300  		ch = append(ch, sb)
   301  	}
   302  	return ch
   303  }
   304  
   305  func (fb *FileBuilder) findChild(name string) Builder {
   306  	return fb.symbols[name]
   307  }
   308  
   309  func (fb *FileBuilder) removeChild(b Builder) {
   310  	if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb {
   311  		return
   312  	}
   313  
   314  	switch b.(type) {
   315  	case *MessageBuilder:
   316  		fb.messages = deleteBuilder(b.GetName(), fb.messages).([]*MessageBuilder)
   317  	case *FieldBuilder:
   318  		fb.extensions = deleteBuilder(b.GetName(), fb.extensions).([]*FieldBuilder)
   319  	case *EnumBuilder:
   320  		fb.enums = deleteBuilder(b.GetName(), fb.enums).([]*EnumBuilder)
   321  	case *ServiceBuilder:
   322  		fb.services = deleteBuilder(b.GetName(), fb.services).([]*ServiceBuilder)
   323  	}
   324  	delete(fb.symbols, b.GetName())
   325  	b.setParent(nil)
   326  }
   327  
   328  func (fb *FileBuilder) renamedChild(b Builder, oldName string) error {
   329  	if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb {
   330  		return nil
   331  	}
   332  
   333  	if err := fb.addSymbol(b); err != nil {
   334  		return err
   335  	}
   336  	delete(fb.symbols, oldName)
   337  	return nil
   338  }
   339  
   340  func (fb *FileBuilder) addSymbol(b Builder) error {
   341  	if ex, ok := fb.symbols[b.GetName()]; ok {
   342  		return fmt.Errorf("file %q already contains element (%T) named %q", fb.GetName(), ex, b.GetName())
   343  	}
   344  	fb.symbols[b.GetName()] = b
   345  	return nil
   346  }
   347  
   348  func (fb *FileBuilder) findFullyQualifiedElement(fqn string) Builder {
   349  	if fb.Package != "" {
   350  		if !strings.HasPrefix(fqn, fb.Package+".") {
   351  			return nil
   352  		}
   353  		fqn = fqn[len(fb.Package)+1:]
   354  	}
   355  	names := strings.Split(fqn, ".")
   356  	var b Builder = fb
   357  	for b != nil && len(names) > 0 {
   358  		b = b.findChild(names[0])
   359  		names = names[1:]
   360  	}
   361  	return b
   362  }
   363  
   364  // GetMessage returns the top-level message with the given name. If no such
   365  // message exists in the file, nil is returned.
   366  func (fb *FileBuilder) GetMessage(name string) *MessageBuilder {
   367  	b := fb.symbols[name]
   368  	if mb, ok := b.(*MessageBuilder); ok {
   369  		return mb
   370  	} else {
   371  		return nil
   372  	}
   373  }
   374  
   375  // RemoveMessage removes the top-level message with the given name. If no such
   376  // message exists in the file, this is a no-op. This returns the file builder,
   377  // for method chaining.
   378  func (fb *FileBuilder) RemoveMessage(name string) *FileBuilder {
   379  	fb.TryRemoveMessage(name)
   380  	return fb
   381  }
   382  
   383  // TryRemoveMessage removes the top-level message with the given name and
   384  // returns false if the file has no such message.
   385  func (fb *FileBuilder) TryRemoveMessage(name string) bool {
   386  	b := fb.symbols[name]
   387  	if mb, ok := b.(*MessageBuilder); ok {
   388  		fb.removeChild(mb)
   389  		return true
   390  	}
   391  	return false
   392  }
   393  
   394  // AddMessage adds the given message to this file. If an error prevents the
   395  // message from being added, this method panics. This returns the file builder,
   396  // for method chaining.
   397  func (fb *FileBuilder) AddMessage(mb *MessageBuilder) *FileBuilder {
   398  	if err := fb.TryAddMessage(mb); err != nil {
   399  		panic(err)
   400  	}
   401  	return fb
   402  }
   403  
   404  // TryAddMessage adds the given message to this file, returning any error that
   405  // prevents the message from being added (such as a name collision with another
   406  // element already added to the file).
   407  func (fb *FileBuilder) TryAddMessage(mb *MessageBuilder) error {
   408  	if err := fb.addSymbol(mb); err != nil {
   409  		return err
   410  	}
   411  	Unlink(mb)
   412  	mb.setParent(fb)
   413  	fb.messages = append(fb.messages, mb)
   414  	return nil
   415  }
   416  
   417  // GetExtension returns the top-level extension with the given name. If no such
   418  // extension exists in the file, nil is returned.
   419  func (fb *FileBuilder) GetExtension(name string) *FieldBuilder {
   420  	b := fb.symbols[name]
   421  	if exb, ok := b.(*FieldBuilder); ok {
   422  		return exb
   423  	} else {
   424  		return nil
   425  	}
   426  }
   427  
   428  // RemoveExtension removes the top-level extension with the given name. If no
   429  // such extension exists in the file, this is a no-op. This returns the file
   430  // builder, for method chaining.
   431  func (fb *FileBuilder) RemoveExtension(name string) *FileBuilder {
   432  	fb.TryRemoveExtension(name)
   433  	return fb
   434  }
   435  
   436  // TryRemoveExtension removes the top-level extension with the given name and
   437  // returns false if the file has no such extension.
   438  func (fb *FileBuilder) TryRemoveExtension(name string) bool {
   439  	b := fb.symbols[name]
   440  	if exb, ok := b.(*FieldBuilder); ok {
   441  		fb.removeChild(exb)
   442  		return true
   443  	}
   444  	return false
   445  }
   446  
   447  // AddExtension adds the given extension to this file. If an error prevents the
   448  // extension from being added, this method panics. This returns the file
   449  // builder, for method chaining.
   450  func (fb *FileBuilder) AddExtension(exb *FieldBuilder) *FileBuilder {
   451  	if err := fb.TryAddExtension(exb); err != nil {
   452  		panic(err)
   453  	}
   454  	return fb
   455  }
   456  
   457  // TryAddExtension adds the given extension to this file, returning any error
   458  // that prevents the extension from being added (such as a name collision with
   459  // another element already added to the file).
   460  func (fb *FileBuilder) TryAddExtension(exb *FieldBuilder) error {
   461  	if !exb.IsExtension() {
   462  		return fmt.Errorf("field %s is not an extension", exb.GetName())
   463  	}
   464  	if err := fb.addSymbol(exb); err != nil {
   465  		return err
   466  	}
   467  	Unlink(exb)
   468  	exb.setParent(fb)
   469  	fb.extensions = append(fb.extensions, exb)
   470  	return nil
   471  }
   472  
   473  // GetEnum returns the top-level enum with the given name. If no such enum
   474  // exists in the file, nil is returned.
   475  func (fb *FileBuilder) GetEnum(name string) *EnumBuilder {
   476  	b := fb.symbols[name]
   477  	if eb, ok := b.(*EnumBuilder); ok {
   478  		return eb
   479  	} else {
   480  		return nil
   481  	}
   482  }
   483  
   484  // RemoveEnum removes the top-level enum with the given name. If no such enum
   485  // exists in the file, this is a no-op. This returns the file builder, for
   486  // method chaining.
   487  func (fb *FileBuilder) RemoveEnum(name string) *FileBuilder {
   488  	fb.TryRemoveEnum(name)
   489  	return fb
   490  }
   491  
   492  // TryRemoveEnum removes the top-level enum with the given name and returns
   493  // false if the file has no such enum.
   494  func (fb *FileBuilder) TryRemoveEnum(name string) bool {
   495  	b := fb.symbols[name]
   496  	if eb, ok := b.(*EnumBuilder); ok {
   497  		fb.removeChild(eb)
   498  		return true
   499  	}
   500  	return false
   501  }
   502  
   503  // AddEnum adds the given enum to this file. If an error prevents the enum from
   504  // being added, this method panics. This returns the file builder, for method
   505  // chaining.
   506  func (fb *FileBuilder) AddEnum(eb *EnumBuilder) *FileBuilder {
   507  	if err := fb.TryAddEnum(eb); err != nil {
   508  		panic(err)
   509  	}
   510  	return fb
   511  }
   512  
   513  // TryAddEnum adds the given enum to this file, returning any error that
   514  // prevents the enum from being added (such as a name collision with another
   515  // element already added to the file).
   516  func (fb *FileBuilder) TryAddEnum(eb *EnumBuilder) error {
   517  	if err := fb.addSymbol(eb); err != nil {
   518  		return err
   519  	}
   520  	Unlink(eb)
   521  	eb.setParent(fb)
   522  	fb.enums = append(fb.enums, eb)
   523  	return nil
   524  }
   525  
   526  // GetService returns the top-level service with the given name. If no such
   527  // service exists in the file, nil is returned.
   528  func (fb *FileBuilder) GetService(name string) *ServiceBuilder {
   529  	b := fb.symbols[name]
   530  	if sb, ok := b.(*ServiceBuilder); ok {
   531  		return sb
   532  	} else {
   533  		return nil
   534  	}
   535  }
   536  
   537  // RemoveService removes the top-level service with the given name. If no such
   538  // service exists in the file, this is a no-op. This returns the file builder,
   539  // for method chaining.
   540  func (fb *FileBuilder) RemoveService(name string) *FileBuilder {
   541  	fb.TryRemoveService(name)
   542  	return fb
   543  }
   544  
   545  // TryRemoveService removes the top-level service with the given name and
   546  // returns false if the file has no such service.
   547  func (fb *FileBuilder) TryRemoveService(name string) bool {
   548  	b := fb.symbols[name]
   549  	if sb, ok := b.(*ServiceBuilder); ok {
   550  		fb.removeChild(sb)
   551  		return true
   552  	}
   553  	return false
   554  }
   555  
   556  // AddService adds the given service to this file. If an error prevents the
   557  // service from being added, this method panics. This returns the file builder,
   558  // for method chaining.
   559  func (fb *FileBuilder) AddService(sb *ServiceBuilder) *FileBuilder {
   560  	if err := fb.TryAddService(sb); err != nil {
   561  		panic(err)
   562  	}
   563  	return fb
   564  }
   565  
   566  // TryAddService adds the given service to this file, returning any error that
   567  // prevents the service from being added (such as a name collision with another
   568  // element already added to the file).
   569  func (fb *FileBuilder) TryAddService(sb *ServiceBuilder) error {
   570  	if err := fb.addSymbol(sb); err != nil {
   571  		return err
   572  	}
   573  	Unlink(sb)
   574  	sb.setParent(fb)
   575  	fb.services = append(fb.services, sb)
   576  	return nil
   577  }
   578  
   579  func (fb *FileBuilder) addExtensionsFromImport(dep *desc.FileDescriptor) {
   580  	if fb.origExts == nil {
   581  		fb.origExts = &dynamic.ExtensionRegistry{}
   582  	}
   583  	fb.origExts.AddExtensionsFromFile(dep)
   584  	// we also add any extensions from this dependency's "public" imports since
   585  	// they are also visible to the importing file
   586  	for _, publicDep := range dep.GetPublicDependencies() {
   587  		fb.addExtensionsFromImport(publicDep)
   588  	}
   589  }
   590  
   591  // AddDependency adds the given file as an explicit import. Normally,
   592  // dependencies can be inferred during the build process by finding the files
   593  // for all referenced types (such as message and enum types used in this file).
   594  // However, this does not work for custom options, which must be known in order
   595  // to be interpretable. And they aren't known unless an explicit import is added
   596  // for the file that contains the custom options.
   597  //
   598  // Knowledge of custom options can also be provided by using BuilderOptions with
   599  // an ExtensionRegistry, when building the file.
   600  func (fb *FileBuilder) AddDependency(dep *FileBuilder) *FileBuilder {
   601  	if fb.explicitDeps == nil {
   602  		fb.explicitDeps = map[*FileBuilder]struct{}{}
   603  	}
   604  	fb.explicitDeps[dep] = struct{}{}
   605  	return fb
   606  }
   607  
   608  // AddImportedDependency adds the given file as an explicit import. Normally,
   609  // dependencies can be inferred during the build process by finding the files
   610  // for all referenced types (such as message and enum types used in this file).
   611  // However, this does not work for custom options, which must be known in order
   612  // to be interpretable. And they aren't known unless an explicit import is added
   613  // for the file that contains the custom options.
   614  //
   615  // Knowledge of custom options can also be provided by using BuilderOptions with
   616  // an ExtensionRegistry, when building the file.
   617  func (fb *FileBuilder) AddImportedDependency(dep *desc.FileDescriptor) *FileBuilder {
   618  	if fb.explicitImports == nil {
   619  		fb.explicitImports = map[*desc.FileDescriptor]struct{}{}
   620  	}
   621  	fb.explicitImports[dep] = struct{}{}
   622  	return fb
   623  }
   624  
   625  // PruneUnusedDependencies removes all imports that are not actually used in the
   626  // file. Note that this undoes any calls to AddDependency or AddImportedDependency
   627  // which means that custom options may be missing from the resulting built
   628  // descriptor unless BuilderOptions are used that include an ExtensionRegistry with
   629  // knowledge of all custom options.
   630  //
   631  // When FromFile is used to create a FileBuilder from an existing descriptor, all
   632  // imports are usually preserved in any subsequent built descriptor. But this method
   633  // can be used to remove imports from the original file, like if mutations are made
   634  // to the file's contents such that not all imports are needed anymore. When FromFile
   635  // is used, any custom options present in the original descriptor will be correctly
   636  // retained. If the file is mutated such that new custom options are added to the file,
   637  // they may be missing unless AddImportedDependency is called after pruning OR
   638  // BuilderOptions are used that include an ExtensionRegistry with knowledge of the
   639  // new custom options.
   640  func (fb *FileBuilder) PruneUnusedDependencies() *FileBuilder {
   641  	fb.explicitImports = nil
   642  	fb.explicitDeps = nil
   643  	return fb
   644  }
   645  
   646  // SetOptions sets the file options for this file and returns the file, for
   647  // method chaining.
   648  func (fb *FileBuilder) SetOptions(options *descriptorpb.FileOptions) *FileBuilder {
   649  	fb.Options = options
   650  	return fb
   651  }
   652  
   653  // SetPackageName sets the name of the package for this file and returns the
   654  // file, for method chaining.
   655  func (fb *FileBuilder) SetPackageName(pkg string) *FileBuilder {
   656  	fb.Package = pkg
   657  	return fb
   658  }
   659  
   660  // SetProto3 sets whether this file is declared to use "proto3" syntax or not
   661  // and returns the file, for method chaining. If this is called with a value
   662  // of false, then "proto2" syntax is assumed. To instead set the file to use
   663  // editions, call SetEdition.
   664  func (fb *FileBuilder) SetProto3(isProto3 bool) *FileBuilder {
   665  	fb.IsProto3 = isProto3
   666  	fb.Edition = 0
   667  	return fb
   668  }
   669  
   670  // SetEdition sets the edition that this file uses and returns the file, for
   671  // method chaining. This supports the use of the EDITION_PROTO2 and EDITION_PROTO3
   672  // values to actually set the value as "proto2" or "proto3" syntax respectively.
   673  // If a value less than EDITION_PROTO2 is provided, the invalid value is ignored
   674  // and the file is instead set to use "proto2" syntax.
   675  func (fb *FileBuilder) SetEdition(edition descriptorpb.Edition) *FileBuilder {
   676  	if edition <= descriptorpb.Edition_EDITION_PROTO2 {
   677  		fb.IsProto3 = false
   678  		fb.Edition = 0
   679  	} else if edition == descriptorpb.Edition_EDITION_PROTO3 {
   680  		fb.IsProto3 = true
   681  		fb.Edition = 0
   682  	} else {
   683  		fb.IsProto3 = false
   684  		fb.Edition = edition
   685  	}
   686  	return fb
   687  }
   688  
   689  func (fb *FileBuilder) buildProto(deps []*desc.FileDescriptor) (*descriptorpb.FileDescriptorProto, error) {
   690  	name := fb.name
   691  	if name == "" {
   692  		name = uniqueFileName()
   693  	}
   694  	var syntax *string
   695  	var edition *descriptorpb.Edition
   696  	if fb.IsProto3 {
   697  		syntax = proto.String("proto3")
   698  	} else if fb.Edition > 0 {
   699  		if fb.Edition < descriptorpb.Edition_EDITION_2023 ||
   700  			fb.Edition >= descriptorpb.Edition_EDITION_MAX ||
   701  			descriptorpb.Edition_name[int32(fb.Edition)] == "" ||
   702  			strings.HasSuffix(fb.Edition.String(), "_TEST_ONLY") {
   703  			// Not a valid edition!
   704  			return nil, fmt.Errorf("builder contains unknown or invalid edition: %v", fb.Edition)
   705  		}
   706  		syntax = proto.String("editions")
   707  		edition = fb.Edition.Enum()
   708  	}
   709  	var pkg *string
   710  	if fb.Package != "" {
   711  		pkg = proto.String(fb.Package)
   712  	}
   713  
   714  	path := make([]int32, 0, 10)
   715  	sourceInfo := descriptorpb.SourceCodeInfo{}
   716  	addCommentsTo(&sourceInfo, path, &fb.comments)
   717  	addCommentsTo(&sourceInfo, append(path, internal.File_syntaxTag), &fb.SyntaxComments)
   718  	addCommentsTo(&sourceInfo, append(path, internal.File_packageTag), &fb.PackageComments)
   719  
   720  	imports := make([]string, 0, len(deps))
   721  	for _, dep := range deps {
   722  		imports = append(imports, dep.GetName())
   723  	}
   724  	sort.Strings(imports)
   725  
   726  	messages := make([]*descriptorpb.DescriptorProto, 0, len(fb.messages))
   727  	for _, mb := range fb.messages {
   728  		path := append(path, internal.File_messagesTag, int32(len(messages)))
   729  		if md, err := mb.buildProto(path, &sourceInfo); err != nil {
   730  			return nil, err
   731  		} else {
   732  			messages = append(messages, md)
   733  		}
   734  	}
   735  
   736  	enums := make([]*descriptorpb.EnumDescriptorProto, 0, len(fb.enums))
   737  	for _, eb := range fb.enums {
   738  		path := append(path, internal.File_enumsTag, int32(len(enums)))
   739  		if ed, err := eb.buildProto(path, &sourceInfo); err != nil {
   740  			return nil, err
   741  		} else {
   742  			enums = append(enums, ed)
   743  		}
   744  	}
   745  
   746  	extensions := make([]*descriptorpb.FieldDescriptorProto, 0, len(fb.extensions))
   747  	for _, exb := range fb.extensions {
   748  		path := append(path, internal.File_extensionsTag, int32(len(extensions)))
   749  		if exd, err := exb.buildProto(path, &sourceInfo, isExtendeeMessageSet(exb)); err != nil {
   750  			return nil, err
   751  		} else {
   752  			extensions = append(extensions, exd)
   753  		}
   754  	}
   755  
   756  	services := make([]*descriptorpb.ServiceDescriptorProto, 0, len(fb.services))
   757  	for _, sb := range fb.services {
   758  		path := append(path, internal.File_servicesTag, int32(len(services)))
   759  		if sd, err := sb.buildProto(path, &sourceInfo); err != nil {
   760  			return nil, err
   761  		} else {
   762  			services = append(services, sd)
   763  		}
   764  	}
   765  
   766  	return &descriptorpb.FileDescriptorProto{
   767  		Name:           proto.String(name),
   768  		Package:        pkg,
   769  		Dependency:     imports,
   770  		Options:        fb.Options,
   771  		Syntax:         syntax,
   772  		Edition:        edition,
   773  		MessageType:    messages,
   774  		EnumType:       enums,
   775  		Extension:      extensions,
   776  		Service:        services,
   777  		SourceCodeInfo: &sourceInfo,
   778  	}, nil
   779  }
   780  
   781  func isExtendeeMessageSet(flb *FieldBuilder) bool {
   782  	if flb.localExtendee != nil {
   783  		return flb.localExtendee.Options.GetMessageSetWireFormat()
   784  	}
   785  	return flb.foreignExtendee.GetMessageOptions().GetMessageSetWireFormat()
   786  }
   787  
   788  // Build constructs a file descriptor based on the contents of this file
   789  // builder. If there are any problems constructing the descriptor, including
   790  // resolving symbols referenced by the builder or failing to meet certain
   791  // validation rules, an error is returned.
   792  func (fb *FileBuilder) Build() (*desc.FileDescriptor, error) {
   793  	fd, err := fb.BuildDescriptor()
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  	return fd.(*desc.FileDescriptor), nil
   798  }
   799  
   800  // BuildDescriptor constructs a file descriptor based on the contents of this
   801  // file builder. Most usages will prefer Build() instead, whose return type is a
   802  // concrete descriptor type. This method is present to satisfy the Builder
   803  // interface.
   804  func (fb *FileBuilder) BuildDescriptor() (desc.Descriptor, error) {
   805  	return doBuild(fb, BuilderOptions{})
   806  }