@@ -143,35 +143,50 @@ open class BasicFormat: SyntaxRewriter {
143143 return node. requiresIndent
144144 }
145145
146- /// Whether a leading newline on `token` should be added.
147- open func requiresLeadingNewline( _ token: TokenSyntax ) -> Bool {
148- // We don't want to add newlines inside string interpolation
149- if isInsideStringInterpolation ( token) {
146+ open func requiresNewline( between first: TokenSyntax ? , and second: TokenSyntax ? ) -> Bool {
147+ // We don't want to add newlines inside string interpolation.
148+ // When first or second `TokenSyntax` is a multiline quote we want special handling
149+ // even if it's inside a string interpolation, because it still requires newline
150+ // after open quote and before close quote.
151+ if let first,
152+ isInsideStringInterpolation ( first) ,
153+ first. tokenKind != . multilineStringQuote,
154+ second? . tokenKind != . multilineStringQuote
155+ {
150156 return false
151- }
152-
153- if token. requiresLeadingNewline {
154- return true
155- }
156-
157- var ancestor : Syntax = Syntax ( token)
158- while let parent = ancestor. parent {
159- ancestor = parent
160- if ancestor. position != token. position || ancestor. firstToken ( viewMode: viewMode) != token {
161- break
162- }
163- if let ancestorsParent = ancestor. parent, childrenSeparatedByNewline ( ancestorsParent) {
157+ } else if let second {
158+ if second. requiresLeadingNewline {
164159 return true
165160 }
166- switch ancestor. keyPathInParent {
167- case \IfConfigClauseSyntax . elements:
168- return true
169- default :
170- break
161+
162+ var ancestor : Syntax = Syntax ( second)
163+ while let parent = ancestor. parent {
164+ ancestor = parent
165+ if ancestor. position != second. position || ancestor. firstToken ( viewMode: viewMode) != second {
166+ break
167+ }
168+ if let ancestorsParent = ancestor. parent, childrenSeparatedByNewline ( ancestorsParent) {
169+ return true
170+ }
171+ switch ancestor. keyPathInParent {
172+ case \IfConfigClauseSyntax . elements:
173+ return true
174+ default :
175+ break
176+ }
171177 }
172178 }
173179
174- return false
180+ switch ( first? . tokenKind, second? . tokenKind) {
181+ case ( . multilineStringQuote, . backslash) , // string interpolation segment inside a multi-line string literal
182+ ( . multilineStringQuote, . multilineStringQuote) , // empty multi-line string literal
183+ ( . multilineStringQuote, . stringSegment) , // segment starting a multi-line string literal
184+ ( . stringSegment, . multilineStringQuote) , // ending a multi-line string literal that has a string interpolation segment at its end
185+ ( . rightParen, . multilineStringQuote) : // ending a multi-line string literal that has a string interpolation segment at its end
186+ return true
187+ default :
188+ return false
189+ }
175190 }
176191
177192 open func requiresWhitespace( between first: TokenSyntax ? , and second: TokenSyntax ? ) -> Bool {
@@ -276,6 +291,12 @@ open class BasicFormat: SyntaxRewriter {
276291 let previousToken = self . previousToken ?? token. previousToken ( viewMode: viewMode)
277292 let nextToken = token. nextToken ( viewMode: viewMode)
278293
294+ /// In addition to existing trivia of `previousToken`, also considers
295+ /// `previousToken` as ending with whitespace if it and `token` should be
296+ /// separated by whitespace.
297+ /// It does not consider whether a newline should be added between
298+ /// `previousToken` and the `token` because that newline should be added to
299+ /// the next token's trailing trivia.
279300 lazy var previousTokenWillEndWithWhitespace : Bool = {
280301 guard let previousToken = previousToken else {
281302 return false
@@ -284,6 +305,8 @@ open class BasicFormat: SyntaxRewriter {
284305 || ( requiresWhitespace ( between: previousToken, and: token) && isMutable ( previousToken) )
285306 } ( )
286307
308+ /// This method does not consider any posssible mutations to `previousToken`
309+ /// because newlines should be added to the next token's leading trivia.
287310 lazy var previousTokenWillEndWithNewline : Bool = {
288311 guard let previousToken = previousToken else {
289312 // Assume that the start of the tree is equivalent to a newline so we
@@ -293,10 +316,7 @@ open class BasicFormat: SyntaxRewriter {
293316 if previousToken. trailingTrivia. endsWithNewline {
294317 return true
295318 }
296- if case . stringSegment( let segment) = previousToken. tokenKind, segment. last? . isNewline ?? false {
297- return true
298- }
299- return false
319+ return previousToken. isStringSegmentWithLastCharacterBeingNewline
300320 } ( )
301321
302322 lazy var previousTokenIsStringLiteralEndingInNewline : Bool = {
@@ -305,26 +325,29 @@ open class BasicFormat: SyntaxRewriter {
305325 // don't add a leading newline to the file.
306326 return true
307327 }
308- if case . stringSegment( let segment) = previousToken. tokenKind, segment. last? . isNewline ?? false {
309- return true
310- }
311- return false
328+ return previousToken. isStringSegmentWithLastCharacterBeingNewline
312329 } ( )
313330
331+ /// Also considers `nextToken` as starting with a whitespace if a newline
332+ /// should be added to it. It does not check whether `token` and `nextToken`
333+ /// should be separated by whitespace because the whitespace should be added
334+ /// to the `token`’s leading trivia.
314335 lazy var nextTokenWillStartWithWhitespace : Bool = {
315336 guard let nextToken = nextToken else {
316337 return false
317338 }
318339 return nextToken. leadingTrivia. startsWithWhitespace
319- || ( requiresLeadingNewline ( nextToken) && isMutable ( nextToken) )
340+ || ( requiresNewline ( between : token , and : nextToken) && isMutable ( nextToken) )
320341 } ( )
321342
343+ /// Also considers `nextToken` as starting with a leading newline if `token`
344+ /// and `nextToken` should be separated by a newline.
322345 lazy var nextTokenWillStartWithNewline : Bool = {
323346 guard let nextToken = nextToken else {
324347 return false
325348 }
326349 return nextToken. leadingTrivia. startsWithNewline
327- || ( requiresLeadingNewline ( nextToken) && isMutable ( nextToken) )
350+ || ( requiresNewline ( between : token , and : nextToken) && isMutable ( nextToken) && !token . trailingTrivia . endsWithNewline && !token . isStringSegmentWithLastCharacterBeingNewline )
328351 } ( )
329352
330353 /// This token's trailing trivia + any spaces or tabs at the start of the
@@ -337,7 +360,7 @@ open class BasicFormat: SyntaxRewriter {
337360 var leadingTrivia = token. leadingTrivia
338361 var trailingTrivia = token. trailingTrivia
339362
340- if requiresLeadingNewline ( token) {
363+ if requiresNewline ( between : previousToken , and : token) {
341364 // Add a leading newline if the token requires it unless
342365 // - it already starts with a newline or
343366 // - the previous token ends with a newline
@@ -402,3 +425,14 @@ open class BasicFormat: SyntaxRewriter {
402425 return token. detach ( ) . with ( \. leadingTrivia, leadingTrivia) . with ( \. trailingTrivia, trailingTrivia)
403426 }
404427}
428+
429+ fileprivate extension TokenSyntax {
430+ var isStringSegmentWithLastCharacterBeingNewline : Bool {
431+ switch self . tokenKind {
432+ case . stringSegment( let segment) :
433+ return segment. last? . isNewline ?? false
434+ default :
435+ return false
436+ }
437+ }
438+ }
0 commit comments