@@ -42,7 +42,7 @@ private enum Error: Swift.Error {
42
42
}
43
43
44
44
// FIXME: An abstraction for tests to work around missing memberwise initializers in `TSCUtility.SerializedDiagnostics`.
45
- protocol AnySourceLocation {
45
+ protocol AnySourceLocation : Hashable {
46
46
var filename : String { get }
47
47
var line : UInt64 { get }
48
48
var column : UInt64 { get }
@@ -96,9 +96,103 @@ extension AnyDiagnostic {
96
96
}
97
97
98
98
extension SerializedDiagnostics . Diagnostic : AnyDiagnostic { }
99
- extension SerializedDiagnostics . SourceLocation : AnySourceLocation { }
99
+ extension SerializedDiagnostics . SourceLocation : AnySourceLocation , @retroactive Hashable {
100
+ public func hash( into hasher: inout Hasher ) {
101
+ hasher. combine ( self . filename)
102
+ hasher. combine ( self . line)
103
+ hasher. combine ( self . column)
104
+ }
105
+ }
106
+
100
107
extension SerializedDiagnostics . FixIt : AnyFixIt { }
101
108
109
+ /// Encapsulates initial diagnostic skipping behavior.
110
+ private struct PrimaryDiagnosticFilter < Diagnostic: AnyDiagnostic > : ~ Copyable {
111
+ /// A hashable type storing the minimum data necessary to uniquely identify
112
+ /// a diagnostic for our purposes.
113
+ private struct DiagnosticID : Hashable {
114
+ private let location : Diagnostic . SourceLocation
115
+ private let message : String
116
+ private let level : SerializedDiagnostics . Diagnostic . Level
117
+
118
+ init ( diagnostic: Diagnostic ) {
119
+ self . level = diagnostic. level
120
+ self . message = diagnostic. text
121
+ // Force the location. We should be filtering out diagnostics
122
+ // without a location.
123
+ self . location = diagnostic. location!
124
+ }
125
+ }
126
+
127
+ private var uniquePrimaryDiagnostics : Set < DiagnosticID > = [ ]
128
+
129
+ let categories : Set < String >
130
+
131
+ init ( categories: Set < String > ) {
132
+ self . categories = categories
133
+ }
134
+
135
+ /// Returns a Boolean value indicating whether to skip the given primary
136
+ /// diagnostic and its notes.
137
+ mutating func shouldSkip( primaryDiagnosticWithNotes: some Collection < Diagnostic > ) -> Bool {
138
+ let diagnostic = primaryDiagnosticWithNotes [ primaryDiagnosticWithNotes. startIndex]
139
+ precondition ( diagnostic. isPrimary)
140
+
141
+ // Skip if ignored.
142
+ if diagnostic. isIgnored {
143
+ return true
144
+ }
145
+
146
+ // Skip if no location.
147
+ if diagnostic. hasNoLocation {
148
+ return true
149
+ }
150
+
151
+ // Skip if categories are given and the diagnostic does not
152
+ // belong to any of them.
153
+ if !self . categories. isEmpty {
154
+ guard let category = diagnostic. category, self . categories. contains ( category) else {
155
+ return true
156
+ }
157
+ }
158
+
159
+ let notes = primaryDiagnosticWithNotes. dropFirst ( )
160
+ precondition ( notes. allSatisfy ( \. isNote) )
161
+
162
+ // Consider the diagnostic compromised if a note does not have a
163
+ // location.
164
+ if notes. contains ( where: \. hasNoLocation) {
165
+ return true
166
+ }
167
+
168
+ switch notes. count ( where: \. hasFixIt) {
169
+ case 0 :
170
+ // No notes have fix-its. Skip if neither does the primary
171
+ // diagnostic.
172
+ guard diagnostic. hasFixIt else {
173
+ return true
174
+ }
175
+ case 1 :
176
+ break
177
+ default :
178
+ // Skip if more than 1 note has a fix-it. These diagnostics
179
+ // generally require user intervention.
180
+ // TODO: This will have to be done lazier once we support printing them.
181
+ return true
182
+ }
183
+
184
+ // Skip if we've seen this primary diagnostic before.
185
+ //
186
+ // NB: This check is done last to prevent the set from growing without
187
+ // need.
188
+ guard self . uniquePrimaryDiagnostics. insert ( . init( diagnostic: diagnostic) ) . inserted else {
189
+ return true
190
+ }
191
+
192
+ return false
193
+ }
194
+ }
195
+
102
196
/// The backing API for `SwiftFixitCommand`.
103
197
package struct SwiftFixIt /*: ~Copyable */ {
104
198
private typealias DiagnosticsPerFile = [ SourceFile : [ SwiftDiagnostics . Diagnostic ] ]
@@ -132,50 +226,8 @@ package struct SwiftFixIt /*: ~Copyable */ {
132
226
) throws {
133
227
self . fileSystem = fileSystem
134
228
135
- func shouldSkip( primaryDiagnosticWithNotes: some Collection < Diagnostic > ) -> Bool {
136
- let diagnostic = primaryDiagnosticWithNotes [ primaryDiagnosticWithNotes. startIndex]
137
-
138
- // Skip if ignored.
139
- if diagnostic. isIgnored {
140
- return true
141
- }
142
-
143
- // Skip if no location.
144
- if diagnostic. hasNoLocation {
145
- return true
146
- }
147
-
148
- // Skip if categories were given and the diagnostic does not
149
- // belong to any of them.
150
- if !categories. isEmpty {
151
- guard let category = diagnostic. category, categories. contains ( category) else {
152
- return true
153
- }
154
- }
155
-
156
- let notes = primaryDiagnosticWithNotes. dropFirst ( )
157
-
158
- // Consider the diagnostic compromised if a note does not have a
159
- // location.
160
- if notes. contains ( where: \. hasNoLocation) {
161
- return true
162
- }
163
-
164
- let numberOfNotesWithFixIts = notes. count ( where: \. hasFixIt)
165
- switch numberOfNotesWithFixIts {
166
- case 0 :
167
- // Skip if neither the primary diagnostic nor any of its notes
168
- // has a fix-it.
169
- return !diagnostic. hasFixIt
170
- case 1 :
171
- return false
172
- default :
173
- // Skip if more than 1 note has a fix-it. These diagnostics
174
- // generally require user intervention.
175
- // TODO: This will have to done lazier once we support printing them.
176
- return true
177
- }
178
- }
229
+ var filter = PrimaryDiagnosticFilter < Diagnostic > ( categories: categories)
230
+ _ = consume categories
179
231
180
232
// Build a map from source files to `SwiftDiagnostics` diagnostics.
181
233
var diagnosticsPerFile : DiagnosticsPerFile = [ : ]
@@ -193,7 +245,7 @@ package struct SwiftFixIt /*: ~Copyable */ {
193
245
194
246
let primaryDiagnosticWithNotes = diagnostics [ currentPrimaryIndex ..< nextPrimaryIndex]
195
247
196
- if shouldSkip ( primaryDiagnosticWithNotes: primaryDiagnosticWithNotes) {
248
+ if filter . shouldSkip ( primaryDiagnosticWithNotes: primaryDiagnosticWithNotes) {
197
249
continue
198
250
}
199
251
0 commit comments