Make go build
linter check files on disk (#465)
* #418 Make `go build` linter check files on disk * Omit errors from imported go packages
This commit is contained in:
parent
65fe914fb8
commit
0ce46fe7c8
@ -1,4 +1,4 @@
|
|||||||
" Author: Joshua Rubin <joshua@rubixconsulting.com>
|
" Author: Joshua Rubin <joshua@rubixconsulting.com>, Ben Reedy <https://github.com/breed808>
|
||||||
" Description: go build for Go files
|
" Description: go build for Go files
|
||||||
|
|
||||||
" inspired by work from dzhou121 <dzhou121@gmail.com>
|
" inspired by work from dzhou121 <dzhou121@gmail.com>
|
||||||
@ -11,198 +11,55 @@ function! ale_linters#go#gobuild#GoEnv(buffer) abort
|
|||||||
return 'go env GOPATH GOROOT'
|
return 'go env GOPATH GOROOT'
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:SplitChar = has('unix') ? ':' : ':'
|
function! ale_linters#go#gobuild#GetCommand(buffer, goenv_output) abort
|
||||||
|
if !exists('s:go_env')
|
||||||
" get a list of all source directories from $GOPATH and $GOROOT
|
|
||||||
function! s:SrcDirs() abort
|
|
||||||
let l:paths = split(s:go_env.GOPATH, s:SplitChar)
|
|
||||||
call add(l:paths, s:go_env.GOROOT)
|
|
||||||
|
|
||||||
return l:paths
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
" figure out from a directory like `/home/user/go/src/some/package` that the
|
|
||||||
" import for that path is simply `some/package`
|
|
||||||
function! s:PackageImportPath(buffer) abort
|
|
||||||
let l:bufname = resolve(bufname(a:buffer))
|
|
||||||
let l:pkgdir = fnamemodify(l:bufname, ':p:h')
|
|
||||||
|
|
||||||
for l:path in s:SrcDirs()
|
|
||||||
let l:path = l:path . '/src/'
|
|
||||||
|
|
||||||
if stridx(l:pkgdir, l:path) == 0
|
|
||||||
return l:pkgdir[strlen(l:path):]
|
|
||||||
endif
|
|
||||||
endfor
|
|
||||||
|
|
||||||
return ''
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
" get the package info data structure using `go list`
|
|
||||||
function! ale_linters#go#gobuild#GoList(buffer, goenv_output) abort
|
|
||||||
if !empty(a:goenv_output)
|
|
||||||
let s:go_env = {
|
let s:go_env = {
|
||||||
\ 'GOPATH': a:goenv_output[0],
|
\ 'GOPATH': a:goenv_output[0],
|
||||||
\ 'GOROOT': a:goenv_output[1],
|
\ 'GOROOT': a:goenv_output[1],
|
||||||
\}
|
\}
|
||||||
endif
|
endif
|
||||||
|
|
||||||
return 'go list -json ' . shellescape(s:PackageImportPath(a:buffer))
|
return 'GOPATH=' . s:go_env.GOPATH . ' go test -c -o /dev/null %s'
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:filekeys = [
|
|
||||||
\ 'GoFiles',
|
|
||||||
\ 'CgoFiles',
|
|
||||||
\ 'CFiles',
|
|
||||||
\ 'CXXFiles',
|
|
||||||
\ 'MFiles',
|
|
||||||
\ 'HFiles',
|
|
||||||
\ 'FFiles',
|
|
||||||
\ 'SFiles',
|
|
||||||
\ 'SwigFiles',
|
|
||||||
\ 'SwigCXXFiles',
|
|
||||||
\ 'SysoFiles',
|
|
||||||
\ 'TestGoFiles',
|
|
||||||
\ 'XTestGoFiles',
|
|
||||||
\]
|
|
||||||
|
|
||||||
" get the go and test go files from the package
|
|
||||||
" will return empty list if the package has any cgo or other invalid files
|
|
||||||
function! s:PkgFiles(pkginfo) abort
|
|
||||||
let l:files = []
|
|
||||||
|
|
||||||
for l:key in s:filekeys
|
|
||||||
if has_key(a:pkginfo, l:key)
|
|
||||||
call extend(l:files, a:pkginfo[l:key])
|
|
||||||
endif
|
|
||||||
endfor
|
|
||||||
|
|
||||||
" resolve the path of the file relative to the window directory
|
|
||||||
return map(l:files, 'shellescape(fnamemodify(resolve(a:pkginfo.Dir . ''/'' . v:val), '':p''))')
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! ale_linters#go#gobuild#CopyFiles(buffer, golist_output) abort
|
|
||||||
let l:tempdir = tempname()
|
|
||||||
let l:temppkgdir = l:tempdir . '/src/' . s:PackageImportPath(a:buffer)
|
|
||||||
call mkdir(l:temppkgdir, 'p', 0700)
|
|
||||||
|
|
||||||
if empty(a:golist_output)
|
|
||||||
return 'echo ' . shellescape(l:tempdir)
|
|
||||||
endif
|
|
||||||
|
|
||||||
" parse the output
|
|
||||||
let l:pkginfo = json_decode(join(a:golist_output, "\n"))
|
|
||||||
|
|
||||||
" get all files for the package
|
|
||||||
let l:files = s:PkgFiles(l:pkginfo)
|
|
||||||
|
|
||||||
" copy the files to a temp directory with $GOPATH structure
|
|
||||||
return 'cp ' . join(l:files, ' ') . ' ' . shellescape(l:temppkgdir) . ' && echo ' . shellescape(l:tempdir)
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! ale_linters#go#gobuild#GetCommand(buffer, copy_output) abort
|
|
||||||
" If for some reason we don't get any output from the last command, stop
|
|
||||||
" here.
|
|
||||||
if empty(a:copy_output)
|
|
||||||
return ''
|
|
||||||
endif
|
|
||||||
|
|
||||||
let l:tempdir = a:copy_output[0]
|
|
||||||
let l:importpath = s:PackageImportPath(a:buffer)
|
|
||||||
|
|
||||||
" write the a:buffer and any modified buffers from the package to the tempdir
|
|
||||||
for l:bufnum in range(1, bufnr('$'))
|
|
||||||
" ignore unloaded buffers (can't be a:buffer or a modified buffer)
|
|
||||||
if !bufloaded(l:bufnum)
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
|
|
||||||
" ignore non-Go buffers
|
|
||||||
if getbufvar(l:bufnum, '&ft') !=# 'go'
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
|
|
||||||
" only consider buffers other than a:buffer if they have the same import
|
|
||||||
" path as a:buffer and are modified
|
|
||||||
if l:bufnum != a:buffer
|
|
||||||
if s:PackageImportPath(l:bufnum) !=# l:importpath
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
|
|
||||||
if !getbufvar(l:bufnum, '&mod')
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
call writefile(getbufline(l:bufnum, 1, '$'), l:tempdir . '/src/' . s:PkgFile(l:bufnum))
|
|
||||||
endfor
|
|
||||||
|
|
||||||
let l:gopaths = [ l:tempdir ]
|
|
||||||
call extend(l:gopaths, split(s:go_env.GOPATH, s:SplitChar))
|
|
||||||
|
|
||||||
return 'GOPATH=' . shellescape(join(l:gopaths, s:SplitChar)) . ' go test -c -o /dev/null ' . shellescape(l:importpath)
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:PkgFile(buffer) abort
|
|
||||||
let l:bufname = resolve(bufname(a:buffer))
|
|
||||||
let l:importpath = s:PackageImportPath(a:buffer)
|
|
||||||
let l:fname = fnamemodify(l:bufname, ':t')
|
|
||||||
|
|
||||||
return l:importpath . '/' . l:fname
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:FindBuffer(file) abort
|
|
||||||
for l:buffer in range(1, bufnr('$'))
|
|
||||||
if !buflisted(l:buffer)
|
|
||||||
continue
|
|
||||||
endif
|
|
||||||
|
|
||||||
let l:pkgfile = s:PkgFile(l:buffer)
|
|
||||||
|
|
||||||
if a:file =~ '/' . l:pkgfile . '$'
|
|
||||||
return l:buffer
|
|
||||||
endif
|
|
||||||
endfor
|
|
||||||
|
|
||||||
return -1
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+'
|
|
||||||
let s:handler_pattern = '^\(' . s:path_pattern . '\):\(\d\+\):\?\(\d\+\)\?: \(.\+\)$'
|
|
||||||
|
|
||||||
let s:multibuffer = 0
|
|
||||||
|
|
||||||
function! ale_linters#go#gobuild#Handler(buffer, lines) abort
|
function! ale_linters#go#gobuild#Handler(buffer, lines) abort
|
||||||
let l:output = []
|
return ale_linters#go#gobuild#HandleGoBuildErrors(a:buffer, bufname(a:buffer), a:lines)
|
||||||
|
endfunction
|
||||||
|
|
||||||
for l:line in a:lines
|
function! ale_linters#go#gobuild#HandleGoBuildErrors(buffer, full_filename, lines) abort
|
||||||
let l:match = matchlist(l:line, s:handler_pattern)
|
" Matches patterns line the following:
|
||||||
|
"
|
||||||
|
" file.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args
|
||||||
|
" file.go:53:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
|
||||||
|
" file.go:5:2: expected declaration, found 'STRING' "log"
|
||||||
|
|
||||||
if len(l:match) == 0
|
" go test returns relative paths so use tail of filename as part of pattern matcher
|
||||||
continue
|
let l:filename = fnamemodify(a:full_filename, ':t')
|
||||||
endif
|
let l:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+'
|
||||||
|
let l:pattern = '^' . l:path_pattern . ':\(\d\+\):\?\(\d\+\)\?:\? \(.\+\)$'
|
||||||
|
let l:output = []
|
||||||
|
|
||||||
let l:buffer = s:FindBuffer(l:match[1])
|
for l:line in a:lines
|
||||||
|
let l:match = matchlist(l:line, l:pattern)
|
||||||
|
|
||||||
if l:buffer == -1
|
" Omit errors from imported go packages
|
||||||
continue
|
if len(l:match) == 0 || l:line !~ l:filename
|
||||||
endif
|
continue
|
||||||
|
endif
|
||||||
|
|
||||||
if !s:multibuffer && l:buffer != a:buffer
|
" vcol is Needed to indicate that the column is a character.
|
||||||
" strip lines from other buffers
|
call add(l:output, {
|
||||||
continue
|
\ 'bufnr': a:buffer,
|
||||||
endif
|
\ 'lnum': l:match[1] + 0,
|
||||||
|
\ 'vcol': 0,
|
||||||
|
\ 'col': l:match[2] + 0,
|
||||||
|
\ 'text': l:match[3],
|
||||||
|
\ 'type': 'E',
|
||||||
|
\ 'nr': -1,
|
||||||
|
\})
|
||||||
|
endfor
|
||||||
|
|
||||||
call add(l:output, {
|
return l:output
|
||||||
\ 'bufnr': l:buffer,
|
|
||||||
\ 'lnum': l:match[2] + 0,
|
|
||||||
\ 'col': l:match[3] + 0,
|
|
||||||
\ 'text': l:match[4],
|
|
||||||
\ 'type': 'E',
|
|
||||||
\})
|
|
||||||
endfor
|
|
||||||
|
|
||||||
return l:output
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
call ale#linter#Define('go', {
|
call ale#linter#Define('go', {
|
||||||
@ -210,9 +67,8 @@ call ale#linter#Define('go', {
|
|||||||
\ 'executable': 'go',
|
\ 'executable': 'go',
|
||||||
\ 'command_chain': [
|
\ 'command_chain': [
|
||||||
\ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'},
|
\ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'},
|
||||||
\ {'callback': 'ale_linters#go#gobuild#GoList', 'output_stream': 'stdout'},
|
|
||||||
\ {'callback': 'ale_linters#go#gobuild#CopyFiles', 'output_stream': 'stdout'},
|
|
||||||
\ {'callback': 'ale_linters#go#gobuild#GetCommand', 'output_stream': 'stderr'},
|
\ {'callback': 'ale_linters#go#gobuild#GetCommand', 'output_stream': 'stderr'},
|
||||||
\ ],
|
\ ],
|
||||||
\ 'callback': 'ale_linters#go#gobuild#Handler',
|
\ 'callback': 'ale_linters#go#gobuild#Handler',
|
||||||
|
\ 'lint_file': 1,
|
||||||
\})
|
\})
|
||||||
|
Loading…
Reference in New Issue
Block a user