Skip to content

Commit b953b57

Browse files
committed
FixItApplier: Eliminate quadratic compactMap
1 parent 9084149 commit b953b57

File tree

1 file changed

+32
-12
lines changed

1 file changed

+32
-12
lines changed

Sources/SwiftIDEUtils/FixItApplier.swift

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ public enum FixItApplier {
4646
return self.apply(edits: edits, to: tree)
4747
}
4848

49-
/// Apply the given edits to the syntax tree.
49+
/// Applies the given edits to the given syntax tree.
5050
///
5151
/// - Parameters:
52-
/// - edits: The edits to apply to the syntax tree
53-
/// - tree: he syntax tree to which the edits should be applied.
54-
/// - Returns: A `String` representation of the modified syntax tree after applying the edits.
52+
/// - edits: The edits to apply.
53+
/// - tree: The syntax tree to which the edits should be applied.
54+
/// - Returns: A `String` representation of the modified syntax tree.
5555
public static func apply(
5656
edits: [SourceEdit],
5757
to tree: some SyntaxProtocol
@@ -62,17 +62,27 @@ public enum FixItApplier {
6262
while let edit = edits.first {
6363
edits = Array(edits.dropFirst())
6464

65+
// Empty edits do nothing.
66+
guard !edit.isEmpty else {
67+
continue
68+
}
69+
6570
let startIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.startUtf8Offset)
6671
let endIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.endUtf8Offset)
6772

6873
source.replaceSubrange(startIndex..<endIndex, with: edit.replacement)
6974

70-
edits = edits.compactMap { remainingEdit -> SourceEdit? in
71-
if remainingEdit.range.overlaps(edit.range) {
75+
// Drop any subsequent edits that conflict with one we just applied, and
76+
// adjust the range of the rest.
77+
for i in edits.indices {
78+
let remainingEdit = edits[i]
79+
80+
guard !remainingEdit.range.overlaps(edit.range) else {
7281
// The edit overlaps with the previous edit. We can't apply both
73-
// without conflicts. Apply the one that's listed first and drop the
74-
// later edit.
75-
return nil
82+
// without conflicts. Drop this one by swapping it for a no-op
83+
// edit.
84+
edits[i] = SourceEdit()
85+
continue
7686
}
7787

7888
// If the remaining edit starts after or at the end of the edit that we just applied,
@@ -82,10 +92,8 @@ public enum FixItApplier {
8292
let startPosition = AbsolutePosition(utf8Offset: remainingEdit.startUtf8Offset + shift)
8393
let endPosition = AbsolutePosition(utf8Offset: remainingEdit.endUtf8Offset + shift)
8494

85-
return SourceEdit(range: startPosition..<endPosition, replacement: remainingEdit.replacement)
95+
edits[i] = SourceEdit(range: startPosition..<endPosition, replacement: remainingEdit.replacement)
8696
}
87-
88-
return remainingEdit
8997
}
9098
}
9199

@@ -101,4 +109,16 @@ private extension SourceEdit {
101109
var endUtf8Offset: Int {
102110
return range.upperBound.utf8Offset
103111
}
112+
113+
var isEmpty: Bool {
114+
self.range.isEmpty && self.replacement.isEmpty
115+
}
116+
117+
init() {
118+
self = SourceEdit(
119+
range: AbsolutePosition(utf8Offset: 0)..<AbsolutePosition(utf8Offset: 0),
120+
replacement: []
121+
)
122+
precondition(self.isEmpty)
123+
}
104124
}

0 commit comments

Comments
 (0)