Skip to content

Commit 201ee75

Browse files
committed
Replace the ReplaceTrivia rewriter with direct trivia mutations.
Previously, we were using a `SyntaxRewriter` to replace trivia on nodes. Historically, this originated from a time (I believe) when trivia wasn't directly mutable on nodes. The vast majority of these uses were replacing the extents: the leading trivia of the first token of a node or the trailing trivia of the last token. This can be done more easily now by just mutating those properties of the nodes. Even in cases where we were doing something slightly more advanced, it's clearer to do in-place mutation and reconstruction; having this specialized rewriter no longer held its weight.
1 parent 36372a7 commit 201ee75

13 files changed

+100
-186
lines changed

Sources/SwiftFormatRules/AddModifierRewriter.swift

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
2424
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
2525
// token.
2626
guard var modifiers = node.modifiers else {
27-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
28-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
27+
let result = setOnlyModifier(in: node, keywordKeypath: \.bindingSpecifier)
2928
return DeclSyntax(result)
3029
}
3130
// If variable already has an accessor keyword, skip (do not overwrite)
@@ -40,8 +39,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
4039
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
4140
// token.
4241
guard var modifiers = node.modifiers else {
43-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
44-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
42+
let result = setOnlyModifier(in: node, keywordKeypath: \.funcKeyword)
4543
return DeclSyntax(result)
4644
}
4745
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -53,8 +51,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
5351
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
5452
// token.
5553
guard var modifiers = node.modifiers else {
56-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
57-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
54+
let result = setOnlyModifier(in: node, keywordKeypath: \.associatedtypeKeyword)
5855
return DeclSyntax(result)
5956
}
6057
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -66,8 +63,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
6663
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
6764
// token.
6865
guard var modifiers = node.modifiers else {
69-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
70-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
66+
let result = setOnlyModifier(in: node, keywordKeypath: \.classKeyword)
7167
return DeclSyntax(result)
7268
}
7369
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -79,8 +75,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
7975
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
8076
// token.
8177
guard var modifiers = node.modifiers else {
82-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
83-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
78+
let result = setOnlyModifier(in: node, keywordKeypath: \.enumKeyword)
8479
return DeclSyntax(result)
8580
}
8681
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -92,8 +87,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
9287
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
9388
// token.
9489
guard var modifiers = node.modifiers else {
95-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
96-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
90+
let result = setOnlyModifier(in: node, keywordKeypath: \.protocolKeyword)
9791
return DeclSyntax(result)
9892
}
9993
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -105,8 +99,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
10599
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
106100
// token.
107101
guard var modifiers = node.modifiers else {
108-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
109-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
102+
let result = setOnlyModifier(in: node, keywordKeypath: \.structKeyword)
110103
return DeclSyntax(result)
111104
}
112105
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -118,8 +111,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
118111
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
119112
// token.
120113
guard var modifiers = node.modifiers else {
121-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
122-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
114+
let result = setOnlyModifier(in: node, keywordKeypath: \.typealiasKeyword)
123115
return DeclSyntax(result)
124116
}
125117
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -131,8 +123,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
131123
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
132124
// token.
133125
guard var modifiers = node.modifiers else {
134-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
135-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
126+
let result = setOnlyModifier(in: node, keywordKeypath: \.initKeyword)
136127
return DeclSyntax(result)
137128
}
138129
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -144,8 +135,7 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
144135
// Check for modifiers, and, if none, insert the modifier and relocate trivia from the displaced
145136
// token.
146137
guard var modifiers = node.modifiers else {
147-
let nodeWithModifier = node.with(\.modifiers, .init([modifierKeyword]))
148-
let result = nodeByRelocatingTrivia(in: nodeWithModifier) { $0.modifiers }
138+
let result = setOnlyModifier(in: node, keywordKeypath: \.subscriptKeyword)
149139
return DeclSyntax(result)
150140
}
151141
guard modifiers.accessLevelModifier == nil else { return DeclSyntax(node) }
@@ -161,24 +151,16 @@ fileprivate final class AddModifierRewriter: SyntaxRewriter {
161151
/// this method does nothing and returns the given node as-is.
162152
/// - Parameter node: A node that was updated to include a new modifier.
163153
/// - Parameter modifiersProvider: A closure that returns all modifiers for the given node.
164-
private func nodeByRelocatingTrivia<NodeType: DeclSyntaxProtocol>(
154+
private func setOnlyModifier<NodeType: DeclSyntaxProtocol & WithModifiersSyntax>(
165155
in node: NodeType,
166-
for modifiersProvider: (NodeType) -> DeclModifierListSyntax?
156+
keywordKeypath: WritableKeyPath<NodeType, TokenSyntax>
167157
) -> NodeType {
168-
guard let modifier = modifiersProvider(node)?.firstAndOnly,
169-
let movingLeadingTrivia = modifier.nextToken(viewMode: .sourceAccurate)?.leadingTrivia
170-
else {
171-
// Otherwise, there's no trivia that needs to be relocated so the node is fine.
172-
return node
173-
}
174-
let nodeWithTrivia = replaceTrivia(
175-
on: node,
176-
token: modifier.firstToken(viewMode: .sourceAccurate),
177-
leadingTrivia: movingLeadingTrivia)
178-
return replaceTrivia(
179-
on: nodeWithTrivia,
180-
token: modifiersProvider(nodeWithTrivia)?.first?.nextToken(viewMode: .sourceAccurate),
181-
leadingTrivia: [])
158+
var node = node
159+
var modifier = modifierKeyword
160+
modifier.leadingTrivia = node[keyPath: keywordKeypath].leadingTrivia
161+
node[keyPath: keywordKeypath].leadingTrivia = []
162+
node.modifiers = .init([modifier])
163+
return node
182164
}
183165
}
184166

Sources/SwiftFormatRules/DoNotUseSemicolons.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,11 @@ public final class DoNotUseSemicolons: SyntaxFormatRule {
5959
if previousHadSemicolon, let firstToken = newItem.firstToken(viewMode: .sourceAccurate),
6060
!firstToken.leadingTrivia.containsNewlines
6161
{
62-
let leadingTrivia = .newlines(1) + firstToken.leadingTrivia
63-
newItem = replaceTrivia(
64-
on: newItem,
65-
token: firstToken,
66-
leadingTrivia: leadingTrivia
67-
)
62+
newItem.leadingTrivia = .newlines(1) + firstToken.leadingTrivia
6863
}
6964

7065
// If there's a semicolon, diagnose and remove it.
7166
if let semicolon = item.semicolon {
72-
7367
// Exception: do not remove the semicolon if it is separating a 'do' statement from a
7468
// 'while' statement.
7569
if Syntax(item).as(CodeBlockItemSyntax.self)?

Sources/SwiftFormatRules/FullyIndirectEnum.swift

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ public final class FullyIndirectEnum: SyntaxFormatRule {
4545
}
4646

4747
let newCase = caseMember.with(\.modifiers, modifiers.remove(name: "indirect"))
48-
let formattedCase = formatCase(
49-
unformattedCase: newCase, leadingTrivia: firstModifier.leadingTrivia)
48+
let formattedCase = rearrangeLeadingTrivia(firstModifier.leadingTrivia, on: newCase)
5049
return member.with(\.decl, DeclSyntax(formattedCase))
5150
}
5251

@@ -55,15 +54,13 @@ public final class FullyIndirectEnum: SyntaxFormatRule {
5554
// line breaks/comments/indentation.
5655
let firstTok = node.firstToken(viewMode: .sourceAccurate)!
5756
let leadingTrivia: Trivia
58-
let newEnumDecl: EnumDeclSyntax
57+
var newEnumDecl = node
5958

6059
if firstTok.tokenKind == .keyword(.enum) {
6160
leadingTrivia = firstTok.leadingTrivia
62-
newEnumDecl = replaceTrivia(
63-
on: node, token: node.firstToken(viewMode: .sourceAccurate), leadingTrivia: [])
61+
newEnumDecl.leadingTrivia = []
6462
} else {
6563
leadingTrivia = []
66-
newEnumDecl = node
6764
}
6865

6966
let newModifier = DeclModifierSyntax(
@@ -94,19 +91,23 @@ public final class FullyIndirectEnum: SyntaxFormatRule {
9491
}
9592

9693
/// Transfers given leading trivia to the first token in the case declaration.
97-
private func formatCase(
98-
unformattedCase: EnumCaseDeclSyntax,
99-
leadingTrivia: Trivia?
94+
private func rearrangeLeadingTrivia(
95+
_ leadingTrivia: Trivia,
96+
on enumCaseDecl: EnumCaseDeclSyntax
10097
) -> EnumCaseDeclSyntax {
101-
if let modifiers = unformattedCase.modifiers, let first = modifiers.first {
102-
return replaceTrivia(
103-
on: unformattedCase, token: first.firstToken(viewMode: .sourceAccurate), leadingTrivia: leadingTrivia
104-
)
98+
var formattedCase = enumCaseDecl
99+
100+
if var modifiers = formattedCase.modifiers, var firstModifier = modifiers.first {
101+
// If the case has modifiers, attach the leading trivia to the first one.
102+
firstModifier.leadingTrivia = leadingTrivia
103+
modifiers[modifiers.startIndex] = firstModifier
104+
formattedCase.modifiers = modifiers
105105
} else {
106-
return replaceTrivia(
107-
on: unformattedCase, token: unformattedCase.caseKeyword, leadingTrivia: leadingTrivia
108-
)
106+
// Otherwise, attach the trivia to the `case` keyword itself.
107+
formattedCase.caseKeyword.leadingTrivia = leadingTrivia
109108
}
109+
110+
return formattedCase
110111
}
111112
}
112113

Sources/SwiftFormatRules/ModifierListSyntax+Convenience.swift

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,22 @@ extension DeclModifierListSyntax {
5454
mutating func triviaPreservingInsert(
5555
_ modifier: DeclModifierSyntax, at index: SyntaxChildrenIndex
5656
) {
57-
let modifier = replaceTrivia(
58-
on: modifier,
59-
token: modifier.name,
60-
trailingTrivia: .spaces(1))
57+
var modifier = modifier
58+
modifier.trailingTrivia = [.spaces(1)]
6159

6260
guard index == self.startIndex else {
6361
self.insert(modifier, at: index)
6462
return
6563
}
66-
guard let firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else {
64+
guard var firstMod = first, let firstTok = firstMod.firstToken(viewMode: .sourceAccurate) else {
6765
self.insert(modifier, at: index)
6866
return
6967
}
7068

71-
let formattedMod = replaceTrivia(
72-
on: modifier,
73-
token: modifier.firstToken(viewMode: .sourceAccurate),
74-
leadingTrivia: firstTok.leadingTrivia)
75-
self[self.startIndex] = replaceTrivia(
76-
on: firstMod,
77-
token: firstTok,
78-
leadingTrivia: [],
79-
trailingTrivia: .spaces(1))
80-
self.insert(formattedMod, at: self.startIndex)
69+
modifier.leadingTrivia = firstTok.leadingTrivia
70+
firstMod.leadingTrivia = []
71+
firstMod.trailingTrivia = [.spaces(1)]
72+
self[self.startIndex] = firstMod
73+
self.insert(modifier, at: self.startIndex)
8174
}
8275
}

Sources/SwiftFormatRules/NoAccessLevelOnExtensionDeclaration.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
4848
leftBrace: node.memberBlock.leftBrace,
4949
members: addMemberAccessKeywords(memDeclBlock: node.memberBlock, keyword: accessKeywordToAdd),
5050
rightBrace: node.memberBlock.rightBrace)
51-
let newKeyword = replaceTrivia(
52-
on: node.extensionKeyword,
53-
token: node.extensionKeyword,
54-
leadingTrivia: accessKeyword.leadingTrivia)
51+
var newKeyword = node.extensionKeyword
52+
newKeyword.leadingTrivia = accessKeyword.leadingTrivia
5553
let result = node.with(\.memberBlock, newMembers)
5654
.with(\.modifiers, modifiers.remove(name: accessKeyword.name.text))
5755
.with(\.extensionKeyword, newKeyword)
@@ -62,10 +60,8 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
6260
diagnose(
6361
.removeRedundantAccessKeyword(name: node.extendedType.description),
6462
on: accessKeyword)
65-
let newKeyword = replaceTrivia(
66-
on: node.extensionKeyword,
67-
token: node.extensionKeyword,
68-
leadingTrivia: accessKeyword.leadingTrivia)
63+
var newKeyword = node.extensionKeyword
64+
newKeyword.leadingTrivia = accessKeyword.leadingTrivia
6965
let result = node.with(\.modifiers, modifiers.remove(name: accessKeyword.name.text))
7066
.with(\.extensionKeyword, newKeyword)
7167
return DeclSyntax(result)
@@ -82,10 +78,9 @@ public final class NoAccessLevelOnExtensionDeclaration: SyntaxFormatRule {
8278
keyword: DeclModifierSyntax
8379
) -> MemberBlockItemListSyntax {
8480
var newMembers: [MemberBlockItemSyntax] = []
85-
let formattedKeyword = replaceTrivia(
86-
on: keyword,
87-
token: keyword.name,
88-
leadingTrivia: [])
81+
82+
var formattedKeyword = keyword
83+
formattedKeyword.leadingTrivia = []
8984

9085
for memberItem in memDeclBlock.members {
9186
let member = memberItem.decl

Sources/SwiftFormatRules/NoEmptyTrailingClosureParentheses.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,24 @@ public final class NoEmptyTrailingClosureParentheses: SyntaxFormatRule {
2929
{
3030
return super.visit(node)
3131
}
32-
guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate)?.with(\.leadingTrivia, []).with(\.trailingTrivia, []) else {
32+
guard let name = node.calledExpression.lastToken(viewMode: .sourceAccurate) else {
3333
return super.visit(node)
3434
}
3535

36-
diagnose(.removeEmptyTrailingParentheses(name: "\(name)"), on: node)
36+
diagnose(.removeEmptyTrailingParentheses(name: "\(name.trimmedDescription)"), on: node)
3737

3838
// Need to visit `calledExpression` before creating a new node so that the location data (column
3939
// and line numbers) is available.
40-
guard let rewrittenCalledExpr = ExprSyntax(rewrite(Syntax(node.calledExpression))) else {
40+
guard var rewrittenCalledExpr = ExprSyntax(rewrite(Syntax(node.calledExpression))) else {
4141
return super.visit(node)
4242
}
43-
let formattedExp = replaceTrivia(
44-
on: rewrittenCalledExpr,
45-
token: rewrittenCalledExpr.lastToken(viewMode: .sourceAccurate),
46-
trailingTrivia: .spaces(1))
47-
let formattedClosure = visit(trailingClosure).as(ClosureExprSyntax.self)
48-
let result = node.with(\.leftParen, nil).with(\.rightParen, nil).with(\.calledExpression, formattedExp)
49-
.with(\.trailingClosure, formattedClosure)
43+
rewrittenCalledExpr.trailingTrivia = [.spaces(1)]
44+
45+
var result = node
46+
result.leftParen = nil
47+
result.rightParen = nil
48+
result.calledExpression = rewrittenCalledExpr
49+
result.trailingClosure = rewrite(trailingClosure).as(ClosureExprSyntax.self)
5050
return ExprSyntax(result)
5151
}
5252
}

Sources/SwiftFormatRules/NoParensAroundConditions.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,13 @@ public final class NoParensAroundConditions: SyntaxFormatRule {
4646

4747
guard
4848
let visitedTuple = visit(tuple).as(TupleExprSyntax.self),
49-
let visitedExpr = visitedTuple.elements.first?.expression
49+
var visitedExpr = visitedTuple.elements.first?.expression
5050
else {
5151
return expr
5252
}
53-
return replaceTrivia(
54-
on: visitedExpr,
55-
token: visitedExpr.lastToken(viewMode: .sourceAccurate),
56-
leadingTrivia: visitedTuple.leftParen.leadingTrivia,
57-
trailingTrivia: visitedTuple.rightParen.trailingTrivia
58-
)
53+
visitedExpr.leadingTrivia = visitedTuple.leftParen.leadingTrivia
54+
visitedExpr.trailingTrivia = visitedTuple.rightParen.trailingTrivia
55+
return visitedExpr
5956
}
6057

6158
public override func visit(_ node: IfExprSyntax) -> ExprSyntax {

Sources/SwiftFormatRules/OneVariableDeclarationPerLine.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,7 @@ private struct VariableDeclSplitter<Node: SyntaxProtocol> {
163163
// We intentionally don't try to infer the indentation for subsequent
164164
// lines because the pretty printer will re-indent them correctly; we just
165165
// need to ensure that a newline is inserted before new decls.
166-
varDecl = replaceTrivia(
167-
on: varDecl, token: varDecl.firstToken(viewMode: .sourceAccurate), leadingTrivia: .newlines(1))
166+
varDecl.leadingTrivia = [.newlines(1)]
168167
fixedUpTrivia = true
169168
}
170169

Sources/SwiftFormatRules/OrderedImports.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,10 @@ fileprivate func convertToCodeBlockItems(lines: [Line]) -> [CodeBlockItemSyntax]
359359
func append(codeBlockItem: CodeBlockItemSyntax) {
360360
// Comments and newlines are always located in the leading trivia of an AST node, so we need
361361
// not deal with trailing trivia.
362-
output.append(
363-
replaceTrivia(
364-
on: codeBlockItem,
365-
token: codeBlockItem.firstToken(viewMode: .sourceAccurate),
366-
leadingTrivia: Trivia(pieces: triviaBuffer)
367-
)
368-
)
362+
var codeBlockItem = codeBlockItem
363+
codeBlockItem.leadingTrivia = Trivia(pieces: triviaBuffer)
364+
output.append(codeBlockItem)
365+
369366
triviaBuffer = []
370367
triviaBuffer += line.trailingTrivia
371368
}

0 commit comments

Comments
 (0)