" Author: Joshua Rubin " Description: go build for Go files " inspired by work from dzhou121 function! ale_linters#go#gobuild#GoEnv(buffer) abort if exists('s:go_env') return '' endif return 'go env GOPATH GOROOT' endfunction let s:SplitChar = has('unix') ? ':' : ':' " 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 = { \ 'GOPATH': a:goenv_output[0], \ 'GOROOT': a:goenv_output[1], \} endif return 'go list -json ' . shellescape(s:PackageImportPath(a:buffer)) 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 let l:output = [] for l:line in a:lines let l:match = matchlist(l:line, s:handler_pattern) if len(l:match) == 0 continue endif let l:buffer = s:FindBuffer(l:match[1]) if l:buffer == -1 continue endif if !s:multibuffer && l:buffer != a:buffer " strip lines from other buffers continue endif call add(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 call ale#linter#Define('go', { \ 'name': 'go build', \ 'executable': 'go', \ 'command_chain': [ \ {'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#Handler', \})