protoc_plugin.go 9.1 KB

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