internal/lsp: clean up formatting header handling

This is mostly to set things up to use golden files the same way for other commands.

Change-Id: I7fcc7165706763e655b0e46f0790b367fe5d3d59
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174018
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-04-26 12:45:14 -04:00
parent f97eea45a1
commit 35c670923e
5 changed files with 85 additions and 47 deletions

View File

@ -5,9 +5,13 @@
package cmd_test
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/cmd"
@ -73,3 +77,65 @@ func captureStdOut(t testing.TB, f func()) string {
}
return string(data)
}
// normalizePaths replaces all paths present in s with just the fragment portion
// this is used to make golden files not depend on the temporary paths of the files
func (r *runner) normalizePaths(s string) string {
type entry struct {
path string
index int
}
match := make([]entry, len(r.data.Exported.Modules))
// collect the initial state of all the matchers
for i, m := range r.data.Exported.Modules {
// any random file will do, we collect the first one only
for f := range m.Files {
path := strings.TrimSuffix(r.data.Exported.File(m.Name, f), f)
index := strings.Index(s, path)
match[i] = entry{path, index}
break
}
}
// result should be the same or shorter than the input
buf := bytes.NewBuffer(make([]byte, 0, len(s)))
last := 0
for {
// find the nearest path match to the start of the buffer
next := -1
nearest := len(s)
for i, c := range match {
if c.index >= 0 && nearest > c.index {
nearest = c.index
next = i
}
}
// if there are no matches, we copy the rest of the string and are done
if next < 0 {
buf.WriteString(s[last:])
return buf.String()
}
// we have a match
n := &match[next]
// copy up to the start of the match
buf.WriteString(s[last:n.index])
// skip over the non fragment prefix
last = n.index + len(n.path)
// now try to convert the fragment part
for last < len(s) {
r, size := utf8.DecodeRuneInString(s[last:])
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '/' {
buf.WriteRune(r)
} else if r == '\\' {
buf.WriteRune('/')
} else {
break
}
last += size
}
// see what the next match for this path is
n.index = strings.Index(s[last:], n.path)
if n.index >= 0 {
n.index += last
}
}
}

View File

@ -97,7 +97,7 @@ func (f *format) Run(ctx context.Context, args ...string) error {
}
if f.Diff {
printIt = false
u := diff.ToUnified(filename, filename, lines, ops)
u := diff.ToUnified(filename+".orig", filename, lines, ops)
fmt.Print(u)
}
if printIt {

View File

@ -7,9 +7,9 @@ package cmd_test
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os/exec"
"regexp"
"strings"
"testing"
@ -26,14 +26,7 @@ var formatModes = [][]string{
func (r *runner) Format(t *testing.T, data tests.Formats) {
for _, spn := range data {
for _, mode := range formatModes {
isDiff := false
tag := "gofmt"
for _, arg := range mode {
tag += arg
if arg == "-d" {
isDiff = true
}
}
tag := "gofmt" + strings.Join(mode, "")
uri := spn.URI()
filename, err := uri.Filename()
if err != nil {
@ -45,16 +38,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
contents := buf.String()
// strip the unwanted diff line
if isDiff {
if strings.HasPrefix(contents, "diff -u") {
if i := strings.IndexRune(contents, '\n'); i >= 0 && i < len(contents)-1 {
contents = contents[i+1:]
}
}
contents, _ = stripFileHeader(contents)
}
contents := r.normalizePaths(fixFileHeader(buf.String()))
return ioutil.WriteFile(golden, []byte(contents), 0666)
}))
if expect == "" {
@ -66,13 +50,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
got := captureStdOut(t, func() {
tool.Main(context.Background(), app, append([]string{"format"}, args...))
})
if isDiff {
got, err = stripFileHeader(got)
if err != nil {
t.Errorf("%v: got: %v\n%v", filename, err, got)
continue
}
}
got = r.normalizePaths(got)
// check the first two lines are the expected file header
if expect != got {
t.Errorf("format failed with %#v expected:\n%s\ngot:\n%s", args, expect, got)
@ -81,23 +59,12 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
}
}
func stripFileHeader(s string) (string, error) {
s = strings.TrimSpace(s)
if !strings.HasPrefix(s, "---") {
return s, fmt.Errorf("missing original")
var unifiedHeader = regexp.MustCompile(`^diff -u.*\n(---\s+\S+\.go\.orig)\s+[\d-:. ]+(\n\+\+\+\s+\S+\.go)\s+[\d-:. ]+(\n@@)`)
func fixFileHeader(s string) string {
match := unifiedHeader.FindStringSubmatch(s)
if match == nil {
return s
}
if i := strings.IndexRune(s, '\n'); i >= 0 && i < len(s)-1 {
s = s[i+1:]
} else {
return s, fmt.Errorf("no EOL for original")
}
if !strings.HasPrefix(s, "+++") {
return s, fmt.Errorf("missing output")
}
if i := strings.IndexRune(s, '\n'); i >= 0 && i < len(s)-1 {
s = s[i+1:]
} else {
return s, fmt.Errorf("no EOL for output")
}
return s, nil
return strings.Join(append(match[1:], s[len(match[0]):]), "")
}

View File

@ -1,3 +1,5 @@
--- format/bad_format.go.orig
+++ format/bad_format.go
@@ -1,16 +1,13 @@
package format //@format("package")
@ -14,4 +16,5 @@
-
-
var x int //@diag("x", "LSP", "x declared but not used")
}
}

View File

@ -1,5 +1,7 @@
--- format/newline_format.go.orig
+++ format/newline_format.go
@@ -1,2 +1,2 @@
package format //@format("package")
-func _() {}
\ No newline at end of file
+func _() {}
+func _() {}