ProtobufCodeGenParser.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 Foundation
  17. internal import SwiftProtobuf
  18. package import SwiftProtobufPluginLibrary
  19. package import struct GRPCCodeGen.CodeGenerationRequest
  20. package import struct GRPCCodeGen.Dependency
  21. package import struct GRPCCodeGen.MethodDescriptor
  22. package import struct GRPCCodeGen.Name
  23. package import struct GRPCCodeGen.ServiceDescriptor
  24. package import struct GRPCCodeGen.SourceGenerator
  25. /// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object.
  26. package struct ProtobufCodeGenParser {
  27. let extraModuleImports: [String]
  28. let protoToModuleMappings: ProtoFileToModuleMappings
  29. let accessLevel: SourceGenerator.Config.AccessLevel
  30. package init(
  31. protoFileModuleMappings: ProtoFileToModuleMappings,
  32. extraModuleImports: [String],
  33. accessLevel: SourceGenerator.Config.AccessLevel
  34. ) {
  35. self.extraModuleImports = extraModuleImports
  36. self.protoToModuleMappings = protoFileModuleMappings
  37. self.accessLevel = accessLevel
  38. }
  39. package func parse(descriptor: FileDescriptor) throws -> CodeGenerationRequest {
  40. let namer = SwiftProtobufNamer(
  41. currentFile: descriptor,
  42. protoFileToModuleMappings: self.protoToModuleMappings
  43. )
  44. var header = descriptor.header
  45. // Ensuring there is a blank line after the header.
  46. if !header.isEmpty && !header.hasSuffix("\n\n") {
  47. header.append("\n")
  48. }
  49. let leadingTrivia = """
  50. // DO NOT EDIT.
  51. // swift-format-ignore-file
  52. //
  53. // Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
  54. // Source: \(descriptor.name)
  55. //
  56. // For information on using the generated types, please see the documentation:
  57. // https://github.com/grpc/grpc-swift
  58. """
  59. let lookupSerializer: (String) -> String = { messageType in
  60. "GRPCProtobuf.ProtobufSerializer<\(messageType)>()"
  61. }
  62. let lookupDeserializer: (String) -> String = { messageType in
  63. "GRPCProtobuf.ProtobufDeserializer<\(messageType)>()"
  64. }
  65. let services = descriptor.services.map {
  66. GRPCCodeGen.ServiceDescriptor(
  67. descriptor: $0,
  68. package: descriptor.package,
  69. protobufNamer: namer,
  70. file: descriptor
  71. )
  72. }
  73. return CodeGenerationRequest(
  74. fileName: descriptor.name,
  75. leadingTrivia: header + leadingTrivia,
  76. dependencies: self.codeDependencies(file: descriptor),
  77. services: services,
  78. lookupSerializer: lookupSerializer,
  79. lookupDeserializer: lookupDeserializer
  80. )
  81. }
  82. }
  83. extension ProtobufCodeGenParser {
  84. fileprivate func codeDependencies(
  85. file: FileDescriptor
  86. ) -> [Dependency] {
  87. var codeDependencies: [Dependency] = [
  88. Dependency(module: "GRPCProtobuf", accessLevel: .internal),
  89. Dependency(module: "SwiftProtobuf", accessLevel: self.accessLevel),
  90. ]
  91. // Adding as dependencies the modules containing generated code or types for
  92. // '.proto' files imported in the '.proto' file we are parsing.
  93. codeDependencies.append(
  94. contentsOf: (self.protoToModuleMappings.neededModules(forFile: file) ?? []).map {
  95. Dependency(module: $0, accessLevel: self.accessLevel)
  96. }
  97. )
  98. // Adding extra imports passed in as an option to the plugin.
  99. codeDependencies.append(
  100. contentsOf: self.extraModuleImports.sorted().map {
  101. Dependency(module: $0, accessLevel: self.accessLevel)
  102. }
  103. )
  104. return codeDependencies
  105. }
  106. }
  107. extension GRPCCodeGen.ServiceDescriptor {
  108. fileprivate init(
  109. descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor,
  110. package: String,
  111. protobufNamer: SwiftProtobufNamer,
  112. file: FileDescriptor
  113. ) {
  114. let methods = descriptor.methods.map {
  115. GRPCCodeGen.MethodDescriptor(
  116. descriptor: $0,
  117. protobufNamer: protobufNamer
  118. )
  119. }
  120. let name = Name(
  121. base: descriptor.name,
  122. // The service name from the '.proto' file is expected to be in upper camel case
  123. generatedUpperCase: descriptor.name,
  124. generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
  125. )
  126. // Packages that are based on the path of the '.proto' file usually
  127. // contain dots. For example: "grpc.test".
  128. let namespace = Name(
  129. base: package,
  130. generatedUpperCase: protobufNamer.formattedUpperCasePackage(file: file),
  131. generatedLowerCase: protobufNamer.formattedLowerCasePackage(file: file)
  132. )
  133. let documentation = descriptor.protoSourceComments()
  134. self.init(documentation: documentation, name: name, namespace: namespace, methods: methods)
  135. }
  136. }
  137. extension GRPCCodeGen.MethodDescriptor {
  138. fileprivate init(
  139. descriptor: SwiftProtobufPluginLibrary.MethodDescriptor,
  140. protobufNamer: SwiftProtobufNamer
  141. ) {
  142. let name = Name(
  143. base: descriptor.name,
  144. // The method name from the '.proto' file is expected to be in upper camel case
  145. generatedUpperCase: descriptor.name,
  146. generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
  147. )
  148. let documentation = descriptor.protoSourceComments()
  149. self.init(
  150. documentation: documentation,
  151. name: name,
  152. isInputStreaming: descriptor.clientStreaming,
  153. isOutputStreaming: descriptor.serverStreaming,
  154. inputType: protobufNamer.fullName(message: descriptor.inputType),
  155. outputType: protobufNamer.fullName(message: descriptor.outputType)
  156. )
  157. }
  158. }
  159. extension FileDescriptor {
  160. fileprivate var header: String {
  161. var header = String()
  162. // Field number used to collect the syntax field which is usually the first
  163. // declaration in a.proto file.
  164. // See more here:
  165. // https://github.com/apple/swift-protobuf/blob/main/Protos/SwiftProtobuf/google/protobuf/descriptor.proto
  166. let syntaxPath = IndexPath(index: 12)
  167. if let syntaxLocation = self.sourceCodeInfoLocation(path: syntaxPath) {
  168. header = syntaxLocation.asSourceComment(
  169. commentPrefix: "///",
  170. leadingDetachedPrefix: "//"
  171. )
  172. }
  173. return header
  174. }
  175. }
  176. extension SwiftProtobufNamer {
  177. internal func formattedUpperCasePackage(file: FileDescriptor) -> String {
  178. let unformattedPackage = self.typePrefix(forFile: file)
  179. return unformattedPackage.trimTrailingUnderscores()
  180. }
  181. internal func formattedLowerCasePackage(file: FileDescriptor) -> String {
  182. let upperCasePackage = self.formattedUpperCasePackage(file: file)
  183. let lowerCaseComponents = upperCasePackage.split(separator: "_").map { component in
  184. NamingUtils.toLowerCamelCase(String(component))
  185. }
  186. return lowerCaseComponents.joined(separator: "_")
  187. }
  188. }
  189. extension String {
  190. internal func trimTrailingUnderscores() -> String {
  191. if let index = self.lastIndex(where: { $0 != "_" }) {
  192. return String(self[...index])
  193. } else {
  194. return ""
  195. }
  196. }
  197. }