in JavaScript

JSLint Validation with Vim

Here’s a topic that has been done over and over, but I wanted to share this method of using the popular JSLint javascript validation tool within my favourite text editor, Vim. I had tried a few methods previously that didn’t satisfy me because the interface from Vim to JSLint required the use of Vim’s make command and it was just sloppy.

My key requirements were:

  • Some form of persistent visual line differentiation on lines with error(s)
  • Error message text displayed within Vim command line when line under cursor contains error(s)
  • Error lines are navigable using Vim custom hotkey commands

The solution that finally worked for me was to use an adapted implementation of a method implemented by Honza Porkorny. The method requires use of node.js (the node executable must be on your $PATH) and Vim plugin syntastic.

Directory structure of files:

$HOME
  .vim
     syntax_checkers
       javascript.vim
  bin
     runjslint.js
     jslint.js

My version of the method alters

~/.vim/syntax_checkers/javascript.vim

(note: location of file may be different on your system) as such:

Replace:

if !executable("jsl")

With:

if !executable("runjslint.js")

runjslint.js is an executable (via node.js) javascript file that is somewhere on your $PATH.

Then replace:

function! SyntaxCheckers_javascript_GetLocList()
  let makeprg = "jsl -nologo -nofilelisting -nosummary -nocontext -process ".shellescape(expand('%'))
  let errorformat='%W%f(%l): lint warning: %m,%-Z%p^,%W%f(%l): warning: %m,%-Z%p^,%E%f(%l): SyntaxError: %m,%-Z%p^,%-G'
  return SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat })
endfunction

With:

function! SyntaxCheckers_javascript_GetLocList()
  let makeprg = "runjslint.js ".shellescape(expand('%'))
  let errorformat='%W%f(%l:%c): lint warning: %m,%-Z%p^,%W%f(%l:%c): warning: %m,%-Z%p^,%E%f(%l:%c): SyntaxError: %m,%-Z%p^,%-G'
  return SyntasticMake({ 'makeprg': makeprg, 'errorformat': errorformat })
endfunction

The contents of the runjslint.js file I mentioned earlier are a bit involved in order to string this whole method together.

(function () {
  // Node-specific stuff
  // Notice that JSLINT is defined here as well
  var fs, sys, filename, input, errors, warning, JSLINT;

  fs = require('fs');
  sys = require('sys');

  // JSLINT object included here
  JSLINT = require('./jslint').JSLINT;

  // capture the command line argument
  filename = process.argv[2];

  // bail if no argument was specified
  if (!filename) {
    sys.puts('Usage: node runjslint.js file.js');
    process.exit(1);
  }

  // read the file specified
  input = fs.readFileSync(filename);
  input = input.toString("utf-8");

  // run jslint on the file
  JSLINT(input, {
    evil:    true,
    onevar:  true,
    undef:   true,
    devel:   true,
    browser: true,
    indent:  4
  });

  errors = JSLINT.errors;

  // if we have errors, print them out
  if (errors.length > 0) {

    for (var i = 0; i < errors.length; i++) {
      warning = errors[i];

      if (warning) {
        // format: filename.js(3): lint warning: reason
        // e.g.: main.js(12): lint warning: Weird assignment
        var r = filename + "(" + warning.line + ":";
            r += warning.character+"):";
            r += "SyntaxError: " + warning.reason;
        sys.puts(r);
      }
    }
  }

}());

Now if you followed Hanzo Porkorny’s method, you’ll notice that he added similar code above to the bottom of the jslint file, which is fine, but I didn’t want to have to do too much work to refit JSLint with the runjslint.js contents every time a new version of JSLint was introduced.

Here is the only addition I made to the jslint file (at the very bottom):

// for running with node.js
exports.JSLINT = JSLINT;

This allows for the inclusion of the JSLINT object into the executable runjslint.js file. Now, when a new version of JSLint is released, I only need to replace one line of code at the very bottom of the file. Easy.

So far this method satisfies the first base requirement of mine, persistent visual line differentiation on error lines, but not the second or third. Well, syntastic does allow navigation using location list hopping, but this was not intuitive for me since the location list would always pop up and I’d have to close it again. So I rolled my own function for navigation, building off of syntastics error storage within Vim’s location list.

Unfortunately, the Google pretty print plugin does not understand Vim script syntax so the code listing is sans colour. The Vim script code is as follows (I just stuck it in my .vimrc file for now):

" navigate syntastic errors without opening the location list
nnoremap <leader>n :call GotoError(1)
nnoremap <leader>p :call GotoError(-1)

fun! GotoError(dir)
  " if current file is javascript
  if matchstr(expand('%'),'\.js$') == '.js'

    " if syntastic location list is not active, bug out!
    if !exists('b:syntastic_loclist')
        return
    endif

    " if location list is empty
    if len(b:syntastic_loclist)  upperBound
        let b:cur_error = upperBound
    endif

    " move cursor to line AND column of error
    call cursor(loclist[b:cur_error]['lnum'], loclist[b:cur_error]['col'])

    " display error text in Vim command line
    redraw
    echo loclist[b:cur_error]['text']
  else
    redraw
    echo 'Current file is not a javascript file. JSLint validation disabled.'
  endif
endfun

Now you can use whatever custom key bindings you want to bind to the function GotoError(). Just pass 1 as the argument to cycle forward through the errors and -1 to cycle backwards.

That about does it. Let me know if this post was useful to you. Would love to hear feedback… well, constructive feedback anyway.

Write a Comment

Comment