Skip to content

Commit 1625299

Browse files
committed
Add a lineReader to provide two-line lookahead
1 parent 837bd65 commit 1625299

File tree

4 files changed

+92
-25
lines changed

4 files changed

+92
-25
lines changed

diff/diff_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func TestParseHunksAndPrintHunks(t *testing.T) {
7474
{filename: "oneline_hunk.diff"},
7575
{filename: "empty.diff"},
7676
{filename: "sample_hunk_lines_start_with_minuses.diff"},
77+
{filename: "sample_hunk_lines_start_with_minuses_pluses.diff"},
7778
}
7879
for _, test := range tests {
7980
diffData, err := ioutil.ReadFile(filepath.Join("testdata", test.filename))

diff/parse.go

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ func ParseMultiFileDiff(diff []byte) ([]*FileDiff, error) {
2323
// NewMultiFileDiffReader returns a new MultiFileDiffReader that reads
2424
// a multi-file unified diff from r.
2525
func NewMultiFileDiffReader(r io.Reader) *MultiFileDiffReader {
26-
return &MultiFileDiffReader{reader: bufio.NewReader(r)}
26+
return &MultiFileDiffReader{reader: newLineReader(r)}
2727
}
2828

2929
// MultiFileDiffReader reads a multi-file unified diff.
3030
type MultiFileDiffReader struct {
3131
line int
3232
offset int64
33-
reader *bufio.Reader
33+
reader *lineReader
3434

3535
// TODO(sqs): line and offset tracking in multi-file diffs is broken; add tests and fix
3636

@@ -85,7 +85,7 @@ func (r *MultiFileDiffReader) ReadFile() (*FileDiff, error) {
8585
// caused by the lack of any hunks, or a malformatted hunk, so we
8686
// need to perform the check here.
8787
hr := fr.HunksReader()
88-
line, err := readLine(r.reader)
88+
line, err := r.reader.readLine()
8989
if err != nil && err != io.EOF {
9090
return fd, err
9191
}
@@ -141,14 +141,14 @@ func ParseFileDiff(diff []byte) (*FileDiff, error) {
141141
// NewFileDiffReader returns a new FileDiffReader that reads a file
142142
// unified diff.
143143
func NewFileDiffReader(r io.Reader) *FileDiffReader {
144-
return &FileDiffReader{reader: bufio.NewReader(r)}
144+
return &FileDiffReader{reader: &lineReader{reader: bufio.NewReader(r)}}
145145
}
146146

147147
// FileDiffReader reads a unified file diff.
148148
type FileDiffReader struct {
149149
line int
150150
offset int64
151-
reader *bufio.Reader
151+
reader *lineReader
152152

153153
// fileHeaderLine is the first file header line, set by:
154154
//
@@ -266,7 +266,7 @@ func (r *FileDiffReader) readOneFileHeader(prefix []byte) (filename string, time
266266

267267
if r.fileHeaderLine == nil {
268268
var err error
269-
line, err = readLine(r.reader)
269+
line, err = r.reader.readLine()
270270
if err == io.EOF {
271271
return "", nil, &ParseError{r.line, r.offset, ErrNoFileHeader}
272272
} else if err != nil {
@@ -318,7 +318,7 @@ func (r *FileDiffReader) ReadExtendedHeaders() ([]string, error) {
318318
var line []byte
319319
if r.fileHeaderLine == nil {
320320
var err error
321-
line, err = readLine(r.reader)
321+
line, err = r.reader.readLine()
322322
if err == io.EOF {
323323
return xheaders, &ParseError{r.line, r.offset, ErrExtendedHeadersEOF}
324324
} else if err != nil {
@@ -447,15 +447,15 @@ func ParseHunks(diff []byte) ([]*Hunk, error) {
447447
// NewHunksReader returns a new HunksReader that reads unified diff hunks
448448
// from r.
449449
func NewHunksReader(r io.Reader) *HunksReader {
450-
return &HunksReader{reader: bufio.NewReader(r)}
450+
return &HunksReader{reader: &lineReader{reader: bufio.NewReader(r)}}
451451
}
452452

453453
// A HunksReader reads hunks from a unified diff.
454454
type HunksReader struct {
455455
line int
456456
offset int64
457457
hunk *Hunk
458-
reader *bufio.Reader
458+
reader *lineReader
459459

460460
nextHunkHeaderLine []byte
461461
}
@@ -474,7 +474,7 @@ func (r *HunksReader) ReadHunk() (*Hunk, error) {
474474
line = r.nextHunkHeaderLine
475475
r.nextHunkHeaderLine = nil
476476
} else {
477-
line, err = readLine(r.reader)
477+
line, err = r.reader.readLine()
478478
if err != nil {
479479
if err == io.EOF && r.hunk != nil {
480480
return r.hunk, nil
@@ -518,12 +518,15 @@ func (r *HunksReader) ReadHunk() (*Hunk, error) {
518518
// If the line starts with `---` and the next one with `+++` we're
519519
// looking at a non-extended file header and need to abort.
520520
if bytes.HasPrefix(line, []byte("---")) {
521-
ok, err := peekPrefix(r.reader, "+++")
521+
ok, err := r.reader.nextLineStartsWith("+++")
522522
if err != nil {
523523
return r.hunk, err
524524
}
525525
if ok {
526-
return r.hunk, &ParseError{r.line, r.offset, &ErrBadHunkLine{Line: line}}
526+
ok2, _ := r.reader.nextNextLineStartsWith(string(hunkPrefix))
527+
if ok2 {
528+
return r.hunk, &ParseError{r.line, r.offset, &ErrBadHunkLine{Line: line}}
529+
}
527530
}
528531
}
529532

@@ -593,19 +596,6 @@ func linePrefix(c byte) bool {
593596
return false
594597
}
595598

596-
// peekPrefix peeks into the given reader to check whether the next
597-
// bytes match the given prefix.
598-
func peekPrefix(reader *bufio.Reader, prefix string) (bool, error) {
599-
next, err := reader.Peek(len(prefix))
600-
if err != nil {
601-
if err == io.EOF {
602-
return false, nil
603-
}
604-
return false, err
605-
}
606-
return bytes.HasPrefix(next, []byte(prefix)), nil
607-
}
608-
609599
// normalizeHeader takes a header of the form:
610600
// "@@ -linestart[,chunksize] +linestart[,chunksize] @@ section"
611601
// and returns two strings, with the first in the form:

diff/reader_util.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,77 @@ package diff
22

33
import (
44
"bufio"
5+
"bytes"
56
"io"
67
)
78

9+
func newLineReader(r io.Reader) *lineReader {
10+
return &lineReader{reader: bufio.NewReader(r)}
11+
}
12+
13+
// lineReader is a wrapper around a bufio.Reader that caches the next line to
14+
// provide lookahead functionality for the next two lines.
15+
type lineReader struct {
16+
reader *bufio.Reader
17+
18+
cachedNextLine []byte
19+
cachedNextLineErr error
20+
}
21+
22+
// readLine returns the next unconsumed line and advances the internal cache of
23+
// the lineReader.
24+
func (l *lineReader) readLine() ([]byte, error) {
25+
if l.cachedNextLine == nil && l.cachedNextLineErr == nil {
26+
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
27+
}
28+
29+
if l.cachedNextLineErr != nil {
30+
return nil, l.cachedNextLineErr
31+
}
32+
33+
next := l.cachedNextLine
34+
35+
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
36+
37+
return next, nil
38+
}
39+
40+
// nextLineStartsWith looks at the line that would be returned by the next call
41+
// to readLine to check whether it has the given prefix.
42+
//
43+
// io.EOF and bufio.ErrBufferFull errors are ignored so that the function can
44+
// be used when at the end of the file.
45+
func (l *lineReader) nextLineStartsWith(prefix string) (bool, error) {
46+
return l.lineHasPrefix(l.cachedNextLine, prefix, l.cachedNextLineErr)
47+
}
48+
49+
// nextNextLineStartsWith checks the prefix of the line *after* the line that
50+
// would be returned by the next readLine.
51+
//
52+
// io.EOF and bufio.ErrBufferFull errors are ignored so that the function can
53+
// be used when at the end of the file.
54+
func (l *lineReader) nextNextLineStartsWith(prefix string) (bool, error) {
55+
next, err := l.reader.Peek(len(prefix))
56+
return l.lineHasPrefix(next, prefix, err)
57+
}
58+
59+
// lineHasPrefix checks whether the given line has the given prefix with
60+
// bytes.HasPrefix.
61+
//
62+
// The readErr should be the error that was returned when the line was read.
63+
// lineHasPrefix checks the error to adjust its return value to, e.g., return
64+
// false and ignore the error when readErr is io.EOF.
65+
func (l *lineReader) lineHasPrefix(line []byte, prefix string, readErr error) (bool, error) {
66+
if readErr != nil {
67+
if readErr == io.EOF || readErr == bufio.ErrBufferFull {
68+
return false, nil
69+
}
70+
return false, readErr
71+
}
72+
73+
return bytes.HasPrefix(line, []byte(prefix)), nil
74+
}
75+
876
// readLine is a helper that mimics the functionality of calling bufio.Scanner.Scan() and
977
// bufio.Scanner.Bytes(), but without the token size limitation. It will read and return
1078
// the next line in the Reader with the trailing newline stripped. It will return an
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@@ -1,5 +1,5 @@
2+
select 1;
3+
--- this is my query
4+
+++ this is my query
5+
select 2;
6+
select 3;
7+
--- this is the last line
8+
+++ this is the last line

0 commit comments

Comments
 (0)