1010//
1111//===----------------------------------------------------------------------===//
1212
13- import SwiftSyntax
13+ @ _spi ( RawSyntax ) import SwiftSyntax
1414
1515/// A rewriter that performs a "basic" format of the passed tree.
1616///
@@ -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 ? = nil ,
5361 initialIndentation: Trivia = [ ] ,
@@ -86,6 +94,9 @@ open class BasicFormat: SyntaxRewriter {
8694 }
8795
8896 open override func visitPre( _ node: Syntax ) {
97+ if node. is ( StringLiteralExprSyntax . self) {
98+ stringLiteralNestingLevel += 1
99+ }
89100 if requiresIndent ( node) {
90101 if let firstToken = node. firstToken ( viewMode: viewMode) ,
91102 let tokenIndentation = firstToken. leadingTrivia. indentation ( isOnNewline: false ) ,
@@ -101,6 +112,9 @@ open class BasicFormat: SyntaxRewriter {
101112 }
102113
103114 open override func visitPost( _ node: Syntax ) {
115+ if node. is ( StringLiteralExprSyntax . self) {
116+ stringLiteralNestingLevel -= 1
117+ }
104118 if requiresIndent ( node) {
105119 decreaseIndentationLevel ( )
106120 }
@@ -274,7 +288,7 @@ open class BasicFormat: SyntaxRewriter {
274288 ( . keyword( . `init`) , . leftParen) , // init()
275289 ( . keyword( . self ) , . period) , // self.someProperty
276290 ( . keyword( . Self) , . period) , // self.someProperty
277- ( . keyword( . set) , . leftParen) , // var mYar : Int { set(value) {} }
291+ ( . keyword( . set) , . leftParen) , // var mVar : Int { set(value) {} }
278292 ( . keyword( . subscript) , . leftParen) , // subscript(x: Int)
279293 ( . keyword( . super) , . period) , // super.someProperty
280294 ( . leftBrace, . rightBrace) , // {}
@@ -351,6 +365,26 @@ open class BasicFormat: SyntaxRewriter {
351365 return true
352366 }
353367
368+ /// Change the text of a token during formatting.
369+ ///
370+ /// This allows formats to e.g. replace missing tokens by placeholder tokens.
371+ ///
372+ /// - Parameter token: The token whose text should be changed
373+ /// - Returns: The new text or `nil` if the text should not be changed
374+ open func transformTokenText( _ token: TokenSyntax ) -> String ? {
375+ return nil
376+ }
377+
378+ /// Change the presence of a token during formatting.
379+ ///
380+ /// This allows formats to e.g. replace missing tokens by placeholder tokens.
381+ ///
382+ /// - Parameter token: The token whose presence should be changed
383+ /// - Returns: The new presence or `nil` if the presence should not be changed
384+ open func transformTokenPresence( _ token: TokenSyntax ) -> SourcePresence ? {
385+ return nil
386+ }
387+
354388 // MARK: - Formatting a token
355389
356390 open override func visit( _ token: TokenSyntax ) -> TokenSyntax {
@@ -360,6 +394,8 @@ open class BasicFormat: SyntaxRewriter {
360394 let isInitialToken = self . previousToken == nil
361395 let previousToken = self . previousToken ?? token. previousToken ( viewMode: viewMode)
362396 let nextToken = token. nextToken ( viewMode: viewMode)
397+ let transformedTokenText = self . transformTokenText ( token)
398+ let transformedTokenPresence = self . transformTokenPresence ( token)
363399
364400 /// In addition to existing trivia of `previousToken`, also considers
365401 /// `previousToken` as ending with whitespace if it and `token` should be
@@ -375,7 +411,7 @@ open class BasicFormat: SyntaxRewriter {
375411 || ( requiresWhitespace ( between: previousToken, and: token) && isMutable ( previousToken) )
376412 } ( )
377413
378- /// This method does not consider any posssible mutations to `previousToken`
414+ /// This method does not consider any possible mutations to `previousToken`
379415 /// because newlines should be added to the next token's leading trivia.
380416 let previousTokenWillEndWithNewline : Bool = {
381417 guard let previousToken = previousToken else {
@@ -419,6 +455,14 @@ open class BasicFormat: SyntaxRewriter {
419455 if nextToken. leadingTrivia. startsWithNewline {
420456 return true
421457 }
458+ if nextToken. leadingTrivia. isEmpty {
459+ if nextToken. text. first? . isNewline ?? false {
460+ return true
461+ }
462+ if nextToken. text. isEmpty && nextToken. trailingTrivia. startsWithNewline {
463+ return true
464+ }
465+ }
422466 if requiresNewline ( between: token, and: nextToken) ,
423467 isMutable ( nextToken) ,
424468 !token. trailingTrivia. endsWithNewline,
@@ -439,6 +483,19 @@ open class BasicFormat: SyntaxRewriter {
439483 return trailingTrivia + Trivia( pieces: nextTokenLeadingWhitespace)
440484 } ( )
441485
486+ /// Whether the leading trivia of the token is followed by a newline.
487+ let leadingTriviaIsFollowedByNewline : Bool = {
488+ if ( transformedTokenText ?? token. text) . isEmpty && token. trailingTrivia. startsWithNewline {
489+ return true
490+ } else if token. text. first? . isNewline ?? false {
491+ return true
492+ } else if ( transformedTokenText ?? token. text) . isEmpty && token. trailingTrivia. isEmpty && nextTokenWillStartWithNewline {
493+ return true
494+ } else {
495+ return false
496+ }
497+ } ( )
498+
442499 if requiresNewline ( between: previousToken, and: token) {
443500 // Add a leading newline if the token requires it unless
444501 // - it already starts with a newline or
@@ -458,9 +515,12 @@ open class BasicFormat: SyntaxRewriter {
458515 }
459516 }
460517
461- if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] {
518+ if leadingTrivia. indentation ( isOnNewline: isInitialToken || previousTokenWillEndWithNewline) == [ ] && !token . isStringSegment {
462519 // If the token starts on a new line and does not have indentation, this
463- // is the last non-indented token. Store its indentation level
520+ // is the last non-indented token. Store its indentation level.
521+ // But never consider string segments as anchor points since you can’t
522+ // indent individual lines of a multi-line string literals without breaking
523+ // their integrity.
464524 anchorPoints [ token] = currentIndentationLevel
465525 }
466526
@@ -488,14 +548,17 @@ open class BasicFormat: SyntaxRewriter {
488548 var leadingTriviaIndentation = self . currentIndentationLevel
489549 var trailingTriviaIndentation = self . currentIndentationLevel
490550
491- // If the trivia contain user-defined indentation, find their anchor point
551+ // If the trivia contains user-defined indentation, find their anchor point
492552 // and indent the token relative to that anchor point.
493- if leadingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) ,
553+ // Always indent string literals relative to their anchor point because
554+ // their indentation has structural meaning and we just want to maintain
555+ // what the user wrote.
556+ if leadingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) || isInsideStringLiteral,
494557 let anchorPointIndentation = self . anchorPointIndentation ( for: token)
495558 {
496559 leadingTriviaIndentation = anchorPointIndentation
497560 }
498- if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) ,
561+ if combinedTrailingTrivia. containsIndentation ( isOnNewline: previousTokenWillEndWithNewline) || isInsideStringLiteral ,
499562 let anchorPointIndentation = self . anchorPointIndentation ( for: token)
500563 {
501564 trailingTriviaIndentation = anchorPointIndentation
@@ -504,18 +567,36 @@ open class BasicFormat: SyntaxRewriter {
504567 leadingTrivia = leadingTrivia. indented ( indentation: leadingTriviaIndentation, isOnNewline: previousTokenIsStringLiteralEndingInNewline)
505568 trailingTrivia = trailingTrivia. indented ( indentation: trailingTriviaIndentation, isOnNewline: false )
506569
507- leadingTrivia = leadingTrivia. trimmingTrailingWhitespaceBeforeNewline ( isBeforeNewline: false )
570+ leadingTrivia = leadingTrivia. trimmingTrailingWhitespaceBeforeNewline ( isBeforeNewline: leadingTriviaIsFollowedByNewline )
508571 trailingTrivia = trailingTrivia. trimmingTrailingWhitespaceBeforeNewline ( isBeforeNewline: nextTokenWillStartWithNewline)
509572
510- if leadingTrivia == token. leadingTrivia && trailingTrivia == token. trailingTrivia {
511- return token
573+ var result = token. detached
574+ if leadingTrivia != result. leadingTrivia {
575+ result = result. with ( \. leadingTrivia, leadingTrivia)
512576 }
513-
514- return token. detached. with ( \. leadingTrivia, leadingTrivia) . with ( \. trailingTrivia, trailingTrivia)
577+ if trailingTrivia != result. trailingTrivia {
578+ result = result. with ( \. trailingTrivia, trailingTrivia)
579+ }
580+ if let transformedTokenText {
581+ let newKind = TokenKind . fromRaw ( kind: token. tokenKind. decomposeToRaw ( ) . rawKind, text: transformedTokenText)
582+ result = result. with ( \. tokenKind, newKind) . with ( \. presence, . present)
583+ }
584+ if let transformedTokenPresence {
585+ result = result. with ( \. presence, transformedTokenPresence)
586+ }
587+ return result
515588 }
516589}
517590
518591fileprivate extension TokenSyntax {
592+ var isStringSegment : Bool {
593+ if case . stringSegment = self . tokenKind {
594+ return true
595+ } else {
596+ return false
597+ }
598+ }
599+
519600 var isStringSegmentWithLastCharacterBeingNewline : Bool {
520601 switch self . tokenKind {
521602 case . stringSegment( let segment) :
0 commit comments