Skip to content

Commit 699529e

Browse files
authored
feat(sidekick/swift): PascalCase naming (#5085)
The Swift naming convention for types is `PascalCase`. This PR introduces a function to convert names to that style, taking care of the names that conflict with Swift keywords. There are corner cases around names that are ALL CAPS, or start with ALL CAPS. These are more prevalent than one may believe in Google Cloud, so it is worthwhile having the extra code for them.
1 parent 6066147 commit 699529e

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

internal/sidekick/swift/names.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package swift
1616

1717
import (
1818
"fmt"
19+
"strings"
20+
"unicode"
1921

2022
"github.com/iancoleman/strcase"
2123
)
@@ -152,6 +154,28 @@ func escapeKeyword(s string) string {
152154
return s
153155
}
154156

157+
// camelCase converts an identifier to camelCase (note the leading lowercase) and, if needed, escapes it.
158+
//
159+
// This function is used for field names and method names, where the Swift style is `camelCase`.
155160
func camelCase(s string) string {
156161
return escapeKeyword(strcase.ToLowerCamel(s))
157162
}
163+
164+
// pascalCase converts an identifier to PascalCase (note the leading uppercase) and, if needed, escapes it.
165+
//
166+
// This function is used for services, messages, and enums, where the Swift style is `PascalCase`.
167+
func pascalCase(s string) string {
168+
// In Swift, it is conventional to preserve ALL CAPS names:
169+
// https://www.swift.org/documentation/api-design-guidelines/#conventions
170+
if strings.ToUpper(s) == s {
171+
return escapeKeyword(s)
172+
}
173+
// Symbols that are already `PascalCase` should need no mapping. This works
174+
// better than calling `strcase.ToCamel()` in cases like `IAMPolicy`, which
175+
// would be converted to `Iampolicy`. We are trusting that the original
176+
// name in API definition chose to keep the acronym for a reason.
177+
if unicode.IsUpper(rune(s[0])) && !strings.ContainsRune(s, '_') {
178+
return escapeKeyword(s)
179+
}
180+
return escapeKeyword(strcase.ToCamel(s))
181+
}

internal/sidekick/swift/names_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package swift
1616

1717
import (
1818
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
1921
)
2022

2123
func TestEscapeKeyword(t *testing.T) {
@@ -66,8 +68,33 @@ func TestCamelCase(t *testing.T) {
6668
} {
6769
t.Run(test.input, func(t *testing.T) {
6870
got := camelCase(test.input)
69-
if got != test.want {
70-
t.Errorf("camelCase(%q) = %q, want %q", test.input, got, test.want)
71+
if diff := cmp.Diff(test.want, got); diff != "" {
72+
t.Errorf("mismatch (-want +got):\n%s", diff)
73+
}
74+
})
75+
}
76+
}
77+
78+
func TestPascalCase(t *testing.T) {
79+
for _, test := range []struct {
80+
input string
81+
want string
82+
}{
83+
{input: "SecretManagerService", want: "SecretManagerService"},
84+
{input: "CreateSecretRequest", want: "CreateSecretRequest"},
85+
{input: "IAMPolicy", want: "IAMPolicy"},
86+
{input: "IAM", want: "IAM"},
87+
88+
// Keywords that should be escaped after pascalCase
89+
{input: "Protocol", want: "Protocol_"},
90+
{input: "Type", want: "Type_"},
91+
{input: "Self", want: "`Self`"},
92+
{input: "Any", want: "`Any`"},
93+
} {
94+
t.Run(test.input, func(t *testing.T) {
95+
got := pascalCase(test.input)
96+
if diff := cmp.Diff(test.want, got); diff != "" {
97+
t.Errorf("mismatch (-want +got):\n%s", diff)
7198
}
7299
})
73100
}

0 commit comments

Comments
 (0)