Skip to content

Commit 26669c5

Browse files
committed
Require \(raw: <expr>) for plain strings
While we want `SyntaxStringInterpolation` to allow the interpolation of values known to be valid syntax into a literal, allowing plain old strings goes a little too far—you don’t know where that string’s been. Require a `raw:` label to indicate that a conversion is required, while also supporting any type (not just `CustomStringConvertible` conformers) now that the behavior is more explicit. Leave the original support in place but deprecated.
1 parent 715f125 commit 26669c5

File tree

6 files changed

+65
-8
lines changed

6 files changed

+65
-8
lines changed

Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,19 @@ extension SyntaxStringInterpolation: StringInterpolationProtocol {
8282
}
8383

8484
// Append a value of any CustomStringConvertible type as source text.
85+
@available(*, deprecated, renamed: "appendInterpolation(raw:)", message: "use '\\(raw: <value>)' to interpolate a plain string directly into Swift code")
8586
public mutating func appendInterpolation<T: CustomStringConvertible>(
8687
_ value: T
8788
) {
8889
sourceText.append(contentsOf: value.description.utf8)
8990
self.lastIndentation = nil
9091
}
9192

93+
public mutating func appendInterpolation<T>(raw value: T) {
94+
sourceText.append(contentsOf: String(describing: value).utf8)
95+
self.lastIndentation = nil
96+
}
97+
9298
// Append a value of any metatype as source text
9399
public mutating func appendInterpolation<T>(
94100
_ type: T.Type

Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public protocol HasTrailingCodeBlock {
3838

3939
public extension HasTrailingCodeBlock where Self: SyntaxExpressibleByStringInterpolation {
4040
init(_ signature: String, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) {
41-
self = "\(signature) {}"
41+
self = "\(raw: signature) {}"
4242
self.body = CodeBlock(statements: bodyBuilder())
4343
}
4444
}
@@ -58,7 +58,7 @@ public protocol HasTrailingOptionalCodeBlock {
5858

5959
public extension HasTrailingOptionalCodeBlock where Self: SyntaxExpressibleByStringInterpolation {
6060
init(_ signature: String, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) {
61-
self = "\(signature) {}"
61+
self = "\(raw: signature) {}"
6262
self.body = CodeBlock(statements: bodyBuilder())
6363
}
6464
}
@@ -77,7 +77,7 @@ public protocol HasTrailingMemberDeclBlock {
7777

7878
public extension HasTrailingMemberDeclBlock where Self: SyntaxExpressibleByStringInterpolation {
7979
init(_ signature: String, @MemberDeclListBuilder membersBuilder: () -> MemberDeclListSyntax) {
80-
self = "\(signature) {}"
80+
self = "\(raw: signature) {}"
8181
self.members = MemberDeclBlock(members: membersBuilder())
8282
}
8383
}

Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ private extension TriviaPiece {
6262
let (label, value) = Mirror(reflecting: self).children.first!
6363
switch value {
6464
case let value as String:
65-
return FunctionCallExpr(callee: ".\(label!)") {
65+
return FunctionCallExpr(callee: ".\(raw: label!)") {
6666
TupleExprElement(expression: StringLiteralExpr(content: value))
6767
}
6868
case let value as Int:
69-
return FunctionCallExpr(callee: ".\(label!)") {
69+
return FunctionCallExpr(callee: ".\(raw: label!)") {
7070
TupleExprElement(expression: IntegerLiteralExpr(value))
7171
}
7272
default:
@@ -131,7 +131,7 @@ extension SyntaxProtocol {
131131
tokenInitializerName = String(tokenKindStr[..<tokenKindStr.firstIndex(of: "(")!])
132132
requiresExplicitText = true
133133
}
134-
return FunctionCallExpr(callee: ".\(tokenInitializerName)") {
134+
return FunctionCallExpr(callee: ".\(raw: tokenInitializerName)") {
135135
if requiresExplicitText {
136136
TupleExprElement(
137137
expression: StringLiteralExpr(content: token.text)

Tests/SwiftSyntaxBuilderTest/FunctionTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class FunctionTests: XCTestCase {
3737
func testArguments() {
3838
let buildable = FunctionCallExpr(callee: "test") {
3939
for param in (1...5) {
40-
TupleExprElement(label: param.isMultiple(of: 2) ? "p\(param)" : nil, expression: "value\(param)")
40+
TupleExprElement(label: param.isMultiple(of: 2) ? "p\(param)" : nil, expression: "value\(raw: param)")
4141
}
4242
}
4343
AssertBuildResult(buildable, "test(value1, p2: value2, value3, p4: value4, value5)")

Tests/SwiftSyntaxBuilderTest/StringInterpolation.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ final class StringInterpolationTests: XCTestCase {
7777
let name = "Type"
7878
let id = 17
7979

80+
let structNode: DeclSyntax =
81+
"""
82+
struct \(raw: name) {
83+
static var id = \(raw: id)
84+
}
85+
"""
86+
XCTAssertTrue(structNode.is(StructDeclSyntax.self))
87+
}
88+
89+
@available(*, deprecated)
90+
func testStructGeneratorDeprecated() {
91+
let name = "Type"
92+
let id = 17
93+
8094
let structNode: DeclSyntax =
8195
"""
8296
struct \(name) {
@@ -117,6 +131,43 @@ final class StringInterpolationTests: XCTestCase {
117131
}
118132

119133
func testParserBuilderInStringInterpolation() {
134+
let cases = SwitchCaseList {
135+
for i in 0..<2 {
136+
SwitchCase("""
137+
case \(raw: i):
138+
return \(raw: i + 1)
139+
""")
140+
}
141+
SwitchCase("""
142+
default:
143+
return -1
144+
""")
145+
}
146+
let plusOne = FunctionDeclSyntax("""
147+
func plusOne(base: Int) -> Int {
148+
switch base {
149+
\(cases, format: TwoSpacesFormat())
150+
}
151+
}
152+
""")
153+
154+
AssertStringsEqualWithDiff(plusOne.description.trimmingTrailingWhitespace(), """
155+
func plusOne(base: Int) -> Int {
156+
switch base {
157+
158+
case 0:
159+
return 1
160+
case 1:
161+
return 2
162+
default:
163+
return -1
164+
}
165+
}
166+
""")
167+
}
168+
169+
@available(*, deprecated)
170+
func testParserBuilderInStringInterpolationDeprecated() {
120171
let cases = SwitchCaseList {
121172
for i in 0..<2 {
122173
SwitchCase("""

Tests/SwiftSyntaxBuilderTest/StructTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ final class StructTests: XCTestCase {
8282
if i.isMultiple(of: 2) {
8383
VariableDecl(letOrVarKeyword: .let) {
8484
PatternBinding(
85-
pattern: IdentifierPattern("var\(i)"),
85+
pattern: IdentifierPattern("var\(raw: i)"),
8686
typeAnnotation: TypeAnnotation(type: Type("String"))
8787
)
8888
}

0 commit comments

Comments
 (0)