Skip to content

Commit c5183b1

Browse files
authored
Merge pull request #588 from allevato/replace-trivia
Replace the `ReplaceTrivia` rewriter with direct trivia mutations.
2 parents 36372a7 + 201ee75 commit c5183b1

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)