whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
)
|
||||
|
||||
func NewHandler(logger logger, noLinterName bool) jsonrpc2.Handler {
|
||||
handler := &langHandler{
|
||||
logger: logger,
|
||||
request: make(chan DocumentURI),
|
||||
noLinterName: noLinterName,
|
||||
}
|
||||
go handler.linter()
|
||||
|
||||
return jsonrpc2.HandlerWithError(handler.handle)
|
||||
}
|
||||
|
||||
type langHandler struct {
|
||||
logger logger
|
||||
conn *jsonrpc2.Conn
|
||||
request chan DocumentURI
|
||||
command []string
|
||||
noLinterName bool
|
||||
|
||||
rootURI string
|
||||
rootDir string
|
||||
}
|
||||
|
||||
func (h *langHandler) errToDiagnostics(err error) []Diagnostic {
|
||||
var message string
|
||||
switch e := err.(type) {
|
||||
case *exec.ExitError:
|
||||
message = string(e.Stderr)
|
||||
default:
|
||||
h.logger.DebugJSON("golangci-lint-langserver: errToDiagnostics message", message)
|
||||
message = e.Error()
|
||||
}
|
||||
return []Diagnostic{
|
||||
{Severity: DSError, Message: message},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *langHandler) lint(uri DocumentURI) ([]Diagnostic, error) {
|
||||
diagnostics := make([]Diagnostic, 0)
|
||||
|
||||
path := uriToPath(string(uri))
|
||||
dir, file := filepath.Split(path)
|
||||
|
||||
args := make([]string, 0, len(h.command))
|
||||
args = append(args, h.command[1:]...)
|
||||
args = append(args, dir)
|
||||
//nolint:gosec
|
||||
cmd := exec.Command(h.command[0], args...)
|
||||
if strings.HasPrefix(path, h.rootDir) {
|
||||
cmd.Dir = h.rootDir
|
||||
file = path[len(h.rootDir)+1:]
|
||||
} else {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
h.logger.DebugJSON("golangci-lint-langserver: golingci-lint cmd", cmd)
|
||||
|
||||
b, err := cmd.Output()
|
||||
if err == nil {
|
||||
return diagnostics, nil
|
||||
} else if len(b) == 0 {
|
||||
// golangci-lint would output critical error to stderr rather than stdout
|
||||
// https://github.com/nametake/golangci-lint-langserver/issues/24
|
||||
return h.errToDiagnostics(err), nil
|
||||
}
|
||||
|
||||
var result GolangCILintResult
|
||||
if err := json.Unmarshal(b, &result); err != nil {
|
||||
return h.errToDiagnostics(err), nil
|
||||
}
|
||||
|
||||
h.logger.DebugJSON("golangci-lint-langserver: result:", result)
|
||||
|
||||
for _, issue := range result.Issues {
|
||||
issue := issue
|
||||
|
||||
if file != issue.Pos.Filename {
|
||||
continue
|
||||
}
|
||||
|
||||
d := Diagnostic{
|
||||
Range: Range{
|
||||
Start: Position{
|
||||
Line: max(issue.Pos.Line-1, 0),
|
||||
Character: max(issue.Pos.Column-1, 0),
|
||||
},
|
||||
End: Position{
|
||||
Line: max(issue.Pos.Line-1, 0),
|
||||
Character: max(issue.Pos.Column-1, 0),
|
||||
},
|
||||
},
|
||||
Severity: issue.DiagSeverity(),
|
||||
Source: &issue.FromLinter,
|
||||
Message: h.diagnosticMessage(&issue),
|
||||
}
|
||||
diagnostics = append(diagnostics, d)
|
||||
}
|
||||
|
||||
return diagnostics, nil
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (h *langHandler) diagnosticMessage(issue *Issue) string {
|
||||
if h.noLinterName {
|
||||
return issue.Text
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: %s", issue.FromLinter, issue.Text)
|
||||
}
|
||||
|
||||
func (h *langHandler) linter() {
|
||||
for {
|
||||
uri, ok := <-h.request
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
diagnostics, err := h.lint(uri)
|
||||
if err != nil {
|
||||
h.logger.Printf("%s", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := h.conn.Notify(
|
||||
context.Background(),
|
||||
"textDocument/publishDiagnostics",
|
||||
&PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: diagnostics,
|
||||
}); err != nil {
|
||||
h.logger.Printf("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *langHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
|
||||
h.logger.DebugJSON("golangci-lint-langserver: request:", req)
|
||||
|
||||
switch req.Method {
|
||||
case "initialize":
|
||||
return h.handleInitialize(ctx, conn, req)
|
||||
case "initialized":
|
||||
return
|
||||
case "shutdown":
|
||||
return h.handleShutdown(ctx, conn, req)
|
||||
case "textDocument/didOpen":
|
||||
return h.handleTextDocumentDidOpen(ctx, conn, req)
|
||||
case "textDocument/didClose":
|
||||
return h.handleTextDocumentDidClose(ctx, conn, req)
|
||||
case "textDocument/didChange":
|
||||
return h.handleTextDocumentDidChange(ctx, conn, req)
|
||||
case "textDocument/didSave":
|
||||
return h.handleTextDocumentDidSave(ctx, conn, req)
|
||||
case "workspace/didChangeConfiguration":
|
||||
return h.handlerWorkspaceDidChangeConfiguration(ctx, conn, req)
|
||||
}
|
||||
|
||||
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("method not supported: %s", req.Method)}
|
||||
}
|
||||
|
||||
func (h *langHandler) handleInitialize(_ context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
|
||||
var params InitializeParams
|
||||
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.rootURI = params.RootURI
|
||||
h.rootDir = uriToPath(params.RootURI)
|
||||
h.conn = conn
|
||||
h.command = params.InitializationOptions.Command
|
||||
|
||||
return InitializeResult{
|
||||
Capabilities: ServerCapabilities{
|
||||
TextDocumentSync: TextDocumentSyncOptions{
|
||||
Change: TDSKNone,
|
||||
OpenClose: true,
|
||||
Save: true,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handleShutdown(_ context.Context, _ *jsonrpc2.Conn, _ *jsonrpc2.Request) (result interface{}, err error) {
|
||||
close(h.request)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handleTextDocumentDidOpen(_ context.Context, _ *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
|
||||
var params DidOpenTextDocumentParams
|
||||
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.request <- params.TextDocument.URI
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handleTextDocumentDidClose(_ context.Context, _ *jsonrpc2.Conn, _ *jsonrpc2.Request) (result interface{}, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handleTextDocumentDidChange(_ context.Context, _ *jsonrpc2.Conn, _ *jsonrpc2.Request) (result interface{}, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handleTextDocumentDidSave(_ context.Context, _ *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
|
||||
var params DidSaveTextDocumentParams
|
||||
if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.request <- params.TextDocument.URI
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (h *langHandler) handlerWorkspaceDidChangeConfiguration(_ context.Context, _ *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user