Syntax Checker Guide

LCD 47 edited this page Dec 2, 2016 · 66 revisions

Syntastic API is undergoing changes

New checkers are not accepted until the new API stabilizes.
The document below is now obsolete, and shall be updated in due time.

1. Checker directory structure
2. Checker anatomy
2.1. IsAvailable() callback
2.2. GetLocList() callback
2.3. GetHighlightRegex(item) callback
2.4. Call to g:SyntasticRegistry.CreateAndRegisterChecker()
3. Helper functions
3.1. SyntasticMake(options)
3.2. makeprgBuild(options)
4. Final steps
5. Duplicate checkers
6. Checkers disabled for security reasons
7. External checkers

1. Checker directory structure

Checkers go in syntax_checkers/<filetype>/<checker_name>.vim

<checker_name> can be essentially anything, but is usually named after the checker exe.

2. Checker anatomy

We will use a (slightly modified) version of the ruby/mri checker as an example:

if exists('g:loaded_syntastic_ruby_mri_checker')
let g:loaded_syntastic_ruby_mri_checker = 1

if !exists('g:syntastic_ruby_mri_sort')
    let g:syntastic_ruby_mri_sort = 1

let s:save_cpo = &cpo
set cpo&vim

function! SyntaxCheckers_ruby_mri_IsAvailable() dict
    return executable(self.getExec())

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.'\\>'

    return ''

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': '' }

    return SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat, 'env': env })

call g:SyntasticRegistry.CreateAndRegisterChecker({
            \ 'filetype': 'ruby',
            \ 'name': 'mri',
            \ 'exec': 'ruby' })

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: set sw=4 sts=4 et fdm=marker:

Lets go over the parts of this file in detail.

2.1. IsAvailable() callback (optional)

This callback is used by the core to determine whether the checker is available. It should verify that the checker exe is installed and that any other environment dependencies are met. The checker exe is set by the exec attribute in the call to CreateAndRegisterChecker() (see below), and is available here as self.getExec().

If the callback should just check that the checker exe is present, you may omit this function: there is a default callback that simply returns executable(self.getExec()).

The checker exe with all special characters properly escaped for being run in a shell is available as self.getExecEscaped(). This is useful for example when checking versions:

let checker_new = syntastic#util#versionIsAtLeast(syntastic#util#parseVersion(
            \ self.getExecEscaped() . ' --version'), [2, 1])

Please note that the results of IsAvailable() are cached. Thus, if you install a known checker after syntastic has started, the new checker will be picked up by syntastic only the next time you restart Vim.

2.2. GetLocList() callback (mandatory)

This callback should perform the syntax check and return the results in the form of a quickfix list. See :help getqflist() for details of the format.

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.

2.3. GetHighlightRegex(item) callback (optional)

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.

2.4. Call to g:SyntasticRegistry.CreateAndRegisterChecker() (mandatory)

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.

3. Helper functions

There are also a number of functions provided by syntastic's API that you might find useful. The main ones are:

3.1. SyntasticMake(options)

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 the text field
  • filterForeignErrors - filters out the errors referencing other files
  • guards - makes sure line numbers are not past end of buffers (warning: this is slow).

3.2. makeprgBuild(options)

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.

4. Final steps

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, add a short description of the checker to the manual. If your checker has non-standard options, or if it needs special installation steps, a non-obvious workflow, special Vim options, etc., make sure to document those too. Please avoid duplicating information that can be easily found in the checker's official documentation though.

5. Duplicate checkers

Sometimes a checker can handle several 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(). For example:

" in cpp/cppcheck.vim
if exists('g:loaded_syntastic_cpp_cppcheck_checker')
let g:loaded_syntastic_cpp_cppcheck_checker = 1

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 of c/cppcheck.vim
let s:default_args = {
    \ 'c': '',
    \ 'cpp': '-D__cplusplus' }


" in SyntaxCheckers_c_cppcheck_GetLocList()
let makeprg = self.makeprgBuild({
    \ 'args': get(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').

Using get(s:default_args, self.getFiletype(), '') above instead of the seemingly more natural s:default_args[self.getFiletype()] is important: it allows external checkers to redirect here from filetypes not specified in s:default_args.

6. Checkers disabled for security reasons

Some checkers have to be disabled by default for security reasons (this is the case for example for checkers that executes the code being checked), and the user has to enable these checkers explicitly by setting a variable to 1 in order to run them. From the point of view of the checker registry this is accomplished by adding an attribute 'enable': '<variable-name>' to CreateAndRegisterChecker():

call g:SyntasticRegistry.CreateAndRegisterChecker({
    \ 'filetype': 'perl',
    \ 'name': 'perl',
    \ 'enable': 'enable_perl_checker'})

If you do this, the variable g:syntastic_<variable-name> has to be set to 1 for the checker to run. This variable also has a buffer-local version, that takes precedence over it in the buffers where it is defined.

7. External checkers

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': 'MyPreprocess',
        \ 'Postprocess': ['MyPostprocess'] })

This will tell syntastic to call global funcions MyPreprocess() and MyPostprocess(), rather than syntastic#preprocess#MyPreprocess() and syntastic#postprocess#MyPostprocess() 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>')
    let g:syntastic_extra_filetypes = ['<filetype>']

Syntastic won't try to register your checker until it actually has to run it. Consequently, adding the above piece of code to the plugin file itself won't work. You need to add it to a script that gets run at initialization time, e.g. to a file plugin/your_plugin.vim in the root directory of your plugin.

On the positive side, you shouldn't need to worry about your plugin being loaded before, or after syntastic.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.