lint.vim 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. if !exists("g:go_metalinter_command")
  2. let g:go_metalinter_command = ""
  3. endif
  4. if !exists("g:go_metalinter_autosave_enabled")
  5. let g:go_metalinter_autosave_enabled = ['vet', 'golint']
  6. endif
  7. if !exists("g:go_metalinter_enabled")
  8. let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
  9. endif
  10. if !exists("g:go_metalinter_disabled")
  11. let g:go_metalinter_disabled = []
  12. endif
  13. if !exists("g:go_golint_bin")
  14. let g:go_golint_bin = "golint"
  15. endif
  16. if !exists("g:go_errcheck_bin")
  17. let g:go_errcheck_bin = "errcheck"
  18. endif
  19. function! go#lint#Gometa(autosave, ...) abort
  20. if a:0 == 0
  21. let goargs = [expand('%:p:h')]
  22. else
  23. let goargs = a:000
  24. endif
  25. let bin_path = go#path#CheckBinPath("gometalinter")
  26. if empty(bin_path)
  27. return
  28. endif
  29. let cmd = [bin_path]
  30. let cmd += ["--disable-all"]
  31. if a:autosave || empty(g:go_metalinter_command)
  32. " linters
  33. let linters = a:autosave ? g:go_metalinter_autosave_enabled : g:go_metalinter_enabled
  34. for linter in linters
  35. let cmd += ["--enable=".linter]
  36. endfor
  37. for linter in g:go_metalinter_disabled
  38. let cmd += ["--disable=".linter]
  39. endfor
  40. " gometalinter has a --tests flag to tell its linters whether to run
  41. " against tests. While not all of its linters respect this flag, for those
  42. " that do, it means if we don't pass --tests, the linter won't run against
  43. " test files. One example of a linter that will not run against tests if
  44. " we do not specify this flag is errcheck.
  45. let cmd += ["--tests"]
  46. else
  47. " the user wants something else, let us use it.
  48. let cmd += split(g:go_metalinter_command, " ")
  49. endif
  50. if a:autosave
  51. " redraw so that any messages that were displayed while writing the file
  52. " will be cleared
  53. redraw
  54. " Include only messages for the active buffer for autosave.
  55. let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
  56. endif
  57. " gometalinter has a default deadline of 5 seconds.
  58. "
  59. " For async mode (s:lint_job), we want to override the default deadline only
  60. " if we have a deadline configured.
  61. "
  62. " For sync mode (go#util#System), always explicitly pass the 5 seconds
  63. " deadline if there is no other deadline configured. If a deadline is
  64. " configured, then use it.
  65. " Call gometalinter asynchronously.
  66. if go#util#has_job() && has('lambda')
  67. let deadline = get(g:, 'go_metalinter_deadline', 0)
  68. if deadline != 0
  69. let cmd += ["--deadline=" . deadline]
  70. endif
  71. let cmd += goargs
  72. call s:lint_job({'cmd': cmd}, a:autosave)
  73. return
  74. endif
  75. " We're calling gometalinter synchronously.
  76. let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")]
  77. let cmd += goargs
  78. let [l:out, l:err] = go#util#Exec(cmd)
  79. if a:autosave
  80. let l:listtype = go#list#Type("GoMetaLinterAutoSave")
  81. else
  82. let l:listtype = go#list#Type("GoMetaLinter")
  83. endif
  84. if l:err == 0
  85. call go#list#Clean(l:listtype)
  86. echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
  87. else
  88. " GoMetaLinter can output one of the two, so we look for both:
  89. " <file>:<line>:[<column>]: <message> (<linter>)
  90. " <file>:<line>:: <message> (<linter>)
  91. " This can be defined by the following errorformat:
  92. let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
  93. " Parse and populate our location list
  94. call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
  95. let errors = go#list#Get(l:listtype)
  96. call go#list#Window(l:listtype, len(errors))
  97. if !a:autosave
  98. call go#list#JumpToFirst(l:listtype)
  99. endif
  100. endif
  101. endfunction
  102. " Golint calls 'golint' on the current directory. Any warnings are populated in
  103. " the location list
  104. function! go#lint#Golint(...) abort
  105. let bin_path = go#path#CheckBinPath(g:go_golint_bin)
  106. if empty(bin_path)
  107. return
  108. endif
  109. let bin_path = go#util#Shellescape(bin_path)
  110. if a:0 == 0
  111. let out = go#util#System(bin_path . " " . go#util#Shellescape(go#package#ImportPath()))
  112. else
  113. let out = go#util#System(bin_path . " " . go#util#Shelljoin(a:000))
  114. endif
  115. if empty(out)
  116. echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None
  117. return
  118. endif
  119. let l:listtype = go#list#Type("GoLint")
  120. call go#list#Parse(l:listtype, out, "GoLint")
  121. let errors = go#list#Get(l:listtype)
  122. call go#list#Window(l:listtype, len(errors))
  123. call go#list#JumpToFirst(l:listtype)
  124. endfunction
  125. " Vet calls 'go vet' on the current directory. Any warnings are populated in
  126. " the location list
  127. function! go#lint#Vet(bang, ...) abort
  128. call go#cmd#autowrite()
  129. echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None
  130. if a:0 == 0
  131. let out = go#util#System('go vet ' . go#util#Shellescape(go#package#ImportPath()))
  132. else
  133. let out = go#util#System('go tool vet ' . go#util#Shelljoin(a:000))
  134. endif
  135. let l:listtype = go#list#Type("GoVet")
  136. if go#util#ShellError() != 0
  137. let errorformat="%-Gexit status %\\d%\\+," . &errorformat
  138. call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet")
  139. let errors = go#list#Get(l:listtype)
  140. call go#list#Window(l:listtype, len(errors))
  141. if !empty(errors) && !a:bang
  142. call go#list#JumpToFirst(l:listtype)
  143. endif
  144. echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
  145. else
  146. call go#list#Clean(l:listtype)
  147. redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
  148. endif
  149. endfunction
  150. " ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
  151. " the location list
  152. function! go#lint#Errcheck(...) abort
  153. if a:0 == 0
  154. let import_path = go#package#ImportPath()
  155. if import_path == -1
  156. echohl Error | echomsg "vim-go: package is not inside GOPATH src" | echohl None
  157. return
  158. endif
  159. else
  160. let import_path = go#util#Shelljoin(a:000)
  161. endif
  162. let bin_path = go#path#CheckBinPath(g:go_errcheck_bin)
  163. if empty(bin_path)
  164. return
  165. endif
  166. echon "vim-go: " | echohl Identifier | echon "errcheck analysing ..." | echohl None
  167. redraw
  168. let command = go#util#Shellescape(bin_path) . ' -abspath ' . import_path
  169. let out = go#tool#ExecuteInDir(command)
  170. let l:listtype = go#list#Type("GoErrCheck")
  171. if go#util#ShellError() != 0
  172. let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m"
  173. " Parse and populate our location list
  174. call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck')
  175. let errors = go#list#Get(l:listtype)
  176. if empty(errors)
  177. echohl Error | echomsg "GoErrCheck returned error" | echohl None
  178. echo out
  179. return
  180. endif
  181. if !empty(errors)
  182. echohl Error | echomsg "GoErrCheck found errors" | echohl None
  183. call go#list#Populate(l:listtype, errors, 'Errcheck')
  184. call go#list#Window(l:listtype, len(errors))
  185. if !empty(errors)
  186. call go#list#JumpToFirst(l:listtype)
  187. endif
  188. endif
  189. else
  190. call go#list#Clean(l:listtype)
  191. echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None
  192. endif
  193. endfunction
  194. function! go#lint#ToggleMetaLinterAutoSave() abort
  195. if get(g:, "go_metalinter_autosave", 0)
  196. let g:go_metalinter_autosave = 0
  197. call go#util#EchoProgress("auto metalinter disabled")
  198. return
  199. end
  200. let g:go_metalinter_autosave = 1
  201. call go#util#EchoProgress("auto metalinter enabled")
  202. endfunction
  203. function! s:lint_job(args, autosave)
  204. let state = {
  205. \ 'status_dir': expand('%:p:h'),
  206. \ 'started_at': reltime(),
  207. \ 'messages': [],
  208. \ 'exited': 0,
  209. \ 'closed': 0,
  210. \ 'exit_status': 0,
  211. \ 'winnr': winnr(),
  212. \ 'autosave': a:autosave
  213. \ }
  214. call go#statusline#Update(state.status_dir, {
  215. \ 'desc': "current status",
  216. \ 'type': "gometalinter",
  217. \ 'state': "analysing",
  218. \})
  219. " autowrite is not enabled for jobs
  220. call go#cmd#autowrite()
  221. if a:autosave
  222. let state.listtype = go#list#Type("GoMetaLinterAutoSave")
  223. else
  224. let state.listtype = go#list#Type("GoMetaLinter")
  225. endif
  226. function! s:callback(chan, msg) dict closure
  227. call add(self.messages, a:msg)
  228. endfunction
  229. function! s:exit_cb(job, exitval) dict
  230. let self.exited = 1
  231. let self.exit_status = a:exitval
  232. let status = {
  233. \ 'desc': 'last status',
  234. \ 'type': "gometaliner",
  235. \ 'state': "finished",
  236. \ }
  237. if a:exitval
  238. let status.state = "failed"
  239. endif
  240. let elapsed_time = reltimestr(reltime(self.started_at))
  241. " strip whitespace
  242. let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
  243. let status.state .= printf(" (%ss)", elapsed_time)
  244. call go#statusline#Update(self.status_dir, status)
  245. if self.closed
  246. call self.show_errors()
  247. endif
  248. endfunction
  249. function! s:close_cb(ch) dict
  250. let self.closed = 1
  251. if self.exited
  252. call self.show_errors()
  253. endif
  254. endfunction
  255. function state.show_errors()
  256. let l:winnr = winnr()
  257. " make sure the current window is the window from which gometalinter was
  258. " run when the listtype is locationlist so that the location list for the
  259. " correct window will be populated.
  260. if self.listtype == 'locationlist'
  261. exe self.winnr . "wincmd w"
  262. endif
  263. let l:errorformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m'
  264. call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter')
  265. let errors = go#list#Get(self.listtype)
  266. call go#list#Window(self.listtype, len(errors))
  267. " move to the window that was active before processing the errors, because
  268. " the user may have moved around within the window or even moved to a
  269. " different window since saving. Moving back to current window as of the
  270. " start of this function avoids the perception that the quickfix window
  271. " steals focus when linting takes a while.
  272. if self.autosave
  273. exe l:winnr . "wincmd w"
  274. endif
  275. if get(g:, 'go_echo_command_info', 1)
  276. call go#util#EchoSuccess("linting finished")
  277. endif
  278. endfunction
  279. " explicitly bind the callbacks to state so that self within them always
  280. " refers to state. See :help Partial for more information.
  281. let start_options = {
  282. \ 'callback': funcref("s:callback", [], state),
  283. \ 'exit_cb': funcref("s:exit_cb", [], state),
  284. \ 'close_cb': funcref("s:close_cb", [], state),
  285. \ }
  286. call job_start(a:args.cmd, start_options)
  287. if get(g:, 'go_echo_command_info', 1)
  288. call go#util#EchoProgress("linting started ...")
  289. endif
  290. endfunction
  291. " vim: sw=2 ts=2 et