Many linters are external (shell) commands you can invoke with a filename, and which exit with non-zero status-codes on failure.
The problems a given linter will report upon will obviously vary, but actually executing a linter automatically is broadly tool-agnostic, so automating is pretty simple:
- When you save a file, and it is of a type which has a linter associated with it:
- Run the external linter command passing the filename as an argument.
- If the command exits with a non-zero exit-code assume the linter reported failure of some kind.
- Then show the output it produced.
- If the command exits with a zero exit-code we assume success and do nothing.
- If the command exits with a non-zero exit-code assume the linter reported failure of some kind.
- Run the external linter command passing the filename as an argument.
So the only difficult part is knowing what command to execute as a linter for any given filetype:
- For a shell-script we'd call "
shellcheck ..
". - For a terraform file we'd call "
tflint ..
". - For a python-file we'd call "
python3 -m py_compile ..
"- Well something similar, we don't need to have a .pyc file created, etc, etc.
For extra flexibility and options we can also allow the execution of Emacs Lisp to allow custom checks to be made. Same process here:
- If the lisp returns nil, do nothing.
- If the lisp returns some content, treat it as a message/warning to display to the user.
The save-check package is my naive approach to the problem, and contains some simple defaults which work well for me. Loading and using the package is pretty simple, with modern emacs I can enable it like so:
(use-package save-check
:config
(setq save-check-show-eval t)
(global-save-check-mode t))
Now if I want to look for duplicate keys when YAML files are saved, in addition to the standard linting the package enables by default, I can write a simple function:
- Look for all top-level headers.
- A simple regular expression will find them
^([a-zA-Z0-9]+):
- A simple regular expression will find them
- Find any duplicates in the list, filtering out false-positives which are expected.
- If any remain then return them to display - because that's a linter-detected failure.
I wrote the following simple function to look for duplicates:
(defun save-check-custom-yaml ()
"Determine whether there are duplicate top-level keys in the current buffer.
This function is primarily written to be invoked by `save-check' on YAML buffers,
but the code is actually not specific to that purpose."
(interactive)
(let ((matches))
;; get the matches - i.e. top-level keys
(save-match-data
(save-excursion
(save-restriction
(widen)
(goto-char 1)
(while (search-forward-regexp "^\\([a-zA-Z0-9]+\\):" nil t 1)
(push (match-string-no-properties 0) matches)))))
;; now matches contains a list of all top-level keys - look for any duplicate entries
(setq matches (delete-dups (seq-filter
(lambda (el) (member el (cdr (member el matches))))
matches)))
;; remove false-positives which might occur more than once even without error.
(dolist (known '("apiVersion:" "kind:" "metadata:" "spec:"))
(setq matches (delete known matches)))
;; if we have duplicates - format the response based on one vs. multiple duplicates.
(if matches
(if (= 1 (length matches))
(format "There is a duplicate top-level key %s" matches)
(format "There are %d duplicate top-level keys %s" (length matches) matches))
nil)))
With that custom function defined I can now add it to the list of known linters:
(add-to-list 'save-check-config
'(:mode yaml-mode
:eval (save-check-custom-yaml)))
And now when I save YAML files any duplicated top-level keys will show themselves when I save the file.