@@ -199,24 +199,39 @@ extension MemberAccessExpr {
199
199
// MARK: - StringLiteralExpr
200
200
201
201
extension String {
202
- /// Replace literal newlines with "\r", "\n".
203
- fileprivate func replacingNewlines( ) -> String {
204
- var result = " "
205
- var input = self [ ... ]
206
- while let firstNewline = input. firstIndex ( where: { $0. isNewline } ) {
202
+ /// Replace literal newlines with "\r", "\n", "\u{2028}", and ASCII control characters with "\0", "\u{7}"
203
+ fileprivate func escapingForStringLiteral( usingDelimiter delimiter: String ) -> String {
204
+ // String literals cannot contain "unprintable" ASCII characters (control
205
+ // characters, etc.) besides tab. As a matter of style, we also choose to
206
+ // escape Unicode newlines like "\u{2028}" even though swiftc will allow
207
+ // them in string literals.
208
+ func needsEscaping( _ scalar: UnicodeScalar ) -> Bool {
209
+ return ( scalar. isASCII && scalar != " \t " && !scalar. isPrintableASCII)
210
+ || Character ( scalar) . isNewline
211
+ }
212
+
213
+ // Work at the Unicode scalar level so that "\r\n" isn't combined.
214
+ var result = String . UnicodeScalarView ( )
215
+ var input = self . unicodeScalars [ ... ]
216
+ while let firstNewline = input. firstIndex ( where: needsEscaping ( _: ) ) {
207
217
result += input [ ..< firstNewline]
208
- if input [ firstNewline] == " \r " {
209
- result += " \\ r "
210
- } else if input [ firstNewline] == " \r \n " {
211
- result += " \\ r \\ n "
212
- } else {
213
- result += " \\ n "
218
+
219
+ result += " \\ \( delimiter) " . unicodeScalars
220
+ switch input [ firstNewline] {
221
+ case " \r " :
222
+ result += " r " . unicodeScalars
223
+ case " \n " :
224
+ result += " n " . unicodeScalars
225
+ case " \0 " :
226
+ result += " 0 " . unicodeScalars
227
+ case let other:
228
+ result += " u{ \( String ( other. value, radix: 16 ) ) } " . unicodeScalars
214
229
}
215
230
input = input [ input. index ( after: firstNewline) ... ]
216
- continue
217
231
}
232
+ result += input
218
233
219
- return result + input
234
+ return String ( result)
220
235
}
221
236
}
222
237
@@ -269,10 +284,6 @@ extension StringLiteralExpr {
269
284
closeQuote: Token = . stringQuote,
270
285
closeDelimiter: Token ? = nil
271
286
) {
272
- let contentToken = Token . stringSegment ( content. replacingNewlines ( ) )
273
- let segment = StringSegment ( content: contentToken)
274
- let segments = StringLiteralSegments ( [ . stringSegment( segment) ] )
275
-
276
287
var openDelimiter = openDelimiter
277
288
var closeDelimiter = closeDelimiter
278
289
if openDelimiter == nil , closeDelimiter == nil {
@@ -285,6 +296,11 @@ extension StringLiteralExpr {
285
296
}
286
297
}
287
298
299
+ let escapedContent = content. escapingForStringLiteral ( usingDelimiter: closeDelimiter? . text ?? " " )
300
+ let contentToken = Token . stringSegment ( escapedContent)
301
+ let segment = StringSegment ( content: contentToken)
302
+ let segments = StringLiteralSegments ( [ . stringSegment( segment) ] )
303
+
288
304
self . init (
289
305
openDelimiter: openDelimiter,
290
306
openQuote: openQuote,
0 commit comments