mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/lsp: adding command line access to diagnostics
Change-Id: I011e337ec2bce93199cf762c09e002442ca1bd0d Reviewed-on: https://go-review.googlesource.com/c/tools/+/167697 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
8889bfc21e
commit
ab21143f23
110
internal/lsp/cmd/check.go
Normal file
110
internal/lsp/cmd/check.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// 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"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
// definition implements the definition noun for the query command.
|
||||||
|
type check struct {
|
||||||
|
app *Application
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkClient struct {
|
||||||
|
baseClient
|
||||||
|
diagnostics chan entry
|
||||||
|
}
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
uri span.URI
|
||||||
|
diagnostics []protocol.Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) Name() string { return "check" }
|
||||||
|
func (c *check) Usage() string { return "<filename>" }
|
||||||
|
func (c *check) ShortHelp() string { return "show diagnostic results for the specified file" }
|
||||||
|
func (c *check) DetailedHelp(f *flag.FlagSet) {
|
||||||
|
fmt.Fprint(f.Output(), `
|
||||||
|
Example: show the diagnostic results of this file:
|
||||||
|
|
||||||
|
$ gopls check internal/lsp/cmd/check.go
|
||||||
|
|
||||||
|
gopls check flags are:
|
||||||
|
`)
|
||||||
|
f.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the check on the files specified by args and prints the
|
||||||
|
// results to stdout.
|
||||||
|
func (c *check) Run(ctx context.Context, args ...string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
// no files, so no results
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
client := &checkClient{
|
||||||
|
diagnostics: make(chan entry),
|
||||||
|
}
|
||||||
|
client.app = c.app
|
||||||
|
checking := map[span.URI][]byte{}
|
||||||
|
// now we ready to kick things off
|
||||||
|
server, err := c.app.connect(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
uri := span.FileURI(arg)
|
||||||
|
content, err := ioutil.ReadFile(arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
checking[uri] = content
|
||||||
|
p := &protocol.DidOpenTextDocumentParams{}
|
||||||
|
p.TextDocument.URI = string(uri)
|
||||||
|
p.TextDocument.Text = string(content)
|
||||||
|
if err := server.DidOpen(ctx, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// now wait for results
|
||||||
|
for entry := range client.diagnostics {
|
||||||
|
//TODO:timeout?
|
||||||
|
content, found := checking[entry.uri]
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
f := fset.AddFile(string(entry.uri), -1, len(content))
|
||||||
|
f.SetLinesForContent(content)
|
||||||
|
m := protocol.NewColumnMapper(entry.uri, fset, f, content)
|
||||||
|
for _, d := range entry.diagnostics {
|
||||||
|
spn, err := m.RangeSpan(d.Range)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not convert position %v for %q", d.Range, d.Message)
|
||||||
|
}
|
||||||
|
fmt.Printf("%v: %v\n", spn, d.Message)
|
||||||
|
}
|
||||||
|
delete(checking, entry.uri)
|
||||||
|
if len(checking) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("did not get all results")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *checkClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
|
||||||
|
c.diagnostics <- entry{
|
||||||
|
uri: span.URI(p.URI),
|
||||||
|
diagnostics: p.Diagnostics,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
78
internal/lsp/cmd/check_test.go
Normal file
78
internal/lsp/cmd/check_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages/packagestest"
|
||||||
|
"golang.org/x/tools/internal/lsp/cmd"
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
"golang.org/x/tools/internal/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type diagnostics map[string][]source.Diagnostic
|
||||||
|
|
||||||
|
func (l diagnostics) collect(spn span.Span, msgSource, msg string) {
|
||||||
|
fname, err := spn.URI().Filename()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//TODO: diagnostics with range
|
||||||
|
spn = span.New(spn.URI(), spn.Start(), span.Point{})
|
||||||
|
l[fname] = append(l[fname], source.Diagnostic{
|
||||||
|
Span: spn,
|
||||||
|
Message: msg,
|
||||||
|
Source: msgSource,
|
||||||
|
Severity: source.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l diagnostics) test(t *testing.T, e *packagestest.Exported) {
|
||||||
|
count := 0
|
||||||
|
for fname, want := range l {
|
||||||
|
if len(want) == 1 && want[0].Message == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args := []string{"check", fname}
|
||||||
|
app := &cmd.Application{}
|
||||||
|
app.Config = *e.Config
|
||||||
|
out := captureStdOut(t, func() {
|
||||||
|
tool.Main(context.Background(), app, args)
|
||||||
|
})
|
||||||
|
// parse got into a collection of reports
|
||||||
|
got := map[string]struct{}{}
|
||||||
|
for _, l := range strings.Split(out, "\n") {
|
||||||
|
// parse and reprint to normalize the span
|
||||||
|
bits := strings.SplitN(l, ": ", 2)
|
||||||
|
if len(bits) == 2 {
|
||||||
|
spn := span.Parse(strings.TrimSpace(bits[0]))
|
||||||
|
spn = span.New(spn.URI(), spn.Start(), span.Point{})
|
||||||
|
l = fmt.Sprintf("%s: %s", spn, strings.TrimSpace(bits[1]))
|
||||||
|
}
|
||||||
|
got[l] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, diag := range want {
|
||||||
|
expect := fmt.Sprintf("%v: %v", diag.Span, diag.Message)
|
||||||
|
_, found := got[expect]
|
||||||
|
if !found {
|
||||||
|
t.Errorf("missing diagnostic %q", expect)
|
||||||
|
} else {
|
||||||
|
delete(got, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for extra, _ := range got {
|
||||||
|
t.Errorf("extra diagnostic %q", extra)
|
||||||
|
}
|
||||||
|
count += len(want)
|
||||||
|
}
|
||||||
|
if count != expectedDiagnosticsCount {
|
||||||
|
t.Errorf("got %v diagnostics expected %v", count, expectedDiagnosticsCount)
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,15 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
|
"golang.org/x/tools/internal/jsonrpc2"
|
||||||
|
"golang.org/x/tools/internal/lsp"
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/tool"
|
"golang.org/x/tools/internal/tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,6 +82,11 @@ func (app *Application) Run(ctx context.Context, args ...string) error {
|
|||||||
tool.Main(ctx, &app.Serve, args)
|
tool.Main(ctx, &app.Serve, args)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if app.Config.Dir == "" {
|
||||||
|
if wd, err := os.Getwd(); err == nil {
|
||||||
|
app.Config.Dir = wd
|
||||||
|
}
|
||||||
|
}
|
||||||
app.Config.Mode = packages.LoadSyntax
|
app.Config.Mode = packages.LoadSyntax
|
||||||
app.Config.Tests = true
|
app.Config.Tests = true
|
||||||
if app.Config.Fset == nil {
|
if app.Config.Fset == nil {
|
||||||
@ -101,5 +113,77 @@ func (app *Application) commands() []tool.Application {
|
|||||||
return []tool.Application{
|
return []tool.Application{
|
||||||
&app.Serve,
|
&app.Serve,
|
||||||
&query{app: app},
|
&query{app: app},
|
||||||
|
&check{app: app},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Application) connect(ctx context.Context, client protocol.Client) (protocol.Server, error) {
|
||||||
|
var server protocol.Server
|
||||||
|
if app.Remote != "" {
|
||||||
|
conn, err := net.Dial("tcp", app.Remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stream := jsonrpc2.NewHeaderStream(conn, conn)
|
||||||
|
_, server = protocol.RunClient(ctx, stream, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server = lsp.NewServer(client)
|
||||||
|
}
|
||||||
|
params := &protocol.InitializeParams{}
|
||||||
|
params.RootURI = string(span.FileURI(app.Config.Dir))
|
||||||
|
if _, err := server.Initialize(ctx, params); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseClient struct {
|
||||||
|
protocol.Server
|
||||||
|
app *Application
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
|
||||||
|
func (c *baseClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error { return nil }
|
||||||
|
func (c *baseClient) Telemetry(ctx context.Context, t interface{}) error { return nil }
|
||||||
|
func (c *baseClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) Configuration(ctx context.Context, p *protocol.ConfigurationParams) ([]interface{}, error) {
|
||||||
|
results := make([]interface{}, len(p.Items))
|
||||||
|
for i, item := range p.Items {
|
||||||
|
if item.Section != "gopls" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
env := map[string]interface{}{}
|
||||||
|
for _, value := range c.app.Config.Env {
|
||||||
|
l := strings.SplitN(value, "=", 2)
|
||||||
|
if len(l) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
env[l[0]] = l[1]
|
||||||
|
}
|
||||||
|
results[i] = map[string]interface{}{"env": env}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
func (c *baseClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
package cmd_test
|
package cmd_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -50,14 +46,6 @@ func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
exported := packagestest.Export(t, exporter, modules)
|
exported := packagestest.Export(t, exporter, modules)
|
||||||
defer exported.Cleanup()
|
defer exported.Cleanup()
|
||||||
|
|
||||||
// Merge the exported.Config with the view.Config.
|
|
||||||
cfg := *exported.Config
|
|
||||||
cfg.Fset = token.NewFileSet()
|
|
||||||
cfg.Context = context.Background()
|
|
||||||
cfg.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
|
||||||
return parser.ParseFile(fset, filename, src, parser.AllErrors|parser.ParseComments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a first pass to collect special markers for completion.
|
// Do a first pass to collect special markers for completion.
|
||||||
if err := exported.Expect(map[string]interface{}{
|
if err := exported.Expect(map[string]interface{}{
|
||||||
"item": func(name string, r packagestest.Range, _, _ string) {
|
"item": func(name string, r packagestest.Range, _, _ string) {
|
||||||
@ -113,34 +101,10 @@ func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type diagnostics map[span.Span][]source.Diagnostic
|
|
||||||
type completionItems map[span.Range]*source.CompletionItem
|
type completionItems map[span.Range]*source.CompletionItem
|
||||||
type completions map[span.Span][]span.Span
|
type completions map[span.Span][]span.Span
|
||||||
type formats map[span.URI]span.Span
|
type formats map[span.URI]span.Span
|
||||||
|
|
||||||
func (l diagnostics) collect(spn span.Span, msgSource, msg string) {
|
|
||||||
l[spn] = append(l[spn], source.Diagnostic{
|
|
||||||
Span: spn,
|
|
||||||
Message: msg,
|
|
||||||
Source: msgSource,
|
|
||||||
Severity: source.SeverityError,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l diagnostics) test(t *testing.T, e *packagestest.Exported) {
|
|
||||||
count := 0
|
|
||||||
for _, want := range l {
|
|
||||||
if len(want) == 1 && want[0].Message == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
count += len(want)
|
|
||||||
}
|
|
||||||
if count != expectedDiagnosticsCount {
|
|
||||||
t.Errorf("got %v diagnostics expected %v", count, expectedDiagnosticsCount)
|
|
||||||
}
|
|
||||||
//TODO: add command line diagnostics tests when it works
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l completionItems) collect(spn span.Range, label, detail, kind string) {
|
func (l completionItems) collect(spn span.Range, label, detail, kind string) {
|
||||||
var k source.CompletionItemKind
|
var k source.CompletionItemKind
|
||||||
switch kind {
|
switch kind {
|
||||||
|
@ -25,12 +25,18 @@ import (
|
|||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewServer
|
||||||
|
func NewServer(client protocol.Client) protocol.Server {
|
||||||
|
return &server{
|
||||||
|
client: client,
|
||||||
|
configured: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RunServer starts an LSP server on the supplied stream, and waits until the
|
// RunServer starts an LSP server on the supplied stream, and waits until the
|
||||||
// stream is closed.
|
// stream is closed.
|
||||||
func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error {
|
func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error {
|
||||||
s := &server{
|
s := NewServer(nil).(*server)
|
||||||
configured: make(chan struct{}),
|
|
||||||
}
|
|
||||||
conn, client := protocol.RunServer(ctx, stream, s, opts...)
|
conn, client := protocol.RunServer(ctx, stream, s, opts...)
|
||||||
s.client = client
|
s.client = client
|
||||||
return conn.Wait(ctx)
|
return conn.Wait(ctx)
|
||||||
|
@ -58,7 +58,14 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
|
|||||||
}
|
}
|
||||||
pkg := f.GetPackage(ctx)
|
pkg := f.GetPackage(ctx)
|
||||||
if pkg == nil {
|
if pkg == nil {
|
||||||
return nil, fmt.Errorf("diagnostics: no package found for %v", f.URI())
|
return map[span.URI][]Diagnostic{
|
||||||
|
uri: []Diagnostic{{
|
||||||
|
Source: "LSP",
|
||||||
|
Span: span.New(uri, span.Point{}, span.Point{}),
|
||||||
|
Message: fmt.Sprintf("not part of a package"),
|
||||||
|
Severity: SeverityError,
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
// Prepare the reports we will send for this package.
|
// Prepare the reports we will send for this package.
|
||||||
reports := make(map[span.URI][]Diagnostic)
|
reports := make(map[span.URI][]Diagnostic)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user