About Archive Tags RSS Feed


A simple package for running many linters

3 March 2024 13:00

I used to configure Emacs to run a linter when saving some specific type of files. For example I'd have a perl-utilities package to reformat perl code, and run the perl-linter on saving, then I'd have a hook to do the same thing for Dockerfiles, etc, etc.

It occurred to me recently that I should have a linter for both JSON and YAML files, since I have to edit those filetypes so damn often, and that there wasn't a great solution for those - Until it occurred to me I wrote sysbox which is a simple collection of tools in one binary, and that supports some validation commands:

sysbox validate-json /path/to/file
sysbox validate-yaml /path/to/file
sysbox validate-xml  /path/to/file

With that in mind it became obvious that what I want to do is pretty much always the same:

  • Run an external command, when the file is saved.
    • If the exit-code of that command is "success" (i.e. zero):
      • Do nothing.
    • If the exit-code is "failure" (i.e. non-zero):
      • Show the output.

And this process is the same for ANY of the linters I run. The only thing that changes is the command to run, based on the mode/type of file in question.

That lead to the following configuration:

(defvar save-check-config

        (:mode cperl-mode
         :exec "perl -wc -I. %f"
         :cond (executable-find "perl"))

        (:mode dockerfile-mode
         :exec "hadolint --no-color %f"
         :cond (executable-find "hadolint"))

        (:mode json-mode
         :exec "sysbox validate-json %f"
         :cond (executable-find "sysbox"))

        (:mode nxml-mode
         :exec "sysbox validate-xml %f"
         :cond (executable-find "sysbox"))

        (:mode perl-mode
         :exec "perl -wc -I. %s"
         :cond (executable-find "perl"))

        ;; This avoids creating .pyc files, which would happen if we had
        ;; used the more natural/obvious "python3 -m py_compile %s" approach
        (:mode python-mode
         :exec "python3 -c 'import ast; ast.parse(open(\"%f\").read())'"
         :cond (executable-find "python3"))

        (:mode sh-mode
         :exec "shellcheck %f"
         :cond (executable-find "shellcheck"))

        (:mode terraform-mode
         :exec "tflint --no-color --chdir %d"
         :cond (executable-find "tflint"))

        (:mode yaml-mode
         :exec "sysbox validate-yaml %f"
         :cond (executable-find "sysbox"))

Basically a list of things:

  • We have the mode of files to which the linter/validator applies.
  • We have the command to run
    • %f is changed to the filename which has just been saved.
    • %d is changed to the directory-name containing that file.
  • We add a :cond key to decide if we should run.
    • Which basically is used for "if the binary is found .. run it, otherwise silently do nothing".

I'm quite pleased with how simple the package was to write, and now I have all my linting configuration in one-place.

I'd be tempted to do the same for "format on save", but to be honest with LSP most of the code I care about has that in-place already.

Should I rename to "multi-lint[er].el"? Probably, but I guess we'll see in the future.



Comments on this entry

icon Leandro at 13:51 on 3 March 2024

That looks similar to flycheck https://www.flycheck.org/en/latest/

icon Steve Kemp at 14:20 on 3 March 2024

Yes I guess it is. I've been vaguely aware of flycheck, but I haven't used it in a long time. Mostly I ignored it because It tends to do more complex things, like show errors as you work - rather than just as you save.

Another thing I looked at a while back was a code-formatter with a similar setup, using the mode to know what to format, if anything:

  • https://github.com/lassik/emacs-format-all-the-code

I tend to have some modes that do format on save, but they're all LSP-based so I figured I should leave that alone and stick with what works.

Add your comment

Your Comment

Your submission will be ignored if any of the fields are left blank, but your email address will never be displayed.