checker.vim 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. if exists('g:loaded_syntastic_checker') || !exists('g:loaded_syntastic_plugin')
  2. finish
  3. endif
  4. let g:loaded_syntastic_checker = 1
  5. let g:SyntasticChecker = {}
  6. " Public methods {{{1
  7. function! g:SyntasticChecker.New(args, ...) abort " {{{2
  8. let newObj = copy(self)
  9. let newObj._filetype = a:args['filetype']
  10. let newObj._name = a:args['name']
  11. if a:0
  12. " redirected checker
  13. let newObj._exec_default = get(a:args, 'exec', a:1['_exec_default'])
  14. let filetype = a:1['_filetype']
  15. let name = a:1['_name']
  16. let prefix = 'SyntaxCheckers_' . filetype . '_' . name . '_'
  17. if exists('g:syntastic_' . filetype . '_' . name . '_sort') && !exists('g:syntastic_' . newObj._filetype . '_' . newObj._name . '_sort')
  18. let g:syntastic_{newObj._filetype}_{newObj._name}_sort = g:syntastic_{filetype}_{name}_sort
  19. endif
  20. if has_key(a:args, 'enable')
  21. let newObj._enable = a:args['enable']
  22. elseif has_key(a:1, '_enable')
  23. let newObj._enable = a:1['_enable']
  24. endif
  25. else
  26. let newObj._exec_default = get(a:args, 'exec', newObj._name)
  27. if newObj._exec_default ==# ''
  28. let newObj._exec_default = '<dummy>'
  29. endif
  30. let prefix = 'SyntaxCheckers_' . newObj._filetype . '_' . newObj._name . '_'
  31. if has_key(a:args, 'enable')
  32. let newObj._enable = a:args['enable']
  33. endif
  34. endif
  35. let newObj._locListFunc = function(prefix . 'GetLocList')
  36. if exists('*' . prefix . 'IsAvailable')
  37. let newObj._isAvailableFunc = function(prefix . 'IsAvailable')
  38. else
  39. let newObj._isAvailableFunc = function('s:_isAvailableDefault')
  40. endif
  41. if exists('*' . prefix . 'GetHighlightRegex')
  42. let newObj._highlightRegexFunc = function(prefix . 'GetHighlightRegex')
  43. endif
  44. return newObj
  45. endfunction " }}}2
  46. function! g:SyntasticChecker.getFiletype() abort " {{{2
  47. return self._filetype
  48. endfunction " }}}2
  49. function! g:SyntasticChecker.getName() abort " {{{2
  50. return self._name
  51. endfunction " }}}2
  52. function! g:SyntasticChecker.getCName() abort " {{{2
  53. return self._filetype . '/' . self._name
  54. endfunction " }}}2
  55. " Synchronise _exec with user's setting. Force re-validation if needed.
  56. "
  57. " XXX: This function must be called at least once before calling either
  58. " getExec() or getExecEscaped(). Normally isAvailable() does that for you
  59. " automatically, but you should keep still this in mind if you change the
  60. " current checker workflow.
  61. function! g:SyntasticChecker.syncExec(...) abort " {{{2
  62. if a:0
  63. let self._exec = a:1
  64. else
  65. let suffix = self._name . '_exec'
  66. let self._exec = expand(
  67. \ syntastic#util#var(self._filetype . '_' . suffix,
  68. \ syntastic#util#var(suffix, self._exec_default)), 1 )
  69. endif
  70. endfunction " }}}2
  71. function! g:SyntasticChecker.getExec() abort " {{{2
  72. return self._exec
  73. endfunction " }}}2
  74. function! g:SyntasticChecker.getExecEscaped() abort " {{{2
  75. return syntastic#util#shescape(self._exec)
  76. endfunction " }}}2
  77. function! g:SyntasticChecker.getLocListRaw() abort " {{{2
  78. let checker_start = reltime()
  79. let name = self.getCName()
  80. if has_key(self, '_enable')
  81. let status = syntastic#util#var(self._enable, -1)
  82. if type(status) != type(0)
  83. call syntastic#log#error('checker ' . name . ': invalid value ' . strtrans(string(status)) .
  84. \ ' for g:syntastic_' . self._enable . '; try 0 or 1 instead')
  85. return []
  86. endif
  87. if status < 0
  88. call syntastic#log#error('checker ' . name . ': checks disabled for security reasons; ' .
  89. \ 'set g:syntastic_' . self._enable . ' to 1 to override')
  90. endif
  91. if status <= 0
  92. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, 'getLocList: checker ' . name . ' enabled but not forced')
  93. return []
  94. else
  95. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, 'getLocList: checker ' . name . ' forced')
  96. endif
  97. endif
  98. try
  99. let list = self._locListFunc()
  100. if self._exec !=# ''
  101. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE, 'getLocList: checker ' . name . ' returned ' . v:shell_error)
  102. endif
  103. catch /\m\C^Syntastic: checker error$/
  104. let list = []
  105. if self._exec !=# ''
  106. call syntastic#log#error('checker ' . name . ' returned abnormal status ' . v:shell_error)
  107. else
  108. call syntastic#log#error('checker ' . name . ' aborted')
  109. endif
  110. endtry
  111. call self._populateHighlightRegexes(list)
  112. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_LOCLIST, name . ' raw:', list)
  113. call self._quietMessages(list)
  114. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_TRACE,
  115. \ 'getLocList: checker ' . name . ' run in ' . split(reltimestr(reltime(checker_start)))[0] . 's')
  116. return list
  117. endfunction " }}}2
  118. function! g:SyntasticChecker.getLocList() abort " {{{2
  119. return g:SyntasticLoclist.New(self.getLocListRaw())
  120. endfunction " }}}2
  121. function! g:SyntasticChecker.getVersion(...) abort " {{{2
  122. if !exists('self._version')
  123. let command = a:0 ? a:1 : self.getExecEscaped() . ' --version'
  124. let version_output = syntastic#util#system(command)
  125. call self.log('getVersion: ' . string(command) . ': ' .
  126. \ string(split(version_output, "\n", 1)) .
  127. \ (v:shell_error ? ' (exit code ' . v:shell_error . ')' : '') )
  128. let parsed_ver = syntastic#util#parseVersion(version_output)
  129. if len(parsed_ver)
  130. call self.setVersion(parsed_ver)
  131. else
  132. call syntastic#log#ndebug(g:_SYNTASTIC_DEBUG_LOCLIST, 'checker output:', split(version_output, "\n", 1))
  133. call syntastic#log#error("checker " . self.getCName() . ": can't parse version string (abnormal termination?)")
  134. endif
  135. endif
  136. return get(self, '_version', [])
  137. endfunction " }}}2
  138. function! g:SyntasticChecker.setVersion(version) abort " {{{2
  139. if len(a:version)
  140. let self._version = copy(a:version)
  141. call self.log(self.getExec() . ' version =', a:version)
  142. endif
  143. endfunction " }}}2
  144. function! g:SyntasticChecker.log(msg, ...) abort " {{{2
  145. let leader = self.getCName() . ': '
  146. if a:0
  147. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_CHECKERS, leader . a:msg, a:1)
  148. else
  149. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_CHECKERS, leader . a:msg)
  150. endif
  151. endfunction " }}}2
  152. function! g:SyntasticChecker.makeprgBuild(opts) abort " {{{2
  153. let basename = self._filetype . '_' . self._name . '_'
  154. let parts = []
  155. call extend(parts, self._getOpt(a:opts, basename, 'exe', self.getExecEscaped()))
  156. call extend(parts, self._getOpt(a:opts, basename, 'args', ''))
  157. call extend(parts, self._getOpt(a:opts, basename, 'fname', syntastic#util#shexpand('%')))
  158. call extend(parts, self._getOpt(a:opts, basename, 'post_args', ''))
  159. call extend(parts, self._getOpt(a:opts, basename, 'tail', ''))
  160. return join(parts)
  161. endfunction " }}}2
  162. function! g:SyntasticChecker.isAvailable() abort " {{{2
  163. call self.syncExec()
  164. if !has_key(self, '_available')
  165. let self._available = {}
  166. endif
  167. if !has_key(self._available, self._exec)
  168. let self._available[self._exec] = self._isAvailableFunc()
  169. endif
  170. return self._available[self._exec]
  171. endfunction " }}}2
  172. function! g:SyntasticChecker.isDisabled() abort " {{{2
  173. return has_key(self, '_enable') && syntastic#util#var(self._enable, -1) <= 0
  174. endfunction " }}}2
  175. function! g:SyntasticChecker.wantSort() abort " {{{2
  176. return syntastic#util#var(self._filetype . '_' . self._name . '_sort', 0)
  177. endfunction " }}}2
  178. " This method is no longer used by syntastic. It's here only to maintain
  179. " backwards compatibility with external checkers which might depend on it.
  180. function! g:SyntasticChecker.setWantSort(val) abort " {{{2
  181. if !exists('g:syntastic_' . self._filetype . '_' . self._name . '_sort')
  182. let g:syntastic_{self._filetype}_{self._name}_sort = a:val
  183. endif
  184. endfunction " }}}2
  185. " }}}1
  186. " Private methods {{{1
  187. function! g:SyntasticChecker._quietMessages(errors) abort " {{{2
  188. " wildcard quiet_messages
  189. let quiet_filters = copy(syntastic#util#var('quiet_messages', {}))
  190. if type(quiet_filters) != type({})
  191. call syntastic#log#warn('ignoring invalid syntastic_quiet_messages')
  192. unlet quiet_filters
  193. let quiet_filters = {}
  194. endif
  195. " per checker quiet_messages
  196. let name = self._filetype . '_' . self._name
  197. try
  198. call extend( quiet_filters, copy(syntastic#util#var(name . '_quiet_messages', {})), 'force' )
  199. catch /\m^Vim\%((\a\+)\)\=:E712/
  200. call syntastic#log#warn('ignoring invalid syntastic_' . name . '_quiet_messages')
  201. endtry
  202. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_LOCLIST, 'quiet_messages filter:', quiet_filters)
  203. if !empty(quiet_filters)
  204. call syntastic#util#dictFilter(a:errors, quiet_filters)
  205. call syntastic#log#debug(g:_SYNTASTIC_DEBUG_LOCLIST, 'filtered by quiet_messages:', a:errors)
  206. endif
  207. endfunction " }}}2
  208. function! g:SyntasticChecker._populateHighlightRegexes(errors) abort " {{{2
  209. if has_key(self, '_highlightRegexFunc')
  210. for e in a:errors
  211. if e['valid']
  212. let term = self._highlightRegexFunc(e)
  213. if term !=# ''
  214. let e['hl'] = term
  215. endif
  216. endif
  217. endfor
  218. endif
  219. endfunction " }}}2
  220. function! g:SyntasticChecker._getOpt(opts, basename, name, default) abort " {{{2
  221. let ret = []
  222. call extend( ret, syntastic#util#argsescape(get(a:opts, a:name . '_before', '')) )
  223. call extend( ret, syntastic#util#argsescape(syntastic#util#var( a:basename . a:name, get(a:opts, a:name, a:default) )) )
  224. call extend( ret, syntastic#util#argsescape(get(a:opts, a:name . '_after', '')) )
  225. return ret
  226. endfunction " }}}2
  227. " }}}1
  228. " Private functions {{{1
  229. function! s:_isAvailableDefault() dict " {{{2
  230. return executable(self.getExec())
  231. endfunction " }}}2
  232. " }}}1
  233. " vim: set sw=4 sts=4 et fdm=marker: