Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for SuggestedFixes #5232

Merged
merged 57 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6a296b0
chore: migrate dogsled
ldez Dec 11, 2024
b73ec12
chore: migrate gochecknoinits
ldez Dec 11, 2024
08d2476
chore: migrate gci
ldez Dec 13, 2024
d351aa8
chore: migrate gocritic
ldez Dec 13, 2024
36b2549
chore: migrate godot
ldez Dec 13, 2024
12d2c13
chore: migrate goheader
ldez Dec 13, 2024
30451ed
chore: migrate mirror
ldez Dec 13, 2024
6a40069
chore: migrate protogetter
ldez Dec 13, 2024
ebcff94
chore: migrate tagalign
ldez Dec 13, 2024
89dc119
chore: migrate whitespace
ldez Dec 13, 2024
0acc998
feat: add GetFilePositionFor
ldez Dec 13, 2024
19a66e6
chore: rewrite diff hunkChangesParser
ldez Dec 13, 2024
e7c65cb
chore: refactor nolintlint
ldez Dec 12, 2024
bf20023
chore: copy x/tools/internal/diff
ldez Dec 16, 2024
e5eefef
feat: new fixer
ldez Dec 16, 2024
632b805
chore: migrate nolintlint
ldez Dec 16, 2024
87f42b5
chore: migrate gofmt
ldez Dec 16, 2024
acfdecc
chore: migrate gofumpt
ldez Dec 16, 2024
473ab4e
chore: migrate goimports
ldez Dec 16, 2024
a85e53b
chore: migrate misspell
ldez Dec 16, 2024
75e4944
feat: support autofix for revive
ldez Dec 16, 2024
7b016e6
chore: tag all linters with autofix
ldez Dec 16, 2024
8444ec1
tests: autofix canonicalheader
ldez Dec 16, 2024
410281c
tests: autofix dupword
ldez Dec 16, 2024
e5c03da
tests: autofix fatcontext
ldez Dec 16, 2024
993ac5e
tests: autofix err113
ldez Dec 16, 2024
ddced09
tests: autofix errorlint
ldez Dec 16, 2024
4813ac7
tests: autofix iface
ldez Dec 16, 2024
ca2e133
tests: autofix importas
ldez Dec 16, 2024
54472f6
tests: autofix intrange
ldez Dec 16, 2024
46c6a38
tests: autofix nakedret
ldez Dec 16, 2024
ba516f4
tests: autofix nlreturn
ldez Dec 16, 2024
159bf02
tests: autofix perfsprint
ldez Dec 16, 2024
a642640
tests: autofix testifylint
ldez Dec 16, 2024
26c61a8
tests: autofix wsl
ldez Dec 16, 2024
65fd897
tests: autofix govet
ldez Dec 16, 2024
fe005f2
tests: autofix gosimple
ldez Dec 16, 2024
0cea44b
tests: autofix stylecheck
ldez Dec 16, 2024
6f36d37
tests: autofix staticcheck
ldez Dec 16, 2024
0dbb927
tests: autofix whitespace
ldez Dec 16, 2024
c4ba66d
tests: multiple fixes
ldez Dec 16, 2024
3c47cda
chore: migrate LLL
ldez Dec 16, 2024
66c31e6
chore: simplify godot
ldez Dec 16, 2024
b8d5d41
chore: migrate forbidigo
ldez Dec 16, 2024
30af54f
chore: migrate godox
ldez Dec 16, 2024
7d46339
chore: migrate makezero
ldez Dec 16, 2024
600e10e
chore: migrate nestif
ldez Dec 16, 2024
c7e3c0f
chore: migrate prealloc
ldez Dec 16, 2024
e1fbf35
chore: migrate unparam
ldez Dec 16, 2024
fe88914
tests: add cgo tests
ldez Dec 16, 2024
225f0f9
tests: ignore cgo tests for some linters
ldez Dec 16, 2024
94073d2
fix: dupl on Windows
ldez Dec 16, 2024
3763e1a
review
ldez Dec 16, 2024
f4b2e7a
tests: add missing whitespace tests
ldez Dec 16, 2024
898a5da
chore: add comments about TextEdits positions adjustements
ldez Dec 17, 2024
63b9001
feat: improve error handling
ldez Dec 17, 2024
936e333
fix: skip suggested changes for cgo
ldez Dec 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ require (
github.com/gostaticanalysis/forcetypeassert v0.1.0
github.com/gostaticanalysis/nilerr v0.1.1
github.com/hashicorp/go-version v1.7.0
github.com/hexops/gotextdiff v1.0.3
github.com/jgautheron/goconst v1.7.1
github.com/jingyugao/rowserrcheck v1.1.1
github.com/jjti/go-spancheck v0.6.4
Expand Down Expand Up @@ -157,12 +156,14 @@ require (
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/modinfo v0.3.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

176 changes: 176 additions & 0 deletions internal/x/tools/diff/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package diff computes differences between text files or strings.
package diff

import (
"fmt"
"sort"
"strings"
)

// An Edit describes the replacement of a portion of a text file.
type Edit struct {
Start, End int // byte offsets of the region to replace
New string // the replacement
}

func (e Edit) String() string {
return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New)
}

// Apply applies a sequence of edits to the src buffer and returns the
// result. Edits are applied in order of start offset; edits with the
// same start offset are applied in they order they were provided.
//
// Apply returns an error if any edit is out of bounds,
// or if any pair of edits is overlapping.
func Apply(src string, edits []Edit) (string, error) {
edits, size, err := validate(src, edits)
if err != nil {
return "", err
}

// Apply edits.
out := make([]byte, 0, size)
lastEnd := 0
for _, edit := range edits {
if lastEnd < edit.Start {
out = append(out, src[lastEnd:edit.Start]...)
}
out = append(out, edit.New...)
lastEnd = edit.End
}
out = append(out, src[lastEnd:]...)

if len(out) != size {
panic("wrong size")
}

return string(out), nil
}

// ApplyBytes is like Apply, but it accepts a byte slice.
// The result is always a new array.
func ApplyBytes(src []byte, edits []Edit) ([]byte, error) {
res, err := Apply(string(src), edits)
return []byte(res), err
}

// validate checks that edits are consistent with src,
// and returns the size of the patched output.
// It may return a different slice.
func validate(src string, edits []Edit) ([]Edit, int, error) {
if !sort.IsSorted(editsSort(edits)) {
edits = append([]Edit(nil), edits...)
SortEdits(edits)
}

// Check validity of edits and compute final size.
size := len(src)
lastEnd := 0
for _, edit := range edits {
if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) {
return nil, 0, fmt.Errorf("diff has out-of-bounds edits")
}
if edit.Start < lastEnd {
return nil, 0, fmt.Errorf("diff has overlapping edits")
}
size += len(edit.New) + edit.Start - edit.End
lastEnd = edit.End
}

return edits, size, nil
}

// SortEdits orders a slice of Edits by (start, end) offset.
// This ordering puts insertions (end = start) before deletions
// (end > start) at the same point, but uses a stable sort to preserve
// the order of multiple insertions at the same point.
// (Apply detects multiple deletions at the same point as an error.)
func SortEdits(edits []Edit) {
sort.Stable(editsSort(edits))
}

type editsSort []Edit

func (a editsSort) Len() int { return len(a) }
func (a editsSort) Less(i, j int) bool {
if cmp := a[i].Start - a[j].Start; cmp != 0 {
return cmp < 0
}
return a[i].End < a[j].End
}
func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// lineEdits expands and merges a sequence of edits so that each
// resulting edit replaces one or more complete lines.
// See ApplyEdits for preconditions.
func lineEdits(src string, edits []Edit) ([]Edit, error) {
edits, _, err := validate(src, edits)
if err != nil {
return nil, err
}

// Do all deletions begin and end at the start of a line,
// and all insertions end with a newline?
// (This is merely a fast path.)
for _, edit := range edits {
if edit.Start >= len(src) || // insertion at EOF
edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start
edit.End > 0 && src[edit.End-1] != '\n' || // not at line start
edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert
goto expand // slow path
}
}
return edits, nil // aligned

expand:
if len(edits) == 0 {
return edits, nil // no edits (unreachable due to fast path)
}
expanded := make([]Edit, 0, len(edits)) // a guess
prev := edits[0]
// TODO(adonovan): opt: start from the first misaligned edit.
// TODO(adonovan): opt: avoid quadratic cost of string += string.
for _, edit := range edits[1:] {
between := src[prev.End:edit.Start]
if !strings.Contains(between, "\n") {
// overlapping lines: combine with previous edit.
prev.New += between + edit.New
prev.End = edit.End
} else {
// non-overlapping lines: flush previous edit.
expanded = append(expanded, expandEdit(prev, src))
prev = edit
}
}
return append(expanded, expandEdit(prev, src)), nil // flush final edit
}

// expandEdit returns edit expanded to complete whole lines.
func expandEdit(edit Edit, src string) Edit {
// Expand start left to start of line.
// (delta is the zero-based column number of start.)
start := edit.Start
if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 {
edit.Start -= delta
edit.New = src[start-delta:start] + edit.New
}

// Expand end right to end of line.
end := edit.End
if end > 0 && src[end-1] != '\n' ||
edit.New != "" && edit.New[len(edit.New)-1] != '\n' {
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
edit.End = len(src) // extend to EOF
} else {
edit.End = end + nl + 1 // extend beyond \n
}
}
edit.New += src[end:edit.End]

return edit
}
179 changes: 179 additions & 0 deletions internal/x/tools/diff/lcs/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package lcs

import (
"log"
"sort"
)

// lcs is a longest common sequence
type lcs []diag

// A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i<Len.
// All computed diagonals are parts of a longest common subsequence.
type diag struct {
X, Y int
Len int
}

// sort sorts in place, by lowest X, and if tied, inversely by Len
func (l lcs) sort() lcs {
sort.Slice(l, func(i, j int) bool {
if l[i].X != l[j].X {
return l[i].X < l[j].X
}
return l[i].Len > l[j].Len
})
return l
}

// validate that the elements of the lcs do not overlap
// (can only happen when the two-sided algorithm ends early)
// expects the lcs to be sorted
func (l lcs) valid() bool {
for i := 1; i < len(l); i++ {
if l[i-1].X+l[i-1].Len > l[i].X {
return false
}
if l[i-1].Y+l[i-1].Len > l[i].Y {
return false
}
}
return true
}

// repair overlapping lcs
// only called if two-sided stops early
func (l lcs) fix() lcs {
// from the set of diagonals in l, find a maximal non-conflicting set
// this problem may be NP-complete, but we use a greedy heuristic,
// which is quadratic, but with a better data structure, could be D log D.
// indepedent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs
// which has to have monotone x and y
if len(l) == 0 {
return nil
}
sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len })
tmp := make(lcs, 0, len(l))
tmp = append(tmp, l[0])
for i := 1; i < len(l); i++ {
var dir direction
nxt := l[i]
for _, in := range tmp {
if dir, nxt = overlap(in, nxt); dir == empty || dir == bad {
break
}
}
if nxt.Len > 0 && dir != bad {
tmp = append(tmp, nxt)
}
}
tmp.sort()
if false && !tmp.valid() { // debug checking
log.Fatalf("here %d", len(tmp))
}
return tmp
}

type direction int

const (
empty direction = iota // diag is empty (so not in lcs)
leftdown // proposed acceptably to the left and below
rightup // proposed diag is acceptably to the right and above
bad // proposed diag is inconsistent with the lcs so far
)

// overlap trims the proposed diag prop so it doesn't overlap with
// the existing diag that has already been added to the lcs.
func overlap(exist, prop diag) (direction, diag) {
if prop.X <= exist.X && exist.X < prop.X+prop.Len {
// remove the end of prop where it overlaps with the X end of exist
delta := prop.X + prop.Len - exist.X
prop.Len -= delta
if prop.Len <= 0 {
return empty, prop
}
}
if exist.X <= prop.X && prop.X < exist.X+exist.Len {
// remove the beginning of prop where overlaps with exist
delta := exist.X + exist.Len - prop.X
prop.Len -= delta
if prop.Len <= 0 {
return empty, prop
}
prop.X += delta
prop.Y += delta
}
if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len {
// remove the end of prop that overlaps (in Y) with exist
delta := prop.Y + prop.Len - exist.Y
prop.Len -= delta
if prop.Len <= 0 {
return empty, prop
}
}
if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len {
// remove the beginning of peop that overlaps with exist
delta := exist.Y + exist.Len - prop.Y
prop.Len -= delta
if prop.Len <= 0 {
return empty, prop
}
prop.X += delta // no test reaches this code
prop.Y += delta
}
if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y {
return leftdown, prop
}
if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y {
return rightup, prop
}
// prop can't be in an lcs that contains exist
return bad, prop
}

// manipulating Diag and lcs

// prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs
// or to its first Diag. prepend is only called to extend diagonals
// the backward direction.
func (lcs lcs) prepend(x, y int) lcs {
if len(lcs) > 0 {
d := &lcs[0]
if int(d.X) == x+1 && int(d.Y) == y+1 {
// extend the diagonal down and to the left
d.X, d.Y = int(x), int(y)
d.Len++
return lcs
}
}

r := diag{X: int(x), Y: int(y), Len: 1}
lcs = append([]diag{r}, lcs...)
return lcs
}

// append appends a diagonal, or extends the existing one.
// by adding the edge (x,y)-(x+1.y+1). append is only called
// to extend diagonals in the forward direction.
func (lcs lcs) append(x, y int) lcs {
if len(lcs) > 0 {
last := &lcs[len(lcs)-1]
// Expand last element if adjoining.
if last.X+last.Len == x && last.Y+last.Len == y {
last.Len++
return lcs
}
}

return append(lcs, diag{X: x, Y: y, Len: 1})
}

// enforce constraint on d, k
func ok(d, k int) bool {
return d >= 0 && -d <= k && k <= d
}
Loading
Loading