mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
internal/lsp: move diagnostics logic to source directory
Change-Id: I6bea7a76501e852bbf381eb5dbc79217e1ad10ac Reviewed-on: https://go-review.googlesource.com/c/148889 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
4b1f3b6b16
commit
7f27c5d70a
@ -5,94 +5,34 @@
|
|||||||
package lsp
|
package lsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/token"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
func diagnostics(v *source.View, uri source.URI) (map[string][]protocol.Diagnostic, error) {
|
func toProtocolDiagnostics(v *source.View, diagnostics []source.Diagnostic) []protocol.Diagnostic {
|
||||||
pkg, err := v.GetFile(uri).GetPackage()
|
reports := []protocol.Diagnostic{}
|
||||||
if err != nil {
|
for _, diag := range diagnostics {
|
||||||
return nil, err
|
tok := v.Config.Fset.File(diag.Range.Start)
|
||||||
|
reports = append(reports, protocol.Diagnostic{
|
||||||
|
Message: diag.Message,
|
||||||
|
Range: toProtocolRange(tok, diag.Range),
|
||||||
|
Severity: toProtocolSeverity(diag.Severity),
|
||||||
|
Source: "LSP",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if pkg == nil {
|
return reports
|
||||||
return nil, fmt.Errorf("package for %v not found", uri)
|
|
||||||
}
|
|
||||||
reports := make(map[string][]protocol.Diagnostic)
|
|
||||||
for _, filename := range pkg.GoFiles {
|
|
||||||
reports[filename] = []protocol.Diagnostic{}
|
|
||||||
}
|
|
||||||
var parseErrors, typeErrors []packages.Error
|
|
||||||
for _, err := range pkg.Errors {
|
|
||||||
switch err.Kind {
|
|
||||||
case packages.ParseError:
|
|
||||||
parseErrors = append(parseErrors, err)
|
|
||||||
case packages.TypeError:
|
|
||||||
typeErrors = append(typeErrors, err)
|
|
||||||
default:
|
|
||||||
// ignore other types of errors
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Don't report type errors if there are parse errors.
|
|
||||||
errors := typeErrors
|
|
||||||
if len(parseErrors) > 0 {
|
|
||||||
errors = parseErrors
|
|
||||||
}
|
|
||||||
for _, err := range errors {
|
|
||||||
pos := parseErrorPos(err)
|
|
||||||
line := float64(pos.Line) - 1
|
|
||||||
col := float64(pos.Column) - 1
|
|
||||||
diagnostic := protocol.Diagnostic{
|
|
||||||
// TODO(rstambler): Add support for diagnostic ranges.
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: line,
|
|
||||||
Character: col,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: line,
|
|
||||||
Character: col,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Severity: protocol.SeverityError,
|
|
||||||
Source: "LSP: Go compiler",
|
|
||||||
Message: err.Msg,
|
|
||||||
}
|
|
||||||
if _, ok := reports[pos.Filename]; ok {
|
|
||||||
reports[pos.Filename] = append(reports[pos.Filename], diagnostic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reports, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseErrorPos(pkgErr packages.Error) (pos token.Position) {
|
func toProtocolSeverity(severity source.DiagnosticSeverity) protocol.DiagnosticSeverity {
|
||||||
remainder1, first, hasLine := chop(pkgErr.Pos)
|
switch severity {
|
||||||
remainder2, second, hasColumn := chop(remainder1)
|
case source.SeverityError:
|
||||||
if hasLine && hasColumn {
|
return protocol.SeverityError
|
||||||
pos.Filename = remainder2
|
case source.SeverityWarning:
|
||||||
pos.Line = second
|
return protocol.SeverityWarning
|
||||||
pos.Column = first
|
case source.SeverityHint:
|
||||||
} else if hasLine {
|
return protocol.SeverityHint
|
||||||
pos.Filename = remainder1
|
case source.SeverityInformation:
|
||||||
pos.Line = first
|
return protocol.SeverityInformation
|
||||||
}
|
}
|
||||||
return pos
|
return protocol.SeverityError // default
|
||||||
}
|
|
||||||
|
|
||||||
func chop(text string) (remainder string, value int, ok bool) {
|
|
||||||
i := strings.LastIndex(text, ":")
|
|
||||||
if i < 0 {
|
|
||||||
return text, 0, false
|
|
||||||
}
|
|
||||||
v, err := strconv.ParseInt(text[i+1:], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return text, 0, false
|
|
||||||
}
|
|
||||||
return text[:i], int(v), true
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ func TestLSP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
||||||
dir := "testdata"
|
const dir = "testdata"
|
||||||
|
|
||||||
files := packagestest.MustCopyFileTree(dir)
|
files := packagestest.MustCopyFileTree(dir)
|
||||||
subdirs, err := ioutil.ReadDir(dir)
|
subdirs, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,7 +96,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Severity: protocol.SeverityError,
|
Severity: protocol.SeverityError,
|
||||||
Source: "LSP: Go compiler",
|
Source: "LSP",
|
||||||
Message: msg,
|
Message: msg,
|
||||||
}
|
}
|
||||||
if _, ok := expectedDiagnostics[pos.Filename]; ok {
|
if _, ok := expectedDiagnostics[pos.Filename]; ok {
|
||||||
@ -153,11 +154,12 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
func testDiagnostics(t *testing.T, v *source.View, pkgs []*packages.Package, wants map[string][]protocol.Diagnostic) {
|
func testDiagnostics(t *testing.T, v *source.View, pkgs []*packages.Package, wants map[string][]protocol.Diagnostic) {
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
for _, filename := range pkg.GoFiles {
|
for _, filename := range pkg.GoFiles {
|
||||||
diagnostics, err := diagnostics(v, source.ToURI(filename))
|
f := v.GetFile(source.ToURI(filename))
|
||||||
|
diagnostics, err := source.Diagnostics(context.Background(), v, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
got := diagnostics[filename]
|
got := toProtocolDiagnostics(v, diagnostics[filename])
|
||||||
sort.Slice(got, func(i int, j int) bool {
|
sort.Slice(got, func(i int, j int) bool {
|
||||||
return got[i].Range.Start.Line < got[j].Range.Start.Line
|
return got[i].Range.Start.Line < got[j].Range.Start.Line
|
||||||
})
|
})
|
||||||
|
@ -49,10 +49,8 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
|
|||||||
},
|
},
|
||||||
DocumentFormattingProvider: true,
|
DocumentFormattingProvider: true,
|
||||||
DocumentRangeFormattingProvider: true,
|
DocumentRangeFormattingProvider: true,
|
||||||
CompletionProvider: protocol.CompletionOptions{
|
CompletionProvider: protocol.CompletionOptions{},
|
||||||
TriggerCharacters: []string{"."},
|
DefinitionProvider: true,
|
||||||
},
|
|
||||||
DefinitionProvider: true,
|
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -119,14 +117,16 @@ func (s *server) cacheAndDiagnoseFile(ctx context.Context, uri protocol.Document
|
|||||||
f := s.view.GetFile(source.URI(uri))
|
f := s.view.GetFile(source.URI(uri))
|
||||||
f.SetContent([]byte(text))
|
f.SetContent([]byte(text))
|
||||||
go func() {
|
go func() {
|
||||||
reports, err := diagnostics(s.view, f.URI)
|
f := s.view.GetFile(source.URI(uri))
|
||||||
if err == nil {
|
reports, err := source.Diagnostics(ctx, s.view, f)
|
||||||
for filename, diagnostics := range reports {
|
if err != nil {
|
||||||
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
return // handle error?
|
||||||
URI: protocol.DocumentURI(source.ToURI(filename)),
|
}
|
||||||
Diagnostics: diagnostics,
|
for filename, diagnostics := range reports {
|
||||||
})
|
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
||||||
}
|
URI: protocol.DocumentURI(source.ToURI(filename)),
|
||||||
|
Diagnostics: toProtocolDiagnostics(s.view, diagnostics),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
148
internal/lsp/source/diagnostics.go
Normal file
148
internal/lsp/source/diagnostics.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2018 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 source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go/token"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Diagnostic struct {
|
||||||
|
Range Range
|
||||||
|
Severity DiagnosticSeverity
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiagnosticSeverity int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SeverityError DiagnosticSeverity = iota
|
||||||
|
SeverityWarning
|
||||||
|
SeverityHint
|
||||||
|
SeverityInformation
|
||||||
|
)
|
||||||
|
|
||||||
|
func Diagnostics(ctx context.Context, v *View, f *File) (map[string][]Diagnostic, error) {
|
||||||
|
pkg, err := f.GetPackage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Prepare the reports we will send for this package.
|
||||||
|
reports := make(map[string][]Diagnostic)
|
||||||
|
for _, filename := range pkg.GoFiles {
|
||||||
|
reports[filename] = []Diagnostic{}
|
||||||
|
}
|
||||||
|
var parseErrors, typeErrors []packages.Error
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
switch err.Kind {
|
||||||
|
case packages.ParseError:
|
||||||
|
parseErrors = append(parseErrors, err)
|
||||||
|
case packages.TypeError:
|
||||||
|
typeErrors = append(typeErrors, err)
|
||||||
|
default:
|
||||||
|
// ignore other types of errors
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't report type errors if there are parse errors.
|
||||||
|
diags := typeErrors
|
||||||
|
if len(parseErrors) > 0 {
|
||||||
|
diags = parseErrors
|
||||||
|
}
|
||||||
|
for _, diag := range diags {
|
||||||
|
filename, start := v.errorPos(diag)
|
||||||
|
// TODO(rstambler): Add support for diagnostic ranges.
|
||||||
|
end := start
|
||||||
|
diagnostic := Diagnostic{
|
||||||
|
Range: Range{
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
},
|
||||||
|
Message: diag.Msg,
|
||||||
|
Severity: SeverityError,
|
||||||
|
}
|
||||||
|
if _, ok := reports[filename]; ok {
|
||||||
|
reports[filename] = append(reports[filename], diagnostic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) errorPos(pkgErr packages.Error) (string, token.Pos) {
|
||||||
|
remainder1, first, hasLine := chop(pkgErr.Pos)
|
||||||
|
remainder2, second, hasColumn := chop(remainder1)
|
||||||
|
var pos token.Position
|
||||||
|
if hasLine && hasColumn {
|
||||||
|
pos.Filename = remainder2
|
||||||
|
pos.Line = second
|
||||||
|
pos.Column = first
|
||||||
|
} else if hasLine {
|
||||||
|
pos.Filename = remainder1
|
||||||
|
pos.Line = first
|
||||||
|
}
|
||||||
|
f := v.GetFile(ToURI(pos.Filename))
|
||||||
|
if f == nil {
|
||||||
|
return "", token.NoPos
|
||||||
|
}
|
||||||
|
tok, err := f.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", token.NoPos
|
||||||
|
}
|
||||||
|
return pos.Filename, fromTokenPosition(tok, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func chop(text string) (remainder string, value int, ok bool) {
|
||||||
|
i := strings.LastIndex(text, ":")
|
||||||
|
if i < 0 {
|
||||||
|
return text, 0, false
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseInt(text[i+1:], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return text, 0, false
|
||||||
|
}
|
||||||
|
return text[:i], int(v), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromTokenPosition converts a token.Position (1-based line and column
|
||||||
|
// number) to a token.Pos (byte offset value).
|
||||||
|
// It requires the token file the pos belongs to in order to do this.
|
||||||
|
func fromTokenPosition(f *token.File, pos token.Position) token.Pos {
|
||||||
|
line := lineStart(f, pos.Line)
|
||||||
|
return line + token.Pos(pos.Column-1) // TODO: this is wrong, bytes not characters
|
||||||
|
}
|
||||||
|
|
||||||
|
// this functionality was borrowed from the analysisutil package
|
||||||
|
func lineStart(f *token.File, line int) token.Pos {
|
||||||
|
// Use binary search to find the start offset of this line.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): eventually replace this function with the
|
||||||
|
// simpler and more efficient (*go/token.File).LineStart, added
|
||||||
|
// in go1.12.
|
||||||
|
|
||||||
|
min := 0 // inclusive
|
||||||
|
max := f.Size() // exclusive
|
||||||
|
for {
|
||||||
|
offset := (min + max) / 2
|
||||||
|
pos := f.Pos(offset)
|
||||||
|
posn := f.Position(pos)
|
||||||
|
if posn.Line == line {
|
||||||
|
return pos - (token.Pos(posn.Column) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if min+1 >= max {
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
|
||||||
|
if posn.Line < line {
|
||||||
|
min = offset
|
||||||
|
} else {
|
||||||
|
max = offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user