Emacs and flymake for python, javascript, php, rst

Emacs is a wonderfull editor, every sane people know this. Here's how to extend your flymake to some trendy languages.

You'll find here a general overview by answering question as What's flymake ? for emacs, and How does it work ?.

Following are the snippet of code to have nice flymake integration for:

  1. Python
  2. PHP
  3. Javascript
  4. ReSTructured Text

Happy coding !

What's flymake ?

flymake-mode in emacs is meant to "compile" your code while you are typing (no need to save) so as to give you interesting highlights on particular bits of code you've written.

If these can spot syntax errors, code analyzers can go much beyond and show you logical errors as unused variables, unreachable code, an so on. And there's more: you can be hinted when breaking code style conventions that you've chosen, or even code smell detectors (too much method in an object, too much local variable, ...)

How does it work ?

The big picture

flymake-mode is very simple, as you type it'll create a copy of your current emacs buffer in a file usualy called <filename>-flymake.<ext>.

Then, it'll launch an executable on this file and will collect it's standard output.

To work out of the box, the format should be something like this:

myfile.myext:<line no>: <ERROR|WARNING>: error message

This is a typical output of my pycheckers script which combines pep8 code style checker and pylint (syntax, logical, code style, code smell checking):

converter.py:38: ERROR: E501 (pep8) line too long (141 characters)
converter.py:130: ERROR: E303 (pep8) too many blank lines (2)
converter.py:156: WARNING: W0212 (pylint) Access to a protected member _fields_id of a client class
converter.py:185: WARNING: R0911 (pylint) Too many return statements (9/6)

emacs will then use this output to highlight WARNING and ERROR message in two different colors.

Note

Besides helping you in getting better code every day, having the same format, one by line, with clear tagging of the error type and the analyzer can be extremely usefull in continuous integration to get a valuable metric on code quality.

What do I need ?

You'll need to enable flymake once for all by adding the following line to your ~/.emacs, which will launch flymake whenever you'll open a file:

(add-hook 'find-file-hook 'flymake-find-file-hook)

Then to set up flymake-mode for a langage you'll need to:

1. add a specific snippet of code in ~/.emacs to configure the analyzer (ie: to set the actual name of the executable analyzer and its command line arguments if any).

2. create the executable code as it often require some tweaking to get the correct output. This stage is optional if your analyzer output is already in the correct format.

Ok, where's the code ?

Python

Analyzer

My pycheckers code analyzer is actually written in python and will call pep8 and pylint and ensure that the output format is correct. These two analyzers are available on PyPI and thus are installable thanks to:

pip install pylint
pip install pep8

Please check the analyzer code at: https://gist.github.com/3754270

I typically put the analyzer in ~/bin/pycheckers and ensure that it is executable.

Emacs

Here's the emacs snippet:

;; Python flymake configuration

(when (load "flymake" t)
  (defun flymake-pycheckers-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 "~/bin/pycheckers.py"  (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
            '("\\.py\\'" flymake-pycheckers-init)))
For the lazier

This snippet will do the complete job for you:

mkdir -p ~/bin &&
cd ~/bin &&
wget https://raw.github.com/gist/3754270/a4fecffaff6ab702f5c8d37332afc90f6461e40b/pycheckers.py &&
chmod +x pycheckers.py &&
cat <<EOF >> ~/.emacs

;; Python flymake configuration

(when (load "flymake" t)
  (defun flymake-pycheckers-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 "~/bin/pycheckers.py"  (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
            '("\\.py\\'" flymake-pycheckers-init)))

EOF

You'll need to restart emacs for this to work. (Or highlight the added snippet in the end of your .emacs and call M-x eval-region)

Php

Analyzer

I've also a phpcheckers, even if it is very small, I feel better to know that I could grep out some specific warnings or add another sub-analyzer quite easily, and feel free to add your own include path as you wish in the 'packages-path' arguments:

#!/bin/bash

phplint --modules-path ~/var/lib/phplint/modules \
        --packages-path /usr/share/php \
        --no-print-notices "$@"

So i'm using only phplint here.

Emacs

Here's the emacs snippet:

;; PHP flymake configuration

(when (load "flymake" t)
  (defun flymake-phplint-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 "phpcheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
             '("\\.php[345]?$" flymake-phplint-init))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.inc$" flymake-phplint-init)))
For the lazier

This snippet will do the complete job for you:

mkdir -p ~/bin &&
cd ~/bin &&
cat <<EOF > ~/bin/phpcheckers &&
#!/bin/bash

phplint --modules-path ~/var/lib/phplint/modules \
        --packages-path /usr/share/php \
        --no-print-notices "\$@"
EOF
chmod +x phpcheckers &&
cat <<EOF >> ~/.emacs

;; PHP flymake configuration

(when (load "flymake" t)
  (defun flymake-phplint-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 "~/bin/phpcheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
            '("\\.php[345]?$" flymake-phplint-init))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.inc$" flymake-phplint-init)))

EOF

You'll need to restart emacs for this to work. (Or highlight the added snippet in the end of your .emacs and call M-x eval-region)

Javascript

Analyzer

I use a combination of jslint and jshint.

This is more work as jslint is widely criticized for behing too spotty on some conventions only shared by its author, I find it useful when tamed down to normal behavior.

Second, it's not so easy to get the correct format out of jslint. So I created a jslint-emacs executable that lies in ~/bin/jslint-emacs. Here's the code:

#!/bin/bash

filename="$1"

jslint "$filename" --nomen --maxerr 999999 |

## reformat from:
##
##  #45 Expected exactly one space between 'function' and '('.
##      init: function(parent, dataset, csv_import_record){ // Line 127, Pos 23
##
## to:
##
##  filename.js:94: WARNING: (jslint) Expected exactly one space between 'function' and '('.

         sed -r "N;s/^\s*#[0-9]+ (.*)\n\s+.*\/\/ Line ([0-9]+),.*/$filename:\2: WARNING: W000 (jslint) \1/" |

## Remove garbages lines that were not match by previous regex
## as their is a small header not matching regexp.

         grep -E "^$filename:[0-9]+:" |

         grep -v "at column.*not column.*." |
         grep -v "Expected '{' and instead saw 'return'" |
         grep -v "Expected exactly one space between ')' and 'return'" |
         grep -v "Combine this with the previous 'var' statement" |
         grep -v "Unexpected 'in'. Compare with undefined, or use .*"

## force errorlevel 0 so as to avoid emacs ``flymake-mode`` to complain
exit 0

I've found that jshint can use a specific reporter written in javascript, which seems perfect to get the correct ouput from it.

This is my ~/bin/jshint-emacs:

#!/bin/bash

jshint "$1" --reporter ~/.emacs.d/jslint-emacs-reporter.js

And of course, my ~/.emacs.d/jslint-emacs-reporter.js is available as a gist here: https://gist.github.com/3754379

It seems that this javascript reporter could be used also with jslint but I didn't figure out how to do so. (This was maybe because I've already written the sed/grep thing before).

Of course, this is my ~/bin/jscheckers:

#!/bin/bash

jslint-emacs "$1"
jshint-emacs "$1"
Emacs

Here's the emacs snippet:

;; Javascript flymake configuration

(when (load "flymake" t)
  (defun flymake-jslint-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 "jscheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.js\\'" flymake-jslint-init)))
For the lazier

This snippet will do the complete job for you:

mkdir -p ~/bin &&
cd ~/bin &&
cat <<EOF > ~/bin/jslint-emacs &&
#!/bin/bash

filename="\$1"

jslint "\$filename" --nomen --maxerr 999999 |

## reformat from:
##
##  #45 Expected exactly one space between 'function' and '('.
##      init: function(parent, dataset, csv_import_record){ // Line 127, Pos 23
##
## to:
##
##  filename.js:94: WARNING: (jslint) Expected exactly one space between 'function' and '('.

         sed -r "N;s/^\s*#[0-9]+ (.*)\n\s+.*\/\/ Line ([0-9]+),.*/\$filename:\2: WARNING: W000 (jslint) \1/" |

## Remove garbages lines that were not match by previous regex
## as their is a small header not matching regexp.

         grep -E "^\$filename:[0-9]+:" |

         grep -v "at column.*not column.*." |
         grep -v "Expected '{' and instead saw 'return'" |
         grep -v "Expected exactly one space between ')' and 'return'" |
         grep -v "Combine this with the previous 'var' statement" |
      grep -v "Unexpected 'in'. Compare with undefined, or use .*"

## force errorlevel 0 so as to avoid emacs ``flymake-mode`` to complain
exit 0
EOF
chmod +x jslint-emacs &&
cat <<EOF > ~/bin/jscheckers &&
#!/bin/bash

~/bin/jslint-emacs "\$1"
~/bin/jshint-emacs "\$1"

EOF
chmod +x jscheckers &&
mkdir -p ~/.emacs.d &&
cd ~/.emacs.d &&
wget https://raw.github.com/gist/3754379/3c43c50b4d85f50b8314af8e498b1b4f2cee58ea/jslint-emacs-reporter.js &&
cat <<EOF >> ~/.emacs

;; Javascript flymake configuration

(when (load "flymake" t)
  (defun flymake-jslint-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 "jscheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.js\\'" flymake-jslint-init)))

EOF

You'll need to restart emacs for this to work. (Or highlight the added snippet in the end of your .emacs and call M-x eval-region)

ReSTructured Text

Analyzer

I was surprised to see that rst2html will make very decent rst analyzer.

rst2html is provided by python docutils package.

My ~/bin/rstcheckers:

#!/bin/bash

rst2html "$1" > /dev/null

Surprising isn't it ? As it isn't a code analyzer but the compiler itself, it does it's job really well, and in combination with flymake, it gives you a very convenient on-the-fly syntax checking.

Note that >/dev/null is required as rst2html jobs is to output HTML from your rst (which we don't want to see), and it casts errors on the standard error pipe.

Emacs

Here's the emacs snippet:

;; ReSTructured Text Flymake configuration

(when (load "flymake" t)
  (defun flymake-rst-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 "rstcheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.rst\\'" flymake-rst-init)))
For the lazier

This snippet will do the complete job for you:

mkdir -p ~/bin &&
cd ~/bin &&
cat <<EOF > ~/bin/rstcheckers &&
#!/bin/bash

rst2html "\$1" > /dev/null
EOF
chmod +x rstcheckers &&
cat <<EOF >> ~/.emacs

;; ReSTructured Text Flymake configuration

(when (load "flymake" t)
  (defun flymake-rst-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 "rstcheckers" (list local-file))))

  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.rst\\'" flymake-rst-init)))

EOF

You'll need to restart emacs for this to work. (Or highlight the added snippet in the end of your .emacs and call M-x eval-region)

Conclusion

I hope this will let you add some more language to your emacs flymake-mode. If you spot any errors, please let me know.

  • http://arjuna.deltoso.net Arjuna Del Toso

    Nice article man, two small nits:

    1) if you use pycheckers.py in a shared hosting like dreamhost it will fail, the fix is trivial pastebin

    2) I would add (add-hook 'find-file-hook 'flymake-find-file-hook) to .emacs to have flymake automatically enabled so you don’t need to M-x it for every file

    • http://www.kalysto.org vaab

      Thank you for your appreciation, and your additions.
      1) Your modification (in pastebin) will lead pycheckers in an endless loop when launched anywhere outside /home.
      Perhaps could you send me the traceback you’ve got, so I can figure out a working solution in your case and the general case.
      2) This instruction is an essential part for using flymake that I have in my ~/.emacs and that somehow got missing in this blog post. Thank you for spotting this.

  • Jesse

    I finally got flymake to display logical warnings. Thank you for this