-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Syntax Checker Guide
1. Checker directory structure
2. Checker anatomy
2.1. IsAvailable()
callback (optional)
2.2. GetLocList()
callback (mandatory)
2.3. GetHighlightRegex(item)
callback (optional)
2.4. g:SyntasticRegistry.CreateAndRegisterChecker()
2.5. Loclists
3. Helper functions
3.1. SyntasticMake(options)
3.2. makeprgBuild(options)
3.3. setWantSort(do_sort)
4. Final steps
5. Duplicate checkers
6. External checkers
Checkers go in syntax_checkers/<filetype>/<checker_name>.vim
<checker_name>
can be essentially anything, but is usually named after
the checker exe.
A checker consists of five parts:
- initalisation code
- an optional function
IsAvailable()
- a mandatory function
GetLocList()
- an optional function
GetHighlightRegex()
- a mandatory call to register the checker (part of the initialisation code).
The initialisation code can be whatever you want, but it should at the very
least prevent the checker from being loaded multiple times, reset cpoptions
to the defaults, and call g:SyntasticRegistry.CreateAndRegisterChecker()
to register the checker. The code is run once, when the parent directory
(corresponding to the current filetype) is scanned for checkers. It may use
file-scoped variables (see :help s:var
).
The IsAvailable()
function is a "dictionary" function (see :help self
) in
the context of the current checker. It's called when the checker is about to
run for the first time, and it's supposed to verify that the external checker
is installed, and all prerequisites for running it are met. It should return
a true or a false value accordingly. If you omit this function, a default
is filled in by syntastic, that simply verifies that the external checker is
executable. The returned value is cached until Vim exits.
The GetLocList()
function is also a dictionary function in the context of the
current checker. It is called every time a checker is run, and is does the
main work: sets up the arguments for the external checker, runs it, parses the
results, make any other adjustments needed (such as shifting column numbers, or
sorting the errors), and returns the results to syntastic.
The GetHighlightRegex()
function is called once for each error returned by
GetLocList()
, and should return a pattern for highlighting the corresponding
piece of code in the file being checked. If you omit it, syntastic does only
some basic highlighting (it highlights the place of the error, provided that
column information is available).
We will use a (slightly modified) version of the ruby/mri checker as an example:
if exists("g:loaded_syntastic_ruby_mri_checker")
finish
endif
let g:loaded_syntastic_ruby_mri_checker = 1
let s:save_cpo = &cpo
set cpo&vim
function! SyntaxCheckers_ruby_mri_IsAvailable() dict
return executable(self.getExec())
endfunction
function! SyntaxCheckers_ruby_mri_GetHighlightRegex(item)
if match(a:item['text'], 'assigned but unused variable') > -1
let term = split(a:item['text'], ' - ')[1]
return '\V\\<'.term.'\\>'
endif
return ''
endfunction
function! SyntaxCheckers_ruby_mri_GetLocList() dict
let makeprg = self.makeprgBuild({
\ 'args': '-w -T1',
\ 'args_after': '-c' })
"this is a hack to filter out a repeated useless warning in rspec files
"containing lines like
"
" foo.should == 'bar'
"
"Which always generate the warning below. Note that ruby >= 1.9.3 includes
"the word "possibly" in the warning
let errorformat = '%-G%.%#warning: %\(possibly %\)%\?useless use of == in void context,'
" filter out lines starting with ...
" long lines are truncated and wrapped in ... %p then returns the wrong
" column offset
let errorformat .= '%-G%\%.%\%.%\%.%.%#,'
let errorformat .=
\ '%-GSyntax OK,' .
\ '%E%f:%l: syntax error\, %m,' .
\ '%Z%p^,' .
\ '%W%f:%l: warning: %m,' .
\ '%Z%p^,' .
\ '%W%f:%l: %m,' .
\ '%-C%.%#'
let env = { 'RUBYOPT': '' }
let loclist = SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat, 'env': env })
call self.setWantSort(1)
return loclist
endfunction
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'filetype': 'ruby',
\ 'name': 'mri',
\ 'exec': 'ruby' })
let &cpo = s:save_cpo
unlet s:save_cpo
" vim: set et sts=4 sw=4:
Lets go over the parts of this file in detail.
This callback is a dictionary function in the context of the current checker. It's called by the core to determine whether the external checker is available. It should verify that the checker's exe is installed, and that any other prerequisites for running it are met. It should return a true or false value accordingly.
Being a dictionary function, IsAvailable()
is actually a method in the
current checker and it can call other methods of the checker. The methods of
interest at this point are probably only self.getExec()
, which returns the
name of the external checker's binary, and self.getExecEscaped()
, which is
the same thing with all special characters escaped for being run in a shell.
The checker exe returned by self.getExec()
is set by the exec
attribute in the call to CreateAndRegisterChecker()
(see
below), but it can be overridden by the user with
the g:syntastic_<filetype>_<name>_exec
variables.
If the callback should just check that the checker's exe is present, you
may omit this function: there is a default callback that simply returns
executable(self.getExec())
.
The self.getExecEscaped()
is useful for example for finding out the version
of the external checker:
let checker_new = syntastic#util#versionIsAtLeast(syntastic#util#getVersion(
\ self.getExecEscaped() . ' --version'), [2, 1])
Please note that the results of IsAvailable()
are cached. Thus if you install
an external checker after syntastic has started, the new checker will be picked
up by syntastic only the next time you restart Vim.
This callback is also a dictionary function in the context of the current checker. It is called every time a checker is run, and it should perform the syntax check and return the results in the form of a quickfix list, possibly augmented with a few additional fields (see below).
The function usually follows the same format for all checkers:
- Build a
makeprg
(the program that performs the syntax check). - Build an
errorformat
string (the string that tells syntastic how to parse the output from the checker program). - Call
SyntasticMake()
with both of the above.
Notice how unexpected/strange parts of the errorformat string are documented on their own. This is good practice for maintenance reasons.
Syntastic can highlight the erroneous parts of lines, and this callback is used to determine what those parts are.
This callback is optional.
For example, in ruby, this is a common warning:
warning: assigned but unused variable - SomeVariable
The above example would return \V\<SomeVariable\>
which would cause
occurrences of SomeVariable
to be highlighted on the line in question.
The parameter a:item
that gets passed in is an element from a quickfix
list; see :help getqflist()
for the contents. The callback should
return a regular expression pattern matching the current error. At
runtime a \%l
is prepended to this pattern (see :help /\%l
), in
order to localise the match to the relevant line.
Please note that, unlike the above callbacks, GetHighlightRegex()
is
not a dictionary function.
Every checker needs to call this function to tell syntastic that it exists. The parameters that are passed in will determine the filetype the checker is used for, the names of the functions that syntastic looks for, and the name of the checker's exe.
The exec
attribute determines the checker's exe, and it can be omitted
if it is the same as name
. The user can override its value by setting
the variable g:syntastic_<filetype>_<name>_exec
.
In the above example, if we had passed in
{ 'filetype': 'java', 'name': 'foobar' }
then syntastic would use the checker for java files, the checker's
exe would be foobar
, and syntastic would look for functions named
SyntaxCheckers_java_foobar_GetLocList
etc.
Almost every checker calls this function to actually perform the check.
The function sets up the environment according to the options given, runs the checker, resets the environment, and returns the location list.
The argument a:options
can contain the following keys:
makeprg
errorformat
The corresponding options are set for the duration of the function call. They are set with :let, so don't escape spaces.
a:options
may also contain:
-
defaults
- a dict containing default values for the returned errors -
subtype
- all errors will be assigned the given subtype -
preprocess
- a function to be applied to the checker's output before being parsed by syntastic -
postprocess
- a list of functions to be applied to the parsed error list -
cwd
- change directory to the given path before running the checker -
env
- a dict containing environment variables to set before running the checker -
returns
- a list of valid exit codes for the checker
The defaults
option is useful in situations where e.g. the error
output doesn't contain a filename or a meaningful message. In this case
you could pass in
'defaults': { 'bufnr': bufnr(''), 'text': 'Syntax error' }
.
This would cause all items in the returned loclist to have the given
bufnr
and text
values.
Pass in 'subtype': 'Style'
to cause all location list items to be
marked as "Style" errors rather than syntax errors. This is useful for
tools like PHP mess detector.
Currently, the postprocessing functions that can be specified are:
-
compressWhitespace
- replaces runs of whitespace in error text with single blanks -
cygwinRemoveCR
- removes carriage return characters from error text -
decodeXMLEntities
- decodes XML entities in thetext
field -
filterForeignErrors
- filters out the errors referencing other files -
guards
- makes sure line numbers are not past end of buffers (warning: this is slow).
Use this function to build a makeprg
. This function is preferable
over manually building the makeprg string, as it allows users to
override parts of it as they like.
The argument a:options
can contain the following keys:
-
exe_before
,exe
,exe_after
-
args_before
,args
,args_after
-
fname_before
,fname
,fname_after
-
post_args_before
,post_args
,post_args_after
-
tail_before
,tail
,tail_after
The function returns a makeprg
of the form
<exe> <args> <fname> <post_args> <tail>
.
Each <option>
consist actually of
<option_before> <option> <option_after>
, but we omitted
<option_before>
and <option_after>
for clarity.
All arguments are optional. Each <option>
can be overridden
by the user at runtime by setting global variables
g:syntastic_<filetype>_<checker-name>_<option>
. In contrast,
<option_before>
and <option_after>
are internal to the checker, and
can't be changed by the user. Thus in the example above, -w -T1
can be
overridden by the runtime variable g:syntastic_ruby_mri_args
, but -c
is always included in the final makeprg
.
Variables g:syntastic_<filetype>_<checker-name>_<option>
also have
buffer-local versions b:syntastic_<filetype>_<checker-name>_<option>
.
As you probably expect, these take precedence over the global ones.
If omitted, exe
defaults to self.getExecEscaped()
(which is the
checker's exe set by CreateAndRegisterChecker()
), and fname
is the
name of the file being checked, both properly escaped for being passed
to a shell.
The values in a:options
can be either strings, or lists (of strings).
Strings are used unchanged, and the user is responsible for escaping
all characters that are special to the shell. List values have their
elements automatically escaped (using Vim's idea of shell escaping for
the current shell), then the results are joined, separated by spaces.
This applies both to the values set inside the checkers, and to the
values read from user variables, as described above.
Use this function to enable sorting of the list of errors returned by the checker.
By default, errors are passed along as they are produced by the checker. If you enable sorting, they are grouped by file, then sorted by line number, then grouped by type (errors take priority over warnings), then sorted by column number.
If your new checker handles a filetype previously unknown to
syntastic, you should also add it to s:defaultCheckers
in
plugin/syntastic/registry.vim
. If you don't, syntastic won't suggest
the new filetype in command completions.
Last but not least, write a short description of the checker for the wiki, and include it in your pull request. If your checker has non-standard options, make sure to document them too.
Sometimes a checker can handle several different filetypes. The
preferred way to deal with this situation is to write the code for a
single filetype, then add redirections to it from the other filetypes,
rather than copying the code and changing the names. To make a
redirection, just add an attribute
'redirect': '<target-filetype>/<target-checker>'
to
CreateAndRegisterChecker()
, and add the files in the home directory of
the target filetype to runtimepath
. For example:
if exists("g:loaded_syntastic_cpp_cppcheck_checker")
finish
endif
let g:loaded_syntastic_cpp_cppcheck_checker = 1
runtime! syntax_checkers/c/*.vim
call g:SyntasticRegistry.CreateAndRegisterChecker({
\ 'filetype': 'cpp',
\ 'name': 'cppcheck',
\ 'redirect': 'c/cppcheck'})
" vim: set et sts=4 sw=4:
The redirected checkers will have their own user options
g:syntastic_<filetype>_<checker-name>_<option>
independently of those
of the target checker, but the defaults and the internal options set by
makeprgBuild()
will be the same for all filetypes. If you must have
different defaults or different internal options for makeprgBuild()
,
you can define a file-scoped dictionary of options indexed by filetype,
and use self.getFiletype()
as a selector. For example:
" in the preamble
let s:default_args = {
\ 'c': '',
\ 'cpp': '-D__cplusplus' }
...
" in SyntaxCheckers_c_cppcheck_GetLocList()
let makeprg = self.makeprgBuild({
\ 'args': s:default_args[self.getFiletype()],
\ 'args_after': '-q --enable=style' })
In the example above, the cppcheck checker can handle both C and C++
files, the main code is the c/cppcheck, and cpp/cppcheck is just
a redirection to it. Options g:syntastic_c_cppcheck_args
and
g:syntastic_cpp_cppcheck_args
are independent of each other (and apply
only to the corresponding filetypes), but the internal args_after
is
still the same for both checkers (namely '-q --enable=style'
).
Syntastic can use checkers located in foreign plugins,
outside syntastic's tree. To create such a checker, write
it following the above description and put it in a file
syntax_checkers/<filetype>/<checker>.vim
in the root directory of your
plugin.
If your checker needs to call preprocess or postprocess functions
defined outside syntastic's tree, you have to add them to Preprocess
and Postprocess
options to SyntasticMake()
, rather than the usual
(lower case) preprocess
and postprocess
:
let loclist = SyntasticMake({
\ 'makeprg': makeprg,
\ 'errorformat': errorformat,
\ 'Preprocess': 'my_preprocess',
\ 'Postprocess': ['my_postprocess'] })
This will tell syntastic to call my_preprocess()
and
my_postprocess()
, rather than syntastic#preprocess#my_preprocess()
and syntastic#postprocess#my_postprocess()
it would normally call.
If your checker adds a filetype previously unknown to syntastic and you
care about proper tab completion, you need to tell syntastic about it.
The variable g:syntastic_extra_filetypes
can be used for this purpose.
It's supposed to be a list of filetypes handled by external checkers, so
you might want to add something like this to the initialization section
of your plugin:
if exists('g:syntastic_extra_filetypes')
call add(g:syntastic_extra_filetypes, '<filetype>')
else
let g:syntastic_extra_filetypes = ['<filetype>']
endif
Syntastic won't try to register your checker until it actually has to run it, so normally you shouldn't need to worry about your plugin being loaded before, or after syntastic.