ProtobufCodeGenParser.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. * Copyright 2024, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. internal import SwiftProtobuf
  17. package import SwiftProtobufPluginLibrary
  18. package import struct GRPCCodeGen.CodeGenerationRequest
  19. package import struct GRPCCodeGen.CodeGenerator
  20. package import struct GRPCCodeGen.Dependency
  21. package import struct GRPCCodeGen.MethodDescriptor
  22. package import struct GRPCCodeGen.MethodName
  23. package import struct GRPCCodeGen.ServiceDescriptor
  24. package import struct GRPCCodeGen.ServiceName
  25. #if canImport(FoundationEssentials)
  26. internal import struct FoundationEssentials.IndexPath
  27. #else
  28. internal import struct Foundation.IndexPath
  29. #endif
  30. /// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object.
  31. package struct ProtobufCodeGenParser {
  32. let extraModuleImports: [String]
  33. let protoToModuleMappings: ProtoFileToModuleMappings
  34. let accessLevel: CodeGenerator.Config.AccessLevel
  35. let moduleNames: ProtobufCodeGenerator.Config.ModuleNames
  36. package init(
  37. protoFileModuleMappings: ProtoFileToModuleMappings,
  38. extraModuleImports: [String],
  39. accessLevel: CodeGenerator.Config.AccessLevel,
  40. moduleNames: ProtobufCodeGenerator.Config.ModuleNames
  41. ) {
  42. self.extraModuleImports = extraModuleImports
  43. self.protoToModuleMappings = protoFileModuleMappings
  44. self.accessLevel = accessLevel
  45. self.moduleNames = moduleNames
  46. }
  47. package func parse(descriptor: FileDescriptor) throws -> CodeGenerationRequest {
  48. let namer = SwiftProtobufNamer(
  49. currentFile: descriptor,
  50. protoFileToModuleMappings: self.protoToModuleMappings
  51. )
  52. var header = descriptor.header
  53. // Ensuring there is a blank line after the header.
  54. if !header.isEmpty && !header.hasSuffix("\n\n") {
  55. header.append("\n")
  56. }
  57. let leadingTrivia = """
  58. // DO NOT EDIT.
  59. // swift-format-ignore-file
  60. // swiftlint:disable all
  61. //
  62. // Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
  63. // Source: \(descriptor.name)
  64. //
  65. // For information on using the generated types, please see the documentation:
  66. // https://github.com/grpc/grpc-swift
  67. """
  68. let services = descriptor.services.map {
  69. GRPCCodeGen.ServiceDescriptor(
  70. descriptor: $0,
  71. package: descriptor.package,
  72. protobufNamer: namer,
  73. file: descriptor
  74. )
  75. }
  76. return CodeGenerationRequest(
  77. fileName: descriptor.name,
  78. leadingTrivia: header + leadingTrivia,
  79. dependencies: self.codeDependencies(file: descriptor),
  80. services: services,
  81. makeSerializerCodeSnippet: { messageType in
  82. "\(self.moduleNames.grpcProtobuf).ProtobufSerializer<\(messageType)>()"
  83. },
  84. makeDeserializerCodeSnippet: { messageType in
  85. "\(self.moduleNames.grpcProtobuf).ProtobufDeserializer<\(messageType)>()"
  86. }
  87. )
  88. }
  89. }
  90. extension ProtobufCodeGenParser {
  91. fileprivate func codeDependencies(file: FileDescriptor) -> [Dependency] {
  92. guard file.services.count > 0 else {
  93. return []
  94. }
  95. var codeDependencies: [Dependency] = [
  96. Dependency(module: self.moduleNames.grpcProtobuf, accessLevel: .internal)
  97. ]
  98. // If there's a dependency on a bundled proto then add the SwiftProtobuf import.
  99. //
  100. // Importing SwiftProtobuf unconditionally results in warnings in the generated
  101. // code if access-levels are used on imports and no bundled protos are used.
  102. let dependsOnBundledProto = file.dependencies.contains { descriptor in
  103. SwiftProtobufInfo.isBundledProto(file: descriptor)
  104. }
  105. if dependsOnBundledProto {
  106. let dependency = Dependency(
  107. module: self.moduleNames.swiftProtobuf,
  108. accessLevel: self.accessLevel
  109. )
  110. codeDependencies.append(dependency)
  111. }
  112. // Adding as dependencies the modules containing generated code or types for
  113. // '.proto' files imported in the '.proto' file we are parsing.
  114. codeDependencies.append(
  115. contentsOf: (self.protoToModuleMappings.neededModules(forFile: file) ?? []).map {
  116. Dependency(module: $0, accessLevel: self.accessLevel)
  117. }
  118. )
  119. // Adding extra imports passed in as an option to the plugin.
  120. codeDependencies.append(
  121. contentsOf: self.extraModuleImports.sorted().map {
  122. Dependency(module: $0, accessLevel: self.accessLevel)
  123. }
  124. )
  125. return codeDependencies
  126. }
  127. }
  128. extension GRPCCodeGen.ServiceDescriptor {
  129. fileprivate init(
  130. descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor,
  131. package: String,
  132. protobufNamer: SwiftProtobufNamer,
  133. file: FileDescriptor
  134. ) {
  135. let methods = descriptor.methods.map {
  136. GRPCCodeGen.MethodDescriptor(
  137. descriptor: $0,
  138. protobufNamer: protobufNamer
  139. )
  140. }
  141. let typePrefix = protobufNamer.typePrefix(forFile: file)
  142. let name = ServiceName(
  143. identifyingName: descriptor.fullName,
  144. // The service name from the '.proto' file is expected to be in upper camel case
  145. typeName: typePrefix + descriptor.name,
  146. propertyName: protobufNamer.typePrefixProperty(file: file) + descriptor.name
  147. )
  148. let documentation = descriptor.protoSourceComments()
  149. self.init(documentation: documentation, name: name, methods: methods)
  150. }
  151. }
  152. extension GRPCCodeGen.MethodDescriptor {
  153. fileprivate init(
  154. descriptor: SwiftProtobufPluginLibrary.MethodDescriptor,
  155. protobufNamer: SwiftProtobufNamer
  156. ) {
  157. let name = MethodName(
  158. identifyingName: descriptor.name,
  159. // The method name from the '.proto' file is expected to be in upper camel case
  160. typeName: descriptor.name,
  161. functionName: CamelCaser.toLowerCamelCase(descriptor.name)
  162. )
  163. let documentation = descriptor.protoSourceComments()
  164. self.init(
  165. documentation: documentation,
  166. name: name,
  167. isInputStreaming: descriptor.clientStreaming,
  168. isOutputStreaming: descriptor.serverStreaming,
  169. inputType: protobufNamer.fullName(message: descriptor.inputType),
  170. outputType: protobufNamer.fullName(message: descriptor.outputType)
  171. )
  172. }
  173. }
  174. extension FileDescriptor {
  175. fileprivate var header: String {
  176. var header = String()
  177. // Field number used to collect the syntax field which is usually the first
  178. // declaration in a.proto file.
  179. // See more here:
  180. // https://github.com/apple/swift-protobuf/blob/main/Protos/SwiftProtobuf/google/protobuf/descriptor.proto
  181. let syntaxPath = IndexPath(index: 12)
  182. if let syntaxLocation = self.sourceCodeInfoLocation(path: syntaxPath) {
  183. header = syntaxLocation.asSourceComment(
  184. commentPrefix: "///",
  185. leadingDetachedPrefix: "//"
  186. )
  187. }
  188. return header
  189. }
  190. }
  191. extension SwiftProtobufNamer {
  192. internal func typePrefixProperty(file: FileDescriptor) -> String {
  193. let typePrefix = self.typePrefix(forFile: file)
  194. let lowercased = typePrefix.split(separator: "_").map { component in
  195. NamingUtils.toLowerCamelCase(String(component))
  196. }
  197. let joined = lowercased.joined(separator: "_")
  198. if typePrefix.hasSuffix("_"), !joined.hasSuffix("_") {
  199. // Add the trailing "_" if it was dropped.
  200. return joined + "_"
  201. } else {
  202. return joined
  203. }
  204. }
  205. }
  206. extension String {
  207. internal func trimTrailingUnderscores() -> String {
  208. if let index = self.lastIndex(where: { $0 != "_" }) {
  209. return String(self[...index])
  210. } else {
  211. return ""
  212. }
  213. }
  214. }