internal/lsp: add the format command line
Change-Id: If7c4135b6b81b4f691d0f5eae8b49a1aca028346 Reviewed-on: https://go-review.googlesource.com/c/tools/+/171031 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:
parent
0c752f569a
commit
4eab536980
|
@ -114,8 +114,9 @@ func (app *Application) Run(ctx context.Context, args ...string) error {
|
||||||
func (app *Application) commands() []tool.Application {
|
func (app *Application) commands() []tool.Application {
|
||||||
return []tool.Application{
|
return []tool.Application{
|
||||||
&app.Serve,
|
&app.Serve,
|
||||||
&query{app: app},
|
|
||||||
&check{app: app},
|
&check{app: app},
|
||||||
|
&format{app: app},
|
||||||
|
&query{app: app},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ package cmd_test
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages/packagestest"
|
"golang.org/x/tools/go/packages/packagestest"
|
||||||
|
@ -43,10 +42,6 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.Co
|
||||||
//TODO: add command line completions tests when it works
|
//TODO: add command line completions tests when it works
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Format(t *testing.T, data tests.Formats) {
|
|
||||||
//TODO: add command line formatting tests when it works
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
|
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
|
||||||
//TODO: add command line highlight tests when it works
|
//TODO: add command line highlight tests when it works
|
||||||
}
|
}
|
||||||
|
@ -76,5 +71,5 @@ func captureStdOut(t testing.TB, f func()) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(string(data))
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp"
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
// format implements the format verb for gopls.
|
||||||
|
type format struct {
|
||||||
|
Diff bool `flag:"d" help:"display diffs instead of rewriting files"`
|
||||||
|
Write bool `flag:"w" help:"write result to (source) file instead of stdout"`
|
||||||
|
List bool `flag:"l" help:"list files whose formatting differs from gofmt's"`
|
||||||
|
|
||||||
|
app *Application
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *format) Name() string { return "format" }
|
||||||
|
func (c *format) Usage() string { return "<filerange>" }
|
||||||
|
func (c *format) ShortHelp() string { return "format the code according to the go standard" }
|
||||||
|
func (c *format) DetailedHelp(f *flag.FlagSet) {
|
||||||
|
fmt.Fprint(f.Output(), `
|
||||||
|
The arguments supplied may be simple file names, or ranges within files.
|
||||||
|
|
||||||
|
Example: reformat this file:
|
||||||
|
|
||||||
|
$ gopls format -w internal/lsp/cmd/check.go
|
||||||
|
|
||||||
|
gopls format flags are:
|
||||||
|
`)
|
||||||
|
f.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the check on the files specified by args and prints the
|
||||||
|
// results to stdout.
|
||||||
|
func (f *format) Run(ctx context.Context, args ...string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
// no files, so no results
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
client := &baseClient{}
|
||||||
|
// now we ready to kick things off
|
||||||
|
server, err := f.app.connect(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
spn := span.Parse(arg)
|
||||||
|
m, err := client.AddFile(ctx, spn.URI())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filename, _ := spn.URI().Filename() // this cannot fail, already checked in AddFile above
|
||||||
|
loc, err := m.Location(spn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p := protocol.DocumentRangeFormattingParams{
|
||||||
|
TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
|
||||||
|
Range: loc.Range,
|
||||||
|
}
|
||||||
|
edits, err := server.RangeFormatting(ctx, &p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v: %v", spn, err)
|
||||||
|
}
|
||||||
|
sedits, err := lsp.FromProtocolEdits(m, edits)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v: %v", spn, err)
|
||||||
|
}
|
||||||
|
ops := source.EditsToDiff(sedits)
|
||||||
|
lines := diff.SplitLines(string(m.Content))
|
||||||
|
formatted := strings.Join(diff.ApplyEdits(lines, ops), "")
|
||||||
|
printIt := true
|
||||||
|
if f.List {
|
||||||
|
printIt = false
|
||||||
|
if len(edits) > 0 {
|
||||||
|
fmt.Println(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.Write {
|
||||||
|
printIt = false
|
||||||
|
if len(edits) > 0 {
|
||||||
|
ioutil.WriteFile(filename, []byte(formatted), 0644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.Diff {
|
||||||
|
printIt = false
|
||||||
|
u := diff.ToUnified(filename, filename, lines, ops)
|
||||||
|
fmt.Print(u)
|
||||||
|
}
|
||||||
|
if printIt {
|
||||||
|
fmt.Print(formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// 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 cmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/cmd"
|
||||||
|
"golang.org/x/tools/internal/lsp/tests"
|
||||||
|
"golang.org/x/tools/internal/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var formatModes = [][]string{
|
||||||
|
[]string{},
|
||||||
|
[]string{"-d"},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uri := spn.URI()
|
||||||
|
filename, err := uri.Filename()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args := append(mode, filename)
|
||||||
|
expect := string(r.data.Golden(tag, filename, func(golden string) error {
|
||||||
|
cmd := exec.Command("gofmt", args...)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(golden, []byte(contents), 0666)
|
||||||
|
}))
|
||||||
|
if expect == "" {
|
||||||
|
//TODO: our error handling differs, for now just skip unformattable files
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app := &cmd.Application{}
|
||||||
|
app.Config = r.data.Config
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripFileHeader(s string) (string, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if !strings.HasPrefix(s, "---") {
|
||||||
|
return s, fmt.Errorf("missing original")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
@@ -1,16 +1,13 @@
|
||||||
|
package format //@format("package")
|
||||||
|
|
||||||
|
import (
|
||||||
|
- "runtime"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
+ "runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hello() {
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
var x int //@diag("x", "LSP", "x declared but not used")
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@@ -1,2 +1,2 @@
|
||||||
|
package format //@format("package")
|
||||||
|
-func _() {}
|
||||||
|
\ No newline at end of file
|
||||||
|
+func _() {}
|
Loading…
Reference in New Issue