ProtobufCodeGenParser.swift 7.1 KB

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