emacs

Using flymake to check erb templates

Live syntax checking code in Emacs with flymake is extremely
useful. It’s quite easy to use for syntax checking scripting
languages or for running code analysis tools in the
background. Flymake’s initial goal, however, was syntax checking
compiled languages like C by running a custom make target. The
flexibility needed to make all of this work makes configuring
flymake quite involved.

However, for syntax checking a scripting language, you usually copy
a recipe off the emacswiki and be done with it. There’s also a
recipe for ruby, but there was nothing to syntax check ERB
templates with embedded ruby code.

This is how an ERB template usually looks like:

<h1>My awesome blog!</h1>
 
<% @posts.each do |post| %>
  <div class="post">
    <h2><%= post.title %></h2>
    <%= post.body %>
  </div>
<% end %>

Configuring flymake consists of these steps:

  1. Write a flymake init function that takes the contents of the
    current buffer, writes it to a temporary file, sets
    flymake-temp-source-file-name and returns a list with a program to
    call and the arguments to pass to it. This program should return
    with a non-zero exit code in case of errors or warnings.
  2. Tell flymake to use this function for a certain type of file by
    adding an entry to flymake-allowed-file-name-masks.
  3. You might need to add an entry to flymake-err-line-patterns
    with a regex that matches the output of your syntax checker.
  4. Turn on flymake-mode.

<

p>
For making flymake work with ERB the trick is to get the init
function right. Instead of just copying the buffer we have to
transform it from ERB to plain ruby. This can be done by calling
the erb command line utility with the -x switch. The lines in the
resulting ruby code correspond to their equivalents in the ERB
template which is crucial for flymake to highlight errors
correctly. Here the code:

<

p>

(defun flymake-erb-init ()
  (let* ((check-buffer (current-buffer))
         (temp-file (flymake-create-temp-inplace (buffer-file-name) "flymake"))
         (local-file (file-relative-name
                      temp-file
                      (file-name-directory buffer-file-name))))
    (save-excursion
      (save-restriction
        (widen)
        (with-temp-file temp-file 
          (let ((temp-buffer (current-buffer)))
            (set-buffer check-buffer)
            (call-process-region (point-min) (point-max) "erb" nil temp-buffer nil "-x"))))
      (setq flymake-temp-source-file-name temp-file)
      (list "ruby" (list "-c" local-file)))))

You now need to tell flymake to use this function for files with an
extension of .erb:

(push '("\\.erb$" flymake-erb-init) flymake-allowed-file-name-masks)

If you’re still working with files that have a .rhtml extension you
can use this instead:

(push '("\\.\\(erb\\|rhtml\\)$" flymake-erb-init) flymake-allowed-file-name-masks)

Flymake doesn’t come with a pattern for ruby’s error messages, but
you might still have this line if you’re already got syntax
checking for plain ruby files set up:

(push '("^\\(.*\\):\\([0-9]+\\): \\(.*\\)$" 1 2 nil 3) flymake-err-line-patterns)

As you can only add to a list once the library defining it is
loaded you either have to (require 'flymake) first or wrap these
statements in an eval-after-load:

(eval-after-load "flymake"
  '(progn
     (push '("\\.\\(erb\\|rhtml\\)$" flymake-erb-init) flymake-allowed-file-name-masks)
     (push '("^\\(.*\\):\\([0-9]+\\): \\(.*\\)$" 1 2 nil 3) flymake-err-line-patterns)))

Finally you have to make sure flymake-mode is turned on for all .erb
files. If you’re using html-mode for your templates this might
work:

(add-hook 'html-mode-hook 'flymake-mode-on)

However, ERB templates can be used in different places: for
configuration files, YAML fixtures or maybe even CSS. And you might
also have static HTML pages that are not ERB templates and don’t
need to be checked. A possible way to turn on flymake-mode for all
files with an .erb extension would be to turn it on from the
find-file-hook like this:

(defun turn-on-flymake-for-erb-files ()
  (when (string-match "\.\\(erb\\|rhtml\\)$" (buffer-file-name))
    (flymake-mode-on)))
(add-hook 'find-file-hook 'turn-on-flymake-for-erb-files)

Here’s the complete code for you to copy and paste:

(defun flymake-erb-init ()
  (let* ((check-buffer (current-buffer))
         (temp-file (flymake-create-temp-inplace (buffer-file-name) "flymake"))
         (local-file (file-relative-name
                      temp-file
                      (file-name-directory buffer-file-name))))
    (save-excursion
      (save-restriction
        (widen)
        (with-temp-file temp-file 
          (let ((temp-buffer (current-buffer)))
            (set-buffer check-buffer)
            (call-process-region (point-min) (point-max) "erb" nil temp-buffer nil "-x"))))
      (setq flymake-temp-source-file-name temp-file)
      (list "ruby" (list "-c" local-file)))))
 
(eval-after-load "flymake"
  '(progn
     (push '("\\.\\(erb\\|rhtml\\)$" flymake-erb-init) flymake-allowed-file-name-masks)
     (push '("^\\(.*\\):\\([0-9]+\\): \\(.*\\)$" 1 2 nil 3) flymake-err-line-patterns)))
 
(defun turn-on-flymake-for-erb-files ()
  (when (string-match "\.\\(erb\\|rhtml\\)$" (buffer-file-name))
    (flymake-mode-on)))
(add-hook 'find-file-hook 'turn-on-flymake-for-erb-files)

3 Comments

Comments are closed.