240 lines
5.9 KiB
Go
240 lines
5.9 KiB
Go
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
|
|
}
|