@@ -23,6 +23,8 @@ import (
23
23
24
24
"google.golang.org/protobuf/proto"
25
25
26
+ "github.com/bufbuild/protocompile/internal/ext/cmpx"
27
+ "github.com/bufbuild/protocompile/internal/ext/slicesx"
26
28
compilerpb "github.com/bufbuild/protocompile/internal/gen/buf/compiler/v1alpha1"
27
29
)
28
30
@@ -54,6 +56,10 @@ type Options struct {
54
56
// Higher values mean more debugging information. What debugging information
55
57
// is actually provided is subject to change.
56
58
Tracing int
59
+
60
+ // If set, [Report.Sort] will not discard duplicate diagnostics, as defined
61
+ // in that function's contract.
62
+ KeepDuplicates bool
57
63
}
58
64
59
65
// Diagnose is a type that can be rendered as a diagnostic.
@@ -141,41 +147,63 @@ func (r *Report) CatchICE(resume bool, diagnose func(*Diagnostic)) {
141
147
}
142
148
}
143
149
144
- // Sort canonicalizes this report's diagnostic order according to an specific
145
- // ordering criteria. Diagnostics are sorted by, in order;
150
+ // Canonicalize sorts this report's diagnostics according to an specific
151
+ // ordering criteria. Diagnostics are sorted by, in order:
146
152
//
147
- // File name of primary span, SortOrder value, start offset of primary snippet,
148
- // end offset of primary snippet, content of error message.
153
+ // 1. File name of primary span.
154
+ // 2. SortOrder value.
155
+ // 3. Start offset of primary snippet.
156
+ // 4. End offset of primary snippet.
157
+ // 5. Diagnostic tag.
158
+ // 6. Textual content of error message.
149
159
//
150
160
// Where diagnostics have no primary span, the file is treated as empty and the
151
161
// offsets are treated as zero.
152
162
//
153
163
// These criteria ensure that diagnostics for the same file go together,
154
164
// diagnostics for the same sort order (lex, parse, etc) go together, and they
155
165
// are otherwise ordered by where they occur in the file.
156
- func (r * Report ) Sort () {
157
- slices .SortFunc (r .Diagnostics , func (a , b Diagnostic ) int {
158
- aPrime := a .Primary ()
159
- bPrime := b .Primary ()
160
-
161
- if diff := strings .Compare (aPrime .Path (), bPrime .Path ()); diff != 0 {
162
- return diff
163
- }
164
-
165
- if diff := a .sortOrder - b .sortOrder ; diff != 0 {
166
- return diff
167
- }
166
+ //
167
+ // Canonicalize will deduplicate diagnostics whose primary span and (nonempty)
168
+ // diagnostic tags are equal, selecting the diagnostic that sorts as greatest
169
+ // as the canonical value. This allows later diagnostics to replace earlier
170
+ // diagnostics, so long as they cooperate by using the same tag. Deduplication
171
+ // can be suppressed using [Options].KeepDuplicates.
172
+ func (r * Report ) Canonicalize () {
173
+ slices .SortFunc (r .Diagnostics , cmpx .Join (
174
+ cmpx .Key (func (d Diagnostic ) string { return d .Primary ().Path () }),
175
+ cmpx .Key (func (d Diagnostic ) int { return d .sortOrder }),
176
+ cmpx .Key (func (d Diagnostic ) int { return d .Primary ().Start }),
177
+ cmpx .Key (func (d Diagnostic ) int { return d .Primary ().End }),
178
+ cmpx .Key (func (d Diagnostic ) string { return d .tag }),
179
+ cmpx .Key (func (d Diagnostic ) string { return d .message }),
180
+ ))
181
+
182
+ if r .KeepDuplicates {
183
+ return
184
+ }
168
185
169
- if diff := aPrime .Start - bPrime .Start ; diff != 0 {
170
- return diff
186
+ type key struct {
187
+ span Span
188
+ tag string
189
+ }
190
+ var cur key
191
+ slicesx .Backward (r .Diagnostics )(func (i int , d Diagnostic ) bool {
192
+ if d .tag == "" {
193
+ return true
171
194
}
172
195
173
- if diff := aPrime .End - bPrime .End ; diff != 0 {
174
- return diff
196
+ key := key {d .Primary ().Span (), d .tag }
197
+ if cur .tag != "" && cur == key {
198
+ r .Diagnostics [i ].level = - 1 // Use this to mark which diagnostics to delete.
199
+ } else {
200
+ cur = key
175
201
}
176
202
177
- return strings . Compare ( a . message , b . message )
203
+ return true
178
204
})
205
+
206
+ r .Diagnostics = slices .DeleteFunc (r .Diagnostics , func (d Diagnostic ) bool { return d .level == - 1 })
179
207
}
180
208
181
209
// ToProto converts this report into a Protobuf message for serialization.
0 commit comments