whatcanGOwrong
This commit is contained in:
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: stable
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
project_name: golangci-lint-langserver
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
builds:
|
||||
- main: .
|
||||
binary: golangci-lint-langserver
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X main.Version={{.Version}}
|
||||
- -X main.Revision={{.ShortCommit}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
archives:
|
||||
- name_template: >-
|
||||
{{- .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end -}}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
release:
|
||||
prerelease: auto
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
---
|
||||
repos:
|
||||
- repo: git@github.com:pre-commit/pre-commit-hooks.git
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: git@github.com:adrienverge/yamllint.git
|
||||
rev: v1.20.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
- repo: git@github.com:dnephin/pre-commit-golang.git
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
# - id: go-lint
|
||||
- id: golangci-lint
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Shogo NAMEKI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,5 @@
|
||||
test:
|
||||
@go test ./...
|
||||
|
||||
install:
|
||||
@go install
|
||||
@@ -0,0 +1,107 @@
|
||||
# golangci-lint-langserver
|
||||
|
||||
golangci-lint-langserver is [golangci-lint](https://github.com/golangci/golangci-lint) language server.
|
||||
|
||||
[](https://asciinema.org/a/308369)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Install [golangci-lint](https://golangci-lint.run).
|
||||
|
||||
```console
|
||||
go install github.com/nametake/golangci-lint-langserver@latest
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```console
|
||||
-debug
|
||||
output debug log
|
||||
-nolintername
|
||||
don't show a linter name in message
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You need to set golangci-lint command to initializationOptions with `--out-format json`.
|
||||
|
||||
### Configuration for [coc.nvim](https://github.com/neoclide/coc.nvim)
|
||||
|
||||
coc-settings.json
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"languageserver": {
|
||||
"golangci-lint-languageserver": {
|
||||
"command": "golangci-lint-langserver",
|
||||
"filetypes": ["go"],
|
||||
"initializationOptions": {
|
||||
"command": ["golangci-lint", "run", "--enable-all", "--disable", "lll", "--out-format", "json", "--issues-exit-code=1"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration for [vim-lsp](https://github.com/prabirshrestha/vim-lsp)
|
||||
|
||||
```vim
|
||||
augroup vim_lsp_golangci_lint_langserver
|
||||
au!
|
||||
autocmd User lsp_setup call lsp#register_server({
|
||||
\ 'name': 'golangci-lint-langserver',
|
||||
\ 'cmd': {server_info->['golangci-lint-langserver']},
|
||||
\ 'initialization_options': {'command': ['golangci-lint', 'run', '--enable-all', '--disable', 'lll', '--out-format', 'json', '--issues-exit-code=1']},
|
||||
\ 'whitelist': ['go'],
|
||||
\ })
|
||||
augroup END
|
||||
```
|
||||
|
||||
[vim-lsp-settings](https://github.com/mattn/vim-lsp-settings) provide installer for golangci-lint-langserver.
|
||||
|
||||
### Configuration for [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig)
|
||||
|
||||
**Requires Neovim [v0.6.1](https://github.com/neovim/neovim/releases/tag/v0.6.1) or [nightly](https://github.com/neovim/neovim/releases/tag/nightly).**
|
||||
|
||||
```lua
|
||||
local lspconfig = require 'lspconfig'
|
||||
local configs = require 'lspconfig/configs'
|
||||
|
||||
if not configs.golangcilsp then
|
||||
configs.golangcilsp = {
|
||||
default_config = {
|
||||
cmd = {'golangci-lint-langserver'},
|
||||
root_dir = lspconfig.util.root_pattern('.git', 'go.mod'),
|
||||
init_options = {
|
||||
command = { "golangci-lint", "run", "--enable-all", "--disable", "lll", "--out-format", "json", "--issues-exit-code=1" };
|
||||
}
|
||||
};
|
||||
}
|
||||
end
|
||||
lspconfig.golangci_lint_ls.setup {
|
||||
filetypes = {'go','gomod'}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration for [lsp-mode](https://github.com/emacs-lsp/lsp-mode) (Emacs)
|
||||
|
||||
```emacs-lisp
|
||||
(with-eval-after-load 'lsp-mode
|
||||
(lsp-register-custom-settings
|
||||
'(("golangci-lint.command"
|
||||
["golangci-lint" "run" "--enable-all" "--disable" "lll" "--out-format" "json" "--issues-exit-code=1"])))
|
||||
|
||||
(lsp-register-client
|
||||
(make-lsp-client :new-connection (lsp-stdio-connection
|
||||
'("golangci-lint-langserver"))
|
||||
:activation-fn (lsp-activate-on "go")
|
||||
:language-id "go"
|
||||
:priority 0
|
||||
:server-id 'golangci-lint
|
||||
:add-on? t
|
||||
:library-folders-fn #'lsp-go--library-default-directories
|
||||
:initialization-options (lambda ()
|
||||
(gethash "golangci-lint"
|
||||
(lsp-configuration-section "golangci-lint")))))
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
module github.com/nametake/golangci-lint-langserver
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/sourcegraph/jsonrpc2 v0.0.0-20191222043438-96c4efab7ee2
|
||||
@@ -0,0 +1,4 @@
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/sourcegraph/jsonrpc2 v0.0.0-20191222043438-96c4efab7ee2 h1:5VGNYxMxzZ8Jb2bARgVl1DNg8vpcd9S8b4MbbjWQ8/w=
|
||||
github.com/sourcegraph/jsonrpc2 v0.0.0-20191222043438-96c4efab7ee2/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
type Issue struct {
|
||||
FromLinter string `json:"FromLinter"`
|
||||
Text string `json:"Text"`
|
||||
Severity string `json:"Severity"`
|
||||
SourceLines []string `json:"SourceLines"`
|
||||
Replacement interface{} `json:"Replacement"`
|
||||
Pos struct {
|
||||
Filename string `json:"Filename"`
|
||||
Offset int `json:"Offset"`
|
||||
Line int `json:"Line"`
|
||||
Column int `json:"Column"`
|
||||
} `json:"Pos"`
|
||||
ExpectNoLint bool `json:"ExpectNoLint"`
|
||||
ExpectedNoLintLinter string `json:"ExpectedNoLintLinter"`
|
||||
LineRange struct {
|
||||
From int `json:"From"`
|
||||
To int `json:"To"`
|
||||
} `json:"LineRange,omitempty"`
|
||||
}
|
||||
|
||||
func (i Issue) DiagSeverity() DiagnosticSeverity {
|
||||
if i.Severity == "" {
|
||||
// TODO: How to get default-severity from .golangci.yml, if available?
|
||||
i.Severity = defaultSeverity
|
||||
}
|
||||
|
||||
switch strings.ToLower(i.Severity) {
|
||||
case "err", "error":
|
||||
return DSError
|
||||
case "warn", "warning":
|
||||
return DSWarning
|
||||
case "info", "information":
|
||||
return DSInformation
|
||||
case "hint":
|
||||
return DSHint
|
||||
default:
|
||||
return DSWarning
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused,deadcode
|
||||
type GolangCILintResult struct {
|
||||
Issues []Issue `json:"Issues"`
|
||||
Report struct {
|
||||
Linters []struct {
|
||||
Name string `json:"Name"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
EnabledByDefault bool `json:"EnabledByDefault,omitempty"`
|
||||
} `json:"Linters"`
|
||||
} `json:"Report"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var _ logger = (*stdLogger)(nil)
|
||||
|
||||
type logger interface {
|
||||
Printf(format string, args ...interface{})
|
||||
DebugJSON(label string, arg interface{})
|
||||
}
|
||||
|
||||
type stdLogger struct {
|
||||
debug bool
|
||||
stderr *log.Logger
|
||||
}
|
||||
|
||||
func newStdLogger(debug bool) *stdLogger {
|
||||
return &stdLogger{
|
||||
debug: debug,
|
||||
stderr: log.New(os.Stderr, "", 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *stdLogger) Printf(format string, args ...interface{}) {
|
||||
l.stderr.Printf(format, args...)
|
||||
}
|
||||
|
||||
func (l *stdLogger) DebugJSON(label string, arg interface{}) {
|
||||
if !l.debug {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(arg)
|
||||
if err != nil {
|
||||
l.stderr.Println(err)
|
||||
}
|
||||
|
||||
l.stderr.Println(label, string(b))
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
type DocumentURI string
|
||||
|
||||
type InitializeParams struct {
|
||||
RootURI string `json:"rootUri,omitempty"`
|
||||
InitializationOptions InitializationOptions `json:"initializationOptions,omitempty"`
|
||||
}
|
||||
|
||||
type InitializationOptions struct {
|
||||
Command []string
|
||||
}
|
||||
|
||||
type InitializeResult struct {
|
||||
Capabilities ServerCapabilities `json:"capabilities,omitempty"`
|
||||
}
|
||||
|
||||
type TextDocumentSyncKind int
|
||||
|
||||
//nolint:unused,deadcode
|
||||
const (
|
||||
TDSKNone TextDocumentSyncKind = iota
|
||||
TDSKFull
|
||||
TDSKIncremental
|
||||
)
|
||||
|
||||
type CompletionProvider struct {
|
||||
ResolveProvider bool `json:"resolveProvider,omitempty"`
|
||||
TriggerCharacters []string `json:"triggerCharacters"`
|
||||
}
|
||||
|
||||
type TextDocumentSyncOptions struct {
|
||||
OpenClose bool `json:"openClose,omitempty"`
|
||||
Change TextDocumentSyncKind `json:"change,omitempty"`
|
||||
WillSave bool `json:"willSave,omitempty"`
|
||||
WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"`
|
||||
Save bool `json:"save,omitempty"`
|
||||
}
|
||||
|
||||
type ServerCapabilities struct {
|
||||
TextDocumentSync TextDocumentSyncOptions `json:"textDocumentSync,omitempty"`
|
||||
CompletionProvider *CompletionProvider `json:"completionProvider,omitempty"`
|
||||
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
|
||||
DefinitionProvider bool `json:"definitionProvider,omitempty"`
|
||||
DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"`
|
||||
HoverProvider bool `json:"hoverProvider,omitempty"`
|
||||
CodeActionProvider bool `json:"codeActionProvider,omitempty"`
|
||||
}
|
||||
|
||||
type TextDocumentItem struct {
|
||||
URI DocumentURI `json:"uri"`
|
||||
LanguageID string `json:"languageId"`
|
||||
Version int `json:"version"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type TextDocumentIdentifier struct {
|
||||
URI DocumentURI `json:"uri"`
|
||||
}
|
||||
|
||||
type DidOpenTextDocumentParams struct {
|
||||
TextDocument TextDocumentItem `json:"textDocument"`
|
||||
}
|
||||
|
||||
type DidSaveTextDocumentParams struct {
|
||||
Text *string `json:"text"`
|
||||
TextDocument TextDocumentIdentifier `json:"textDocument"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
URI string `json:"uri"`
|
||||
Range Range `json:"range"`
|
||||
}
|
||||
|
||||
type Range struct {
|
||||
Start Position `json:"start"`
|
||||
End Position `json:"end"`
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
Line int `json:"line"`
|
||||
Character int `json:"character"`
|
||||
}
|
||||
|
||||
type DiagnosticRelatedInformation struct {
|
||||
Location Location `json:"location"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type DiagnosticSeverity int
|
||||
|
||||
//nolint:unused,deadcode
|
||||
const (
|
||||
DSError DiagnosticSeverity = iota + 1
|
||||
DSWarning
|
||||
DSInformation
|
||||
DSHint
|
||||
)
|
||||
|
||||
type Diagnostic struct {
|
||||
Range Range `json:"range"`
|
||||
Severity DiagnosticSeverity `json:"severity,omitempty"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
Message string `json:"message"`
|
||||
RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"`
|
||||
}
|
||||
|
||||
type PublishDiagnosticsParams struct {
|
||||
URI DocumentURI `json:"uri"`
|
||||
Diagnostics []Diagnostic `json:"diagnostics"`
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
)
|
||||
|
||||
var defaultSeverity = "Warn"
|
||||
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "output debug log")
|
||||
noLinterName := flag.Bool("nolintername", false, "don't show a linter name in message")
|
||||
flag.StringVar(&defaultSeverity, "severity", defaultSeverity, "Default severity to use. Choices are: Err(or), Warn(ing), Info(rmation) or Hint")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
logger := newStdLogger(*debug)
|
||||
|
||||
handler := NewHandler(logger, *noLinterName)
|
||||
|
||||
var connOpt []jsonrpc2.ConnOpt
|
||||
|
||||
logger.Printf("golangci-lint-langserver: connections opened")
|
||||
|
||||
<-jsonrpc2.NewConn(
|
||||
context.Background(),
|
||||
jsonrpc2.NewBufferedStream(stdrwc{}, jsonrpc2.VSCodeObjectCodec{}),
|
||||
handler,
|
||||
connOpt...,
|
||||
).DisconnectNotify()
|
||||
|
||||
logger.Printf("golangci-lint-langserver: connections closed")
|
||||
}
|
||||
|
||||
type stdrwc struct{}
|
||||
|
||||
func (stdrwc) Read(p []byte) (int, error) {
|
||||
return os.Stdin.Read(p)
|
||||
}
|
||||
|
||||
func (stdrwc) Write(p []byte) (int, error) {
|
||||
return os.Stdout.Write(p)
|
||||
}
|
||||
|
||||
func (stdrwc) Close() error {
|
||||
if err := os.Stdin.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Stdout.Close()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func uriToPath(uri string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(uri, "file:///"):
|
||||
uri = uri[len("file://"):]
|
||||
case strings.HasPrefix(uri, "file://"):
|
||||
uri = uri[len("file:/"):]
|
||||
}
|
||||
|
||||
if path, err := url.PathUnescape(uri); err == nil {
|
||||
uri = path
|
||||
}
|
||||
|
||||
if isWindowsDriveURIPath(uri) {
|
||||
uri = strings.ToUpper(string(uri[1])) + uri[2:]
|
||||
}
|
||||
|
||||
return filepath.FromSlash(uri)
|
||||
}
|
||||
|
||||
func isWindowsDriveURIPath(uri string) bool {
|
||||
//nolint:gomnd
|
||||
if len(uri) < 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
|
||||
}
|
||||
Reference in New Issue
Block a user