ProtobufCodeGenParser.swift 7.8 KB

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