10
10
//
11
11
//===----------------------------------------------------------------------===//
12
12
13
- import SwiftSyntax
14
13
import SwiftDiagnostics
14
+ import SwiftSyntax
15
15
16
- public class FixItApplier : SyntaxRewriter {
16
+ public class FixItApplier {
17
17
fileprivate struct Edit {
18
18
let startUtf8Offset : Int
19
19
let endUtf8Offset : Int
20
20
let replacement : String
21
21
}
22
22
23
- var changes : [ FixIt . Change ]
23
+ private let changes : [ FixIt . Change ]
24
+ private let tree : any SyntaxProtocol
24
25
25
- private var locationConverter : SourceLocationConverter ?
26
- private var edits : [ Edit ] = [ ]
26
+ private lazy var locationConverter : SourceLocationConverter = {
27
+ return SourceLocationConverter ( fileName: " " , tree: tree)
28
+ } ( )
27
29
28
- init ( diagnostics: [ Diagnostic ] , withMessages messages: [ String ] ? ) {
30
+ init ( diagnostics: [ Diagnostic ] , withMessages messages: [ String ] ? , tree : any SyntaxProtocol ) {
29
31
let messages = messages ?? diagnostics. compactMap { $0. fixIts. first? . message. message }
30
32
31
33
self . changes =
@@ -36,51 +38,70 @@ public class FixItApplier: SyntaxRewriter {
36
38
}
37
39
. flatMap { $0. changes }
38
40
39
- super . init ( viewMode : . all )
41
+ self . tree = tree
40
42
}
41
43
42
- public override func visitPre( _ node: Syntax ) {
43
- if locationConverter == nil && !node. hasParent {
44
- locationConverter = SourceLocationConverter ( fileName: " " , tree: node)
44
+ func rewrite( ) -> String {
45
+ var edits : [ Edit ] = [ ]
45
46
46
- for change in self . changes {
47
- switch change {
48
- case . replace( oldNode: let oldNode, newNode: let newNode) :
49
- let oldStartLocation = oldNode. startLocation ( converter: locationConverter!)
50
- let oldEndLocation = oldNode. startLocation ( converter: locationConverter!)
47
+ for change in changes {
48
+ switch change {
49
+ case . replace( let oldNode, let newNode) :
50
+ edits. append (
51
+ Edit (
52
+ startUtf8Offset: oldNode. position. utf8Offset,
53
+ endUtf8Offset: oldNode. endPosition. utf8Offset,
54
+ replacement: newNode. description
55
+ )
56
+ )
51
57
52
- edits. append ( Edit ( startUtf8Offset: oldStartLocation. offset, endUtf8Offset: oldEndLocation. offset, replacement: newNode. description) )
58
+ case . replaceLeadingTrivia( let token, let newTrivia) :
59
+ edits. append (
60
+ Edit (
61
+ startUtf8Offset: token. position. utf8Offset,
62
+ endUtf8Offset: token. endPosition. utf8Offset,
63
+ replacement: token. with ( \. leadingTrivia, newTrivia) . description
64
+ )
65
+ )
53
66
54
- default :
55
- break
56
- }
67
+ case . replaceTrailingTrivia( let token, let newTrivia) :
68
+ edits. append (
69
+ Edit (
70
+ startUtf8Offset: token. position. utf8Offset,
71
+ endUtf8Offset: token. endPosition. utf8Offset,
72
+ replacement: token. with ( \. trailingTrivia, newTrivia) . description
73
+ )
74
+ )
57
75
}
58
76
}
59
- }
60
77
61
- public override func visitAny( _ node: Syntax ) -> Syntax ? {
62
- return nil
63
- }
78
+ var source = tree. description
64
79
65
- public override func visit( _ node: TokenSyntax ) -> TokenSyntax {
66
- var modifiedNode = node
67
- for change in changes {
68
- switch change {
69
- case . replaceLeadingTrivia( token: let changedNode, newTrivia: let newTrivia) where changedNode. id == node. id:
70
- modifiedNode = node. with ( \. leadingTrivia, newTrivia)
71
- case . replaceTrailingTrivia( token: let changedNode, newTrivia: let newTrivia) where changedNode. id == node. id:
72
- modifiedNode = node. with ( \. trailingTrivia, newTrivia)
73
- default :
74
- break
80
+ // Ensure edits are not overlapping:
81
+ // If we only need to apply at the end of a source, start by reversing edit
82
+ // and then sort edits by decrementing start offset. If they are equal then descrementing end offset.
83
+ edits = edits. reversed ( ) . sorted ( by: { edit1, edit2 in
84
+ if edit1. startUtf8Offset == edit2. startUtf8Offset {
85
+ return edit1. endUtf8Offset > edit2. endUtf8Offset
86
+ } else {
87
+ return edit1. startUtf8Offset > edit2. startUtf8Offset
75
88
}
89
+ } )
90
+
91
+ for edit in edits {
92
+ let startIndex = source. utf8. index ( source. utf8. startIndex, offsetBy: edit. startUtf8Offset)
93
+ let endIndex = source. utf8. index ( source. utf8. startIndex, offsetBy: edit. endUtf8Offset)
94
+
95
+ source. replaceSubrange ( startIndex..< endIndex, with: edit. replacement)
76
96
}
77
- return modifiedNode
97
+
98
+ return source
78
99
}
79
100
80
101
/// If `messages` is `nil`, applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree.
81
102
/// If `messages` is not `nil`, applies only Fix-Its whose message is in `messages`.
82
- public static func applyFixes< T: SyntaxProtocol > ( in diagnostics: [ Diagnostic ] , withMessages messages: [ String ] ? , to tree: T ) -> Syntax {
83
- let applier = FixItApplier ( diagnostics: diagnostics, withMessages: messages)
84
- return applier. rewrite ( tree )
103
+ public static func applyFixes< T: SyntaxProtocol > ( in diagnostics: [ Diagnostic ] , withMessages messages: [ String ] ? , to tree: T ) -> String {
104
+ let applier = FixItApplier ( diagnostics: diagnostics, withMessages: messages, tree : tree )
105
+ return applier. rewrite ( )
85
106
}
86
107
}
0 commit comments