2018-11-16 23:31:26 +00:00
|
|
|
// Copyright 2018 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2018-08-15 18:24:18 +00:00
|
|
|
package protogen
|
|
|
|
|
|
|
|
import (
|
2018-08-23 21:39:30 +00:00
|
|
|
"fmt"
|
2018-08-15 18:24:18 +00:00
|
|
|
"go/token"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
2018-08-23 21:39:30 +00:00
|
|
|
|
2018-09-22 00:44:00 +00:00
|
|
|
"github.com/golang/protobuf/v2/reflect/protoreflect"
|
2018-08-15 18:24:18 +00:00
|
|
|
)
|
|
|
|
|
2018-08-23 21:39:30 +00:00
|
|
|
// A GoIdent is a Go identifier, consisting of a name and import path.
|
2018-11-16 23:31:26 +00:00
|
|
|
// The name is a single identifier and may not be a dot-qualified selector.
|
2018-08-23 21:39:30 +00:00
|
|
|
type GoIdent struct {
|
|
|
|
GoName string
|
|
|
|
GoImportPath GoImportPath
|
|
|
|
}
|
|
|
|
|
|
|
|
func (id GoIdent) String() string { return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName) }
|
2018-08-22 20:46:02 +00:00
|
|
|
|
2018-08-23 21:39:30 +00:00
|
|
|
// newGoIdent returns the Go identifier for a descriptor.
|
|
|
|
func newGoIdent(f *File, d protoreflect.Descriptor) GoIdent {
|
|
|
|
name := strings.TrimPrefix(string(d.FullName()), string(f.Desc.Package())+".")
|
|
|
|
return GoIdent{
|
|
|
|
GoName: camelCase(name),
|
|
|
|
GoImportPath: f.GoImportPath,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-15 18:24:18 +00:00
|
|
|
// A GoImportPath is the import path of a Go package. e.g., "google.golang.org/genproto/protobuf".
|
|
|
|
type GoImportPath string
|
|
|
|
|
|
|
|
func (p GoImportPath) String() string { return strconv.Quote(string(p)) }
|
|
|
|
|
2018-11-16 19:14:14 +00:00
|
|
|
// Ident returns a GoIdent with s as the GoName and p as the GoImportPath.
|
|
|
|
func (p GoImportPath) Ident(s string) GoIdent {
|
|
|
|
return GoIdent{GoName: s, GoImportPath: p}
|
|
|
|
}
|
|
|
|
|
2018-08-15 18:24:18 +00:00
|
|
|
// A GoPackageName is the name of a Go package. e.g., "protobuf".
|
|
|
|
type GoPackageName string
|
|
|
|
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 22:44:37 +00:00
|
|
|
// cleanPackageName converts a string to a valid Go package name.
|
2018-08-15 18:24:18 +00:00
|
|
|
func cleanPackageName(name string) GoPackageName {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 22:44:37 +00:00
|
|
|
return GoPackageName(cleanGoName(name, false))
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleanGoName converts a string to a valid Go identifier.
|
|
|
|
// If mustExport, then the returned identifier is exported if not already.
|
2018-12-14 02:37:25 +00:00
|
|
|
func cleanGoName(s string, mustExport bool) string {
|
|
|
|
// Sanitize the input to the set of valid characters,
|
|
|
|
// which must be '_' or be in the Unicode L or N categories.
|
|
|
|
s = strings.Map(func(r rune) rune {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 22:44:37 +00:00
|
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
return '_'
|
2018-12-14 02:37:25 +00:00
|
|
|
}, s)
|
|
|
|
r, n := utf8.DecodeRuneInString(s)
|
|
|
|
|
|
|
|
// Export the identifier by either uppercasing the first character or by
|
|
|
|
// prepending 'X' (to ensure name starts in the Unicode Lu category).
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 22:44:37 +00:00
|
|
|
if mustExport {
|
2018-12-14 02:37:25 +00:00
|
|
|
// If possible, uppercase the first character. However, not all
|
|
|
|
// characters in the Unicode L category have an Lu equivalent.
|
|
|
|
if unicode.IsUpper(unicode.ToUpper(r)) {
|
|
|
|
return string(unicode.ToUpper(r)) + s[n:]
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 22:44:37 +00:00
|
|
|
}
|
2018-12-14 02:37:25 +00:00
|
|
|
return "X" + s
|
2018-08-15 18:24:18 +00:00
|
|
|
}
|
2018-12-14 02:37:25 +00:00
|
|
|
|
|
|
|
// Prepend '_' in the event of a Go keyword conflict or if
|
|
|
|
// the identifier is invalid (does not start in the Unicode L category).
|
|
|
|
if token.Lookup(s).IsKeyword() || !unicode.IsLetter(r) {
|
|
|
|
return "_" + s
|
|
|
|
}
|
|
|
|
return s
|
2018-08-15 18:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// baseName returns the last path element of the name, with the last dotted suffix removed.
|
|
|
|
func baseName(name string) string {
|
|
|
|
// First, find the last element
|
|
|
|
if i := strings.LastIndex(name, "/"); i >= 0 {
|
|
|
|
name = name[i+1:]
|
|
|
|
}
|
|
|
|
// Now drop the suffix
|
|
|
|
if i := strings.LastIndex(name, "."); i >= 0 {
|
|
|
|
name = name[:i]
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
2018-08-22 20:46:02 +00:00
|
|
|
|
|
|
|
// camelCase converts a name to CamelCase.
|
|
|
|
//
|
|
|
|
// If there is an interior underscore followed by a lower case letter,
|
|
|
|
// drop the underscore and convert the letter to upper case.
|
|
|
|
// There is a remote possibility of this rewrite causing a name collision,
|
|
|
|
// but it's so remote we're prepared to pretend it's nonexistent - since the
|
|
|
|
// C++ generator lowercases names, it's extremely unlikely to have two fields
|
|
|
|
// with different capitalizations.
|
2018-08-23 21:39:30 +00:00
|
|
|
func camelCase(s string) string {
|
2018-08-22 20:46:02 +00:00
|
|
|
// Invariant: if the next letter is lower case, it must be converted
|
|
|
|
// to upper case.
|
|
|
|
// That is, we process a word at a time, where words are marked by _ or
|
|
|
|
// upper case letter. Digits are treated as words.
|
2018-12-14 02:37:25 +00:00
|
|
|
var b []byte
|
|
|
|
for i := 0; i < len(s); i++ {
|
2018-08-22 20:46:02 +00:00
|
|
|
c := s[i]
|
|
|
|
switch {
|
2018-10-09 20:24:04 +00:00
|
|
|
case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]):
|
2018-12-14 02:37:25 +00:00
|
|
|
// Skip over '.' in ".{{lowercase}}".
|
2018-08-22 20:46:02 +00:00
|
|
|
case c == '.':
|
2018-12-14 02:37:25 +00:00
|
|
|
b = append(b, '_') // convert '.' to '_'
|
2018-08-22 20:46:02 +00:00
|
|
|
case c == '_' && (i == 0 || s[i-1] == '.'):
|
2018-12-14 02:37:25 +00:00
|
|
|
// Convert initial '_' to ensure we start with a capital letter.
|
|
|
|
// Do the same for '_' after '.' to match historic behavior.
|
|
|
|
b = append(b, 'X') // convert '_' to 'X'
|
2018-08-22 20:46:02 +00:00
|
|
|
case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]):
|
2018-12-14 02:37:25 +00:00
|
|
|
// Skip over '_' in "_{{lowercase}}".
|
2018-08-22 20:46:02 +00:00
|
|
|
case isASCIIDigit(c):
|
2018-12-14 02:37:25 +00:00
|
|
|
b = append(b, c)
|
2018-08-22 20:46:02 +00:00
|
|
|
default:
|
|
|
|
// Assume we have a letter now - if not, it's a bogus identifier.
|
|
|
|
// The next word is a sequence of characters that must start upper case.
|
|
|
|
if isASCIILower(c) {
|
2018-12-14 02:37:25 +00:00
|
|
|
c -= 'a' - 'A' // convert lowercase to uppercase
|
2018-08-22 20:46:02 +00:00
|
|
|
}
|
2018-12-14 02:37:25 +00:00
|
|
|
b = append(b, c)
|
|
|
|
|
2018-08-22 20:46:02 +00:00
|
|
|
// Accept lower case sequence that follows.
|
2018-12-14 02:37:25 +00:00
|
|
|
for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ {
|
|
|
|
b = append(b, s[i+1])
|
2018-08-22 20:46:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-14 02:37:25 +00:00
|
|
|
return string(b)
|
2018-08-22 20:46:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is c an ASCII lower-case letter?
|
|
|
|
func isASCIILower(c byte) bool {
|
|
|
|
return 'a' <= c && c <= 'z'
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is c an ASCII digit?
|
|
|
|
func isASCIIDigit(c byte) bool {
|
|
|
|
return '0' <= c && c <= '9'
|
|
|
|
}
|