1
2
3
4
5 package driverutil
6
7
8
9 import (
10 "cmp"
11 "fmt"
12 "go/token"
13 "slices"
14
15 "golang.org/x/tools/go/analysis"
16 )
17
18
19
20
21
22
23
24 func ValidateFixes(fset *token.FileSet, a *analysis.Analyzer, fixes []analysis.SuggestedFix) error {
25 fixMessages := make(map[string]bool)
26 for i := range fixes {
27 fix := &fixes[i]
28 if fixMessages[fix.Message] {
29 return fmt.Errorf("analyzer %q suggests two fixes with same Message (%s)", a.Name, fix.Message)
30 }
31 fixMessages[fix.Message] = true
32 if err := validateFix(fset, fix); err != nil {
33 return fmt.Errorf("analyzer %q suggests invalid fix (%s): %v", a.Name, fix.Message, err)
34 }
35 }
36 return nil
37 }
38
39
40
41
42
43 func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
44
45
46
47
48
49 slices.SortStableFunc(fix.TextEdits, func(x, y analysis.TextEdit) int {
50 if sign := cmp.Compare(x.Pos, y.Pos); sign != 0 {
51 return sign
52 }
53 return cmp.Compare(x.End, y.End)
54 })
55
56 var prev *analysis.TextEdit
57 for i := range fix.TextEdits {
58 edit := &fix.TextEdits[i]
59
60
61 start := edit.Pos
62 file := fset.File(start)
63 if file == nil {
64 return fmt.Errorf("no token.File for TextEdit.Pos (%v)", edit.Pos)
65 }
66 fileEnd := token.Pos(file.Base() + file.Size())
67 if end := edit.End; end.IsValid() {
68 if end < start {
69 return fmt.Errorf("TextEdit.Pos (%v) > TextEdit.End (%v)", edit.Pos, edit.End)
70 }
71 endFile := fset.File(end)
72 if endFile != file && end < fileEnd+10 {
73
74
75
76
77
78
79
80
81
82 edit.End = fileEnd
83 continue
84 }
85 if endFile == nil {
86 return fmt.Errorf("no token.File for TextEdit.End (%v; File(start).FileEnd is %d)", end, file.Base()+file.Size())
87 }
88 if endFile != file {
89 return fmt.Errorf("edit #%d spans files (%v and %v)",
90 i, file.Position(edit.Pos), endFile.Position(edit.End))
91 }
92 } else {
93 edit.End = start
94 }
95 if eof := fileEnd; edit.End > eof {
96 return fmt.Errorf("end is (%v) beyond end of file (%v)", edit.End, eof)
97 }
98
99
100
101 if prev != nil && edit.Pos < prev.End {
102 xpos := fset.Position(prev.Pos)
103 xend := fset.Position(prev.End)
104 ypos := fset.Position(edit.Pos)
105 yend := fset.Position(edit.End)
106 return fmt.Errorf("overlapping edits to %s (%d:%d-%d:%d and %d:%d-%d:%d)",
107 xpos.Filename,
108 xpos.Line, xpos.Column,
109 xend.Line, xend.Column,
110 ypos.Line, ypos.Column,
111 yend.Line, yend.Column,
112 )
113 }
114 prev = edit
115 }
116
117 return nil
118 }
119
View as plain text