Skip to content

add support for golangci-lint #2182

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

Merged
merged 6 commits into from
Mar 24, 2019
Merged
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
18 changes: 15 additions & 3 deletions autoload/go/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,27 @@ function! go#config#SetTemplateAutocreate(value) abort
endfunction

function! go#config#MetalinterCommand() abort
return get(g:, "go_metalinter_command", "")
return get(g:, "go_metalinter_command", "gometalinter")
endfunction

function! go#config#MetalinterAutosaveEnabled() abort
return get(g:, 'go_metalinter_autosave_enabled', ['vet', 'golint'])
let l:default_enabled = ["vet", "golint"]

if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif

return get(g:, "go_metalinter_autosave_enabled", default_enabled)
endfunction

function! go#config#MetalinterEnabled() abort
return get(g:, "go_metalinter_enabled", ['vet', 'golint', 'errcheck'])
let l:default_enabled = ["vet", "golint", "errcheck"]

if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif

return get(g:, "go_metalinter_enabled", default_enabled)
endfunction

function! go#config#MetalinterDisabled() abort
Expand Down
98 changes: 70 additions & 28 deletions autoload/go/lint.vim
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,14 @@ function! go#lint#Gometa(bang, autosave, ...) abort
let goargs = a:000
endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block changes things somewhat. When a:autosave is not set, then both gometalinter and golangci-lint should be passed the directory. When a:autosave is set then golangci-lint should be passed the file to check.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, gometalinter also needs to be passed the file to check when a:autosave is set; that already happens for gometalinter a little further down the file. Perhaps this block could be restored to its previous glory, and the section below that ensure that gometalinter is given the --include option when a:autosave is set could be extended to append the file path to goargs when golangci-lint is used.

Copy link
Contributor Author

@micahcoffman micahcoffman Mar 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, that makes sense to me. I did this 2461989, do you think it could just be let goargs = [expand('%p')] instead of what I put in there? What all do we expect goargs could be when autosave is enabled?


if empty(go#config#MetalinterCommand())
let bin_path = go#path#CheckBinPath("gometalinter")
if empty(bin_path)
let l:metalinter = go#config#MetalinterCommand()

if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint'
let cmd = s:metalintercmd(l:metalinter)
if empty(cmd)
return
endif

let cmd = [bin_path]
let cmd += ["--disable-all"]

" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]

" linters
let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
for linter in linters
Expand All @@ -44,24 +36,41 @@ function! go#lint#Gometa(bang, autosave, ...) abort
" will be cleared
redraw

" Include only messages for the active buffer for autosave.
let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
if l:metalinter == "gometalinter"
" Include only messages for the active buffer for autosave.
let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
endif
let cmd += include
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we ensure that only messages for the active buffer are included when using golangci-lint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they unfortunately don't have an include like gometalinter. i'll take another look at their flag options to see if there's something we can do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

figured this out, golangci-lint can be run per file so I added f574b1f

elseif l:metalinter == "golangci-lint"
let goargs[0] = expand('%:p')
endif
let cmd += include
endif

" Call gometalinter asynchronously.
" Call metalinter asynchronously.
let deadline = go#config#MetalinterDeadline()
if deadline != ''
let cmd += ["--deadline=" . deadline]
endif

let cmd += goargs

if l:metalinter == "gometalinter"
" Gometalinter can output one of the two, so we look for both:
" <file>:<line>:<column>:<severity>: <message> (<linter>)
" <file>:<line>::<severity>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
else
" Golangci-lint can output the following:
" <file>:<line>:<column>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:\ %m"
endif

if go#util#has_job()
call s:lint_job({'cmd': cmd}, a:bang, a:autosave)
call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave)
return
endif

Expand All @@ -77,12 +86,6 @@ function! go#lint#Gometa(bang, autosave, ...) abort
call go#list#Clean(l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
else
" GoMetaLinter can output one of the two, so we look for both:
" <file>:<line>:<column>:<severity>: <message> (<linter>)
" <file>:<line>::<severity>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"

" Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')

Expand Down Expand Up @@ -205,8 +208,8 @@ endfunction

function! s:lint_job(args, bang, autosave)
let l:opts = {
\ 'statustype': "gometalinter",
\ 'errorformat': '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m',
\ 'statustype': a:args.statustype,
\ 'errorformat': a:args.errformat,
\ 'for': "GoMetaLinter",
\ 'bang': a:bang,
\ }
Expand All @@ -221,6 +224,45 @@ function! s:lint_job(args, bang, autosave)
call go#job#Spawn(a:args.cmd, l:opts)
endfunction

function! s:metalintercmd(metalinter)
let l:cmd = []
let bin_path = go#path#CheckBinPath(a:metalinter)
if !empty(bin_path)
if a:metalinter == "gometalinter"
let l:cmd = s:gometalintercmd(bin_path)
elseif a:metalinter == "golangci-lint"
let l:cmd = s:golangcilintcmd(bin_path)
endif
endif

return cmd
endfunction

function! s:gometalintercmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["--disable-all"]

" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]
return cmd
endfunction

function! s:golangcilintcmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["run"]
let cmd += ["--print-issued-lines=false"]
let cmd += ["--disable-all"]
" do not use the default exclude patterns, because doing so causes golint
" problems about missing doc strings to be ignored and other things that
" golint identifies.
let cmd += ["--exclude-use-default=false"]
return cmd
endfunction

" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
Expand Down
130 changes: 83 additions & 47 deletions autoload/go/lint_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -3,86 +3,122 @@ let s:cpo_save = &cpo
set cpo&vim

func! Test_Gometa() abort
call s:gometa('gometaliner')
endfunc

func! Test_GometaGolangciLint() abort
call s:gometa('golangci-lint')
endfunc

func! s:gometa(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'

let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]

" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')

let g:go_metalinter_enabled = ['golint']
let g:go_metalinter_enabled = ['golint']

call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')

let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile

call gotest#assert_quickfix(actual, expected)
unlet g:go_metalinter_enabled
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile

call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_enabled
endtry
endfunc

func! Test_GometaWithDisabled() abort
call s:gometawithdisabled('gometalinter')
endfunc

func! Test_GometaWithDisabledGolangciLint() abort
call s:gometawithdisabled('golangci-lint')
endfunc

func! s:gometawithdisabled(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'

let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]

" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')

let g:go_metalinter_disabled = ['vet']
let g:go_metalinter_disabled = ['vet']

call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')

let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile

call gotest#assert_quickfix(actual, expected)
unlet g:go_metalinter_disabled
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile

call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_disabled
endtry
endfunc

func! Test_GometaAutoSave() abort
call s:gometaautosave('gometalinter')
endfunc

func! Test_GometaAutoSaveGolangciLint() abort
call s:gometaautosave('golangci-lint')
endfunc

func! s:gometaautosave(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'

let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]

let winnr = winnr()
let winnr = winnr()

" clear the location lists
call setloclist(l:winnr, [], 'r')
" clear the location lists
call setloclist(l:winnr, [], 'r')

let g:go_metalinter_autosave_enabled = ['golint']
let g:go_metalinter_autosave_enabled = ['golint']

call go#lint#Gometa(0, 1)
call go#lint#Gometa(0, 1)

let actual = getloclist(l:winnr)
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(l:winnr)
endwhile

call gotest#assert_quickfix(actual, expected)
unlet g:go_metalinter_autosave_enabled
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(l:winnr)
endwhile

call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_autosave_enabled
endtry
endfunc

func! Test_Vet()
func! Test_Vet() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
compiler go
Expand Down
9 changes: 5 additions & 4 deletions doc/vim-go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1515,11 +1515,12 @@ it's empty
<
*'g:go_metalinter_command'*

Overrides the command to be executed when |:GoMetaLinter| is called. This is
an advanced settings and is for users who want to have a complete control
over how `gometalinter` should be executed. By default it's empty.
Overrides the command to be executed when |:GoMetaLinter| is called. By
default it's `gometalinter`. `golangci-lint` is also supported. It can also be
used as an advanced setting for users who want to have more control over
the metalinter.
>
let g:go_metalinter_command = ""
let g:go_metalinter_command = "gometalinter"
<
*'g:go_metalinter_deadline'*

Expand Down
1 change: 1 addition & 0 deletions plugin/go.vim
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ let s:packages = {
\ 'golint': ['golang.org/x/lint/golint'],
\ 'gopls': ['golang.org/x/tools/cmd/gopls'],
\ 'gometalinter': ['github.com/alecthomas/gometalinter'],
\ 'golangci-lint': ['github.com/golangci/golangci-lint/cmd/golangci-lint'],
\ 'gomodifytags': ['github.com/fatih/gomodifytags'],
\ 'gorename': ['golang.org/x/tools/cmd/gorename'],
\ 'gotags': ['github.com/jstemmer/gotags'],
Expand Down