@@ -48,6 +48,14 @@ open class BasicFormat: SyntaxRewriter {
4848 /// been visited yet.
4949 private var previousToken : TokenSyntax ? = nil
5050
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+
5159 public init (
5260 indentationWidth: Trivia = . spaces( 4 ) ,
5361 initialIndentation: Trivia = [ ] ,
@@ -83,6 +91,9 @@ open class BasicFormat: SyntaxRewriter {
8391 }
8492
8593 open override func visitPre( _ node: Syntax ) {
94+ if node. is ( StringLiteralExprSyntax . self) {
95+ stringLiteralNestingLevel += 1
96+ }
8697 if requiresIndent ( node) {
8798 if let firstToken = node. firstToken ( viewMode: viewMode) ,
8899 let tokenIndentation = firstToken. leadingTrivia. indentation ( isOnNewline: false ) ,
@@ -98,6 +109,9 @@ open class BasicFormat: SyntaxRewriter {
98109 }
99110
100111 open override func visitPost( _ node: Syntax ) {
112+ if node. is ( StringLiteralExprSyntax . self) {
113+ stringLiteralNestingLevel -= 1
114+ }
101115 if requiresIndent ( node) {
102116 decreaseIndentationLevel ( )
103117 }
@@ -498,10 +512,12 @@ open class BasicFormat: SyntaxRewriter {
498512 }
499513 }
500514
501- let isEmptyLine = token. leadingTrivia. isEmpty && leadingTriviaIsFollowedByNewline
502- if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] && !isEmptyLine {
515+ if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] && !token. isStringSegment {
503516 // 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.
505521 anchorPoints [ token] = currentIndentationLevel
506522 }
507523
@@ -529,14 +545,17 @@ open class BasicFormat: SyntaxRewriter {
529545 var leadingTriviaIndentation = self . currentIndentationLevel
530546 var trailingTriviaIndentation = self . currentIndentationLevel
531547
532- // If the trivia contain user-defined indentation, find their anchor point
548+ // If the trivia contains user-defined indentation, find their anchor point
533549 // 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,
535554 let anchorPointIndentation = self . anchorPointIndentation ( for: token)
536555 {
537556 leadingTriviaIndentation = anchorPointIndentation
538557 }
539- if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) ,
558+ if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) || isInsideStringLiteral ,
540559 let anchorPointIndentation = self . anchorPointIndentation ( for: token)
541560 {
542561 trailingTriviaIndentation = anchorPointIndentation
@@ -567,6 +586,14 @@ open class BasicFormat: SyntaxRewriter {
567586}
568587
569588fileprivate extension TokenSyntax {
589+ var isStringSegment : Bool {
590+ if case . stringSegment = self . tokenKind {
591+ return true
592+ } else {
593+ return false
594+ }
595+ }
596+
570597 var isStringSegmentWithLastCharacterBeingNewline : Bool {
571598 switch self . tokenKind {
572599 case . stringSegment( let segment) :
0 commit comments