Skip to content

Commit 67f0564

Browse files
committed
Add a backslash trivia kind for backslashs that escape the newline in a multi-line string literal
1 parent 3c63260 commit 67f0564

File tree

8 files changed

+96
-32
lines changed

8 files changed

+96
-32
lines changed

CodeGeneration/Sources/SyntaxSupport/Trivia.swift.gyb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ public class Trivia {
2626
public let isComment: Bool
2727

2828
public var lowerName: String { lowercaseFirstWord(name: name) }
29+
30+
public var enumCaseName: String {
31+
if self.isCollection {
32+
if lowerName == "backslash" {
33+
return "backslashes"
34+
} else {
35+
return "\(lowerName)s"
36+
}
37+
} else {
38+
return lowerName
39+
}
40+
}
2941

3042
public var charactersLen: Int { characters.count }
3143

CodeGeneration/Sources/SyntaxSupport/gyb_generated/Trivia.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ public class Trivia {
2121
public let isComment: Bool
2222

2323
public var lowerName: String { lowercaseFirstWord(name: name) }
24+
25+
public var enumCaseName: String {
26+
if self.isCollection {
27+
if lowerName == "backslash" {
28+
return "backslashes"
29+
} else {
30+
return "\(lowerName)s"
31+
}
32+
} else {
33+
return lowerName
34+
}
35+
}
2436

2537
public var charactersLen: Int { characters.count }
2638

@@ -122,6 +134,14 @@ public let TRIVIAS: [Trivia] = [
122134
Trivia(name: "DocBlockComment",
123135
comment: #"A documentation block comment, starting with '/**' and ending with '*/'."#,
124136
isComment: true),
137+
Trivia(name: "Backslash",
138+
comment: #"A backslash that is at the end of a line in a multi-line string literal to escape the newline."#,
139+
characters: [
140+
Character("\\")
141+
],
142+
swiftCharacters: [
143+
Character("\\")
144+
]),
125145
Trivia(name: "UnexpectedText",
126146
comment: #"Any skipped unexpected text."#),
127147
Trivia(name: "Shebang",

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/TriviaFile.swift

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
3838
if trivia.isCollection {
3939
EnumCaseDeclSyntax("""
4040
/// \(raw: trivia.comment)
41-
case \(raw: trivia.lowerName)s(Int)
41+
case \(raw: trivia.enumCaseName)(Int)
4242
""")
4343

4444
} else {
4545
EnumCaseDeclSyntax("""
4646
/// \(raw: trivia.comment)
47-
case \(raw: trivia.lowerName)(String)
47+
case \(raw: trivia.enumCaseName)(String)
4848
""")
4949
}
5050
}
@@ -67,11 +67,11 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
6767
for trivia in TRIVIAS {
6868
if trivia.isCollection {
6969
let joined = trivia.characters.map { "\($0)" }.joined()
70-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)s(count):") {
70+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(count):") {
7171
FunctionCallExprSyntax("printRepeated(\(literal: joined), count: count)")
7272
}
7373
} else {
74-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)(text):") {
74+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(text):") {
7575
FunctionCallExprSyntax("target.write(text)")
7676
}
7777
}
@@ -89,12 +89,12 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
8989
SwitchStmtSyntax(expression: ExprSyntax("self")) {
9090
for trivia in TRIVIAS {
9191
if trivia.isCollection {
92-
SwitchCaseSyntax("case .\(raw: trivia.lowerName)s(let data):") {
93-
ReturnStmtSyntax(#"return "\#(raw: trivia.lowerName)s(\(data))""#)
92+
SwitchCaseSyntax("case .\(raw: trivia.enumCaseName)(let data):") {
93+
ReturnStmtSyntax(#"return "\#(raw: trivia.enumCaseName)(\(data))""#)
9494
}
9595
} else {
96-
SwitchCaseSyntax("case .\(raw: trivia.lowerName)(let name):") {
97-
ReturnStmtSyntax(#"return "\#(raw: trivia.lowerName)(\(name.debugDescription))""#)
96+
SwitchCaseSyntax("case .\(raw: trivia.enumCaseName)(let name):") {
97+
ReturnStmtSyntax(#"return "\#(raw: trivia.enumCaseName)(\(name.debugDescription))""#)
9898
}
9999
}
100100
}
@@ -173,24 +173,24 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
173173
let joined = trivia.characters.map { "\($0)" }.joined()
174174
FunctionDeclSyntax("""
175175
/// Returns a piece of trivia for some number of \(literal: joined) characters.
176-
public static func \(raw: trivia.lowerName)s(_ count: Int) -> Trivia {
177-
return [.\(raw: trivia.lowerName)s(count)]
176+
public static func \(raw: trivia.enumCaseName)(_ count: Int) -> Trivia {
177+
return [.\(raw: trivia.enumCaseName)(count)]
178178
}
179179
""")
180180

181181
VariableDeclSyntax("""
182182
/// Gets a piece of trivia for \(literal: joined) characters.
183183
public static var \(raw: trivia.lowerName): Trivia {
184-
return .\(raw: trivia.lowerName)s(1)
184+
return .\(raw: trivia.enumCaseName)(1)
185185
}
186186
""")
187187

188188

189189
} else {
190190
FunctionDeclSyntax("""
191191
/// Returns a piece of trivia for \(raw: trivia.name).
192-
public static func \(raw: trivia.lowerName)(_ text: String) -> Trivia {
193-
return [.\(raw: trivia.lowerName)(text)]
192+
public static func \(raw: trivia.enumCaseName)(_ text: String) -> Trivia {
193+
return [.\(raw: trivia.enumCaseName)(text)]
194194
}
195195
""")
196196
}
@@ -288,15 +288,15 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
288288
SwitchStmtSyntax(expression: ExprSyntax("self")) {
289289
for trivia in TRIVIAS {
290290
if trivia.isCollection {
291-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)s(count):") {
291+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(count):") {
292292
if trivia.charactersLen != 1 {
293293
ReturnStmtSyntax("return SourceLength(utf8Length: count * \(raw: trivia.charactersLen))")
294294
} else {
295295
ReturnStmtSyntax("return SourceLength(utf8Length: count)")
296296
}
297297
}
298298
} else {
299-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)(text):") {
299+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(text):") {
300300
ReturnStmtSyntax("return SourceLength(of: text)")
301301
}
302302
}
@@ -315,10 +315,10 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
315315
""") {
316316
for trivia in TRIVIAS {
317317
if trivia.isCollection {
318-
EnumCaseDeclSyntax(" case \(raw: trivia.lowerName)s(Int)")
318+
EnumCaseDeclSyntax(" case \(raw: trivia.enumCaseName)(Int)")
319319

320320
} else {
321-
EnumCaseDeclSyntax("case \(raw: trivia.lowerName)(SyntaxText)")
321+
EnumCaseDeclSyntax("case \(raw: trivia.enumCaseName)(SyntaxText)")
322322
}
323323
}
324324

@@ -328,12 +328,12 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
328328
SwitchStmtSyntax(expression: ExprSyntax("piece")) {
329329
for trivia in TRIVIAS {
330330
if trivia.isCollection {
331-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)s(count):") {
332-
ReturnStmtSyntax("return .\(raw: trivia.lowerName)s(count)")
331+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(count):") {
332+
ReturnStmtSyntax("return .\(raw: trivia.enumCaseName)(count)")
333333
}
334334
} else {
335-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)(text):") {
336-
ReturnStmtSyntax("return .\(raw: trivia.lowerName)(arena.intern(text))")
335+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(text):") {
336+
ReturnStmtSyntax("return .\(raw: trivia.enumCaseName)(arena.intern(text))")
337337
}
338338
}
339339
}
@@ -362,12 +362,12 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
362362
SwitchStmtSyntax(expression: ExprSyntax("raw")) {
363363
for trivia in TRIVIAS {
364364
if trivia.isCollection {
365-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)s(count):") {
366-
ExprSyntax("self = .\(raw: trivia.lowerName)s(count)")
365+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(count):") {
366+
ExprSyntax("self = .\(raw: trivia.enumCaseName)(count)")
367367
}
368368
} else {
369-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)(text):") {
370-
ExprSyntax("self = .\(raw: trivia.lowerName)(String(syntaxText: text))")
369+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(text):") {
370+
ExprSyntax("self = .\(raw: trivia.enumCaseName)(String(syntaxText: text))")
371371
}
372372
}
373373
}
@@ -383,15 +383,15 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
383383
SwitchStmtSyntax(expression: ExprSyntax("self")) {
384384
for trivia in TRIVIAS {
385385
if trivia.isCollection {
386-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)s(count):") {
386+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(count):") {
387387
if trivia.charactersLen != 1 {
388388
ReturnStmtSyntax("return count * \(raw: trivia.charactersLen)")
389389
} else {
390390
ReturnStmtSyntax("return count")
391391
}
392392
}
393393
} else {
394-
SwitchCaseSyntax("case let .\(raw: trivia.lowerName)(text):") {
394+
SwitchCaseSyntax("case let .\(raw: trivia.enumCaseName)(text):") {
395395
ReturnStmtSyntax("return text.count")
396396
}
397397
}
@@ -405,11 +405,11 @@ let triviaFile = SourceFileSyntax(leadingTrivia: .docLineComment(generateCopyrig
405405
SwitchStmtSyntax(expression: ExprSyntax("self")) {
406406
for trivia in TRIVIAS {
407407
if trivia.isCollection {
408-
SwitchCaseSyntax("case .\(raw: trivia.lowerName)s(_):") {
408+
SwitchCaseSyntax("case .\(raw: trivia.enumCaseName)(_):") {
409409
ReturnStmtSyntax("return nil")
410410
}
411411
} else {
412-
SwitchCaseSyntax("case .\(raw: trivia.lowerName)(let text):") {
412+
SwitchCaseSyntax("case .\(raw: trivia.enumCaseName)(let text):") {
413413
ReturnStmtSyntax("return text")
414414
}
415415
}

Sources/SwiftParser/StringLiterals.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,9 @@ extension Parser {
313313
// is not part of the reprsented string and should be trivia.
314314

315315
if segment.content.tokenText.hasSuffix("\\\n") {
316-
// TODO: Add a backslash trivia kind
317316
segment = RawStringSegmentSyntax(
318317
segment.unexpectedBeforeContent,
319-
content: segment.content.reclassifyAsTrailingTrivia([.unexpectedText("\\"), .newlines(1)], arena: self.arena),
318+
content: segment.content.reclassifyAsTrailingTrivia([.backslashs(1), .newlines(1)], arena: self.arena),
320319
segment.unexpectedAfterContent,
321320
arena: self.arena
322321
)

Sources/SwiftParserDiagnostics/DiagnosticExtensions.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ extension Trivia {
171171
return Array(repeating: TriviaPiece.formfeeds(1), count: count)
172172
case .newlines(let count):
173173
return Array(repeating: TriviaPiece.newlines(1), count: count)
174+
case .backslashs(let count):
175+
return Array(repeating: TriviaPiece.backslashs(1), count: count)
174176
case .carriageReturns(let count):
175177
return Array(repeating: TriviaPiece.carriageReturns(1), count: count)
176178
case .carriageReturnLineFeeds(let count):

Sources/SwiftSyntax/SourceLocation.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,8 @@ fileprivate extension TriviaPiece {
473473
case let .spaces(count),
474474
let .tabs(count),
475475
let .verticalTabs(count),
476-
let .formfeeds(count):
476+
let .formfeeds(count),
477+
let .backslashs(count):
477478
lineLength += SourceLength(utf8Length: count)
478479
case let .newlines(count),
479480
let .carriageReturns(count):

Sources/SwiftSyntax/generated/Trivia.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public enum TriviaPiece {
6262
/// A documentation block comment, starting with '/**' and ending with '*/'.
6363
case docBlockComment(String)
6464

65+
/// A backslash that is at the end of a line in a multi-line string literal to escape the newline.
66+
case backslashes(Int)
67+
6568
/// Any skipped unexpected text.
6669
case unexpectedText(String)
6770

@@ -102,6 +105,8 @@ extension TriviaPiece: TextOutputStreamable {
102105
target.write(text)
103106
case let .docBlockComment(text):
104107
target.write(text)
108+
case let .backslashes(count):
109+
printRepeated(#"\"#, count: count)
105110
case let .unexpectedText(text):
106111
target.write(text)
107112
case let .shebang(text):
@@ -136,6 +141,8 @@ extension TriviaPiece: CustomDebugStringConvertible {
136141
return "docLineComment(\(name.debugDescription))"
137142
case .docBlockComment(let name):
138143
return "docBlockComment(\(name.debugDescription))"
144+
case .backslashes(let data):
145+
return "backslashes(\(data))"
139146
case .unexpectedText(let name):
140147
return "unexpectedText(\(name.debugDescription))"
141148
case .shebang(let name):
@@ -286,6 +293,16 @@ public struct Trivia {
286293
return [.docBlockComment(text)]
287294
}
288295

296+
/// Returns a piece of trivia for some number of #"\"# characters.
297+
public static func backslashes(_ count: Int) -> Trivia {
298+
return [.backslashes(count)]
299+
}
300+
301+
/// Gets a piece of trivia for #"\"# characters.
302+
public static var backslash: Trivia {
303+
return .backslashes(1)
304+
}
305+
289306
/// Returns a piece of trivia for UnexpectedText.
290307
public static func unexpectedText(_ text: String) -> Trivia {
291308
return [.unexpectedText(text)]
@@ -399,6 +416,8 @@ extension TriviaPiece {
399416
return SourceLength(of: text)
400417
case let .docBlockComment(text):
401418
return SourceLength(of: text)
419+
case let .backslashes(count):
420+
return SourceLength(utf8Length: count)
402421
case let .unexpectedText(text):
403422
return SourceLength(of: text)
404423
case let .shebang(text):
@@ -435,6 +454,8 @@ public enum RawTriviaPiece: Equatable {
435454

436455
case docBlockComment(SyntaxText)
437456

457+
case backslashes(Int)
458+
438459
case unexpectedText(SyntaxText)
439460

440461
case shebang(SyntaxText)
@@ -463,6 +484,8 @@ public enum RawTriviaPiece: Equatable {
463484
return .docLineComment(arena.intern(text))
464485
case let .docBlockComment(text):
465486
return .docBlockComment(arena.intern(text))
487+
case let .backslashes(count):
488+
return .backslashes(count)
466489
case let .unexpectedText(text):
467490
return .unexpectedText(arena.intern(text))
468491
case let .shebang(text):
@@ -508,6 +531,8 @@ extension TriviaPiece {
508531
self = .docLineComment(String(syntaxText: text))
509532
case let .docBlockComment(text):
510533
self = .docBlockComment(String(syntaxText: text))
534+
case let .backslashes(count):
535+
self = .backslashes(count)
511536
case let .unexpectedText(text):
512537
self = .unexpectedText(String(syntaxText: text))
513538
case let .shebang(text):
@@ -541,6 +566,8 @@ extension RawTriviaPiece {
541566
return text.count
542567
case let .docBlockComment(text):
543568
return text.count
569+
case let .backslashes(count):
570+
return count
544571
case let .unexpectedText(text):
545572
return text.count
546573
case let .shebang(text):
@@ -572,6 +599,8 @@ extension RawTriviaPiece {
572599
return text
573600
case .docBlockComment(let text):
574601
return text
602+
case .backslashes(_):
603+
return nil
575604
case .unexpectedText(let text):
576605
return text
577606
case .shebang(let text):

gyb_syntax_support/Trivia.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def is_collection(self):
5656
'A documentation block comment, starting with \'/**\' and ending '
5757
'with \'*/\'.',
5858
is_comment=True),
59+
Trivia('Backslash', 'A backslash that is at the end of a line in a multi-line string literal to escape the newline.', characters=['\\\\']),
5960
Trivia('UnexpectedText', 'Any skipped unexpected text.'),
6061
Trivia('Shebang', 'A script command, starting with \'#!\'.'),
6162
]

0 commit comments

Comments
 (0)