@@ -19,64 +19,53 @@ type diagnosticEntry struct {
19
19
* analysis.Analyzer
20
20
}
21
21
22
- // This file contains two main entities: NogoEdit and NogoChange, which correspond to the low-level
23
- // and high-level abstractions. See them below.
24
-
25
- // The following is about the `NogoEdit`, a low-level abstraction of edits.
26
- // A NogoEdit describes the replacement of a portion of a text file.
27
- type NogoEdit struct {
22
+ // A nogoEdit describes the replacement of a portion of a text file.
23
+ type nogoEdit struct {
28
24
New string // the replacement
29
25
Start int // starting byte offset of the region to replace
30
26
End int // (exclusive) ending byte offset of the region to replace
31
27
}
32
28
33
- // NogoFileEdits represents the mapping of analyzers to their edits for a specific file.
34
- type NogoFileEdits struct {
35
- AnalyzerToEdits map [string ][]NogoEdit // Analyzer as the key, edits as the value
36
- }
29
+ // analyzerToEdits represents the mapping of analyzers to their edits for a specific file.
30
+ type analyzerToEdits map [string ][]nogoEdit // Analyzer as the key, edits as the value
37
31
38
- // NogoChange represents a collection of file edits.
39
- type NogoChange struct {
40
- FileToEdits map [string ]NogoFileEdits // File path as the key, analyzer-to-edits mapping as the value
41
- }
32
+ // nogoChange represents a collection of file edits.
33
+ // It is a map with file paths as keys and analyzerToEdits as values.
34
+ type nogoChange map [string ]analyzerToEdits
42
35
43
- // newChange creates a new NogoChange object.
44
- func newChange () * NogoChange {
45
- return & NogoChange {
46
- FileToEdits : make (map [string ]NogoFileEdits ),
47
- }
36
+ // newChange creates a new nogoChange object.
37
+ func newChange () nogoChange {
38
+ return make (nogoChange )
48
39
}
49
40
50
- func (e NogoEdit ) String () string {
41
+ func (e nogoEdit ) String () string {
51
42
return fmt .Sprintf ("{Start:%d,End:%d,New:%q}" , e .Start , e .End , e .New )
52
43
}
53
44
54
- // sortEdits orders a slice of NogoEdits by (start, end) offset.
45
+ // sortEdits orders a slice of nogoEdits by (start, end) offset.
55
46
// This ordering puts insertions (end = start) before deletions
56
47
// (end > start) at the same point, but uses a stable sort to preserve
57
48
// the order of multiple insertions at the same point.
58
- // (applyEditsBytes detects multiple deletions at the same point as an error.)
59
- func sortEdits (edits []NogoEdit ) {
60
- sort .Stable (editsSort (edits ))
49
+ func sortEdits (edits []nogoEdit ) {
50
+ sort .Stable (byStartEnd (edits ))
61
51
}
62
52
63
- type editsSort []NogoEdit
53
+ type byStartEnd []nogoEdit
64
54
65
- func (a editsSort ) Len () int { return len (a ) }
66
- func (a editsSort ) Less (i , j int ) bool {
67
- if cmp := a [i ].Start - a [j ].Start ; cmp != 0 {
68
- return cmp < 0
55
+ func (a byStartEnd ) Len () int { return len (a ) }
56
+ func (a byStartEnd ) Less (i , j int ) bool {
57
+ if a [i ].Start != a [j ].Start {
58
+ return a [ i ]. Start < a [ j ]. Start
69
59
}
70
60
return a [i ].End < a [j ].End
71
61
}
72
- func (a editsSort ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
73
-
62
+ func (a byStartEnd ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
74
63
75
64
// validateBytes checks that edits are consistent with the src byte slice,
76
65
// and returns the size of the patched output. It may return a different slice if edits are sorted.
77
- func validateBytes (src []byte , edits []NogoEdit ) ([]NogoEdit , int , error ) {
78
- if ! sort .IsSorted (editsSort (edits )) {
79
- edits = append ([]NogoEdit (nil ), edits ... )
66
+ func validateBytes (src []byte , edits []nogoEdit ) ([]nogoEdit , int , error ) {
67
+ if ! sort .IsSorted (byStartEnd (edits )) {
68
+ edits = append ([]nogoEdit (nil ), edits ... )
80
69
sortEdits (edits )
81
70
}
82
71
@@ -96,10 +85,10 @@ func validateBytes(src []byte, edits []NogoEdit) ([]NogoEdit, int, error) {
96
85
return edits , size , nil
97
86
}
98
87
99
- // applyEditsBytes applies a sequence of NogoEdits to the src byte slice and returns the result.
88
+ // applyEditsBytes applies a sequence of nogoEdits to the src byte slice and returns the result.
100
89
// Edits are applied in order of start offset; edits with the same start offset are applied in the order they were provided.
101
90
// applyEditsBytes returns an error if any edit is out of bounds, or if any pair of edits is overlapping.
102
- func applyEditsBytes (src []byte , edits []NogoEdit ) ([]byte , error ) {
91
+ func applyEditsBytes (src []byte , edits []nogoEdit ) ([]byte , error ) {
103
92
edits , size , err := validateBytes (src , edits )
104
93
if err != nil {
105
94
return nil , err
@@ -124,12 +113,11 @@ func applyEditsBytes(src []byte, edits []NogoEdit) ([]byte, error) {
124
113
return out , nil
125
114
}
126
115
127
-
128
- // newChangeFromDiagnostics builds a NogoChange from a set of diagnostics.
129
- // Unlike Diagnostic, NogoChange is independent of the FileSet given it uses perf-file offsets instead of token.Pos.
130
- // This allows NogoChange to be used in contexts where the FileSet is not available, e.g., it remains applicable after it is saved to disk and loaded back.
116
+ // newChangeFromDiagnostics builds a nogoChange from a set of diagnostics.
117
+ // Unlike Diagnostic, nogoChange is independent of the FileSet given it uses perf-file offsets instead of token.Pos.
118
+ // This allows nogoChange to be used in contexts where the FileSet is not available, e.g., it remains applicable after it is saved to disk and loaded back.
131
119
// See https://github.com/golang/tools/blob/master/go/analysis/diagnostic.go for details.
132
- func newChangeFromDiagnostics (entries []diagnosticEntry , fileSet * token.FileSet ) (* NogoChange , error ) {
120
+ func newChangeFromDiagnostics (entries []diagnosticEntry , fileSet * token.FileSet ) (nogoChange , error ) {
133
121
c := newChange ()
134
122
135
123
cwd , err := os .Getwd ()
@@ -162,58 +150,53 @@ func newChangeFromDiagnostics(entries []diagnosticEntry, fileSet *token.FileSet)
162
150
continue
163
151
}
164
152
165
- nogoEdit := NogoEdit {Start : file .Offset (start ), End : file .Offset (end ), New : string (edit .NewText )}
153
+ nEdit := nogoEdit {Start : file .Offset (start ), End : file .Offset (end ), New : string (edit .NewText )}
166
154
fileRelativePath , err := filepath .Rel (cwd , file .Name ())
167
155
if err != nil {
168
156
fileRelativePath = file .Name () // fallback logic
169
157
}
170
- c . addEdit (fileRelativePath , analyzer , nogoEdit )
158
+ addEdit (c , fileRelativePath , analyzer , nEdit )
171
159
}
172
160
}
173
161
}
174
162
175
163
if len (allErrors ) > 0 {
176
164
var errMsg bytes.Buffer
177
165
sep := ""
178
- for _ , err := range allErrors {
166
+ for _ , e := range allErrors {
179
167
errMsg .WriteString (sep )
180
168
sep = "\n "
181
- errMsg .WriteString (err .Error ())
169
+ errMsg .WriteString (e .Error ())
182
170
}
183
171
return c , fmt .Errorf ("errors:\n %s" , errMsg .String ())
184
172
}
185
173
186
174
return c , nil
187
175
}
188
176
189
- // addEdit adds an edit to the NogoChange, organizing by file and analyzer.
190
- func (c * NogoChange ) addEdit (file string , analyzer string , edit NogoEdit ) {
191
- // Ensure the NogoFileEdits structure exists for the file
192
- fileEdits , exists := c .FileToEdits [file ]
177
+ // addEdit adds an edit to the nogoChange, organizing by file and analyzer.
178
+ func addEdit (c nogoChange , file string , analyzer string , edit nogoEdit ) {
179
+ fileEdits , exists := c [file ]
193
180
if ! exists {
194
- fileEdits = NogoFileEdits {
195
- AnalyzerToEdits : make (map [string ][]NogoEdit ),
196
- }
197
- c .FileToEdits [file ] = fileEdits
181
+ fileEdits = make (analyzerToEdits )
182
+ c [file ] = fileEdits
198
183
}
199
-
200
- // Append the edit to the list of edits for the analyzer
201
- fileEdits .AnalyzerToEdits [analyzer ] = append (fileEdits .AnalyzerToEdits [analyzer ], edit )
184
+ fileEdits [analyzer ] = append (fileEdits [analyzer ], edit )
202
185
}
203
186
204
187
// uniqueSortedEdits returns a list of edits that is sorted and
205
188
// contains no duplicate edits. Returns whether there is overlap.
206
189
// Deduplication helps in the cases where two analyzers produce duplicate edits.
207
- func uniqueSortedEdits (edits []NogoEdit ) ([]NogoEdit , bool ) {
190
+ func uniqueSortedEdits (edits []nogoEdit ) ([]nogoEdit , bool ) {
208
191
hasOverlap := false
209
192
if len (edits ) == 0 {
210
193
return edits , hasOverlap
211
194
}
212
- equivalent := func (x , y NogoEdit ) bool {
195
+ equivalent := func (x , y nogoEdit ) bool {
213
196
return x .Start == y .Start && x .End == y .End && x .New == y .New
214
197
}
215
198
sortEdits (edits )
216
- unique := []NogoEdit {edits [0 ]}
199
+ unique := []nogoEdit {edits [0 ]}
217
200
for i := 1 ; i < len (edits ); i ++ {
218
201
prev , cur := edits [i - 1 ], edits [i ]
219
202
if ! equivalent (prev , cur ) { // equivalent ones are safely skipped
@@ -227,23 +210,23 @@ func uniqueSortedEdits(edits []NogoEdit) ([]NogoEdit, bool) {
227
210
return unique , hasOverlap
228
211
}
229
212
230
- // flatten merges all edits for a file from different analyzers into a single map of file-to-edits.
231
- // Edits from each analyzer are processed in a deterministic order, and overlapping edits are skipped.
232
- func flatten (change NogoChange ) map [ string ][] NogoEdit {
233
- fileToEdits := make (map [ string ][] NogoEdit )
213
+ type fileToEdits map [ string ][] nogoEdit // File path as the key, list of nogoEdit as the value
214
+
215
+ func flatten (change nogoChange ) fileToEdits {
216
+ result := make (fileToEdits )
234
217
235
- for file , fileEdits := range change . FileToEdits {
218
+ for file , fileEdits := range change {
236
219
// Get a sorted list of analyzers for deterministic processing order
237
- analyzers := make ([]string , 0 , len (fileEdits . AnalyzerToEdits ))
238
- for analyzer := range fileEdits . AnalyzerToEdits {
220
+ analyzers := make ([]string , 0 , len (fileEdits ))
221
+ for analyzer := range fileEdits {
239
222
analyzers = append (analyzers , analyzer )
240
223
}
241
224
sort .Strings (analyzers )
242
225
243
- mergedEdits := make ([]NogoEdit , 0 )
226
+ mergedEdits := make ([]nogoEdit , 0 )
244
227
245
228
for _ , analyzer := range analyzers {
246
- edits := fileEdits . AnalyzerToEdits [analyzer ]
229
+ edits := fileEdits [analyzer ]
247
230
if len (edits ) == 0 {
248
231
continue
249
232
}
@@ -253,10 +236,6 @@ func flatten(change NogoChange) map[string][]NogoEdit {
253
236
candidateEdits , hasOverlap := uniqueSortedEdits (candidateEdits )
254
237
if hasOverlap {
255
238
// Skip edits from this analyzer if merging them would cause overlaps.
256
- // Apply the non-overlapping edits first. After that, a rerun of bazel build will
257
- // allows these skipped edits to be applied separately.
258
- // Note the resolution happens to each file independently.
259
- // Also for clarity, we would accept all or none of an analyzer.
260
239
continue
261
240
}
262
241
@@ -265,28 +244,24 @@ func flatten(change NogoChange) map[string][]NogoEdit {
265
244
}
266
245
267
246
// Store the final merged edits for the file
268
- fileToEdits [file ] = mergedEdits
247
+ result [file ] = mergedEdits
269
248
}
270
249
271
- return fileToEdits
250
+ return result
272
251
}
273
252
274
- // toCombinedPatch converts all edits to a single consolidated patch.
275
- func toCombinedPatch (fileToEdits map [string ][]NogoEdit ) (string , error ) {
253
+ func toCombinedPatch (fte fileToEdits ) (string , error ) {
276
254
var combinedPatch strings.Builder
277
255
278
- filePaths := make ([]string , 0 , len (fileToEdits ))
279
- for filePath := range fileToEdits {
256
+ filePaths := make ([]string , 0 , len (fte ))
257
+ for filePath := range fte {
280
258
filePaths = append (filePaths , filePath )
281
259
}
282
260
sort .Strings (filePaths ) // Sort file paths alphabetically
283
261
284
262
// Iterate over sorted file paths
285
263
for _ , filePath := range filePaths {
286
- // edits are unique and sorted, as ensured by the flatten() method that is invoked earlier.
287
- // for performance reason, let us skip uniqueSortedEdits() call here,
288
- // although in general a library API shall not assume other calls have been made.
289
- edits := fileToEdits [filePath ]
264
+ edits := fte [filePath ]
290
265
if len (edits ) == 0 {
291
266
continue
292
267
}
@@ -314,7 +289,6 @@ func toCombinedPatch(fileToEdits map[string][]NogoEdit) (string, error) {
314
289
return "" , fmt .Errorf ("failed to generate patch for file %s: %v" , filePath , err )
315
290
}
316
291
317
- // Append the patch for this file to the giant patch
318
292
combinedPatch .WriteString (patch )
319
293
combinedPatch .WriteString ("\n " ) // Ensure separation between file patches
320
294
}
0 commit comments