On-the-fly JavaScript syntax checking in Emacs

I will take you through the following steps in order to get on-the-fly spell checking in your Emacs with flymake-js while coding JavaScript.

Flymake-js showing error message

What is Flymake?

While others idolize those fancy IDEs just because of the automatic syntax checking as you type, Flymake is an Emacs mode that do the same. „It runs the syntax check tool […] in the background, passing it a temporary copy of the current buffer and parses the output for known error/warning message patterns”. Watch Emacs Flymake under power!

Though it’s trivial to get a command line tool whilst coding in C, Perl, Java, etc., only a few knows that you can debug your JavaScript code outside of the browser.

How to debug JavaScript from command line?

Rhino is an open source JavaScript engine, developed in Java. It comes with all the features of JavaScript 1.5, and contains a ”JavaScript shell for executing JavaScript scripts„. So do we need Firebug no more? Of course not. Rhino allows the running of your JavaScript code, but that’s all. It is not a web browser, it does not understand all the objects provided by that. So the question comes straight.

How to simulate web browser environment?

The most I love in the open source world that everything is ready for you. You just need to reach for it.

John Resig, in Bringing the Browser to the Server, demonstrates his wrapper for Rhino, that „is a good-enough browser/DOM environment, written in JavaScript„. It does exactly what we need. Provides all those objects (window, document, etc.) that crucial for syntax checking a web browser dependent code.

Now that we’ve got a browser environment on top of Rhino and Flymake. See how to glue them together.

Catching error messages in Emacs, introducing flymake-js

  1. At first, install all the required packages and libs we need further on. Grab Rhino from the Mozilla project page or check for binary packages of your distribution. Calling the JAR puts you in the Rhino JavaScript shell.

    java -jar ~/dev/lib/rhino-1.6R6.jar
    Rhino 1.6 release 6 2007 07 26
    js>
    
  2. To setup Flymake, wget flymake.el where you store your Emacs modes. No more settings needed at this point.
  3. Create a folder where you will store some project files. Download John’s env.js and place it there.
  4. Thereinafter let’s write little wrapper that builds the browser environment, and when it’s ready, loads the script we want to syntax check. Save the following to the newly created directory as rhino.js, and customize variable project_folder to fit your needs.

    // Where you store your files
    var project_folder = '/home/gabor/dev/slink/js/';
    // Browser environment wrapper over Rhino
    load(project_folder + 'env.js');
    // For DOM constructing
    window.location = project_folder + 'blank.html';
    var my_script = arguments[0];
    // If DOM ready
    window.onload = function(){
        // Avoid recursive inclusion
        if ("rhino_flymake.js" != my_script) {
    	load(my_script);
        }
    }
  5. To finish this step, we need to create a null template HTML for building DOM.

    <html>
      <head></head>
      <body></body>
    </html>
    
  6. Now get your favourite JS code and test your brand new command line install:

    java -jar ~/src/yuicompressor-1.0/lib/rhino-1.6R6.jar rhino.js rhino-flymake.js
  7. The last task to write a JavaScript extension for Flymake. Com’on.
    ;; Flymake JS mode
    
    (require 'flymake)
    
    (defconst flymake-allowed-js-file-name-masks '(
                                                    ("\\.json$" flymake-js-init)
                                                    ("\\.js$" flymake-js-init))
      "Filename extensions that switch on flymake-js mode syntax checks")
    
    (defconst flymake-js-err-line-pattern-re '(
                                               ("^js: \"\\(.+\\)\", line \\([0-9]+\\): \\(.+\\)$" 1 2 nil 3)
                                               ("^js: uncaught JavaScript \\(.+\\)$" nil nil nil 1)
    )
      "Regexp matching JavaScript error messages")
    
    (defun flymake-js-init ()
      (let* ((temp-file (flymake-init-create-temp-buffer-copy
                         'flymake-create-temp-inplace))
             (local-file (file-relative-name
                          temp-file
                          (file-name-directory buffer-file-name))))
        (list "java" (list "-jar" "/home/gabor/src/yuicompressor-1.0/lib/rhino-1.6R6.jar" "/home/gabor/dev/slink/js/rhino.js" local-file))))
    
    (defun flymake-js-load ()
      (setq flymake-allowed-file-name-masks (append flymake-allowed-file-name-masks flymake-allowed-js-file-name-masks))
      (setq flymake-err-line-patterns (append flymake-err-line-patterns flymake-js-err-line-pattern-re))
      (flymake-mode t)
      (local-set-key (kbd "C-c d") 'flymake-display-err-menu-for-current-line))
    
    (provide 'flymake-js)
    

    Save it as flymake-js.el under your Emacs mode folder, and add a hook for the minor mode you edit JavaScript files with. In our case that’s java-mode, so I put the following in my .emacs.

    (require 'flymake-js)
    (add-hook 'java-mode-hook 'flymake-js-load)
    
  8. We’re finished! Bring up your Emacs, load a JS script file, make some mistakes, and get the error messages on-the-fly.

What’s not flymake-js?

Flymake-js only checks for valid syntax but it does not analyize your code, cannot throw runtime errors. Using flymake-js completes, and does not replace Firebug.

John’s env.js on top of Rhino may have some limitations.

Anyway, flymake-js is not a web browser.

Known issues

If runtime exception was thrown, line number is unknown, hence flymake-js shows that error was found at line 1.

Commit other issues via flymake-js Google Code project page.

Comments are welcome at DZone.