Skip to content

Commit c5c4dac

Browse files
committed
Workaround new trivia behavior in SwiftSyntax.
As of swiftlang/swift-syntax#985, the parser treats all trivia following a token up to the next line break as trailing trivia, including comments. This is a change from the former logic, which treated such comments as leading trivia of the following token. A great deal of the logic in swift-format was written in terms of that behavior, and attempts to rewrite it to use the new behavior instead have been difficult to get right in all cases. In the interest of expediency, I'm merging this workaround that shifts the trivia around to restore the old tree that we expected. This shouldn't be considered a long-term fix; we should eventually fix the misbehavior with the new layout and drop the extra mutation.
1 parent fd10c65 commit c5c4dac

File tree

6 files changed

+67
-12
lines changed

6 files changed

+67
-12
lines changed

Sources/SwiftFormat/Parsing.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import Foundation
1414
import SwiftDiagnostics
15+
import SwiftFormatCore
1516
import SwiftOperators
1617
import SwiftParser
1718
import SwiftSyntax
@@ -54,5 +55,5 @@ func parseAndEmitDiagnostics(
5455
throw SwiftFormatError.fileContainsInvalidSyntax
5556
}
5657

57-
return sourceFile
58+
return restoringLegacyTriviaBehavior(sourceFile)
5859
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import SwiftSyntax
2+
3+
/// Rewrites the trivia on tokens in the given source file to restore the legacy trivia behavior
4+
/// before https://github.com/apple/swift-syntax/pull/985 was merged.
5+
///
6+
/// Eventually we should get rid of this and update the core formatting code to adjust to the new
7+
/// behavior, but this workaround lets us keep the current implementation without larger changes.
8+
public func restoringLegacyTriviaBehavior(_ sourceFile: SourceFileSyntax) -> SourceFileSyntax {
9+
return LegacyTriviaBehaviorRewriter().visit(sourceFile).as(SourceFileSyntax.self)!
10+
}
11+
12+
private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter {
13+
/// Trivia that was extracted from the trailing trivia of a token to be prepended to the leading
14+
/// trivia of the next token.
15+
private var pendingLeadingTrivia: Trivia?
16+
17+
override func visit(_ token: TokenSyntax) -> TokenSyntax {
18+
var token = token
19+
if let pendingLeadingTrivia = pendingLeadingTrivia {
20+
token = token.withLeadingTrivia(pendingLeadingTrivia + token.leadingTrivia)
21+
self.pendingLeadingTrivia = nil
22+
}
23+
if token.nextToken != nil,
24+
let firstIndexToMove = token.trailingTrivia.firstIndex(where: shouldTriviaPieceBeMoved)
25+
{
26+
pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...]))
27+
token =
28+
token.withTrailingTrivia(Trivia(pieces: Array(token.trailingTrivia[..<firstIndexToMove])))
29+
}
30+
return token
31+
}
32+
}
33+
34+
/// Returns a value indicating whether the given trivia piece should be moved from a token's
35+
/// trailing trivia to the leading trivia of the following token to restore the legacy trivia
36+
/// behavior.
37+
private func shouldTriviaPieceBeMoved(_ piece: TriviaPiece) -> Bool {
38+
switch piece {
39+
case .spaces, .tabs, .unexpectedText:
40+
return false
41+
default:
42+
return true
43+
}
44+
}

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,15 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
12491249
return .visitChildren
12501250
}
12511251

1252+
override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind {
1253+
arrangeFunctionCallArgumentList(
1254+
node.argumentList,
1255+
leftDelimiter: node.leftParen,
1256+
rightDelimiter: node.rightParen,
1257+
forcesBreakBeforeRightDelimiter: false)
1258+
return .visitChildren
1259+
}
1260+
12521261
override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind {
12531262
// Prioritize keeping ") throws -> <return_type>" together. We can only do this if the function
12541263
// has arguments.
@@ -3595,11 +3604,11 @@ class CommentMovingRewriter: SyntaxRewriter {
35953604
return super.visit(node)
35963605
}
35973606

3598-
override func visit(_ token: TokenSyntax) -> Syntax {
3607+
override func visit(_ token: TokenSyntax) -> TokenSyntax {
35993608
if let rewrittenTrivia = rewriteTokenTriviaMap[token] {
3600-
return Syntax(token.withLeadingTrivia(rewrittenTrivia))
3609+
return token.withLeadingTrivia(rewrittenTrivia)
36013610
}
3602-
return Syntax(token)
3611+
return token
36033612
}
36043613

36053614
override func visit(_ node: InfixOperatorExprSyntax) -> ExprSyntax {

Sources/SwiftFormatRules/ReplaceTrivia.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ fileprivate final class ReplaceTrivia: SyntaxRewriter {
2626
self.trailingTrivia = trailingTrivia
2727
}
2828

29-
override func visit(_ token: TokenSyntax) -> Syntax {
30-
guard token == self.token else { return Syntax(token) }
31-
let newNode = token.withLeadingTrivia(leadingTrivia ?? token.leadingTrivia)
29+
override func visit(_ token: TokenSyntax) -> TokenSyntax {
30+
guard token == self.token else { return token }
31+
return token
32+
.withLeadingTrivia(leadingTrivia ?? token.leadingTrivia)
3233
.withTrailingTrivia(trailingTrivia ?? token.trailingTrivia)
33-
return Syntax(newNode)
3434
}
3535
}
3636

Tests/SwiftFormatPrettyPrintTests/PrettyPrintTestCase.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ class PrettyPrintTestCase: DiagnosingTestCase {
6767
) -> String? {
6868
// Ignore folding errors for unrecognized operators so that we fallback to a reasonable default.
6969
let sourceFileSyntax =
70-
OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in }
71-
.as(SourceFileSyntax.self)!
70+
restoringLegacyTriviaBehavior(
71+
OperatorTable.standardOperators.foldAll(Parser.parse(source: source)) { _ in }
72+
.as(SourceFileSyntax.self)!)
7273
let context = makeContext(sourceFileSyntax: sourceFileSyntax, configuration: configuration)
7374
let printer = PrettyPrinter(
7475
context: context,

Tests/SwiftFormatRulesTests/LintOrFormatRuleTestCase.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
1919
file: StaticString = #file,
2020
line: UInt = #line
2121
) {
22-
let sourceFileSyntax = Parser.parse(source: input)
22+
let sourceFileSyntax = restoringLegacyTriviaBehavior(Parser.parse(source: input))
2323

2424
// Force the rule to be enabled while we test it.
2525
var configuration = Configuration()
@@ -56,7 +56,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
5656
file: StaticString = #file,
5757
line: UInt = #line
5858
) {
59-
let sourceFileSyntax = Parser.parse(source: input)
59+
let sourceFileSyntax = restoringLegacyTriviaBehavior(Parser.parse(source: input))
6060

6161
// Force the rule to be enabled while we test it.
6262
var configuration = configuration ?? Configuration()

0 commit comments

Comments
 (0)