names.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. package protogen
  5. import (
  6. "fmt"
  7. "go/token"
  8. "strconv"
  9. "strings"
  10. "unicode"
  11. "unicode/utf8"
  12. "google.golang.org/protobuf/reflect/protoreflect"
  13. )
  14. // A GoIdent is a Go identifier, consisting of a name and import path.
  15. // The name is a single identifier and may not be a dot-qualified selector.
  16. type GoIdent struct {
  17. GoName string
  18. GoImportPath GoImportPath
  19. }
  20. func (id GoIdent) String() string { return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName) }
  21. // newGoIdent returns the Go identifier for a descriptor.
  22. func newGoIdent(f *File, d protoreflect.Descriptor) GoIdent {
  23. name := strings.TrimPrefix(string(d.FullName()), string(f.Desc.Package())+".")
  24. return GoIdent{
  25. GoName: camelCase(name),
  26. GoImportPath: f.GoImportPath,
  27. }
  28. }
  29. // A GoImportPath is the import path of a Go package. e.g., "google.golang.org/genproto/protobuf".
  30. type GoImportPath string
  31. func (p GoImportPath) String() string { return strconv.Quote(string(p)) }
  32. // Ident returns a GoIdent with s as the GoName and p as the GoImportPath.
  33. func (p GoImportPath) Ident(s string) GoIdent {
  34. return GoIdent{GoName: s, GoImportPath: p}
  35. }
  36. // A GoPackageName is the name of a Go package. e.g., "protobuf".
  37. type GoPackageName string
  38. // cleanPackageName converts a string to a valid Go package name.
  39. func cleanPackageName(name string) GoPackageName {
  40. return GoPackageName(cleanGoName(name))
  41. }
  42. // cleanGoName converts a string to a valid Go identifier.
  43. func cleanGoName(s string) string {
  44. // Sanitize the input to the set of valid characters,
  45. // which must be '_' or be in the Unicode L or N categories.
  46. s = strings.Map(func(r rune) rune {
  47. if unicode.IsLetter(r) || unicode.IsDigit(r) {
  48. return r
  49. }
  50. return '_'
  51. }, s)
  52. // Prepend '_' in the event of a Go keyword conflict or if
  53. // the identifier is invalid (does not start in the Unicode L category).
  54. r, _ := utf8.DecodeRuneInString(s)
  55. if token.Lookup(s).IsKeyword() || !unicode.IsLetter(r) {
  56. return "_" + s
  57. }
  58. return s
  59. }
  60. // baseName returns the last path element of the name, with the last dotted suffix removed.
  61. func baseName(name string) string {
  62. // First, find the last element
  63. if i := strings.LastIndex(name, "/"); i >= 0 {
  64. name = name[i+1:]
  65. }
  66. // Now drop the suffix
  67. if i := strings.LastIndex(name, "."); i >= 0 {
  68. name = name[:i]
  69. }
  70. return name
  71. }
  72. // camelCase converts a name to CamelCase.
  73. //
  74. // If there is an interior underscore followed by a lower case letter,
  75. // drop the underscore and convert the letter to upper case.
  76. // There is a remote possibility of this rewrite causing a name collision,
  77. // but it's so remote we're prepared to pretend it's nonexistent - since the
  78. // C++ generator lowercases names, it's extremely unlikely to have two fields
  79. // with different capitalizations.
  80. func camelCase(s string) string {
  81. // Invariant: if the next letter is lower case, it must be converted
  82. // to upper case.
  83. // That is, we process a word at a time, where words are marked by _ or
  84. // upper case letter. Digits are treated as words.
  85. var b []byte
  86. for i := 0; i < len(s); i++ {
  87. c := s[i]
  88. switch {
  89. case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]):
  90. // Skip over '.' in ".{{lowercase}}".
  91. case c == '.':
  92. b = append(b, '_') // convert '.' to '_'
  93. case c == '_' && (i == 0 || s[i-1] == '.'):
  94. // Convert initial '_' to ensure we start with a capital letter.
  95. // Do the same for '_' after '.' to match historic behavior.
  96. b = append(b, 'X') // convert '_' to 'X'
  97. case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]):
  98. // Skip over '_' in "_{{lowercase}}".
  99. case isASCIIDigit(c):
  100. b = append(b, c)
  101. default:
  102. // Assume we have a letter now - if not, it's a bogus identifier.
  103. // The next word is a sequence of characters that must start upper case.
  104. if isASCIILower(c) {
  105. c -= 'a' - 'A' // convert lowercase to uppercase
  106. }
  107. b = append(b, c)
  108. // Accept lower case sequence that follows.
  109. for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ {
  110. b = append(b, s[i+1])
  111. }
  112. }
  113. }
  114. return string(b)
  115. }
  116. // Is c an ASCII lower-case letter?
  117. func isASCIILower(c byte) bool {
  118. return 'a' <= c && c <= 'z'
  119. }
  120. // Is c an ASCII digit?
  121. func isASCIIDigit(c byte) bool {
  122. return '0' <= c && c <= '9'
  123. }