Skip to content

Add an option to run golangci-lint from the directory of config file #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@ go install github.com/nametake/golangci-lint-langserver@latest
output debug log
-nolintername
don't show a linter name in message
-from-config-dir
run linter from .golangci-lint.yml directory
```

## Options

`-from-config-dir` makes the `golangci-lint` run from the directory with configuration file.
This makes relative paths in the configuration path work at the cost of small slowdown.

Use this option if your `.golangci-lint.yml` file contains relative paths or if `golangci-lint`
[refuses to run from a subdirectory of a project due to some other reason](https://github.com/golangci/golangci-lint/issues/3717).

## Configuration

You need to set golangci-lint command to initializationOptions with `--out-format json`.
Expand Down
105 changes: 89 additions & 16 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,117 @@ import (
"github.com/sourcegraph/jsonrpc2"
)

func NewHandler(logger logger, noLinterName bool) jsonrpc2.Handler {
func NewHandler(logger logger, noLinterName bool, fromConfigDir bool) jsonrpc2.Handler {
handler := &langHandler{
logger: logger,
request: make(chan DocumentURI),
noLinterName: noLinterName,
logger: logger,
request: make(chan DocumentURI),
noLinterName: noLinterName,
fromConfigDir: fromConfigDir,
}
go handler.linter()

return jsonrpc2.HandlerWithError(handler.handle)
}

type langHandler struct {
logger logger
conn *jsonrpc2.Conn
request chan DocumentURI
command []string
noLinterName bool
logger logger
conn *jsonrpc2.Conn
request chan DocumentURI
command []string
noLinterName bool
fromConfigDir bool

rootURI string
}

func (h *langHandler) lint(uri DocumentURI) ([]Diagnostic, error) {
diagnostics := make([]Diagnostic, 0)
func (h *langHandler) findConfigDirectory(file string) (string, error) {
dir, _ := filepath.Split(file)

path := uriToPath(string(uri))
dir, file := filepath.Split(path)
// Maybe this should be configurable too?
findConfigCmd := exec.Command(h.command[0], "config", "path")
findConfigCmd.Dir = dir

configFile, err := findConfigCmd.Output()
if err != nil {
return "", err
}

configDirRel, _ := filepath.Split(string(configFile))

// configFileBytes is relative to dir, we need to make it absolute
//
// This assumes that `dir` is absolute
return filepath.Join(dir, configDirRel), nil
}

func (h *langHandler) runLinter(dir string) (GolangCILintResult, error) {
//nolint:gosec
cmd := exec.Command(h.command[0], h.command[1:]...)
cmd.Dir = dir

// linter might be ran either from the directory of the file or from the directory of the config file
var checkDir string
if h.fromConfigDir {
// Relative paths in .golangci-lint.yml work, but
// - we need to find the directory with config
// - we have to adjust the paths in the result

configDir, err := h.findConfigDirectory(dir)
if err != nil {
return GolangCILintResult{}, err
}

h.logger.Printf("Found golangci-lint config file in directory %s", configDir)

// Find the original directory, relative to the config dir
checkDir, err = filepath.Rel(configDir, dir)
if err != nil {
return GolangCILintResult{}, err
}

// This runs the linter on the subtree. Non-config-dir option does not
// pass any packages to the command line, which is equivalent.
cmd.Args = append(cmd.Args, checkDir+"/...")
cmd.Dir = configDir
} else {
// Relative paths in golangci-lint.yml don't work, but the paths in report are correct,
// and no additional work is needed
cmd.Dir = dir
}

b, err := cmd.Output()
if err == nil {
return diagnostics, nil
if err == nil { // This expects that the golangci-lint exits with non-zero code on errors
return GolangCILintResult{}, nil
}

var result GolangCILintResult
if err := json.Unmarshal(b, &result); err != nil {
return GolangCILintResult{}, err
}

// We need to adjust the paths in the result (see above)
if h.fromConfigDir {
var issues []Issue
for _, issue := range result.Issues {
// Strip checkDir from the path
issue.Pos.Filename, err = filepath.Rel(checkDir, issue.Pos.Filename)
if err != nil {
return GolangCILintResult{}, err
}
issues = append(issues, issue)
}
result.Issues = issues
}

return result, nil
}

func (h *langHandler) lint(uri DocumentURI) ([]Diagnostic, error) {
dir, file := filepath.Split(uriToPath(string(uri)))

diagnostics := make([]Diagnostic, 0)

result, err := h.runLinter(dir)
if err != nil {
return diagnostics, err
}

Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ 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")
fromConfigDir := flag.Bool("from-config-dir", false, "run linter from .golangci-lint.yml directory")
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)
handler := NewHandler(logger, *noLinterName, *fromConfigDir)

var connOpt []jsonrpc2.ConnOpt

Expand Down