summaryrefslogtreecommitdiff
path: root/ale_linters/go/gobuild.vim
blob: e6ca9f9763f4950fc286fe9f3462070414eee4b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
" Author: Joshua Rubin <joshua@rubixconsulting.com>
" Description: go build for Go files

" inspired by work from dzhou121 <dzhou121@gmail.com>

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
  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',
\})