ProtobufCodeGenParser.swift 6.7 KB

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