@@ -48,6 +48,14 @@ open class BasicFormat: SyntaxRewriter {
48
48
/// been visited yet.
49
49
private var previousToken : TokenSyntax ? = nil
50
50
51
+ /// The number of ancestors that are `StringLiteralExprSyntax`.
52
+ private var stringLiteralNestingLevel = 0
53
+
54
+ /// Whether we are currently visiting the subtree of a `StringLiteralExprSyntax`.
55
+ private var isInsideStringLiteral : Bool {
56
+ return stringLiteralNestingLevel > 0
57
+ }
58
+
51
59
public init (
52
60
indentationWidth: Trivia = . spaces( 4 ) ,
53
61
initialIndentation: Trivia = [ ] ,
@@ -83,6 +91,9 @@ open class BasicFormat: SyntaxRewriter {
83
91
}
84
92
85
93
open override func visitPre( _ node: Syntax ) {
94
+ if node. is ( StringLiteralExprSyntax . self) {
95
+ stringLiteralNestingLevel += 1
96
+ }
86
97
if requiresIndent ( node) {
87
98
if let firstToken = node. firstToken ( viewMode: viewMode) ,
88
99
let tokenIndentation = firstToken. leadingTrivia. indentation ( isOnNewline: false ) ,
@@ -98,6 +109,9 @@ open class BasicFormat: SyntaxRewriter {
98
109
}
99
110
100
111
open override func visitPost( _ node: Syntax ) {
112
+ if node. is ( StringLiteralExprSyntax . self) {
113
+ stringLiteralNestingLevel -= 1
114
+ }
101
115
if requiresIndent ( node) {
102
116
decreaseIndentationLevel ( )
103
117
}
@@ -498,10 +512,12 @@ open class BasicFormat: SyntaxRewriter {
498
512
}
499
513
}
500
514
501
- let isEmptyLine = token. leadingTrivia. isEmpty && leadingTriviaIsFollowedByNewline
502
- if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] && !isEmptyLine {
515
+ if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] && !token. isStringSegment {
503
516
// If the token starts on a new line and does not have indentation, this
504
- // is the last non-indented token. Store its indentation level
517
+ // is the last non-indented token. Store its indentation level.
518
+ // But never consider string segments as anchor points since you can’t
519
+ // indent individual lines of a multi-line string literals without breaking
520
+ // their integrity.
505
521
anchorPoints [ token] = currentIndentationLevel
506
522
}
507
523
@@ -529,14 +545,17 @@ open class BasicFormat: SyntaxRewriter {
529
545
var leadingTriviaIndentation = self . currentIndentationLevel
530
546
var trailingTriviaIndentation = self . currentIndentationLevel
531
547
532
- // If the trivia contain user-defined indentation, find their anchor point
548
+ // If the trivia contains user-defined indentation, find their anchor point
533
549
// and indent the token relative to that anchor point.
534
- if leadingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) ,
550
+ // Always indent string literals relative to their anchor point because
551
+ // their indentation has structural meaning and we just want to maintain
552
+ // what the user wrote.
553
+ if leadingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) || isInsideStringLiteral,
535
554
let anchorPointIndentation = self . anchorPointIndentation ( for: token)
536
555
{
537
556
leadingTriviaIndentation = anchorPointIndentation
538
557
}
539
- if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) ,
558
+ if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) || isInsideStringLiteral ,
540
559
let anchorPointIndentation = self . anchorPointIndentation ( for: token)
541
560
{
542
561
trailingTriviaIndentation = anchorPointIndentation
@@ -567,6 +586,14 @@ open class BasicFormat: SyntaxRewriter {
567
586
}
568
587
569
588
fileprivate extension TokenSyntax {
589
+ var isStringSegment : Bool {
590
+ if case . stringSegment = self . tokenKind {
591
+ return true
592
+ } else {
593
+ return false
594
+ }
595
+ }
596
+
570
597
var isStringSegmentWithLastCharacterBeingNewline : Bool {
571
598
switch self . tokenKind {
572
599
case . stringSegment( let segment) :
0 commit comments