-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for SuggestedFixes (#5232)
- Loading branch information
Showing
256 changed files
with
10,890 additions
and
2,108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.