Skip to content

Commit a84f21d

Browse files
authored
Merge pull request #2941 from rintaro/swiftdiags-replace-trivia
[Diagnostics] Fix and adjust FixIt application
2 parents d83584a + edde9b6 commit a84f21d

File tree

4 files changed

+49
-10
lines changed

4 files changed

+49
-10
lines changed

Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,37 @@ extension PluginMessage.Diagnostic {
110110
let text: String
111111
switch $0 {
112112
case .replace(let oldNode, let newNode):
113+
// Replace the whole node including leading/trailing trivia, but if
114+
// the trivia are the same, don't include them in the replacing range.
115+
let leadingMatch = oldNode.leadingTrivia == newNode.leadingTrivia
116+
let trailingMatch = oldNode.trailingTrivia == newNode.trailingTrivia
113117
range = sourceManager.range(
114118
of: oldNode,
115-
from: .afterLeadingTrivia,
116-
to: .beforeTrailingTrivia
119+
from: leadingMatch ? .afterLeadingTrivia : .beforeLeadingTrivia,
120+
to: trailingMatch ? .beforeTrailingTrivia : .afterTrailingTrivia
117121
)
118-
text = newNode.trimmedDescription
122+
var newNode = newNode.detached
123+
if leadingMatch {
124+
newNode.leadingTrivia = []
125+
}
126+
if trailingMatch {
127+
newNode.trailingTrivia = []
128+
}
129+
text = newNode.description
119130
case .replaceLeadingTrivia(let token, let newTrivia):
131+
guard token.leadingTrivia != newTrivia else {
132+
return nil
133+
}
120134
range = sourceManager.range(
121135
of: Syntax(token),
122136
from: .beforeLeadingTrivia,
123137
to: .afterLeadingTrivia
124138
)
125139
text = newTrivia.description
126140
case .replaceTrailingTrivia(let token, let newTrivia):
141+
guard token.trailingTrivia != newTrivia else {
142+
return nil
143+
}
127144
range = sourceManager.range(
128145
of: Syntax(token),
129146
from: .beforeTrailingTrivia,

Sources/SwiftDiagnostics/FixIt.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ extension FixIt {
9797
public var edits: [SourceEdit] {
9898
var existingEdits = [SourceEdit]()
9999
for change in changes {
100-
let edit = change.edit
100+
guard let edit = change.edit else {
101+
continue
102+
}
101103
let isOverlapping = existingEdits.contains { edit.range.overlaps($0.range) }
102104
if !isOverlapping {
103105
// The edit overlaps with the previous edit. We can't apply both
@@ -111,21 +113,40 @@ extension FixIt {
111113
}
112114

113115
private extension FixIt.Change {
114-
var edit: SourceEdit {
116+
var edit: SourceEdit? {
115117
switch self {
116118
case .replace(let oldNode, let newNode):
119+
// Replace the whole node including leading/trailing trivia, but if
120+
// the trivia are the same, don't include them in the replacing range.
121+
let leadingMatch = oldNode.leadingTrivia == newNode.leadingTrivia
122+
let trailingMatch = oldNode.trailingTrivia == newNode.trailingTrivia
123+
let start = leadingMatch ? oldNode.positionAfterSkippingLeadingTrivia : oldNode.position
124+
let end = trailingMatch ? oldNode.endPositionBeforeTrailingTrivia : oldNode.endPosition
125+
var newNode = newNode.detached
126+
if leadingMatch {
127+
newNode.leadingTrivia = []
128+
}
129+
if trailingMatch {
130+
newNode.trailingTrivia = []
131+
}
117132
return SourceEdit(
118-
range: oldNode.position..<oldNode.endPosition,
133+
range: start..<end,
119134
replacement: newNode.description
120135
)
121136

122137
case .replaceLeadingTrivia(let token, let newTrivia):
138+
guard token.leadingTrivia != newTrivia else {
139+
return nil
140+
}
123141
return SourceEdit(
124142
range: token.position..<token.positionAfterSkippingLeadingTrivia,
125143
replacement: newTrivia.description
126144
)
127145

128146
case .replaceTrailingTrivia(let token, let newTrivia):
147+
guard token.trailingTrivia != newTrivia else {
148+
return nil
149+
}
129150
return SourceEdit(
130151
range: token.endPositionBeforeTrailingTrivia..<token.endPosition,
131152
replacement: newTrivia.description

Sources/SwiftIDEUtils/FixItApplier.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public enum FixItApplier {
3636
) -> String {
3737
let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message }
3838

39+
// FIXME: This assumes every fix-it is applied to a node in the 'tree', which is not guaranteed.
3940
let edits =
4041
diagnostics
4142
.flatMap(\.fixIts)

Tests/SwiftDiagnosticsTest/FixItTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ import _SwiftSyntaxTestSupport
1818

1919
final class FixItTests: XCTestCase {
2020
func testEditsForFixIt() throws {
21-
let markedSource = "protocol 1️⃣Multi 2️⃣ident 3️⃣{}"
21+
let markedSource = "protocol 1️⃣Multi2️⃣ 3️⃣ident 4️⃣{}"
2222
let (markers, source) = extractMarkers(markedSource)
2323
let positions = markers.mapValues { AbsolutePosition(utf8Offset: $0) }
24-
XCTAssertEqual(positions.count, 3)
24+
XCTAssertEqual(positions.count, 4)
2525

2626
let expectedEdits = [
27-
SourceEdit(range: positions["1️⃣"]!..<positions["2️⃣"]!, replacement: "Multiident "),
28-
SourceEdit(range: positions["2️⃣"]!..<positions["3️⃣"]!, replacement: ""),
27+
SourceEdit(range: positions["1️⃣"]!..<positions["2️⃣"]!, replacement: "Multiident"),
28+
SourceEdit(range: positions["3️⃣"]!..<positions["4️⃣"]!, replacement: ""),
2929
]
3030
let tree = Parser.parse(source: source)
3131
let diags = ParseDiagnosticsGenerator.diagnostics(for: tree)

0 commit comments

Comments
 (0)