Bläddra i källkod

Fix casing of generated services and methods (#8)

Motivation:

Given a method name, say `ImportCSV`, from a `.proto` file, the
generated method name in upper and lower camel case is `ImportCsv` and
`importCsv` respectively. The generated method name (which is already
expected to be in upper camel case) in upper and lower camel case should
be `ImportCSV` and `importCSV` respectively.

Modifications:

- Replace the method used for generating service and method names in
lower camel case with a newly implemented method.
- Do not convert the base names of services and methods to upper camel
case as they are expected to already be in upper camel case.

Result:

The casing of service and method names will be preserved when generating
stubs.
Clinton Nkwocha 1 år sedan
förälder
incheckning
658814ead1

+ 43 - 0
Sources/GRPCProtobufCodeGen/CamelCaser.swift

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package enum CamelCaser {
+  /// Converts a string from upper camel case to lower camel case.
+  package static func toLowerCamelCase(_ input: String) -> String {
+    guard let indexOfFirstLowercase = input.firstIndex(where: { $0.isLowercase }) else {
+      return input.lowercased()
+    }
+
+    if indexOfFirstLowercase == input.startIndex {
+      // `input` already begins with a lower case letter. As in: "importCSV".
+      return input
+    } else if indexOfFirstLowercase == input.index(after: input.startIndex) {
+      // The second character in `input` is lower case. As in: "ImportCSV".
+      // returns "importCSV"
+      return input[input.startIndex].lowercased() + input[indexOfFirstLowercase...]
+    } else {
+      // The first lower case character is further within `input`. Tentatively, `input` begins
+      // with one or more abbreviations. Therefore, the last encountered upper case character
+      // could be the beginning of the next word. As in: "FOOBARImportCSV".
+
+      let leadingAbbreviation = input[..<input.index(before: indexOfFirstLowercase)]
+      let followingWords = input[input.index(before: indexOfFirstLowercase)...]
+
+      // returns "foobarImportCSV"
+      return leadingAbbreviation.lowercased() + followingWords
+    }
+  }
+}

+ 6 - 5
Sources/GRPCProtobufCodeGen/ProtobufCodeGenParser.swift

@@ -15,7 +15,6 @@
  */
 
 internal import Foundation
-internal import SwiftProtobuf
 internal import SwiftProtobufPluginLibrary
 
 internal import struct GRPCCodeGen.CodeGenerationRequest
@@ -125,8 +124,9 @@ extension CodeGenerationRequest.ServiceDescriptor {
     }
     let name = CodeGenerationRequest.Name(
       base: descriptor.name,
-      generatedUpperCase: NamingUtils.toUpperCamelCase(descriptor.name),
-      generatedLowerCase: NamingUtils.toLowerCamelCase(descriptor.name)
+      // The service name from the '.proto' file is expected to be in upper camel case
+      generatedUpperCase: descriptor.name,
+      generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
     )
 
     // Packages that are based on the path of the '.proto' file usually
@@ -145,8 +145,9 @@ extension CodeGenerationRequest.ServiceDescriptor.MethodDescriptor {
   fileprivate init(descriptor: MethodDescriptor, protobufNamer: SwiftProtobufNamer) {
     let name = CodeGenerationRequest.Name(
       base: descriptor.name,
-      generatedUpperCase: NamingUtils.toUpperCamelCase(descriptor.name),
-      generatedLowerCase: NamingUtils.toLowerCamelCase(descriptor.name)
+      // The method name from the '.proto' file is expected to be in upper camel case
+      generatedUpperCase: descriptor.name,
+      generatedLowerCase: CamelCaser.toLowerCamelCase(descriptor.name)
     )
     let documentation = descriptor.protoSourceComments()
     self.init(

+ 46 - 0
Tests/GRPCProtobufCodeGenTests/CamelCaserTests.swift

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import GRPCProtobufCodeGen
+import Testing
+
+@Suite("CamelCaser")
+struct CamelCaserTests {
+  @Test(
+    "Convert to lower camel case",
+    arguments: [
+      ("ImportCsv", "importCsv"),
+      ("ImportCSV", "importCSV"),
+      ("CSVImport", "csvImport"),
+      ("importCSV", "importCSV"),
+      ("FOOBARImport", "foobarImport"),
+      ("FOO_BARImport", "foo_barImport"),
+      ("My_CSVImport", "my_CSVImport"),
+      ("_CSVImport", "_csvImport"),
+      ("V2Request", "v2Request"),
+      ("V2_Request", "v2_Request"),
+      ("CSV", "csv"),
+      ("I", "i"),
+      ("i", "i"),
+      ("I_", "i_"),
+      ("_", "_"),
+      ("", ""),
+    ]
+  )
+  func toLowerCamelCase(_ input: String, expectedOutput: String) async throws {
+    #expect(CamelCaser.toLowerCamelCase(input) == expectedOutput)
+  }
+}