Skip to content

Commit ca6d6b5

Browse files
committed
Add a function to merge trivia from a node into an existing trivia
Generally when a node is removed, we need to add its trivia to an existing node. Add a small helper function to aid in this case.
1 parent cb0f24e commit ca6d6b5

File tree

4 files changed

+76
-64
lines changed

4 files changed

+76
-64
lines changed

Sources/SwiftParserDiagnostics/DiagnosticExtensions.swift

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ extension FixIt.Changes {
142142
/// where it makes sense and refusing to add e.g. a space after punctuation,
143143
/// where it usually doesn't make sense.
144144
static func transferTriviaAtSides<SyntaxType: SyntaxProtocol>(from nodes: [SyntaxType]) -> Self {
145-
let removedTriviaAtSides = Trivia.merged(nodes.first?.leadingTrivia ?? [], nodes.last?.trailingTrivia ?? [])
145+
let removedTriviaAtSides = (nodes.first?.leadingTrivia ?? []).merging(nodes.last?.trailingTrivia ?? [])
146146
if !removedTriviaAtSides.isEmpty, let previousToken = nodes.first?.previousToken(viewMode: .sourceAccurate) {
147-
let mergedTrivia = Trivia.merged(previousToken.trailingTrivia, removedTriviaAtSides)
147+
let mergedTrivia = previousToken.trailingTrivia.merging(removedTriviaAtSides)
148148
if previousToken.tokenKind.isPunctuation, mergedTrivia.allSatisfy({ $0.isSpaceOrTab }) {
149149
// Punctuation is generally not followed by spaces in Swift.
150150
// If this action would only add spaces to the punctuation, drop it.
@@ -158,49 +158,6 @@ extension FixIt.Changes {
158158
}
159159
}
160160

161-
extension Trivia {
162-
/// Decomposes the trivia into pieces that all have count 1
163-
var decomposed: Trivia {
164-
let pieces = self.flatMap({ (piece: TriviaPiece) -> [TriviaPiece] in
165-
switch piece {
166-
case .spaces(let count):
167-
return Array(repeating: TriviaPiece.spaces(1), count: count)
168-
case .tabs(let count):
169-
return Array(repeating: TriviaPiece.tabs(1), count: count)
170-
case .verticalTabs(let count):
171-
return Array(repeating: TriviaPiece.verticalTabs(1), count: count)
172-
case .formfeeds(let count):
173-
return Array(repeating: TriviaPiece.formfeeds(1), count: count)
174-
case .newlines(let count):
175-
return Array(repeating: TriviaPiece.newlines(1), count: count)
176-
case .backslashes(let count):
177-
return Array(repeating: TriviaPiece.backslashes(1), count: count)
178-
case .pounds(let count):
179-
return Array(repeating: TriviaPiece.pounds(1), count: count)
180-
case .carriageReturns(let count):
181-
return Array(repeating: TriviaPiece.carriageReturns(1), count: count)
182-
case .carriageReturnLineFeeds(let count):
183-
return Array(repeating: TriviaPiece.carriageReturnLineFeeds(1), count: count)
184-
case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText, .shebang:
185-
return [piece]
186-
}
187-
})
188-
return Trivia(pieces: pieces)
189-
}
190-
191-
/// Concatenate `lhs` and `rhs`, merging an infix that is shared between both trivia pieces.
192-
static func merged(_ lhs: Trivia, _ rhs: Trivia) -> Self {
193-
let lhs = lhs.decomposed
194-
let rhs = rhs.decomposed
195-
for infixLength in (0...Swift.min(lhs.count, rhs.count)).reversed() {
196-
if lhs.suffix(infixLength) == rhs.suffix(infixLength) {
197-
return lhs + Trivia(pieces: Array(rhs.dropFirst(infixLength)))
198-
}
199-
}
200-
return lhs + rhs
201-
}
202-
}
203-
204161
extension TriviaPiece {
205162
var isSpaceOrTab: Bool {
206163
switch self {

Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import SwiftDiagnostics
14-
import SwiftSyntax
14+
@_spi(RawSyntax) import SwiftSyntax
1515

1616
/// A diagnostic that `MultiLineStringLiteralIndentatinDiagnosticsGenerator` is building.
1717
/// As indentation errors are found on more lines, this diagnostic is modified

Sources/SwiftSyntax/Trivia.swift

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,62 @@ public struct Trivia {
2525
self.pieces = Array(pieces)
2626
}
2727

28-
/// Creates Trivia with no pieces.
29-
public static var zero: Trivia {
30-
return Trivia(pieces: [])
31-
}
32-
3328
/// Whether the Trivia contains no pieces.
3429
public var isEmpty: Bool {
3530
pieces.isEmpty
3631
}
3732

33+
public var sourceLength: SourceLength {
34+
return pieces.map({ $0.sourceLength }).reduce(.zero, +)
35+
}
36+
37+
/// Get the byteSize of this trivia
38+
public var byteSize: Int {
39+
return sourceLength.utf8Length
40+
}
41+
3842
/// Creates a new `Trivia` by appending the provided `TriviaPiece` to the end.
3943
public func appending(_ piece: TriviaPiece) -> Trivia {
4044
var copy = pieces
4145
copy.append(piece)
4246
return Trivia(pieces: copy)
4347
}
4448

45-
public var sourceLength: SourceLength {
46-
return pieces.map({ $0.sourceLength }).reduce(.zero, +)
49+
/// Creates a new `Trivia` by appending the given trivia to the end.
50+
public func appending(_ trivia: Trivia) -> Trivia {
51+
var copy = pieces
52+
copy.append(contentsOf: trivia.pieces)
53+
return Trivia(pieces: copy)
4754
}
4855

49-
/// Get the byteSize of this trivia
50-
public var byteSize: Int {
51-
return sourceLength.utf8Length
56+
/// Creates a new `Trivia` by merging in the given trivia. Only includes one
57+
/// copy of a common prefix of `self` and `trivia`.
58+
public func merging(_ trivia: Trivia) -> Trivia {
59+
let lhs = self.decomposed
60+
let rhs = trivia.decomposed
61+
for infixLength in (0...Swift.min(lhs.count, rhs.count)).reversed() {
62+
if lhs.suffix(infixLength) == rhs.suffix(infixLength) {
63+
return lhs.appending(Trivia(pieces: Array(rhs.dropFirst(infixLength))))
64+
}
65+
}
66+
return lhs.appending(rhs)
67+
}
68+
69+
/// Creates a new `Trivia` by merging the leading and trailing `Trivia`
70+
/// of `triviaOf` into the end of `self`. Only includes one copy of any
71+
/// common prefixes.
72+
public func merging<T: SyntaxProtocol>(triviaOf node: T) -> Trivia {
73+
return merging(node.leadingTrivia).merging(node.trailingTrivia)
5274
}
5375

5476
/// Concatenates two collections of `Trivia` into one collection.
55-
public static func +(lhs: Trivia, rhs: Trivia) -> Trivia {
56-
return Trivia(pieces: lhs.pieces + rhs.pieces)
77+
public static func + (lhs: Trivia, rhs: Trivia) -> Trivia {
78+
return lhs.appending(rhs)
5779
}
5880

5981
/// Concatenates two collections of `Trivia` into the left-hand side.
60-
public static func +=(lhs: inout Trivia, rhs: Trivia) {
61-
lhs = lhs + rhs
82+
public static func += (lhs: inout Trivia, rhs: Trivia) {
83+
lhs = lhs.appending(rhs)
6284
}
6385
}
6486

@@ -118,13 +140,45 @@ extension Trivia: CustomDebugStringConvertible {
118140
}
119141
}
120142

143+
extension Trivia {
144+
/// Decomposes the trivia into pieces that all have count 1
145+
@_spi(RawSyntax)
146+
public var decomposed: Trivia {
147+
let pieces = self.flatMap({ (piece: TriviaPiece) -> [TriviaPiece] in
148+
switch piece {
149+
case .spaces(let count):
150+
return Array(repeating: TriviaPiece.spaces(1), count: count)
151+
case .tabs(let count):
152+
return Array(repeating: TriviaPiece.tabs(1), count: count)
153+
case .verticalTabs(let count):
154+
return Array(repeating: TriviaPiece.verticalTabs(1), count: count)
155+
case .formfeeds(let count):
156+
return Array(repeating: TriviaPiece.formfeeds(1), count: count)
157+
case .newlines(let count):
158+
return Array(repeating: TriviaPiece.newlines(1), count: count)
159+
case .backslashes(let count):
160+
return Array(repeating: TriviaPiece.backslashes(1), count: count)
161+
case .pounds(let count):
162+
return Array(repeating: TriviaPiece.pounds(1), count: count)
163+
case .carriageReturns(let count):
164+
return Array(repeating: TriviaPiece.carriageReturns(1), count: count)
165+
case .carriageReturnLineFeeds(let count):
166+
return Array(repeating: TriviaPiece.carriageReturnLineFeeds(1), count: count)
167+
case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText, .shebang:
168+
return [piece]
169+
}
170+
})
171+
return Trivia(pieces: pieces)
172+
}
173+
}
174+
121175
extension TriviaPiece {
122176
/// Returns true if the trivia is `.newlines`, `.carriageReturns` or `.carriageReturnLineFeeds`
123177
public var isNewline: Bool {
124178
switch self {
125179
case .newlines,
126-
.carriageReturns,
127-
.carriageReturnLineFeeds:
180+
.carriageReturns,
181+
.carriageReturnLineFeeds:
128182
return true
129183
default:
130184
return false
@@ -137,8 +191,8 @@ extension RawTriviaPiece {
137191
public var isNewline: Bool {
138192
switch self {
139193
case .newlines,
140-
.carriageReturns,
141-
.carriageReturnLineFeeds:
194+
.carriageReturns,
195+
.carriageReturnLineFeeds:
142196
return true
143197
default:
144198
return false

utils/group.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"TokenKind.swift",
1919
"SyntaxTreeViewMode.swift",
2020
"Trivia.swift",
21+
"TriviaPieces.swift",
2122
"Tokens.swift",
2223
],
2324
"Syntax nodes": [

0 commit comments

Comments
 (0)