refactor how we represent and store checkers using python as a demo

Add 2 classes: SyntasticChecker and SyntasticRegistry.

SyntasticChecker represents a checker. It holds funcrefs to the checker
func, the highlight regex func and a new `isAvailable()` func (that
essentially just checks if the checker exe is installed)

SyntasticRegistry is responsible for:
* loading checkers
* storing checkers
* fetching the checkers to use according to availability and the users
  settings

Motivation/benefits:
* in the current system only one checker can be loaded per filetype
* syntax checkers cant be "chained" together
* the system is hard to add features to since fundamental concepts like
  syntax checkers and location lists arent represented explicitly

Things left to do:
* add a call to g:SyntasticRegistry.CreateAndRegisterChecker() to all
  checkers
* add an `isAvailable` function to all checkers
* move all checkers into `syntax_checkers/filetype/checkername.vim` -
  g:SyntasticRegistry assumes this layout, and its a good idea anyway
  for consistency and it makes it easier for users to add their own
  checkers

Things to do after all of the above:
* add a LocationList class and move all the filtering functions onto it
* possibly add an Error class that wraps up each item in a loc list

Random notes:
* with the new system you can select the checkers to use with e.g.
    `let g:syntastic_python_checkers=['flake8', 'pylint']`
  This will try flake8 first, and if no errors are detected it will move
  onto pylint.
This commit is contained in:
Martin Grenfell 2013-01-24 00:01:30 +00:00
parent 8095909dcc
commit 58ba8d3161
7 changed files with 197 additions and 33 deletions

View File

@ -17,6 +17,8 @@ if exists("g:loaded_syntastic_plugin")
endif endif
let g:loaded_syntastic_plugin = 1 let g:loaded_syntastic_plugin = 1
runtime plugin/syntastic/*.vim
let s:running_windows = has("win16") || has("win32") let s:running_windows = has("win16") || has("win32")
if !exists("g:syntastic_enable_signs") if !exists("g:syntastic_enable_signs")
@ -103,6 +105,8 @@ if !exists("g:syntastic_loc_list_height")
let g:syntastic_loc_list_height = 10 let g:syntastic_loc_list_height = 10
endif endif
let s:registry = g:SyntasticRegistry.Instance()
command! SyntasticToggleMode call s:ToggleMode() command! SyntasticToggleMode call s:ToggleMode()
command! SyntasticCheck call s:UpdateErrors(0) <bar> call s:Redraw() command! SyntasticCheck call s:UpdateErrors(0) <bar> call s:Redraw()
command! Errors call s:ShowLocList() command! Errors call s:ShowLocList()
@ -199,15 +203,22 @@ function! s:CacheErrors()
"functions legally for filetypes like "gentoo-metadata" "functions legally for filetypes like "gentoo-metadata"
let fts = substitute(&ft, '-', '_', 'g') let fts = substitute(&ft, '-', '_', 'g')
for ft in split(fts, '\.') for ft in split(fts, '\.')
if SyntasticCheckable(ft) let checkers = s:registry.getActiveCheckers(ft)
let errors = SyntaxCheckers_{ft}_GetLocList() for checker in checkers
let errors = checker.getLocList()
if !empty(errors)
"keep only lines that effectively match an error/warning "keep only lines that effectively match an error/warning
let errors = s:FilterLocList({'valid': 1}, errors) let errors = s:FilterLocList({'valid': 1}, errors)
"make errors have type "E" by default "make errors have type "E" by default
call SyntasticAddToErrors(errors, {'type': 'E'}) call SyntasticAddToErrors(errors, {'type': 'E'})
call extend(s:LocList(), errors) call extend(s:LocList(), errors)
"only get errors from one checker at a time
break
endif endif
endfor endfor
endfor
endif endif
endfunction endfunction
@ -531,18 +542,7 @@ endfunction
"check if a syntax checker exists for the given filetype - and attempt to "check if a syntax checker exists for the given filetype - and attempt to
"load one "load one
function! SyntasticCheckable(ft) function! SyntasticCheckable(ft)
"users can just define a syntax checking function and it will override the return s:registry.checkable(a:ft)
"syntastic default
if exists("*SyntaxCheckers_". a:ft ."_GetLocList")
return 1
endif
if !exists("g:loaded_" . a:ft . "_syntax_checker")
exec "runtime syntax_checkers/" . a:ft . ".vim"
let {"g:loaded_" . a:ft . "_syntax_checker"} = 1
endif
return exists("*SyntaxCheckers_". a:ft ."_GetLocList")
endfunction endfunction
"the args must be arrays of the form [major, minor, macro] "the args must be arrays of the form [major, minor, macro]

View File

@ -0,0 +1,47 @@
if exists("g:loaded_syntastic_checker")
finish
endif
let g:loaded_syntastic_checker=1
let g:SyntasticChecker = {}
" Public methods {{{1
function! g:SyntasticChecker.New(args)
let newObj = copy(self)
let newObj._locListFunc = a:args['loclistFunc']
let newObj._isAvailableFunc = a:args['isAvailableFunc']
let newObj._filetype = a:args['filetype']
let newObj._name = a:args['name']
let newObj._highlightRegexFunc = get(a:args, 'highlightRegexFunc', '')
return newObj
endfunction
function! g:SyntasticChecker.filetype()
return self._filetype
endfunction
function! g:SyntasticChecker.name()
return self._name
endfunction
function! g:SyntasticChecker.getLocList()
return self._locListFunc()
endfunction
function! g:SyntasticChecker.getHighlightRegexFor(error)
if empty(self._highlightRegexFunc)
return []
endif
return self._highlightRegexFunc(error)
endfunction
function! g:SyntasticChecker.isAvailable()
return self._isAvailableFunc()
endfunction
" vim: set sw=4 sts=4 et fdm=marker:

View File

@ -0,0 +1,80 @@
if exists("g:loaded_syntastic_registry")
finish
endif
let g:loaded_syntastic_registry=1
let g:SyntasticRegistry = {}
" Public methods {{{1
function! g:SyntasticRegistry.Instance()
if !exists('s:SyntasticRegistryInstance')
let s:SyntasticRegistryInstance = copy(self)
let s:SyntasticRegistryInstance._checkerMap = {}
endif
return s:SyntasticRegistryInstance
endfunction
function! g:SyntasticRegistry.CreateAndRegisterChecker(args)
let checker = g:SyntasticChecker.New(a:args)
let registry = g:SyntasticRegistry.Instance()
call registry.registerChecker(checker)
endfunction
function! g:SyntasticRegistry.registerChecker(checker)
let ft = a:checker.filetype()
if !has_key(self._checkerMap, ft)
let self._checkerMap[ft] = []
endif
call add(self._checkerMap[ft], a:checker)
endfunction
function! g:SyntasticRegistry.checkable(filetype)
return !empty(self.getActiveCheckers(a:filetype))
endfunction
function! g:SyntasticRegistry.getActiveCheckers(filetype)
let allCheckers = copy(self._checkersFor(a:filetype))
"only use checkers the user has specified
if exists("g:syntastic_" . a:filetype . "_checkers")
let whitelist = g:syntastic_{a:filetype}_checkers
call filter(allCheckers, "index(whitelist, v:val.name()) != -1")
endif
"only use available checkers
return filter(allCheckers, "v:val.isAvailable()")
endfunction
" Private methods {{{1
function! g:SyntasticRegistry._checkersFor(filetype)
call self._loadCheckers(a:filetype)
if empty(self._checkerMap[a:filetype])
return []
endif
return self._checkerMap[a:filetype]
endfunction
function! g:SyntasticRegistry._loadCheckers(filetype)
if self._haveLoadedCheckers(a:filetype)
return
endif
exec "runtime! syntax_checkers/" . a:filetype . "/*.vim"
if !has_key(self._checkerMap, a:filetype)
let self._checkerMap[a:filetype] = []
endif
endfunction
function! g:SyntasticRegistry._haveLoadedCheckers(filetype)
return has_key(self._checkerMap, a:filetype)
endfunction
" vim: set sw=4 sts=4 et fdm=marker:

View File

@ -5,7 +5,11 @@
" kstep <me@kstep.me> " kstep <me@kstep.me>
" "
"============================================================================ "============================================================================
function! SyntaxCheckers_python_GetHighlightRegex(i) function! SyntaxCheckers_python_flake8_IsAvailable()
return executable('flake8')
endfunction
function! SyntaxCheckers_python_flake8_GetHighlightRegex(i)
if match(a:i['text'], 'is assigned to but never used') > -1 if match(a:i['text'], 'is assigned to but never used') > -1
\ || match(a:i['text'], 'imported but unused') > -1 \ || match(a:i['text'], 'imported but unused') > -1
\ || match(a:i['text'], 'undefined name') > -1 \ || match(a:i['text'], 'undefined name') > -1
@ -21,11 +25,17 @@ function! SyntaxCheckers_python_GetHighlightRegex(i)
return '' return ''
endfunction endfunction
function! SyntaxCheckers_python_GetLocList() function! SyntaxCheckers_python_flake8_GetLocList()
let makeprg = syntastic#makeprg#build({ let makeprg = syntastic#makeprg#build({
\ 'exe': 'flake8', \ 'exe': 'flake8',
\ 'args': g:syntastic_python_checker_args,
\ 'subchecker': 'flake8' }) \ 'subchecker': 'flake8' })
let errorformat = '%E%f:%l: could not compile,%-Z%p^,%E%f:%l:%c: %m,%W%f:%l: %m,%-G%.%#' let errorformat = '%E%f:%l: could not compile,%-Z%p^,%E%f:%l:%c: %m,%W%f:%l: %m,%-G%.%#'
return SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat }) return SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat })
endfunction endfunction
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'loclistFunc': function("SyntaxCheckers_python_flake8_GetLocList"),
\ 'highlightRegexFunc': function("SyntaxCheckers_python_flake8_GetHighlightRegex"),
\ 'filetype': 'python',
\ 'name': 'flake8',
\ 'isAvailableFunc': function("SyntaxCheckers_python_flake8_IsAvailable")} )

View File

@ -6,7 +6,11 @@
" Parantapa Bhattacharya <parantapa@gmail.com> " Parantapa Bhattacharya <parantapa@gmail.com>
" "
"============================================================================ "============================================================================
function! SyntaxCheckers_python_GetHighlightRegex(i) function! SyntaxCheckers_python_pyflakes_IsAvailable()
return executable('pyflakes')
endfunction
function! SyntaxCheckers_python_pyflakes_GetHighlightRegex(i)
if match(a:i['text'], 'is assigned to but never used') > -1 if match(a:i['text'], 'is assigned to but never used') > -1
\ || match(a:i['text'], 'imported but unused') > -1 \ || match(a:i['text'], 'imported but unused') > -1
\ || match(a:i['text'], 'undefined name') > -1 \ || match(a:i['text'], 'undefined name') > -1
@ -22,16 +26,21 @@ function! SyntaxCheckers_python_GetHighlightRegex(i)
return '' return ''
endfunction endfunction
function! SyntaxCheckers_python_GetLocList() function! SyntaxCheckers_python_pyflakes_GetLocList()
let makeprg = syntastic#makeprg#build({ let makeprg = syntastic#makeprg#build({
\ 'exe': 'pyflakes', \ 'exe': 'pyflakes',
\ 'args': g:syntastic_python_checker_args, \ 'args': g:syntastic_python_checker_args,
\ 'subchecker': 'pyflakes' }) \ 'subchecker': 'pyflakes' })
let errorformat = '%E%f:%l: could not compile,%-Z%p^,%E%f:%l:%c: %m,%E%f:%l: %m,%-G%.%#' let errorformat = '%E%f:%l: could not compile,%-Z%p^,%E%f:%l:%c: %m,%E%f:%l: %m,%-G%.%#'
let errors = SyntasticMake({ 'makeprg': makeprg, return SyntasticMake({ 'makeprg': makeprg,
\ 'errorformat': errorformat, \ 'errorformat': errorformat,
\ 'defaults': {'text': "Syntax error"} }) \ 'defaults': {'text': "Syntax error"} })
return errors
endfunction endfunction
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'loclistFunc': function('SyntaxCheckers_python_pyflakes_GetLocList'),
\ 'highlightRegexFunc': function('SyntaxCheckers_python_pyflakes_GetHighlightRegex'),
\ 'filetype': 'python',
\ 'name': 'pyflakes',
\ 'isAvailableFunc': function('SyntaxCheckers_python_pyflakes_IsAvailable')} )

View File

@ -4,7 +4,11 @@
"Author: Parantapa Bhattacharya <parantapa at gmail dot com> "Author: Parantapa Bhattacharya <parantapa at gmail dot com>
" "
"============================================================================ "============================================================================
function! SyntaxCheckers_python_GetLocList() function! SyntaxCheckers_python_pylint_IsAvailable()
return executable('pylint')
endfunction
function! SyntaxCheckers_python_pylint_GetLocList()
let makeprg = syntastic#makeprg#build({ let makeprg = syntastic#makeprg#build({
\ 'exe': 'pylint', \ 'exe': 'pylint',
\ 'args': g:syntastic_python_checker_args. ' -f parseable -r n -i y', \ 'args': g:syntastic_python_checker_args. ' -f parseable -r n -i y',
@ -18,5 +22,10 @@ endfunction
function! s:MakeprgTail() function! s:MakeprgTail()
return ' 2>&1 \| sed ''s_: \[\([RCW]\)_: \[W] \[\1_''' . return ' 2>&1 \| sed ''s_: \[\([RCW]\)_: \[W] \[\1_''' .
\ ' \| sed ''s_: \[\([FE]\)_:\ \[E] \[\1_''' \ ' \| sed ''s_: \[\([FE]\)_:\ \[E] \[\1_'''
endfunction endfunction
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'loclistFunc': function('SyntaxCheckers_python_pylint_GetLocList'),
\ 'filetype': 'python',
\ 'name': 'pylint',
\ 'isAvailableFunc': function('SyntaxCheckers_python_pylint_IsAvailable')} )

View File

@ -7,8 +7,11 @@
" http://www.vim.org/scripts/download_script.php?src_id=1392 " http://www.vim.org/scripts/download_script.php?src_id=1392
" "
"============================================================================ "============================================================================
function! SyntaxCheckers_python_pyflakes_IsAvailable()
return executable('python')
endfunction
function! SyntaxCheckers_python_GetLocList() function! SyntaxCheckers_python_pyflakes_GetLocList()
let l:path = shellescape(expand('%')) let l:path = shellescape(expand('%'))
let l:cmd = "compile(open(" . l:path . ").read(), " . l:path . ", 'exec')" let l:cmd = "compile(open(" . l:path . ").read(), " . l:path . ", 'exec')"
let l:makeprg = 'python -c "' . l:cmd . '"' let l:makeprg = 'python -c "' . l:cmd . '"'
@ -25,3 +28,9 @@ function! SyntaxCheckers_python_GetLocList()
return SyntasticMake({ 'makeprg': l:makeprg, 'errorformat': l:errorformat }) return SyntasticMake({ 'makeprg': l:makeprg, 'errorformat': l:errorformat })
endfunction endfunction
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'loclistFunc': function('SyntaxCheckers_python_pyflakes_GetLocList'),
\ 'filetype': 'python',
\ 'name': 'python',
\ 'isAvailableFunc': function('SyntaxCheckers_python_pyflakes_IsAvailable')} )