diff --git a/README.md b/README.md index 5b3509f..1b8ca13 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/handler.go b/handler.go index 06ab267..35f34c9 100644 --- a/handler.go +++ b/handler.go @@ -10,11 +10,12 @@ 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() @@ -22,32 +23,104 @@ func NewHandler(logger logger, noLinterName bool) jsonrpc2.Handler { } 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 } diff --git a/main.go b/main.go index 7848131..f611709 100644 --- a/main.go +++ b/main.go @@ -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