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.
- If the exit-code of that command is "success" (i.e. zero):
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.
Tags: emacs, emacs-lisp 2 comments
https://www.lpenz.org/
That looks similar to flycheck https://www.flycheck.org/en/latest/