names.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package protogen
  2. import (
  3. "go/token"
  4. "strconv"
  5. "strings"
  6. "unicode"
  7. "unicode/utf8"
  8. )
  9. // A GoIdent is a Go identifier.
  10. type GoIdent string
  11. // A GoImportPath is the import path of a Go package. e.g., "google.golang.org/genproto/protobuf".
  12. type GoImportPath string
  13. func (p GoImportPath) String() string { return strconv.Quote(string(p)) }
  14. // A GoPackageName is the name of a Go package. e.g., "protobuf".
  15. type GoPackageName string
  16. // cleanPacakgeName converts a string to a valid Go package name.
  17. func cleanPackageName(name string) GoPackageName {
  18. name = strings.Map(badToUnderscore, name)
  19. // Identifier must not be keyword: insert _.
  20. if token.Lookup(name).IsKeyword() {
  21. name = "_" + name
  22. }
  23. // Identifier must not begin with digit: insert _.
  24. if r, _ := utf8.DecodeRuneInString(name); unicode.IsDigit(r) {
  25. name = "_" + name
  26. }
  27. return GoPackageName(name)
  28. }
  29. // badToUnderscore is the mapping function used to generate Go names from package names,
  30. // which can be dotted in the input .proto file. It replaces non-identifier characters such as
  31. // dot or dash with underscore.
  32. func badToUnderscore(r rune) rune {
  33. if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
  34. return r
  35. }
  36. return '_'
  37. }
  38. // baseName returns the last path element of the name, with the last dotted suffix removed.
  39. func baseName(name string) string {
  40. // First, find the last element
  41. if i := strings.LastIndex(name, "/"); i >= 0 {
  42. name = name[i+1:]
  43. }
  44. // Now drop the suffix
  45. if i := strings.LastIndex(name, "."); i >= 0 {
  46. name = name[:i]
  47. }
  48. return name
  49. }
  50. // camelCase converts a name to CamelCase.
  51. //
  52. // If there is an interior underscore followed by a lower case letter,
  53. // drop the underscore and convert the letter to upper case.
  54. // There is a remote possibility of this rewrite causing a name collision,
  55. // but it's so remote we're prepared to pretend it's nonexistent - since the
  56. // C++ generator lowercases names, it's extremely unlikely to have two fields
  57. // with different capitalizations.
  58. func camelCase(s string) GoIdent {
  59. if s == "" {
  60. return ""
  61. }
  62. var t []byte
  63. i := 0
  64. // Invariant: if the next letter is lower case, it must be converted
  65. // to upper case.
  66. // That is, we process a word at a time, where words are marked by _ or
  67. // upper case letter. Digits are treated as words.
  68. for ; i < len(s); i++ {
  69. c := s[i]
  70. switch {
  71. case c == '.':
  72. t = append(t, '_') // Convert . to _.
  73. case c == '_' && (i == 0 || s[i-1] == '.'):
  74. // Convert initial _ to X so we start with a capital letter.
  75. // Do the same for _ after .; not strictly necessary, but matches
  76. // historic behavior.
  77. t = append(t, 'X')
  78. case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]):
  79. // Skip the underscore in s.
  80. case isASCIIDigit(c):
  81. t = append(t, c)
  82. default:
  83. // Assume we have a letter now - if not, it's a bogus identifier.
  84. // The next word is a sequence of characters that must start upper case.
  85. if isASCIILower(c) {
  86. c ^= ' ' // Make it a capital letter.
  87. }
  88. t = append(t, c) // Guaranteed not lower case.
  89. // Accept lower case sequence that follows.
  90. for i+1 < len(s) && isASCIILower(s[i+1]) {
  91. i++
  92. t = append(t, s[i])
  93. }
  94. }
  95. }
  96. return GoIdent(t)
  97. }
  98. // Is c an ASCII lower-case letter?
  99. func isASCIILower(c byte) bool {
  100. return 'a' <= c && c <= 'z'
  101. }
  102. // Is c an ASCII digit?
  103. func isASCIIDigit(c byte) bool {
  104. return '0' <= c && c <= '9'
  105. }