protoc_plugin.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Copyright (C) 2020 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. //+build ignore
  7. package main
  8. import (
  9. "fmt"
  10. "path/filepath"
  11. "strings"
  12. "unicode"
  13. "github.com/syncthing/syncthing/proto/ext"
  14. "github.com/gogo/protobuf/gogoproto"
  15. "github.com/gogo/protobuf/proto"
  16. "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
  17. "github.com/gogo/protobuf/vanity"
  18. "github.com/gogo/protobuf/vanity/command"
  19. )
  20. func main() {
  21. req := command.Read()
  22. files := req.GetProtoFile()
  23. files = vanity.FilterFiles(files, vanity.NotGoogleProtobufDescriptorProto)
  24. vanity.ForEachFile(files, vanity.TurnOffGoGettersAll)
  25. vanity.ForEachFile(files, TurnOnProtoSizerAll)
  26. vanity.ForEachFile(files, vanity.TurnOffGoEnumPrefixAll)
  27. vanity.ForEachFile(files, vanity.TurnOffGoUnrecognizedAll)
  28. vanity.ForEachFile(files, vanity.TurnOffGoUnkeyedAll)
  29. vanity.ForEachFile(files, vanity.TurnOffGoSizecacheAll)
  30. vanity.ForEachFile(files, vanity.TurnOnMarshalerAll)
  31. vanity.ForEachFile(files, vanity.TurnOnUnmarshalerAll)
  32. vanity.ForEachEnumInFiles(files, HandleCustomEnumExtensions)
  33. vanity.ForEachFile(files, SetPackagePrefix("github.com/syncthing/syncthing"))
  34. vanity.ForEachFile(files, HandleFile)
  35. vanity.ForEachFieldInFilesExcludingExtensions(files, TurnOffNullableForMessages)
  36. resp := command.Generate(req)
  37. command.Write(resp)
  38. }
  39. func TurnOnProtoSizerAll(file *descriptor.FileDescriptorProto) {
  40. vanity.SetBoolFileOption(gogoproto.E_ProtosizerAll, true)(file)
  41. }
  42. func TurnOffNullableForMessages(field *descriptor.FieldDescriptorProto) {
  43. if !vanity.FieldHasBoolExtension(field, gogoproto.E_Nullable) {
  44. _, hasCustomType := GetFieldStringExtension(field, gogoproto.E_Customtype)
  45. if field.IsMessage() || hasCustomType {
  46. vanity.SetBoolFieldOption(gogoproto.E_Nullable, false)(field)
  47. }
  48. }
  49. }
  50. func HandleCustomEnumExtensions(enum *descriptor.EnumDescriptorProto) {
  51. for _, field := range enum.Value {
  52. if field == nil {
  53. continue
  54. }
  55. if field.Options == nil {
  56. field.Options = &descriptor.EnumValueOptions{}
  57. }
  58. customName := gogoproto.GetEnumValueCustomName(field)
  59. if customName != "" {
  60. continue
  61. }
  62. if v, ok := GetEnumValueStringExtension(field, ext.E_Enumgoname); ok {
  63. SetEnumValueStringFieldOption(field, gogoproto.E_EnumvalueCustomname, v)
  64. } else {
  65. SetEnumValueStringFieldOption(field, gogoproto.E_EnumvalueCustomname, toCamelCase(*field.Name, true))
  66. }
  67. }
  68. }
  69. func SetPackagePrefix(prefix string) func(file *descriptor.FileDescriptorProto) {
  70. return func(file *descriptor.FileDescriptorProto) {
  71. if file.Options.GoPackage == nil {
  72. pkg, _ := filepath.Split(file.GetName())
  73. fullPkg := prefix + "/" + strings.TrimSuffix(pkg, "/")
  74. file.Options.GoPackage = &fullPkg
  75. }
  76. }
  77. }
  78. func toCamelCase(input string, firstUpper bool) string {
  79. runes := []rune(strings.ToLower(input))
  80. outputRunes := make([]rune, 0, len(runes))
  81. nextUpper := false
  82. for i, rune := range runes {
  83. if rune == '_' {
  84. nextUpper = true
  85. continue
  86. }
  87. if (firstUpper && i == 0) || nextUpper {
  88. rune = unicode.ToUpper(rune)
  89. nextUpper = false
  90. }
  91. outputRunes = append(outputRunes, rune)
  92. }
  93. return string(outputRunes)
  94. }
  95. func SetStringFieldOption(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc, value string) {
  96. if _, ok := GetFieldStringExtension(field, extension); ok {
  97. return
  98. }
  99. if field.Options == nil {
  100. field.Options = &descriptor.FieldOptions{}
  101. }
  102. if err := proto.SetExtension(field.Options, extension, &value); err != nil {
  103. panic(err)
  104. }
  105. }
  106. func SetEnumValueStringFieldOption(field *descriptor.EnumValueDescriptorProto, extension *proto.ExtensionDesc, value string) {
  107. if _, ok := GetEnumValueStringExtension(field, extension); ok {
  108. return
  109. }
  110. if field.Options == nil {
  111. field.Options = &descriptor.EnumValueOptions{}
  112. }
  113. if err := proto.SetExtension(field.Options, extension, &value); err != nil {
  114. panic(err)
  115. }
  116. }
  117. func GetEnumValueStringExtension(enumValue *descriptor.EnumValueDescriptorProto, extension *proto.ExtensionDesc) (string, bool) {
  118. if enumValue.Options == nil {
  119. return "", false
  120. }
  121. value, err := proto.GetExtension(enumValue.Options, extension)
  122. if err != nil {
  123. return "", false
  124. }
  125. if value == nil {
  126. return "", false
  127. }
  128. if v, ok := value.(*string); !ok || v == nil {
  129. return "", false
  130. } else {
  131. return *v, true
  132. }
  133. }
  134. func GetFieldStringExtension(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc) (string, bool) {
  135. if field.Options == nil {
  136. return "", false
  137. }
  138. value, err := proto.GetExtension(field.Options, extension)
  139. if err != nil {
  140. return "", false
  141. }
  142. if value == nil {
  143. return "", false
  144. }
  145. if v, ok := value.(*string); !ok || v == nil {
  146. return "", false
  147. } else {
  148. return *v, true
  149. }
  150. }
  151. func GetFieldBooleanExtension(field *descriptor.FieldDescriptorProto, extension *proto.ExtensionDesc) (bool, bool) {
  152. if field.Options == nil {
  153. return false, false
  154. }
  155. value, err := proto.GetExtension(field.Options, extension)
  156. if err != nil {
  157. return false, false
  158. }
  159. if value == nil {
  160. return false, false
  161. }
  162. if v, ok := value.(*bool); !ok || v == nil {
  163. return false, false
  164. } else {
  165. return *v, true
  166. }
  167. }
  168. func GetMessageBoolExtension(msg *descriptor.DescriptorProto, extension *proto.ExtensionDesc) (bool, bool) {
  169. if msg.Options == nil {
  170. return false, false
  171. }
  172. value, err := proto.GetExtension(msg.Options, extension)
  173. if err != nil {
  174. return false, false
  175. }
  176. if value == nil {
  177. return false, false
  178. }
  179. val, ok := value.(*bool)
  180. if !ok || val == nil {
  181. return false, false
  182. }
  183. return *val, true
  184. }
  185. func HandleFile(file *descriptor.FileDescriptorProto) {
  186. vanity.ForEachMessageInFiles([]*descriptor.FileDescriptorProto{file}, HandleCustomExtensions(file))
  187. }
  188. func HandleCustomExtensions(file *descriptor.FileDescriptorProto) func(msg *descriptor.DescriptorProto) {
  189. return func(msg *descriptor.DescriptorProto) {
  190. generateXmlTags := true
  191. if generate, ok := GetMessageBoolExtension(msg, ext.E_XmlTags); ok {
  192. generateXmlTags = generate
  193. }
  194. vanity.ForEachField([]*descriptor.DescriptorProto{msg}, func(field *descriptor.FieldDescriptorProto) {
  195. if field.Options == nil {
  196. field.Options = &descriptor.FieldOptions{}
  197. }
  198. deprecated := field.Options.Deprecated != nil && *field.Options.Deprecated == true
  199. if field.Type != nil && *field.Type == descriptor.FieldDescriptorProto_TYPE_INT32 {
  200. SetStringFieldOption(field, gogoproto.E_Casttype, "int")
  201. }
  202. if field.TypeName != nil && *field.TypeName == ".google.protobuf.Timestamp" {
  203. vanity.SetBoolFieldOption(gogoproto.E_Stdtime, true)(field)
  204. }
  205. if goName, ok := GetFieldStringExtension(field, ext.E_Goname); ok {
  206. SetStringFieldOption(field, gogoproto.E_Customname, goName)
  207. } else if deprecated {
  208. SetStringFieldOption(field, gogoproto.E_Customname, "Deprecated"+toCamelCase(*field.Name, true))
  209. }
  210. if goType, ok := GetFieldStringExtension(field, ext.E_Gotype); ok {
  211. SetStringFieldOption(field, gogoproto.E_Customtype, goType)
  212. }
  213. if val, ok := GetFieldBooleanExtension(field, ext.E_DeviceId); ok && val {
  214. if *file.Options.GoPackage != "github.com/syncthing/syncthing/lib/protocol" {
  215. SetStringFieldOption(field, gogoproto.E_Customtype, "github.com/syncthing/syncthing/lib/protocol.DeviceID")
  216. } else {
  217. SetStringFieldOption(field, gogoproto.E_Customtype, "DeviceID")
  218. }
  219. }
  220. if jsonValue, ok := GetFieldStringExtension(field, ext.E_Json); ok {
  221. SetStringFieldOption(field, gogoproto.E_Jsontag, jsonValue)
  222. } else if deprecated {
  223. SetStringFieldOption(field, gogoproto.E_Jsontag, "-")
  224. } else {
  225. SetStringFieldOption(field, gogoproto.E_Jsontag, toCamelCase(*field.Name, false))
  226. }
  227. current := ""
  228. if v, ok := GetFieldStringExtension(field, gogoproto.E_Moretags); ok {
  229. current = v
  230. }
  231. if generateXmlTags {
  232. if len(current) > 0 {
  233. current += " "
  234. }
  235. if xmlValue, ok := GetFieldStringExtension(field, ext.E_Xml); ok {
  236. current += fmt.Sprintf(`xml:"%s"`, xmlValue)
  237. } else {
  238. xmlValue = toCamelCase(*field.Name, false)
  239. // XML dictates element name within the collection, not collection name, so trim plural suffix.
  240. if field.IsRepeated() {
  241. if strings.HasSuffix(xmlValue, "ses") {
  242. // addresses -> address
  243. xmlValue = strings.TrimSuffix(xmlValue, "es")
  244. } else {
  245. // devices -> device
  246. xmlValue = strings.TrimSuffix(xmlValue, "s")
  247. }
  248. }
  249. if deprecated {
  250. xmlValue += ",omitempty"
  251. }
  252. current += fmt.Sprintf(`xml:"%s"`, xmlValue)
  253. }
  254. }
  255. if defaultValue, ok := GetFieldStringExtension(field, ext.E_Default); ok {
  256. if len(current) > 0 {
  257. current += " "
  258. }
  259. current += fmt.Sprintf(`default:"%s"`, defaultValue)
  260. }
  261. if nodefaultValue, ok := GetFieldBooleanExtension(field, ext.E_Nodefault); ok {
  262. if len(current) > 0 {
  263. current += " "
  264. }
  265. current += fmt.Sprintf(`nodefault:"%t"`, nodefaultValue)
  266. }
  267. if restartValue, ok := GetFieldBooleanExtension(field, ext.E_Restart); ok {
  268. if len(current) > 0 {
  269. current += " "
  270. }
  271. current += fmt.Sprintf(`restart:"%t"`, restartValue)
  272. }
  273. SetStringFieldOption(field, gogoproto.E_Moretags, current)
  274. })
  275. }
  276. }