Vimscript: Automatic ESLint detection

Update - as fun as it was to learn some vimscript, I no longer make use of this stuff. I just use ALE and ESLint and all is well.


I have been a vim user for over 15 years, but until recently I had never tried writing any Vimscript. I had occasionally tried (usually in vain) to understand the Vimscript in some of the plugins I use. But actually writing my own seemed inaccessible, or at least, not important enough to try.

That changed recently when I decided that I had too many lines like this in my .vimrc:

autocmd BufNewFile,BufRead some_directory let g:syntastic_javascript_checkers = ['eslint']
autocmd BufNewFile,BufRead some_directory let g:syntastic_javascript_eslint_exec = "some_directory/node_modules/eslint/bin/eslint"
let g:syntastic_javascript_eslint_args = "--rule 'no-console: 1' --rule 'no-debugger: 1'"

where some_directory is the project root directory. Syntastic is a fantastic syntax checking plugin that can use any number of syntax checking rules, including ESLint. If the javascript_checker is set to eslint as above, Syntastic will automatically look for an .eslintrc file and use it to check your code in real time as you write it.

I decided that determining if we should use ESLint for Syntastic should be automated. We could simply look for the presence of eslint under the node_modules directory. If it exists, assume that we are using ESLint.

So, here is my Vimscript code to do that:

function! FindEslint(dir)
    " search up directory tree to node_modules
    let node_modules = finddir('node_modules', a:dir . ';')
    if strlen(node_modules)
        " look for eslint package dir
        let eslint_dir = finddir('eslint', node_modules . '/**1')
        if strlen(eslint_dir)
            return eslint_dir
        endif
        " if no eslint dir, look for gulp-eslint package dir
        let gulp_eslint_dir = finddir('gulp-eslint', node_modules . '/**1')
        if strlen(gulp_eslint_dir)
            return fnamemodify(gulp_eslint_dir . '/node_modules/eslint', ':p')
        endif
    endif
    return 0
endfunction

function! SetLinter(rootdir)
    let eslint_dir = FindEslint(a:rootdir)
    if strlen(eslint_dir)
        let g:syntastic_javascript_checkers = ['eslint']
        let g:syntastic_javascript_eslint_exec = eslint_dir . "/bin/eslint.js"
        let g:syntastic_javascript_eslint_args = "--rule 'no-console: 1' --rule 'no-debugger: 1'"
    endif
endfunction

" set up linting for javascript files
autocmd FileType javascript call SetLinter(expand("<afile>:p:h"))

Starting at the bottom, we first use an autocommand: when the filetype is Javascript, call the SetLinter function on the filename. For more info on the expand command see

:help expand

but basically the usage here takes the file name provided by autocmd, expands it to its full path, and removes the file name, resulting in the parent directory.

SetLinter in turn calls FindEslint on this directory.

a:rootdir

means "the variable rootdir that is a function argument". In Vimscript, you have to be explicit about the scope of the variables you are referencing.

FindEslint uses

finddir

to travel up the directory tree, looking for a node_modules directory. If it finds one, it again uses finddir to look for an eslint directory or a gulp-eslint directory. I use gulp-eslint that this makes sense for me.

If FindEslint finds a directory like this, SetLinter uses that directory to set the appropriate syntastic options.

In the end, I found Vimscript to be somewhat odd compared to other languages I know. On the other hand, the built in documentation made learning the necessary commands relatively easy.

If you use and love vim like I do, I definitely recommend getting your feet wet with Vimscript!