Skip to content

Commit c3e693d

Browse files
authored
feat: add support for SuggestedFixes (golangci#5232)
1 parent 2afa6e1 commit c3e693d

File tree

256 files changed

+10890
-2108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

256 files changed

+10890
-2108
lines changed

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ require (
5353
github.com/gostaticanalysis/forcetypeassert v0.1.0
5454
github.com/gostaticanalysis/nilerr v0.1.1
5555
github.com/hashicorp/go-version v1.7.0
56-
github.com/hexops/gotextdiff v1.0.3
5756
github.com/jgautheron/goconst v1.7.1
5857
github.com/jingyugao/rowserrcheck v1.1.1
5958
github.com/jjti/go-spancheck v0.6.4
@@ -157,12 +156,14 @@ require (
157156
github.com/go-toolsmith/typep v1.1.0 // indirect
158157
github.com/gobwas/glob v0.2.3 // indirect
159158
github.com/golang/protobuf v1.5.3 // indirect
159+
github.com/golangci/modinfo v0.3.3 // indirect
160160
github.com/google/go-cmp v0.6.0 // indirect
161161
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
162162
github.com/gostaticanalysis/comment v1.4.2 // indirect
163163
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
164164
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
165165
github.com/hashicorp/hcl v1.0.0 // indirect
166+
github.com/hexops/gotextdiff v1.0.3 // indirect
166167
github.com/inconshreveable/mousetrap v1.1.0 // indirect
167168
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
168169
github.com/magiconair/properties v1.8.6 // indirect

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/x/tools/diff/diff.go

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package diff computes differences between text files or strings.
6+
package diff
7+
8+
import (
9+
"fmt"
10+
"sort"
11+
"strings"
12+
)
13+
14+
// An Edit describes the replacement of a portion of a text file.
15+
type Edit struct {
16+
Start, End int // byte offsets of the region to replace
17+
New string // the replacement
18+
}
19+
20+
func (e Edit) String() string {
21+
return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New)
22+
}
23+
24+
// Apply applies a sequence of edits to the src buffer and returns the
25+
// result. Edits are applied in order of start offset; edits with the
26+
// same start offset are applied in they order they were provided.
27+
//
28+
// Apply returns an error if any edit is out of bounds,
29+
// or if any pair of edits is overlapping.
30+
func Apply(src string, edits []Edit) (string, error) {
31+
edits, size, err := validate(src, edits)
32+
if err != nil {
33+
return "", err
34+
}
35+
36+
// Apply edits.
37+
out := make([]byte, 0, size)
38+
lastEnd := 0
39+
for _, edit := range edits {
40+
if lastEnd < edit.Start {
41+
out = append(out, src[lastEnd:edit.Start]...)
42+
}
43+
out = append(out, edit.New...)
44+
lastEnd = edit.End
45+
}
46+
out = append(out, src[lastEnd:]...)
47+
48+
if len(out) != size {
49+
panic("wrong size")
50+
}
51+
52+
return string(out), nil
53+
}
54+
55+
// ApplyBytes is like Apply, but it accepts a byte slice.
56+
// The result is always a new array.
57+
func ApplyBytes(src []byte, edits []Edit) ([]byte, error) {
58+
res, err := Apply(string(src), edits)
59+
return []byte(res), err
60+
}
61+
62+
// validate checks that edits are consistent with src,
63+
// and returns the size of the patched output.
64+
// It may return a different slice.
65+
func validate(src string, edits []Edit) ([]Edit, int, error) {
66+
if !sort.IsSorted(editsSort(edits)) {
67+
edits = append([]Edit(nil), edits...)
68+
SortEdits(edits)
69+
}
70+
71+
// Check validity of edits and compute final size.
72+
size := len(src)
73+
lastEnd := 0
74+
for _, edit := range edits {
75+
if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) {
76+
return nil, 0, fmt.Errorf("diff has out-of-bounds edits")
77+
}
78+
if edit.Start < lastEnd {
79+
return nil, 0, fmt.Errorf("diff has overlapping edits")
80+
}
81+
size += len(edit.New) + edit.Start - edit.End
82+
lastEnd = edit.End
83+
}
84+
85+
return edits, size, nil
86+
}
87+
88+
// SortEdits orders a slice of Edits by (start, end) offset.
89+
// This ordering puts insertions (end = start) before deletions
90+
// (end > start) at the same point, but uses a stable sort to preserve
91+
// the order of multiple insertions at the same point.
92+
// (Apply detects multiple deletions at the same point as an error.)
93+
func SortEdits(edits []Edit) {
94+
sort.Stable(editsSort(edits))
95+
}
96+
97+
type editsSort []Edit
98+
99+
func (a editsSort) Len() int { return len(a) }
100+
func (a editsSort) Less(i, j int) bool {
101+
if cmp := a[i].Start - a[j].Start; cmp != 0 {
102+
return cmp < 0
103+
}
104+
return a[i].End < a[j].End
105+
}
106+
func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
107+
108+
// lineEdits expands and merges a sequence of edits so that each
109+
// resulting edit replaces one or more complete lines.
110+
// See ApplyEdits for preconditions.
111+
func lineEdits(src string, edits []Edit) ([]Edit, error) {
112+
edits, _, err := validate(src, edits)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
// Do all deletions begin and end at the start of a line,
118+
// and all insertions end with a newline?
119+
// (This is merely a fast path.)
120+
for _, edit := range edits {
121+
if edit.Start >= len(src) || // insertion at EOF
122+
edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start
123+
edit.End > 0 && src[edit.End-1] != '\n' || // not at line start
124+
edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert
125+
goto expand // slow path
126+
}
127+
}
128+
return edits, nil // aligned
129+
130+
expand:
131+
if len(edits) == 0 {
132+
return edits, nil // no edits (unreachable due to fast path)
133+
}
134+
expanded := make([]Edit, 0, len(edits)) // a guess
135+
prev := edits[0]
136+
// TODO(adonovan): opt: start from the first misaligned edit.
137+
// TODO(adonovan): opt: avoid quadratic cost of string += string.
138+
for _, edit := range edits[1:] {
139+
between := src[prev.End:edit.Start]
140+
if !strings.Contains(between, "\n") {
141+
// overlapping lines: combine with previous edit.
142+
prev.New += between + edit.New
143+
prev.End = edit.End
144+
} else {
145+
// non-overlapping lines: flush previous edit.
146+
expanded = append(expanded, expandEdit(prev, src))
147+
prev = edit
148+
}
149+
}
150+
return append(expanded, expandEdit(prev, src)), nil // flush final edit
151+
}
152+
153+
// expandEdit returns edit expanded to complete whole lines.
154+
func expandEdit(edit Edit, src string) Edit {
155+
// Expand start left to start of line.
156+
// (delta is the zero-based column number of start.)
157+
start := edit.Start
158+
if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 {
159+
edit.Start -= delta
160+
edit.New = src[start-delta:start] + edit.New
161+
}
162+
163+
// Expand end right to end of line.
164+
end := edit.End
165+
if end > 0 && src[end-1] != '\n' ||
166+
edit.New != "" && edit.New[len(edit.New)-1] != '\n' {
167+
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
168+
edit.End = len(src) // extend to EOF
169+
} else {
170+
edit.End = end + nl + 1 // extend beyond \n
171+
}
172+
}
173+
edit.New += src[end:edit.End]
174+
175+
return edit
176+
}

internal/x/tools/diff/lcs/common.go

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package lcs
6+
7+
import (
8+
"log"
9+
"sort"
10+
)
11+
12+
// lcs is a longest common sequence
13+
type lcs []diag
14+
15+
// A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i<Len.
16+
// All computed diagonals are parts of a longest common subsequence.
17+
type diag struct {
18+
X, Y int
19+
Len int
20+
}
21+
22+
// sort sorts in place, by lowest X, and if tied, inversely by Len
23+
func (l lcs) sort() lcs {
24+
sort.Slice(l, func(i, j int) bool {
25+
if l[i].X != l[j].X {
26+
return l[i].X < l[j].X
27+
}
28+
return l[i].Len > l[j].Len
29+
})
30+
return l
31+
}
32+
33+
// validate that the elements of the lcs do not overlap
34+
// (can only happen when the two-sided algorithm ends early)
35+
// expects the lcs to be sorted
36+
func (l lcs) valid() bool {
37+
for i := 1; i < len(l); i++ {
38+
if l[i-1].X+l[i-1].Len > l[i].X {
39+
return false
40+
}
41+
if l[i-1].Y+l[i-1].Len > l[i].Y {
42+
return false
43+
}
44+
}
45+
return true
46+
}
47+
48+
// repair overlapping lcs
49+
// only called if two-sided stops early
50+
func (l lcs) fix() lcs {
51+
// from the set of diagonals in l, find a maximal non-conflicting set
52+
// this problem may be NP-complete, but we use a greedy heuristic,
53+
// which is quadratic, but with a better data structure, could be D log D.
54+
// indepedent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs
55+
// which has to have monotone x and y
56+
if len(l) == 0 {
57+
return nil
58+
}
59+
sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len })
60+
tmp := make(lcs, 0, len(l))
61+
tmp = append(tmp, l[0])
62+
for i := 1; i < len(l); i++ {
63+
var dir direction
64+
nxt := l[i]
65+
for _, in := range tmp {
66+
if dir, nxt = overlap(in, nxt); dir == empty || dir == bad {
67+
break
68+
}
69+
}
70+
if nxt.Len > 0 && dir != bad {
71+
tmp = append(tmp, nxt)
72+
}
73+
}
74+
tmp.sort()
75+
if false && !tmp.valid() { // debug checking
76+
log.Fatalf("here %d", len(tmp))
77+
}
78+
return tmp
79+
}
80+
81+
type direction int
82+
83+
const (
84+
empty direction = iota // diag is empty (so not in lcs)
85+
leftdown // proposed acceptably to the left and below
86+
rightup // proposed diag is acceptably to the right and above
87+
bad // proposed diag is inconsistent with the lcs so far
88+
)
89+
90+
// overlap trims the proposed diag prop so it doesn't overlap with
91+
// the existing diag that has already been added to the lcs.
92+
func overlap(exist, prop diag) (direction, diag) {
93+
if prop.X <= exist.X && exist.X < prop.X+prop.Len {
94+
// remove the end of prop where it overlaps with the X end of exist
95+
delta := prop.X + prop.Len - exist.X
96+
prop.Len -= delta
97+
if prop.Len <= 0 {
98+
return empty, prop
99+
}
100+
}
101+
if exist.X <= prop.X && prop.X < exist.X+exist.Len {
102+
// remove the beginning of prop where overlaps with exist
103+
delta := exist.X + exist.Len - prop.X
104+
prop.Len -= delta
105+
if prop.Len <= 0 {
106+
return empty, prop
107+
}
108+
prop.X += delta
109+
prop.Y += delta
110+
}
111+
if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len {
112+
// remove the end of prop that overlaps (in Y) with exist
113+
delta := prop.Y + prop.Len - exist.Y
114+
prop.Len -= delta
115+
if prop.Len <= 0 {
116+
return empty, prop
117+
}
118+
}
119+
if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len {
120+
// remove the beginning of peop that overlaps with exist
121+
delta := exist.Y + exist.Len - prop.Y
122+
prop.Len -= delta
123+
if prop.Len <= 0 {
124+
return empty, prop
125+
}
126+
prop.X += delta // no test reaches this code
127+
prop.Y += delta
128+
}
129+
if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y {
130+
return leftdown, prop
131+
}
132+
if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y {
133+
return rightup, prop
134+
}
135+
// prop can't be in an lcs that contains exist
136+
return bad, prop
137+
}
138+
139+
// manipulating Diag and lcs
140+
141+
// prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs
142+
// or to its first Diag. prepend is only called to extend diagonals
143+
// the backward direction.
144+
func (lcs lcs) prepend(x, y int) lcs {
145+
if len(lcs) > 0 {
146+
d := &lcs[0]
147+
if int(d.X) == x+1 && int(d.Y) == y+1 {
148+
// extend the diagonal down and to the left
149+
d.X, d.Y = int(x), int(y)
150+
d.Len++
151+
return lcs
152+
}
153+
}
154+
155+
r := diag{X: int(x), Y: int(y), Len: 1}
156+
lcs = append([]diag{r}, lcs...)
157+
return lcs
158+
}
159+
160+
// append appends a diagonal, or extends the existing one.
161+
// by adding the edge (x,y)-(x+1.y+1). append is only called
162+
// to extend diagonals in the forward direction.
163+
func (lcs lcs) append(x, y int) lcs {
164+
if len(lcs) > 0 {
165+
last := &lcs[len(lcs)-1]
166+
// Expand last element if adjoining.
167+
if last.X+last.Len == x && last.Y+last.Len == y {
168+
last.Len++
169+
return lcs
170+
}
171+
}
172+
173+
return append(lcs, diag{X: x, Y: y, Len: 1})
174+
}
175+
176+
// enforce constraint on d, k
177+
func ok(d, k int) bool {
178+
return d >= 0 && -d <= k && k <= d
179+
}

0 commit comments

Comments
 (0)