Skip to content

Commit e4a3ce8

Browse files
authored
Merge pull request #1067 from DougGregor/string-literal-expr-escape-newlines
2 parents e07287b + 0ee1102 commit e4a3ce8

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,28 @@ extension MemberAccessExpr {
192192

193193
// MARK: - StringLiteralExpr
194194

195+
extension String {
196+
/// Replace literal newlines with "\r", "\n".
197+
fileprivate func replacingNewlines() -> String {
198+
var result = ""
199+
var input = self[...]
200+
while let firstNewline = input.firstIndex(where: { $0.isNewline }) {
201+
result += input[..<firstNewline]
202+
if input[firstNewline] == "\r" {
203+
result += "\\r"
204+
} else if input[firstNewline] == "\r\n" {
205+
result += "\\r\\n"
206+
} else {
207+
result += "\\n"
208+
}
209+
input = input[input.index(after: firstNewline)...]
210+
continue
211+
}
212+
213+
return result + input
214+
}
215+
}
216+
195217
extension StringLiteralExpr {
196218
private enum PoundState {
197219
case afterQuote, afterBackslash, none
@@ -241,7 +263,7 @@ extension StringLiteralExpr {
241263
closeQuote: Token = .stringQuote,
242264
closeDelimiter: Token? = nil
243265
) {
244-
let contentToken = Token.stringSegment(content)
266+
let contentToken = Token.stringSegment(content.replacingNewlines())
245267
let segment = StringSegment(content: contentToken)
246268
let segments = StringLiteralSegments([.stringSegment(segment)])
247269

Sources/_SwiftSyntaxMacros/MacroSystem+Examples.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct StringifyMacro: ExpressionMacro {
3333
return MacroResult(ExprSyntax(macro))
3434
}
3535

36-
return MacroResult("(\(argument), #\"\(argument)\"#)")
36+
return MacroResult("(\(argument), \(StringLiteralExprSyntax(content: argument.description)))")
3737
}
3838
}
3939

Tests/SwiftSyntaxBuilderTest/StringLiteralTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,11 @@ final class StringLiteralTests: XCTestCase {
8282
#"\"#
8383
"""##)
8484
}
85+
86+
func testNewlines() {
87+
AssertBuildResult(
88+
StringLiteralExpr(content: "linux\nwindows\r\na"),
89+
#""linux\nwindows\r\na""#
90+
)
91+
}
8592
}

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,39 @@ final class MacroSystemTests: XCTestCase {
3838
"""
3939
1
4040
let a = (2)
41-
let b = (x + y, #"x + y"#)
41+
let b = (x + y, "x + y")
4242
.init(_colorLiteralRed: 0.5, green: 0.5, blue: 0.25, alpha: 1.0)
4343
let c = 9
4444
"""
4545
)
4646
}
4747

48+
func testStringifyExpression() {
49+
let sf: SourceFileSyntax =
50+
"""
51+
_ = #stringify({ () -> Bool in
52+
print("hello")
53+
return true
54+
})
55+
"""
56+
let converter = SourceLocationConverter(file: "test.swift", tree: sf)
57+
let context = MacroEvaluationContext(
58+
moduleName: "MyModule", sourceLocationConverter: converter
59+
)
60+
let transformedSF = MacroSystem.exampleSystem.evaluateMacros(
61+
node: sf, in: context, errorHandler: { error in }
62+
)
63+
AssertStringsEqualWithDiff(
64+
transformedSF.description,
65+
"""
66+
_ = ({ () -> Bool in
67+
print("hello")
68+
return true
69+
}, #"{ () -> Bool in\\n print("hello")\\n return true\\n}"#)
70+
"""
71+
)
72+
}
73+
4874
func testPoundFunctionExpansion() {
4975
let sf: SourceFileSyntax =
5076
"""

0 commit comments

Comments
 (0)