Skip to content

Commit 478554e

Browse files
committed
Add an option to run golangci-lint from the directory of config file
golangci-lint has issues running in subdirectories of the project, e.g. golangci/golangci-lint#3656, golangci/golangci-lint#3717, so running it from the directory where configuration file is located is the only option for some projects. Make it work as an option, because not every project triggers this problem in golangci-lint, so for these projects overhead of looking up directory of config file and adjusting the paths after is not needed.
1 parent 7dc64b5 commit 478554e

File tree

3 files changed

+101
-17
lines changed

3 files changed

+101
-17
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,18 @@ go install github.com/nametake/golangci-lint-langserver@latest
2020
output debug log
2121
-nolintername
2222
don't show a linter name in message
23+
-from-config-dir
24+
run linter from .golangci-lint.yml directory
2325
```
2426

27+
## Options
28+
29+
`-from-config-dir` makes the `golangci-lint` run from the directory with configuration file.
30+
This makes relative paths in the configuration path work at the cost of small slowdown.
31+
32+
Use this option if your `.golangci-lint.yml` file contains relative paths or if `golangci-lint`
33+
[refuses to run from a subdirectory of a project due to some other reason](https://github.com/golangci/golangci-lint/issues/3717).
34+
2535
## Configuration
2636

2737
You need to set golangci-lint command to initializationOptions with `--out-format json`.

handler.go

+89-16
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,117 @@ import (
1010
"github.com/sourcegraph/jsonrpc2"
1111
)
1212

13-
func NewHandler(logger logger, noLinterName bool) jsonrpc2.Handler {
13+
func NewHandler(logger logger, noLinterName bool, fromConfigDir bool) jsonrpc2.Handler {
1414
handler := &langHandler{
15-
logger: logger,
16-
request: make(chan DocumentURI),
17-
noLinterName: noLinterName,
15+
logger: logger,
16+
request: make(chan DocumentURI),
17+
noLinterName: noLinterName,
18+
fromConfigDir: fromConfigDir,
1819
}
1920
go handler.linter()
2021

2122
return jsonrpc2.HandlerWithError(handler.handle)
2223
}
2324

2425
type langHandler struct {
25-
logger logger
26-
conn *jsonrpc2.Conn
27-
request chan DocumentURI
28-
command []string
29-
noLinterName bool
26+
logger logger
27+
conn *jsonrpc2.Conn
28+
request chan DocumentURI
29+
command []string
30+
noLinterName bool
31+
fromConfigDir bool
3032

3133
rootURI string
3234
}
3335

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

37-
path := uriToPath(string(uri))
38-
dir, file := filepath.Split(path)
39+
// Maybe this should be configurable too?
40+
findConfigCmd := exec.Command(h.command[0], "config", "path")
41+
findConfigCmd.Dir = dir
42+
43+
configFileBytes, err := findConfigCmd.Output()
44+
if err != nil {
45+
return "", err
46+
}
47+
48+
configDirRel, _ := filepath.Split(string(configFileBytes))
49+
50+
// configFileBytes is relative to dir, we need to make it absolute
51+
//
52+
// This assumes that `file` is absolute
53+
return filepath.Join(dir, configDirRel), nil
54+
}
3955

56+
func (h *langHandler) runLinter(dir string) (GolangCILintResult, error) {
4057
//nolint:gosec
4158
cmd := exec.Command(h.command[0], h.command[1:]...)
42-
cmd.Dir = dir
59+
60+
// linter might be ran either from the directory of the file or from the directory of the config file
61+
var checkDir string
62+
if h.fromConfigDir {
63+
// Relative paths in .golangci-lint.yml work, but
64+
// - we need to find the directory with config
65+
// - we have to adjust the paths in the result
66+
67+
configDir, err := h.findConfigDirectory(dir)
68+
if err != nil {
69+
return GolangCILintResult{}, err
70+
}
71+
72+
h.logger.Printf("Found golangci-lint config file in directory %s", configDir)
73+
74+
// Find the original directory, relative to the config dir
75+
checkDir, err = filepath.Rel(configDir, dir)
76+
if err != nil {
77+
return GolangCILintResult{}, err
78+
}
79+
80+
// This runs the linter on the subtree. Non-config-dir option does not
81+
// pass any packages to the command line, which is equivalent.
82+
cmd.Args = append(cmd.Args, checkDir+"/...")
83+
cmd.Dir = configDir
84+
} else {
85+
// Relative paths in golangci-lint.yml don't work, but the paths in report are correct,
86+
// and no additional work is needed
87+
cmd.Dir = dir
88+
}
4389

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

4995
var result GolangCILintResult
5096
if err := json.Unmarshal(b, &result); err != nil {
97+
return GolangCILintResult{}, err
98+
}
99+
100+
// We need to adjust the paths in the result (see above)
101+
if h.fromConfigDir {
102+
var issues []Issue
103+
for _, issue := range result.Issues {
104+
// Strip checkDir from the path
105+
issue.Pos.Filename, err = filepath.Rel(checkDir, issue.Pos.Filename)
106+
if err != nil {
107+
return GolangCILintResult{}, err
108+
}
109+
issues = append(issues, issue)
110+
}
111+
result.Issues = issues
112+
}
113+
114+
return result, nil
115+
}
116+
117+
func (h *langHandler) lint(uri DocumentURI) ([]Diagnostic, error) {
118+
dir, file := filepath.Split(uriToPath(string(uri)))
119+
120+
diagnostics := make([]Diagnostic, 0)
121+
122+
result, err := h.runLinter(dir)
123+
if err != nil {
51124
return diagnostics, err
52125
}
53126

main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ var defaultSeverity = "Warn"
1313
func main() {
1414
debug := flag.Bool("debug", false, "output debug log")
1515
noLinterName := flag.Bool("nolintername", false, "don't show a linter name in message")
16+
fromConfigDir := flag.Bool("from-config-dir", false, "run linter from .golangci-lint.yml directory")
1617
flag.StringVar(&defaultSeverity, "severity", defaultSeverity, "Default severity to use. Choices are: Err(or), Warn(ing), Info(rmation) or Hint")
1718

1819
flag.Parse()
1920

2021
logger := newStdLogger(*debug)
2122

22-
handler := NewHandler(logger, *noLinterName)
23+
handler := NewHandler(logger, *noLinterName, *fromConfigDir)
2324

2425
var connOpt []jsonrpc2.ConnOpt
2526

0 commit comments

Comments
 (0)