Skip to content

Commit def5585

Browse files
committed
Merge branch 'main' into hd-draft-multiple-content-types
2 parents 597733d + bf49be1 commit def5585

File tree

2 files changed

+141
-59
lines changed

2 files changed

+141
-59
lines changed

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift

Lines changed: 90 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,62 @@ fileprivate extension String {
7474
///
7575
/// See the proposal SOAR-0001 for details.
7676
///
77-
/// In addition to replacing illegal characters with an underscores, also
77+
/// For example, the string `$nake…` would be returned as `_dollar_nake_x2026_`, because
78+
/// both the dollar and ellipsis sign are not valid characters in a Swift identifier.
79+
/// So, it replaces such characters with their html entity equivalents or unicode hex representation,
80+
/// in case it's not present in the `specialCharsMap`. It marks this replacement with `_` as a delimiter.
81+
///
82+
/// In addition to replacing illegal characters, it also
7883
/// ensures that the identifier starts with a letter and not a number.
7984
var proposedSafeForSwiftCode: String {
80-
// TODO: New logic proposed in SOAR-0001 goes here.
81-
return ""
85+
guard !isEmpty else {
86+
return "_empty"
87+
}
88+
89+
let firstCharSet: CharacterSet = .letters.union(.init(charactersIn: "_"))
90+
let numbers: CharacterSet = .decimalDigits
91+
let otherCharSet: CharacterSet = .alphanumerics.union(.init(charactersIn: "_"))
92+
93+
var sanitizedScalars: [Unicode.Scalar] = []
94+
for (index, scalar) in unicodeScalars.enumerated() {
95+
let allowedSet = index == 0 ? firstCharSet : otherCharSet
96+
let outScalar: Unicode.Scalar
97+
if allowedSet.contains(scalar) {
98+
outScalar = scalar
99+
} else if index == 0 && numbers.contains(scalar) {
100+
sanitizedScalars.append("_")
101+
outScalar = scalar
102+
} else {
103+
sanitizedScalars.append("_")
104+
if let entityName = Self.specialCharsMap[scalar] {
105+
for char in entityName.unicodeScalars {
106+
sanitizedScalars.append(char)
107+
}
108+
} else {
109+
sanitizedScalars.append("x")
110+
let hexString = String(scalar.value, radix: 16, uppercase: true)
111+
for char in hexString.unicodeScalars {
112+
sanitizedScalars.append(char)
113+
}
114+
}
115+
sanitizedScalars.append("_")
116+
continue
117+
}
118+
sanitizedScalars.append(outScalar)
119+
}
120+
121+
let validString = String(UnicodeScalarView(sanitizedScalars))
122+
123+
//Special case for a single underscore.
124+
//We can't add it to the map as its a valid swift identifier in other cases.
125+
if validString == "_" {
126+
return "_underscore_"
127+
}
128+
129+
guard Self.keywords.contains(validString) else {
130+
return validString
131+
}
132+
return "_\(validString)"
82133
}
83134

84135
/// A list of Swift keywords.
@@ -138,62 +189,6 @@ fileprivate extension String {
138189
"true",
139190
"try",
140191
"throws",
141-
"__FILE__",
142-
"__LINE__",
143-
"__COLUMN__",
144-
"__FUNCTION__",
145-
"__DSO_HANDLE__",
146-
"_",
147-
"(",
148-
")",
149-
"{",
150-
"}",
151-
"[",
152-
"]",
153-
"<",
154-
">",
155-
".",
156-
".",
157-
",",
158-
"...",
159-
":",
160-
";",
161-
"=",
162-
"@",
163-
"#",
164-
"&",
165-
"->",
166-
"`",
167-
"\\",
168-
"!",
169-
"?",
170-
"?",
171-
"\"",
172-
"\'",
173-
"\"\"\"",
174-
"#keyPath",
175-
"#line",
176-
"#selector",
177-
"#file",
178-
"#fileID",
179-
"#filePath",
180-
"#column",
181-
"#function",
182-
"#dsohandle",
183-
"#assert",
184-
"#sourceLocation",
185-
"#warning",
186-
"#error",
187-
"#if",
188-
"#else",
189-
"#elseif",
190-
"#endif",
191-
"#available",
192-
"#unavailable",
193-
"#fileLiteral",
194-
"#imageLiteral",
195-
"#colorLiteral",
196-
")",
197192
"yield",
198193
"String",
199194
"Error",
@@ -205,4 +200,40 @@ fileprivate extension String {
205200
"Protocol",
206201
"await",
207202
]
203+
204+
/// A map of ASCII printable characters to their HTML entity names. Used to reduce collisions in generated names.
205+
private static let specialCharsMap: [Unicode.Scalar: String] = [
206+
" ": "space",
207+
"!": "excl",
208+
"\"": "quot",
209+
"#": "num",
210+
"$": "dollar",
211+
"%": "percnt",
212+
"&": "amp",
213+
"'": "apos",
214+
"(": "lpar",
215+
")": "rpar",
216+
"*": "ast",
217+
"+": "plus",
218+
",": "comma",
219+
"-": "hyphen",
220+
".": "period",
221+
"/": "sol",
222+
":": "colon",
223+
";": "semi",
224+
"<": "lt",
225+
"=": "equals",
226+
">": "gt",
227+
"?": "quest",
228+
"@": "commat",
229+
"[": "lbrack",
230+
"\\": "bsol",
231+
"]": "rbrack",
232+
"^": "hat",
233+
"`": "grave",
234+
"{": "lcub",
235+
"|": "verbar",
236+
"}": "rcub",
237+
"~": "tilde",
238+
]
208239
}

Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,55 @@ final class Test_String: Test_Core {
3939
XCTAssertEqual(asSwiftSafeName(input), sanitized)
4040
}
4141
}
42+
43+
func testAsProposedSwiftName() {
44+
let cases: [(String, String)] = [
45+
// Simple
46+
("foo", "foo"),
47+
48+
// Starts with a number
49+
("3foo", "_3foo"),
50+
51+
// Keyword
52+
("default", "_default"),
53+
54+
// Reserved name
55+
("Type", "_Type"),
56+
57+
// Empty string
58+
("", "_empty"),
59+
60+
// Special Char in middle
61+
("inv@lidName", "inv_commat_lidName"),
62+
63+
// Special Char in first position
64+
("!nvalidName", "_excl_nvalidName"),
65+
66+
// Special Char in last position
67+
("invalidNam?", "invalidNam_quest_"),
68+
69+
// Valid underscore case
70+
("__user", "__user"),
71+
72+
// Invalid underscore case
73+
("_", "_underscore_"),
74+
75+
// Special character mixed with character not in map
76+
("$nake…", "_dollar_nake_x2026_"),
77+
78+
// Only special character
79+
("$", "_dollar_"),
80+
81+
// Only special character not in map
82+
("……", "_x2026__x2026_"),
83+
84+
// Non Latin Characters
85+
("$مرحبا", "_dollar_مرحبا"),
86+
]
87+
let translator = makeTranslator(featureFlags: [.proposal0001])
88+
let asSwiftSafeName: (String) -> String = translator.swiftSafeName
89+
for (input, sanitized) in cases {
90+
XCTAssertEqual(asSwiftSafeName(input), sanitized)
91+
}
92+
}
4293
}

0 commit comments

Comments
 (0)