mfussenegger / nvim-lint

An asynchronous linter plugin for Neovim complementary to the built-in Language Server Protocol support.
GNU General Public License v3.0
2.01k stars 209 forks source link

golangci-lint: goimports, gofumpt and revive don't work #227

Closed Integralist closed 1 year ago

Integralist commented 2 years ago

I'm trying to use nvim-lint to run golangci-lint (discussion) but it's not working 😞

This is what I have at the moment

Here is my golangci-lint configuration file ~/.golangci-lint:

# https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
# https://golangci-lint.run/usage/linters/#enabled-by-default-linters
linters:
  enable:
    - deadcode
    - errcheck
    - gosimple
    - gofumpt
    - goimports
    - govet
    - ineffassign
    - revive
    - staticcheck
    - structcheck
    - typecheck
    - unused
    - varcheck

Here is an example code file that has some issues:

package main

// NOTICE I'm using the io/os/fmt packages but I don't import them.
// This is to test whether goimports is being executed when I save/update this file.

// Foo is an unused constant (varcheck where are you?)
const Foo = "not used"

// Bar is an unused variable (varcheck where are you?)
var Bar = "not used"

// NOTE: all the code inside the main function is incorrectly indented, but updating the file doesn't cause gofumpt to be executed, which should fix the issue.
func main() {
                fmt.Println(os.ErrClosed)
                fmt.Println(io.ErrClosedPipe)

                // Here is a very long line which I expect revive to complain about because it's going to be longer than 80 columns in length.
                // I also think gofumpt would reformat this over multiple lines for me.
}

From what I can tell the fundamental problems are gofumpt and goimports aren't running when expected.

The io/os packages did eventually get dynamically added as imports when I typed os.ErrClosed and io.ErrClosedPipe. But for some reason the fmt package would not get added at all no matter how many times I typed a line consisting of that package.

This suggests the goimports tool isn't always being run when it should (e.g. when the file is being written to/updated).

I also noticed that when imports are dynamically added, then the order of the imports were also being sorted correctly (again, that's a side effect of goimports actually being run but just something I wanted to note).

So why does typing any kind of fmt line, like fmt.Println(...) not import that package?

Here is my neovim configuration:

lua <<EOF
  -- https://github.com/mfussenegger/nvim-lint#custom-linters
  local lint = require('lint')
  lint.linters.cargo = {
    cmd = 'cargo check',
    stdin = true,
    args = {},
    stream = 'both',
    ignore_exitcode = false,
    env = nil,
  }
  lint.linters_by_ft = {
    go = {'golangcilint', 'revive'},
    rust = {'cargo'},
  }
  local golangcilint = require("lint.linters.golangcilint")
  golangcilint.append_fname = true
  golangcilint.args = {
    'run',
    '--out-format',
    'json',
    }
EOF
autocmd BufWritePre <buffer> lua require('lint').try_lint()

NOTE: I've tried explicitly setting --config in the above neovim configuration but it made no difference. I removed it because it looks like (when running golangci-lint manually with --verbose that it looks up my ~/.golangci-lint file successfully any way.

Here is the output of manually running the golangci-lint run command and it shows that we have the various linters enabled (sure some are disabled due to go1.18, but none of the ones I've commented in the above code as not working have been disabled so those should have run):

$ golangci-lint run --out-format json --verbose

INFO [config_reader] Config search paths: [./ /Users/integralist/Code/go/testing-golangcilint /Users/integralist/Code/go /Users/integralist/Code /Users/integralist /Users /]
INFO [config_reader] Used config file ../../../.golangci.yml
INFO [lintersdb] Active 13 linters: [deadcode errcheck gofumpt goimports gosimple govet ineffassign revive staticcheck structcheck typecheck unused varcheck]
INFO [loader] Go packages loading at mode 575 (types_sizes|deps|files|imports|compiled_files|exports_file|name) took 103.063542ms
INFO [runner/filename_unadjuster] Pre-built 0 adjustments in 341.208µs
INFO [linters context/goanalysis] analyzers took 1.824912ms with top 10 stages: printf: 685.874µs, ctrlflow: 515.833µs, inspect: 417.374µs, testinggoroutine: 59.083µs, lostcancel: 43.375µs, cgocall: 20.167µs, tests: 7.875µs, bools: 5.583µs, typecheck: 5.583µs, httpresponse: 5.291µs
WARN [linters context] gosimple is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.
WARN [linters context] staticcheck is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.
WARN [linters context] structcheck is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.
WARN [linters context] unused is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649.
INFO [runner] Issues before processing: 104, after processing: 12
INFO [runner] Processors filtering stat (out/in): path_prettifier: 104/104, autogenerated_exclude: 104/104, exclude: 104/104, nolint: 104/104, uniq_by_line: 12/104, cgo: 104/104, max_same_issues: 12/12, source_code: 12/12, path_prefixer: 12/12, filename_unadjuster: 104/104, skip_files: 104/104, identifier_marker: 104/104, max_per_file_from_linter: 12/12, severity-rules: 12/12, sort_results: 12/12, skip_dirs: 104/104, exclude-rules: 104/104, diff: 12/12, max_from_linter: 12/12, path_shortener: 12/12
INFO [runner] processing took 7.203833ms with stages: nolint: 2.286875ms, identifier_marker: 1.642417ms, exclude-rules: 1.208833ms, source_code: 578.958µs, filename_unadjuster: 303.709µs, skip_dirs: 230.625µs, path_prettifier: 201.708µs, max_same_issues: 140.625µs, cgo: 125.416µs, autogenerated_exclude: 59.334µs, max_from_linter: 57.792µs, uniq_by_line: 55.125µs, path_shortener: 51.791µs, max_per_file_from_linter: 48.333µs, diff: 47.583µs, exclude: 42.292µs, severity-rules: 40.5µs, sort_results: 33.167µs, skip_files: 29.208µs, path_prefixer: 19.542µs
INFO [runner] linters took 940.738916ms with stages: goanalysis_metalinter: 932.741458ms, gosimple: 130.333µs, unused: 27.208µs, staticcheck: 5.5µs, structcheck: 3.583µs
{
  "Issues": [
    {
      "FromLinter": "typecheck",
      "Text": "undeclared name: `fmt`",
      "Severity": "",
      "SourceLines": [
        "  fmt.Println(os.ErrClosed) // but the fmt package wasn't added to import list?"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "main.go",
        "Offset": 0,
        "Line": 15,
        "Column": 3
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "undeclared name: `fmt`",
      "Severity": "",
      "SourceLines": [
        "  fmt.Println(io.ErrClosedPipe)"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "main.go",
        "Offset": 0,
        "Line": 16,
        "Column": 3
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "undeclared name: `any`",
      "Severity": "",
      "SourceLines": [
        "\tNew: func() any {"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/io/io.go",
        "Offset": 0,
        "Line": 600,
        "Column": 14
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "a.Lock undefined (type *onceError has no field or method Lock)",
      "Severity": "",
      "SourceLines": [
        "\ta.Lock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/io/pipe.go",
        "Offset": 0,
        "Line": 22,
        "Column": 4
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "a.Unlock undefined (type *onceError has no field or method Unlock)",
      "Severity": "",
      "SourceLines": [
        "\tdefer a.Unlock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/io/pipe.go",
        "Offset": 0,
        "Line": 23,
        "Column": 10
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "a.Lock undefined (type *onceError has no field or method Lock)",
      "Severity": "",
      "SourceLines": [
        "\ta.Lock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/io/pipe.go",
        "Offset": 0,
        "Line": 30,
        "Column": 4
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "a.Unlock undefined (type *onceError has no field or method Unlock)",
      "Severity": "",
      "SourceLines": [
        "\tdefer a.Unlock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/io/pipe.go",
        "Offset": 0,
        "Line": 31,
        "Column": 10
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "i declared but not used",
      "Severity": "",
      "SourceLines": [
        "\t\tfor i := range iovecs {"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/internal/poll/writev.go",
        "Offset": 0,
        "Line": 67,
        "Column": 7
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "m.Lock undefined (type *mmapper has no field or method Lock)",
      "Severity": "",
      "SourceLines": [
        "\tm.Lock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/syscall/syscall_unix.go",
        "Offset": 0,
        "Line": 74,
        "Column": 4
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "m.Unlock undefined (type *mmapper has no field or method Unlock)",
      "Severity": "",
      "SourceLines": [
        "\tdefer m.Unlock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/syscall/syscall_unix.go",
        "Offset": 0,
        "Line": 75,
        "Column": 10
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "m.Lock undefined (type *mmapper has no field or method Lock)",
      "Severity": "",
      "SourceLines": [
        "\tm.Lock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/syscall/syscall_unix.go",
        "Offset": 0,
        "Line": 87,
        "Column": 4
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    },
    {
      "FromLinter": "typecheck",
      "Text": "m.Unlock undefined (type *mmapper has no field or method Unlock)",
      "Severity": "",
INFO File cache stats: 5 entries of total size 38.8KiB
      "SourceLines": [
        "\tdefer m.Unlock()"
      ],
      "Replacement": null,
      "Pos": {
        "Filename": "../../../../../usr/local/go/src/syscall/syscall_unix.go",
        "Offset": 0,
        "Line": 88,
        "Column": 10
      },
      "ExpectNoLint": false,
      "ExpectedNoLintLinter": ""
    }
  ],
  "Report": {
    "Warnings": [
      {
        "Tag": "linters context",
        "Text": "gosimple is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649."
      },
      {
        "Tag": "linters context",
        "Text": "staticcheck is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649."
      },
      {
        "Tag": "linters context",
        "Text": "structcheck is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649."
      },
      {
        "Tag": "linters context",
        "Text": "unused is disabled because of go1.18. You can track the evolution of the go1.18 support by following the https://github.com/golangci/golangci-lint/issues/2649."
      }
    ],
    "Linters": [
      {
        "Name": "asciicheck"
      },
      {
        "Name": "bidichk"
INFO Memory: 12 samples, avg is 117.2MB, max is 139.6MB
      },
      {
        "Name": "bodyclose"
      },
      {
        "Name": "containedctx"
INFO Execution took 1.064127708s
      },
      {
        "Name": "contextcheck"
      },
      {
        "Name": "cyclop"
      },
      {
        "Name": "decorder"
      },
      {
        "Name": "deadcode",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "depguard"
      },
      {
        "Name": "dogsled"
      },
      {
        "Name": "dupl"
      },
      {
        "Name": "durationcheck"
      },
      {
        "Name": "errcheck",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "errchkjson"
      },
      {
        "Name": "errname"
      },
      {
        "Name": "errorlint"
      },
      {
        "Name": "exhaustive"
      },
      {
        "Name": "exhaustivestruct"
      },
      {
        "Name": "exportloopref"
      },
      {
        "Name": "forbidigo"
      },
      {
        "Name": "forcetypeassert"
      },
      {
        "Name": "funlen"
      },
      {
        "Name": "gci"
      },
      {
        "Name": "gochecknoglobals"
      },
      {
        "Name": "gochecknoinits"
      },
      {
        "Name": "gocognit"
      },
      {
        "Name": "goconst"
      },
      {
        "Name": "gocritic"
      },
      {
        "Name": "gocyclo"
      },
      {
        "Name": "godot"
      },
      {
        "Name": "godox"
      },
      {
        "Name": "goerr113"
      },
      {
        "Name": "gofmt"
      },
      {
        "Name": "gofumpt",
        "Enabled": true
      },
      {
        "Name": "goheader"
      },
      {
        "Name": "goimports",
        "Enabled": true
      },
      {
        "Name": "golint"
      },
      {
        "Name": "gomnd"
      },
      {
        "Name": "gomoddirectives"
      },
      {
        "Name": "gomodguard"
      },
      {
        "Name": "goprintffuncname"
      },
      {
        "Name": "gosec"
      },
      {
        "Name": "gosimple",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "govet",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "grouper"
      },
      {
        "Name": "ifshort"
      },
      {
        "Name": "importas"
      },
      {
        "Name": "ineffassign",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "interfacer"
      },
      {
        "Name": "ireturn"
      },
      {
        "Name": "lll"
      },
      {
        "Name": "maintidx"
      },
      {
        "Name": "makezero"
      },
      {
        "Name": "maligned"
      },
      {
        "Name": "misspell"
      },
      {
        "Name": "nakedret"
      },
      {
        "Name": "nestif"
      },
      {
        "Name": "nilerr"
      },
      {
        "Name": "nilnil"
      },
      {
        "Name": "nlreturn"
      },
      {
        "Name": "noctx"
      },
      {
        "Name": "paralleltest"
      },
      {
        "Name": "prealloc"
      },
      {
        "Name": "predeclared"
      },
      {
        "Name": "promlinter"
      },
      {
        "Name": "revive",
        "Enabled": true
      },
      {
        "Name": "rowserrcheck"
      },
      {
        "Name": "scopelint"
      },
      {
        "Name": "sqlclosecheck"
      },
      {
        "Name": "staticcheck",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "structcheck",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "stylecheck"
      },
      {
        "Name": "tagliatelle"
      },
      {
        "Name": "tenv"
      },
      {
        "Name": "testpackage"
      },
      {
        "Name": "thelper"
      },
      {
        "Name": "tparallel"
      },
      {
        "Name": "typecheck",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "unconvert"
      },
      {
        "Name": "unparam"
      },
      {
        "Name": "unused",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "varcheck",
        "Enabled": true,
        "EnabledByDefault": true
      },
      {
        "Name": "varnamelen"
      },
      {
        "Name": "wastedassign"
      },
      {
        "Name": "whitespace"
      },
      {
        "Name": "wrapcheck"
      },
      {
        "Name": "wsl"
      },
      {
        "Name": "nolintlint"
      }
    ]
  }
}

Other neovim configuration I have is (this was placed after the above neovim configuration in my ~/.config/nvim/init.vim file but I've since moved the lspconfig setup to be before it, just in case it made any difference -- it didn't):

lua <<EOF
require('lspconfig').gopls.setup{
    cmd = {'gopls'},
  settings = {
    gopls = {
      analyses = {
        nilness = true,
        unusedparams = true,
        unusedwrite = true,
        useany = true,
      },
      experimentalPostfixCompletions = true,
      gofumpt = true,
      staticcheck = true,
      usePlaceholders = true,
    },
  },
}
EOF
Integralist commented 2 years ago

I've also just tried with a fresh project:

package main

func main() {
  fmt.Println("hello world")
}

The fmt package wasn't auto-imported when writing to the file, and so an error was presented:

undeclared name: fmt

Again this suggests the goimports tool isn't being run when I would expect it to.

But then I went to check for a code action on the fmt.Println line and there were two options shown...

  1. (quickfix) Add import: "fmt"
  2. (source.organizeImports) Organize Imports

Once I selected one of the options, and the package was imported for me, the error disappeared obviously, but then I saw the following error:

File is not `gofumpt`-ed gofumpt [6, 1]
mfussenegger commented 2 years ago

Do I read this right that this was a golangci-lint issue, and that this is resolved?