Skip to content

Commit 0b4461b

Browse files
committed
internal/diff: fix LineEdits bug in slow path
Previously, the expandEdit operation would expand to the end of the line unconditionally, but this caused it to gulp an extra line if it was already line-aligned. This change causes it to do the expansion only if the end is not line-aligned, or the replacement text doesn't end with a newline. Now, removing the fast path no longer causes tests to fail. This also allows us to remove the logic added in CL 489695 to work around issue golang/go#59232. Fixes golang/go#60379 Fixes golang/go#59232 Change-Id: Ia40e4e3bb714d75acb95103a38e8c49a8ef456de Reviewed-on: https://go-review.googlesource.com/c/tools/+/499377 Run-TryBot: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Peter Weinberger <[email protected]>
1 parent 1943c1e commit 0b4461b

File tree

4 files changed

+25
-29
lines changed

4 files changed

+25
-29
lines changed

internal/diff/diff.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Edit struct {
1818
}
1919

2020
func (e Edit) String() string {
21-
return fmt.Sprintf("{Start:%d,End:%d,New:%s}", e.Start, e.End, e.New)
21+
return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New)
2222
}
2323

2424
// Apply applies a sequence of edits to the src buffer and returns the
@@ -116,19 +116,21 @@ func lineEdits(src string, edits []Edit) ([]Edit, error) {
116116

117117
// Do all deletions begin and end at the start of a line,
118118
// and all insertions end with a newline?
119-
// TODO(adonovan, pjw): why does omitting this 'optimization'
120-
// cause tests to fail? (TestDiff/insert-line,extra_newline)
119+
// (This is merely a fast path.)
121120
for _, edit := range edits {
122121
if edit.Start >= len(src) || // insertion at EOF
123122
edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start
124123
edit.End > 0 && src[edit.End-1] != '\n' || // not at line start
125124
edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert
126-
goto expand
125+
goto expand // slow path
127126
}
128127
}
129128
return edits, nil // aligned
130129

131130
expand:
131+
if len(edits) == 0 {
132+
return edits, nil // no edits (unreachable due to fast path)
133+
}
132134
expanded := make([]Edit, 0, len(edits)) // a guess
133135
prev := edits[0]
134136
// TODO(adonovan): opt: start from the first misaligned edit.
@@ -160,10 +162,13 @@ func expandEdit(edit Edit, src string) Edit {
160162

161163
// Expand end right to end of line.
162164
end := edit.End
163-
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
164-
edit.End = len(src) // extend to EOF
165-
} else {
166-
edit.End = end + nl + 1 // extend beyond \n
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+
}
167172
}
168173
edit.New += src[end:edit.End]
169174

internal/diff/diff_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ func FuzzRoundTrip(f *testing.F) {
9595
func TestLineEdits(t *testing.T) {
9696
for _, tc := range difftest.TestCases {
9797
t.Run(tc.Name, func(t *testing.T) {
98-
// if line edits not specified, it is the same as edits
99-
edits := tc.LineEdits
100-
if edits == nil {
101-
edits = tc.Edits
98+
want := tc.LineEdits
99+
if want == nil {
100+
want = tc.Edits // already line-aligned
102101
}
103102
got, err := diff.LineEdits(tc.In, tc.Edits)
104103
if err != nil {
105104
t.Fatalf("LineEdits: %v", err)
106105
}
107-
if !reflect.DeepEqual(got, edits) {
108-
t.Errorf("LineEdits got\n%q, want\n%q\n%#v", got, edits, tc)
106+
if !reflect.DeepEqual(got, want) {
107+
t.Errorf("in=<<%s>>\nout=<<%s>>\nraw edits=%s\nline edits=%s\nwant: %s",
108+
tc.In, tc.Out, tc.Edits, got, want)
109109
}
110110
// make sure that applying the edits gives the expected result
111111
fixed, err := diff.Apply(tc.In, got)

internal/diff/difftest/difftest.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const (
2828

2929
var TestCases = []struct {
3030
Name, In, Out, Unified string
31-
Edits, LineEdits []diff.Edit
31+
Edits, LineEdits []diff.Edit // expectation (LineEdits=nil => already line-aligned)
3232
NoDiff bool
3333
}{{
3434
Name: "empty",
@@ -220,9 +220,9 @@ var TestCases = []struct {
220220
{Start: 14, End: 14, New: "C\n"},
221221
},
222222
LineEdits: []diff.Edit{
223-
{Start: 0, End: 6, New: "C\n"},
224-
{Start: 6, End: 8, New: "B\nA\n"},
225-
{Start: 10, End: 14, New: "A\n"},
223+
{Start: 0, End: 4, New: ""},
224+
{Start: 6, End: 6, New: "B\n"},
225+
{Start: 10, End: 12, New: ""},
226226
{Start: 14, End: 14, New: "C\n"},
227227
},
228228
}, {

internal/diff/unified.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,9 @@ func toUnified(fromName, toName string, content string, edits []Edit) (unified,
155155
last++
156156
}
157157
if edit.New != "" {
158-
for i, content := range splitLines(edit.New) {
159-
toLine++
160-
// Merge identical Delete+Insert.
161-
// This is an unwanted output of converting diffs to line diffs
162-
// that is easiest to fix by postprocessing.
163-
// e.g. issue #59232: ("aaa\nccc\n", "aaa\nbbb\nccc")
164-
// -> [Delete "aaa\n", Insert "aaa\n", Insert "bbb\n", ...].
165-
if i == 0 && last > start && h.lines[len(h.lines)-1].content == content {
166-
h.lines[len(h.lines)-1].kind = opEqual
167-
continue
168-
}
158+
for _, content := range splitLines(edit.New) {
169159
h.lines = append(h.lines, line{kind: opInsert, content: content})
160+
toLine++
170161
}
171162
}
172163
}

0 commit comments

Comments
 (0)