branch: elpa/flymake-collection commit e9a1e2ab269f0418963665d991423f8afae6bae6 Author: Mohsin Kaleem <mohk...@kisara.moe> Commit: Mohsin Kaleem <mohk...@kisara.moe>
Cleanup flymake-rest-define and remove flymake-rest-parse-* This commit deletes the previous `flymake-rest-parse-rx.el` and `flymake-rest-parse-enumerate.el` files and adds the functionality for them into `flymake-rest-define.el`. To use parse-rx use `flymake-rest-define-rx` and to use parse-enumerate use `flymake-rest-define-enumerate`. This was done to make byte compilation cleaner. Now we don't lexically assign any variables (such as flymake-rest-context) when it will remain unused for a lot of syntax checkers. --- checkers/flymake-rest-awk-gawk.el | 14 +- checkers/flymake-rest-clang.el | 14 +- checkers/flymake-rest-eslint.el | 48 +-- checkers/flymake-rest-gcc.el | 14 +- checkers/flymake-rest-html-tidy.el | 12 +- checkers/flymake-rest-jq.el | 11 +- checkers/flymake-rest-jsonlint.el | 11 +- checkers/flymake-rest-less.el | 10 +- checkers/flymake-rest-lua.el | 16 +- checkers/flymake-rest-luacheck.el | 14 +- checkers/flymake-rest-markdownlint.el | 10 +- checkers/flymake-rest-mypy.el | 14 +- checkers/flymake-rest-proselint.el | 41 ++- checkers/flymake-rest-pycodestyle.el | 13 +- checkers/flymake-rest-pylint.el | 44 +-- checkers/flymake-rest-rubocop.el | 14 +- checkers/flymake-rest-shellcheck.el | 41 ++- checkers/flymake-rest-sql-lint.el | 10 +- checkers/flymake-rest-sqlint.el | 32 +- checkers/flymake-rest-xmllint.el | 14 +- checkers/flymake-rest-yamllint.el | 13 +- flymake-rest-define.el | 599 ++++++++++++++++++++++++---------- flymake-rest-parse-enumerate.el | 56 ---- flymake-rest-parse-rx.el | 164 ---------- flymake-rest.el | 4 +- 25 files changed, 619 insertions(+), 614 deletions(-) diff --git a/checkers/flymake-rest-awk-gawk.el b/checkers/flymake-rest-awk-gawk.el index d1b6a694ab..0f4e3dd77f 100644 --- a/checkers/flymake-rest-awk-gawk.el +++ b/checkers/flymake-rest-awk-gawk.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-awk-gawk "flymake-rest-awk-gawk") -(flymake-rest-define flymake-rest-awk-gawk +(flymake-rest-define-rx flymake-rest-awk-gawk "GNU awk's built-in --lint checker." :title "gawk-awk" :pre-let ((gawk-exec (executable-find "gawk"))) @@ -39,14 +38,13 @@ :write-type 'pipe :command (list gawk-exec ;; Avoid code execution. See https://github.com/w0rp/ale/pull/1411 - "--source" "'BEGIN{exit} END{exit 1}'" + "--source" "BEGIN{exit} END{exit 0}" "-f" "-" "--lint" null-device) - :error-parser - (flymake-rest-parse-rx - ((error bol (? "g") "awk: -:" line ": " (or "fatal" "error") ": " (message) eol) - (warning bol (? "g") "awk: -:" line ": " "warning" ": " (message) eol)))) + :regexps + ((error bol (? "g") "awk: -:" line ": " (or "fatal" "error") ": " (message) eol) + (warning bol (? "g") "awk: -:" line ": " "warning" ": " (message) eol))) (provide 'flymake-rest-awk-gawk) diff --git a/checkers/flymake-rest-clang.el b/checkers/flymake-rest-clang.el index b80880c8df..41c9b34918 100644 --- a/checkers/flymake-rest-clang.el +++ b/checkers/flymake-rest-clang.el @@ -26,8 +26,7 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defvar flymake-rest-clang-args '("-pedantic" "-pedantic-errors") @@ -37,7 +36,7 @@ "Default include path for gcc in `flymake-rest-clang'.") ;;;###autoload (autoload 'flymake-rest-clang "flymake-rest-clang") -(flymake-rest-define flymake-rest-clang +(flymake-rest-define-rx flymake-rest-clang "A C/C++ syntax checker using Clang. See URL `http://clang.llvm.org/'." @@ -63,11 +62,10 @@ See URL `http://clang.llvm.org/'." ('c-mode "c") ((or 'c++-mode _) "c++")) "-") - :error-parser - (flymake-rest-parse-rx - ((error bol "<stdin>:" line ":" column ": " (or "fatal" "error") ": " (message) eol) - (warning bol "<stdin>:" line ":" column ": " "warning" ": " (message) eol) - (note bol "<stdin>:" line ":" column ": " "note" ": " (message) eol)))) + :regexps + ((error bol "<stdin>:" line ":" column ": " (? "fatal ") "error" ": " (message) eol) + (warning bol "<stdin>:" line ":" column ": " "warning" ": " (message) eol) + (note bol "<stdin>:" line ":" column ": " "note" ": " (message) eol))) (provide 'flymake-rest-gcc) diff --git a/checkers/flymake-rest-eslint.el b/checkers/flymake-rest-eslint.el index f284690523..7ca7288a65 100644 --- a/checkers/flymake-rest-eslint.el +++ b/checkers/flymake-rest-eslint.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-enumerate)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-eslint "flymake-rest-eslint") -(flymake-rest-define flymake-rest-eslint +(flymake-rest-define-enumerate flymake-rest-eslint "A Javascript syntax and style checker using eslint. See URL `https://eslint.org/'." @@ -45,25 +44,30 @@ See URL `https://eslint.org/'." "--stdin" ,@(when-let ((file (buffer-file-name flymake-rest-source))) (list "--stdin-filename" file))) - :error-parser - (flymake-rest-parse-enumerate - (alist-get - 'messages - (caar - (flymake-rest-parse-json - (buffer-substring-no-properties - (point-min) (point-max))))) - (let-alist it - (let ((loc (cons (car (flymake-diag-region flymake-rest-source .line .column)) - (cdr (flymake-diag-region flymake-rest-source .endLine .endColumn))))) - (list flymake-rest-source - (car loc) - (cdr loc) - (pcase .severity - (2 :error) - (1 :warning) - (_ :note)) - (concat "[" .ruleId "] " .message)))))) + :generator + (alist-get + 'messages + (caar + (flymake-rest-parse-json + (buffer-substring-no-properties + (point-min) (point-max))))) + :enumerate-parser + (let-alist it + (let* ((start-loc (flymake-diag-region flymake-rest-source .line .column)) + (loc (cons (car start-loc) + (cdr + (if (and .endLine .endColumn) + (flymake-diag-region flymake-rest-source + .endLine (1- .endColumn)) + start-loc))))) + (list flymake-rest-source + (car loc) + (cdr loc) + (pcase .severity + (2 :error) + (1 :warning) + (_ :note)) + (concat "[" .ruleId "] " .message))))) (provide 'flymake-rest-eslint) diff --git a/checkers/flymake-rest-gcc.el b/checkers/flymake-rest-gcc.el index bf728f3a4e..16502840f4 100644 --- a/checkers/flymake-rest-gcc.el +++ b/checkers/flymake-rest-gcc.el @@ -23,10 +23,9 @@ ;;; Code: (require 'flymake) -(require 'flymake-rest-define) (eval-when-compile - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defcustom flymake-rest-gcc-args '("-pedantic" "-pedantic-errors") @@ -40,7 +39,7 @@ :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-gcc "flymake-rest-gcc") -(flymake-rest-define flymake-rest-gcc +(flymake-rest-define-rx flymake-rest-gcc "A C/C++ syntax checker using GCC. Requires GCC 4.4 or newer. See URL `https://gcc.gnu.org/'." @@ -67,11 +66,10 @@ Requires GCC 4.4 or newer. See URL `https://gcc.gnu.org/'." ;; code. "-S" "-o" ,null-device "-") - :error-parser - (flymake-rest-parse-rx - ((error bol "<stdin>:" line ":" column ": " (or "fatal" "error") ": " (message) eol) - (warning bol "<stdin>:" line ":" column ": " "warning" ": " (message) eol) - (note bol "<stdin>:" line ":" column ": " "note" ": " (message) eol)))) + :regexps + ((error bol "<stdin>:" line ":" column ": " (? "fatal ") "error" ": " (message) eol) + (warning bol "<stdin>:" line ":" column ": " "warning" ": " (message) eol) + (note bol "<stdin>:" line ":" column ": " "note" ": " (message) eol))) (provide 'flymake-rest-gcc) diff --git a/checkers/flymake-rest-html-tidy.el b/checkers/flymake-rest-html-tidy.el index 6481998a42..41ba2c7000 100644 --- a/checkers/flymake-rest-html-tidy.el +++ b/checkers/flymake-rest-html-tidy.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-html-tidy "flymake-rest-html-tidy") -(flymake-rest-define flymake-rest-html-tidy +(flymake-rest-define-rx flymake-rest-html-tidy "A HTML syntax and style checker using Tidy. See URL `https://github.com/htacg/tidy-html5'." @@ -40,10 +39,9 @@ See URL `https://github.com/htacg/tidy-html5'." (error "Cannot find tidy executable")) :write-type 'pipe :command `(,tidy-exec "-lang" "en" "-e" "-q") - :error-parser - (flymake-rest-parse-rx - ((error bol "line " line " column " column " - Error: " (message) eol) - (warning bol "line " line " column " column " - Warning: " (message) eol)))) + :regexps + ((error bol "line " line " column " column " - Error: " (message) eol) + (warning bol "line " line " column " column " - Warning: " (message) eol))) (provide 'flymake-rest-html-tidy) diff --git a/checkers/flymake-rest-jq.el b/checkers/flymake-rest-jq.el index 3e33cc4e6b..8f0495bd25 100644 --- a/checkers/flymake-rest-jq.el +++ b/checkers/flymake-rest-jq.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-jq "flymake-rest-jq") -(flymake-rest-define flymake-rest-jq +(flymake-rest-define-rx flymake-rest-jq "JSON checker using the jq tool. This checker accepts multiple consecutive JSON values in a @@ -43,11 +42,9 @@ See URL `https://stedolan.github.io/jq/'." (error "Cannot find jq executable")) :write-type 'pipe :command (list jq-exec "." "-" null-device) - :error-parser - (flymake-rest-parse-rx - ((error bol "parse error: " (message) " at line " line ", column " column eol)))) + :regexps + ((error bol "parse error: " (message) " at line " line ", column " column eol))) (provide 'flymake-rest-jq) - ;;; flymake-rest-jq.el ends here diff --git a/checkers/flymake-rest-jsonlint.el b/checkers/flymake-rest-jsonlint.el index 113450b669..a13a8e86dc 100644 --- a/checkers/flymake-rest-jsonlint.el +++ b/checkers/flymake-rest-jsonlint.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-jsonlint "flymake-rest-jsonlint") -(flymake-rest-define flymake-rest-jsonlint +(flymake-rest-define-rx flymake-rest-jsonlint "A JSON syntax and style checker using jsonlint. See URL `https://github.com/zaach/jsonlint'." @@ -40,11 +39,9 @@ See URL `https://github.com/zaach/jsonlint'." (error "Cannot find jsonlint executable")) :write-type 'file :command (list jsonlint-exec "-c" "-q" flymake-rest-temp-file) - :error-parser - (flymake-rest-parse-rx - ((error bol (file-name) ": line " line ", col " column ", " (message) eol)))) + :regexps + ((error bol (file-name) ": line " line ", col " column ", " (message) eol))) (provide 'flymake-rest-jsonlint) - ;;; flymake-rest-jsonlint.el ends here diff --git a/checkers/flymake-rest-less.el b/checkers/flymake-rest-less.el index 7ad0fa2377..0b3a2edc29 100644 --- a/checkers/flymake-rest-less.el +++ b/checkers/flymake-rest-less.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-less "flymake-rest-less") -(flymake-rest-define flymake-rest-less +(flymake-rest-define-rx flymake-rest-less "A LESS syntax checker using lessc. Requires lessc 1.4 or newer. @@ -42,9 +41,8 @@ See URL `http://lesscss.org'." (error "Cannot find lessc executable")) :write-type 'pipe :command (list lessc-exec "--lint" "--no-color" "-") - :error-parser - (flymake-rest-parse-rx - ((error bol (+ not-newline) ": " (message) " in - on line " line ", column " column ":" eol)))) + :regexps + ((error bol (+ not-newline) ": " (message) " in - on line " line ", column " column ":" eol))) (provide 'flymake-rest-less) diff --git a/checkers/flymake-rest-lua.el b/checkers/flymake-rest-lua.el index 13630184b4..712c60bf54 100644 --- a/checkers/flymake-rest-lua.el +++ b/checkers/flymake-rest-lua.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-lua "flymake-rest-lua") -(flymake-rest-define flymake-rest-lua +(flymake-rest-define-rx flymake-rest-lua "A Lua syntax checker using the Lua compiler. See URL `http://www.lua.org/'." @@ -39,12 +38,11 @@ See URL `http://www.lua.org/'." (user-error "Cannot find lua compiler executable")) :write-type 'pipe :command `(,lua-exec "-p" "-") - :error-parser - (flymake-rest-parse-rx - ((error bol - ;; Skip the name of the luac executable. - (minimal-match (zero-or-more not-newline)) - ": stdin:" line ": " (message) eol)))) + :regexps + ((error bol + ;; Skip the name of the luac executable. + (minimal-match (zero-or-more not-newline)) + ": stdin:" line ": " (message) eol))) (provide 'flymake-rest-lua) diff --git a/checkers/flymake-rest-luacheck.el b/checkers/flymake-rest-luacheck.el index 24062b6c49..d34516885f 100644 --- a/checkers/flymake-rest-luacheck.el +++ b/checkers/flymake-rest-luacheck.el @@ -26,8 +26,7 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defcustom flymake-rest-luacheck-standards nil "The standards to use in luacheck. @@ -47,7 +46,7 @@ non-nil, pass the standards via one or more `--std' options." :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-luacheck "flymake-rest-luacheck") -(flymake-rest-define flymake-rest-luacheck +(flymake-rest-define-rx flymake-rest-luacheck "A Lua syntax checker using luacheck. See URL `https://github.com/mpeterv/luacheck'." @@ -68,11 +67,10 @@ See URL `https://github.com/mpeterv/luacheck'." ,@(when-let ((file (buffer-file-name flymake-rest-source))) (list "--filename" file)) "-") - :error-parser - (flymake-rest-parse-rx - ;; NOTE: `luacheck' before 0.11.0 did not output codes for errors, hence the ID is optional in the error pattern. - ((warning bol (optional (file-name)) ":" line ":" column ":" " (" (id "W" (one-or-more digit)) ") " (message) eol) - (error bol (optional (file-name)) ":" line ":" column ":" (optional " (" (id "E" (one-or-more digit)) ") ") (message) eol)))) + :regexps + ;; NOTE: `luacheck' before 0.11.0 did not output codes for errors, hence the ID is optional in the error pattern. + ((warning bol (optional (file-name)) ":" line ":" column ":" " (" (id "W" (one-or-more digit)) ") " (message) eol) + (error bol (optional (file-name)) ":" line ":" column ":" (optional " (" (id "E" (one-or-more digit)) ") ") (message) eol))) (provide 'flymake-rest-luacheck) diff --git a/checkers/flymake-rest-markdownlint.el b/checkers/flymake-rest-markdownlint.el index 16ca58f893..f8d14d3280 100644 --- a/checkers/flymake-rest-markdownlint.el +++ b/checkers/flymake-rest-markdownlint.el @@ -26,8 +26,7 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defcustom flymake-rest-markdownlint-style nil "Path to the style config for markdownlint." @@ -35,7 +34,7 @@ :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-markdownlint "flymake-rest-markdownlint") -(flymake-rest-define flymake-rest-markdownlint +(flymake-rest-define-rx flymake-rest-markdownlint "Markdown checker using mdl. See URL `https://github.com/markdownlint/markdownlint'." @@ -47,9 +46,8 @@ See URL `https://github.com/markdownlint/markdownlint'." :command `(,mdl-exec ,@(and flymake-rest-markdownlint-style `("--style" ,flymake-rest-markdownlint-style))) - :error-parser - (flymake-rest-parse-rx - ((error bol "(stdin):" line ": " (id "MD" (+ digit)) " " (message) eol)))) + :regexps + ((error bol "(stdin):" line ": " (id "MD" (+ digit)) " " (message) eol))) (provide 'flymake-rest-markdownlint) diff --git a/checkers/flymake-rest-mypy.el b/checkers/flymake-rest-mypy.el index 212be607f1..0f3b451e54 100644 --- a/checkers/flymake-rest-mypy.el +++ b/checkers/flymake-rest-mypy.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-mypy "flymake-rest-mypy") -(flymake-rest-define flymake-rest-mypy +(flymake-rest-define-rx flymake-rest-mypy "Mypy syntax and type checker. Requires mypy>=0.580. See URL `http://mypy-lang.org/'." @@ -47,11 +46,10 @@ See URL `http://mypy-lang.org/'." "--show-absolute-path" "--show-error-codes" flymake-rest-temp-file) - :error-parser - (flymake-rest-parse-rx - ((error bol (file-name) ":" line ":" column ": error: " (message) eol) - (warning bol (file-name) ":" line ":" column ": warning: " (message) eol) - (note bol (file-name) ":" line ":" column ": note: " (message) eol)))) + :regexps + ((error bol (file-name) ":" line ":" column ": error: " (message) eol) + (warning bol (file-name) ":" line ":" column ": warning: " (message) eol) + (note bol (file-name) ":" line ":" column ": note: " (message) eol))) (provide 'flymake-rest-mypy) diff --git a/checkers/flymake-rest-proselint.el b/checkers/flymake-rest-proselint.el index dacfc7f886..6443315ff6 100644 --- a/checkers/flymake-rest-proselint.el +++ b/checkers/flymake-rest-proselint.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-enumerate)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-proselint "flymake-rest-proselint") -(flymake-rest-define flymake-rest-proselint +(flymake-rest-define-enumerate flymake-rest-proselint "Flymake checker using Proselint. See URL `http://proselint.com/'." @@ -40,25 +39,23 @@ See URL `http://proselint.com/'." (error "Cannot find proselint executable")) :write-type 'pipe :command `(,proselint-exec "--json" "-") - :error-parser - (flymake-rest-parse-enumerate - (alist-get 'errors - (alist-get 'data - (car - (flymake-rest-parse-json - (buffer-substring-no-properties - (point-min) (point-max)))))) - (let-alist it - ;; (cons (car (flymake-diag-region flymake-rest-source .line .column)) - ;; (cdr (flymake-diag-region flymake-rest-source .endLine .endColumn))) - (list flymake-rest-source - .start - .end - (pcase .severity - ("suggestion" :note) - ("warning" :warning) - ((or "error" _) :error)) - (concat (propertize .check 'face 'flymake-rest-diag-id) " " .message))))) + :generator + (alist-get 'errors + (alist-get 'data + (car + (flymake-rest-parse-json + (buffer-substring-no-properties + (point-min) (point-max)))))) + :enumerate-parser + (let-alist it + (list flymake-rest-source + .start + .end + (pcase .severity + ("suggestion" :note) + ("warning" :warning) + ((or "error" _) :error)) + (concat (propertize .check 'face 'flymake-rest-diag-id) " " .message)))) (provide 'flymake-rest-proselint) diff --git a/checkers/flymake-rest-pycodestyle.el b/checkers/flymake-rest-pycodestyle.el index 34854ae80c..5531efc900 100644 --- a/checkers/flymake-rest-pycodestyle.el +++ b/checkers/flymake-rest-pycodestyle.el @@ -26,11 +26,13 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-pycodestyle "flymake-rest-pycodestyle") -(flymake-rest-define flymake-rest-pycodestyle +(flymake-rest-define-rx flymake-rest-pycodestyle + "Python style guide checker. + +See URL `https://github.com/PyCQA/pycodestyle'." :title "pycodestyle" :pre-let ((pycodestyle-exec (executable-find "pycodestyle"))) :pre-check @@ -39,9 +41,8 @@ :write-type 'file :source-inplace t :command (list pycodestyle-exec flymake-rest-temp-file) - :error-parser - (flymake-rest-parse-rx - ((error bol (file-name) ":" line ":" column ": " (id (or "E" "W") (one-or-more digit)) " " (message) eol)))) + :regexps + ((error bol (file-name) ":" line ":" column ": " (id (or "E" "W") (one-or-more digit)) " " (message) eol))) (provide 'flymake-rest-pycodestyle) diff --git a/checkers/flymake-rest-pylint.el b/checkers/flymake-rest-pylint.el index 05315cbccd..d967591b30 100644 --- a/checkers/flymake-rest-pylint.el +++ b/checkers/flymake-rest-pylint.el @@ -26,11 +26,15 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-enumerate)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-pylint "flymake-rest-pylint") -(flymake-rest-define flymake-rest-pylint +(flymake-rest-define-enumerate flymake-rest-pylint + "A Python syntax and style checker using Pylint. + +This syntax checker requires Pylint 1.0 or newer. + +See URL `https://www.pylint.org/'." :title "pylint" :pre-let ((python-exec (executable-find "python3")) (pylint-exec (executable-find "pylint")) @@ -49,23 +53,23 @@ "--output-format=json" "--from-stdin" file-name) - :error-parser - (flymake-rest-parse-enumerate - (car - (flymake-rest-parse-json - (buffer-substring-no-properties - (point-min) (point-max)))) - (let-alist it - (let ((loc (flymake-diag-region flymake-rest-source .line .column))) - (list flymake-rest-source - (car loc) - (cdr loc) - (pcase .type - ;; See "pylint/utils.py" - ((or "fatal" "error") :error) - ((or "warning" "refactor" "convention") :warning) - ((or "info" _) :note)) - (concat (propertize .message-id 'face 'flymake-rest-diag-id) " " .message)))))) + :generator + (car + (flymake-rest-parse-json + (buffer-substring-no-properties + (point-min) (point-max)))) + :enumerate-parser + (let-alist it + (let ((loc (flymake-diag-region flymake-rest-source .line .column))) + (list flymake-rest-source + (car loc) + (cdr loc) + (pcase .type + ;; See "pylint/utils.py" + ((or "fatal" "error") :error) + ((or "warning" "refactor" "convention") :warning) + ((or "info" _) :note)) + (concat (propertize .message-id 'face 'flymake-rest-diag-id) " " .message))))) (provide 'flymake-rest-pylint) diff --git a/checkers/flymake-rest-rubocop.el b/checkers/flymake-rest-rubocop.el index 0cf09a3c82..be91eafd50 100644 --- a/checkers/flymake-rest-rubocop.el +++ b/checkers/flymake-rest-rubocop.el @@ -26,8 +26,7 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defcustom flymake-rest-rubocop-use-bundler t "When true use bundle exec for rubocop checks." @@ -35,7 +34,7 @@ :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-rubocop "flymake-rest-rubocop") -(flymake-rest-define flymake-rest-rubocop +(flymake-rest-define-rx flymake-rest-rubocop "A Ruby syntax checker using rubocop. See URL `https://github.com/rubocop/rubocop'." @@ -68,11 +67,10 @@ See URL `https://github.com/rubocop/rubocop'." ;; Rubocop takes the original file name as argument when reading ;; from standard input "--stdin" ,file-name) - :error-parser - (flymake-rest-parse-rx - ((error bol (file-name) ":" line ":" column ": " (or "E" "F") ": " (? "[Correctable] ") (message) eol) - (warning bol (file-name) ":" line ":" column ": " "C" ": " (? "[Correctable] ") (message) eol) - (note bol (file-name) ":" line ":" column ": " "W" ": " (? "[Correctable] ") (message) eol)))) + :regexps + ((error bol (file-name) ":" line ":" column ": " (or "E" "F") ": " (? "[Correctable] ") (message) eol) + (warning bol (file-name) ":" line ":" column ": " "C" ": " (? "[Correctable] ") (message) eol) + (note bol (file-name) ":" line ":" column ": " "W" ": " (? "[Correctable] ") (message) eol))) (provide 'flymake-rest-rubocop) diff --git a/checkers/flymake-rest-shellcheck.el b/checkers/flymake-rest-shellcheck.el index 9cf9cbc51d..f71ad410a1 100644 --- a/checkers/flymake-rest-shellcheck.el +++ b/checkers/flymake-rest-shellcheck.el @@ -26,18 +26,17 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-enumerate)) + (require 'flymake-rest-define)) (defcustom flymake-rest-shellcheck-follow-sources t - "Whether to follow" + "Whether to follow sources in `flymake-rest-shellcheck'." :type '(choice (const :tag "Follow source files" t) (const :tag "Follow source files and lint them" lint) (const :tag "Do not follow source files" nil)) :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-shellcheck "flymake-rest-shellcheck") -(flymake-rest-define flymake-rest-shellcheck +(flymake-rest-define-enumerate flymake-rest-shellcheck "A shell script syntax and style checker using Shellcheck. See URL `https://github.com/koalaman/shellcheck/'." @@ -55,23 +54,23 @@ See URL `https://github.com/koalaman/shellcheck/'." ,@(when (eq flymake-rest-shellcheck-follow-sources 'lint) '("--check-sourced")))) "-") - :error-parser - (flymake-rest-parse-enumerate - (car - (flymake-rest-parse-json - (buffer-substring-no-properties - (point-min) (point-max)))) - (let-alist it - (let ((loc (cons (car (flymake-diag-region flymake-rest-source .line .column)) - (cdr (flymake-diag-region flymake-rest-source .endLine .endColumn))))) - (list flymake-rest-source - (car loc) - (cdr loc) - (pcase .level - ("error" :error) - ("warning" :warning) - ((or "info" "style" _) :note)) - (concat (propertize (format "SC%s" .code) 'face 'flymake-rest-diag-id) " " .message)))))) + :generator + (car + (flymake-rest-parse-json + (buffer-substring-no-properties + (point-min) (point-max)))) + :enumerate-parser + (let-alist it + (let ((loc (cons (car (flymake-diag-region flymake-rest-source .line .column)) + (cdr (flymake-diag-region flymake-rest-source .endLine .endColumn))))) + (list flymake-rest-source + (car loc) + (cdr loc) + (pcase .level + ("error" :error) + ("warning" :warning) + ((or "info" "style" _) :note)) + (concat (propertize (format "SC%s" .code) 'face 'flymake-rest-diag-id) " " .message))))) (provide 'flymake-rest-shellcheck) diff --git a/checkers/flymake-rest-sql-lint.el b/checkers/flymake-rest-sql-lint.el index 17b12dc142..fa47fc85c7 100644 --- a/checkers/flymake-rest-sql-lint.el +++ b/checkers/flymake-rest-sql-lint.el @@ -26,8 +26,7 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) (defcustom flymake-rest-sql-lint-driver nil "The SQL driver to pass to sql-lint." @@ -38,7 +37,7 @@ :group 'flymake-rest) ;;;###autoload (autoload 'flymake-rest-sql-lint "flymake-rest-sql-lint") -(flymake-rest-define flymake-rest-sql-lint +(flymake-rest-define-rx flymake-rest-sql-lint "A SQL syntax checker using the sql-lint tool. See URL `https://github.com/joereynolds/sql-lint'." @@ -50,9 +49,8 @@ See URL `https://github.com/joereynolds/sql-lint'." :command `(,lint-exec ,@(when flymake-rest-sql-lint-driver `("--driver" ,flymake-rest-sql-lint-driver))) - :error-parser - (flymake-rest-parse-rx - ((warning bol "stdin:" line " [sql-lint: " (id (one-or-more (any alnum "-"))) "] " (message) eol)))) + :regexps + ((warning bol "stdin:" line " [sql-lint: " (id (one-or-more (any alnum "-"))) "] " (message) eol))) (provide 'flymake-rest-sql-lint) diff --git a/checkers/flymake-rest-sqlint.el b/checkers/flymake-rest-sqlint.el index ffd933d903..8be965d594 100644 --- a/checkers/flymake-rest-sqlint.el +++ b/checkers/flymake-rest-sqlint.el @@ -26,11 +26,10 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-sqlint "flymake-rest-sqlint") -(flymake-rest-define flymake-rest-sqlint +(flymake-rest-define-rx flymake-rest-sqlint "A SQL syntax checker using the sqlint tool. See URL `https://github.com/purcell/sqlint'." @@ -40,20 +39,19 @@ See URL `https://github.com/purcell/sqlint'." (error "Cannot find sqlint executable")) :write-type 'pipe :command (list lint-exec) - :error-parser - (flymake-rest-parse-rx - ((warning bol "stdin:" line ":" column ":WARNING " - (message (one-or-more not-newline) - (zero-or-more "\n" - (one-or-more " ") - (one-or-more not-newline))) - eol) - (error bol "stdin:" line ":" column ":ERROR " - (message (one-or-more not-newline) - (zero-or-more "\n" - (one-or-more " ") - (one-or-more not-newline))) - eol)))) + :regexps + ((warning bol "stdin:" line ":" column ":WARNING " + (message (one-or-more not-newline) + (zero-or-more "\n" + (one-or-more " ") + (one-or-more not-newline))) + eol) + (error bol "stdin:" line ":" column ":ERROR " + (message (one-or-more not-newline) + (zero-or-more "\n" + (one-or-more " ") + (one-or-more not-newline))) + eol))) (provide 'flymake-rest-sqlint) diff --git a/checkers/flymake-rest-xmllint.el b/checkers/flymake-rest-xmllint.el index 5d54fb8503..6d7ca9e7c7 100644 --- a/checkers/flymake-rest-xmllint.el +++ b/checkers/flymake-rest-xmllint.el @@ -26,15 +26,13 @@ (require 'flymake-rest) (eval-when-compile - (require 'flymake-rest-define) - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-xmllint "flymake-rest-xmllint") -(flymake-rest-define flymake-rest-xmllint +(flymake-rest-define-rx flymake-rest-xmllint "A XML syntax checker and validator using the xmllint utility. -The xmllint is part of libxml2, see URL -`http://www.xmlsoft.org/'." +The xmllint is part of libxml2, see URL `http://www.xmlsoft.org/'." :title "xmllint" :pre-let ((xmllint-exec (executable-find "xmllint"))) :pre-check @@ -42,11 +40,9 @@ The xmllint is part of libxml2, see URL (error "Cannot find xmllint executable")) :write-type 'pipe :command `(,xmllint-exec "--noout" "-") - :error-parser - (flymake-rest-parse-rx - ((error bol "-:" line ": " (message) eol)))) + :regexps + ((error bol "-:" line ": " (message) eol))) (provide 'flymake-rest-xmllint) -;;; flymake-rest-xml.el ends here ;;; flymake-rest-xmllint.el ends here diff --git a/checkers/flymake-rest-yamllint.el b/checkers/flymake-rest-yamllint.el index 4e091f026c..01e545472c 100644 --- a/checkers/flymake-rest-yamllint.el +++ b/checkers/flymake-rest-yamllint.el @@ -23,14 +23,14 @@ ;;; Code: (require 'flymake) -(require 'flymake-rest-define) (eval-when-compile - (require 'flymake-rest-parse-rx)) + (require 'flymake-rest-define)) ;;;###autoload (autoload 'flymake-rest-yamllint "flymake-rest-yamllint") -(flymake-rest-define flymake-rest-yamllint +(flymake-rest-define-rx flymake-rest-yamllint "A YAML syntax checker using YAMLLint. + See URL `https://github.com/adrienverge/yamllint'." :title "yamllint" :pre-let ((yamllint-exec (executable-find "yamllint"))) @@ -38,10 +38,9 @@ See URL `https://github.com/adrienverge/yamllint'." (error "Cannot find yamllint executable")) :write-type 'pipe :command (list yamllint-exec "-f" "parsable" "-") - :error-parser - (flymake-rest-parse-rx - ((error bol "stdin:" line ":" column ": " "[error] " (message) eol) - (warning bol "stdin:" line ":" column ": " "[warning] " (message) eol)))) + :regexps + ((error bol "stdin:" line ":" column ": " "[error] " (message) eol) + (warning bol "stdin:" line ":" column ": " "[warning] " (message) eol))) (provide 'flymake-rest-yamllint) diff --git a/flymake-rest-define.el b/flymake-rest-define.el index e3bd813aae..35749edc04 100644 --- a/flymake-rest-define.el +++ b/flymake-rest-define.el @@ -22,14 +22,29 @@ ;;; Commentary: -;; This file provides a macro, adapted heavily from [[https://github.com/karlotness/flymake-quickdef/blob/150c5839768a3d32f988f9dc08052978a68f2ad7/flymake-quickdef.el][flymake-quickdef]], to allow -;; streamlined syntax-checker definitions. The intended purpose is to abstract -;; the process creation, management and cleanup for a checker as much as possible, -;; leaving the developer to only have to specify what command to run and how -;; to parse its output. +;; This file provides a macro `flymake-rest-define', adapted heavily from +;; [[https://github.com/karlotness/flymake-quickdef/blob/150c5839768a3d32f988f9dc08052978a68f2ad7/flymake-quickdef.el][flymake-quickdef]], to allow streamlined syntax-checker definitions. The +;; intended purpose is to abstract the process creation, management and cleanup +;; for a checker as much as possible, leaving the developer to only have to +;; specify what command to run and how to parse its output. + +;; Also in this file you'll find helper macros to parse diagnostics using +;; regexps and simplify JSON processing. +;; +;; `flymake-rest-define-rx' works by defining some regular expressions (one for +;; each severity level of the checker) and then matching each line of the output +;; to a regular expression. Special capture groups have been setup by the parser +;; that should be used by any calling checkers to ensure the correct fields from +;; the output can be parsed. The approach for this was heavily inspired by +;; flychecks :error-parsers feature. +;; +;; `flymake-rest-define' can be used to parse JSON output from the checker into +;; flymake diagnostics. This works by parsing the entire JSON input into a list +;; of diagnostic related data, and then iteratively parsing it into diagnostics. ;;; Code: +(require 'cl-lib) (require 'flymake) ;;;###autoload @@ -40,205 +55,445 @@ finished its removed and killed. In the very often circumstance where a new check is begun while an old check is still pending, the old check is killed and replaced with the new check.") -(defmacro flymake-rest-define (name &optional docstring &rest defs) - "Quickly define a backend for use with Flymake. -This macro creates a new function NAME which is suitable for use with the -variable `flymake-diagnostic-functions'. DOCSTRING if given will become the -docstring of the checker function. + +;;; `flymake-rest-define' + +(defun flymake-rest-define--temp-file (temp-dir temp-file source-inplace) + "Let forms for defining a temporary directory and file. +TEMP-DIR and TEMP-FILE are the symbols used for the corresponding variables. +SOURCE-INPLACE specifies whether the TEMP-DIR should be in the same working +directory as the current buffer." + `((,temp-dir + ,@(let ((forms + (append + (when source-inplace + '((when-let ((dir (or (when-let ((file (buffer-file-name))) + (file-name-directory file)) + default-directory))) + (unless (file-exists-p dir) + (error "Checker needs to be run in the cwd, but the cwd \ +doesn't exist: %s" dir)) + dir))) + '((make-temp-file "flymake-" t))))) + (if (> (length forms) 1) + `((or ,@forms)) + forms))) + (,temp-file + (let ((temporary-file-directory ,temp-dir) + (basename (file-name-nondirectory (or (buffer-file-name) + (buffer-name))))) + (make-temp-file ".flymake_" nil (concat "_" basename)))))) -DEFS is a plist of values used to setup the backend. The only required fields -in DEFS is :command and :error-parser. +(defmacro flymake-rest-define--parse-diags + (title proc-symb diags-symb current-diag-symb source-symb error-parser) + "Helper macro to parse diagnostics into DIAGS-SYMB. +TITLE is the title of the current syntax checker. PROC-SYMB, DIAGS-SYMB, +CURRENT-DIAGS-SYMB, SOURCE-SYMB, ERROR-PARSER are all described in +`flymake-rest-define'." + `(with-current-buffer ,source-symb + (save-restriction + (widen) + (with-current-buffer (process-buffer ,proc-symb) + (goto-char (point-min)) + (save-match-data + (while (setq ,current-diag-symb (progn ,error-parser)) + (let* ((diag-beg (nth 1 ,current-diag-symb)) + (diag-end (nth 2 ,current-diag-symb)) + (diag-type (nth 3 ,current-diag-symb))) + (if (and (integer-or-marker-p diag-beg) + (integer-or-marker-p diag-end)) + ;; Skip any diagnostics with a type of nil. This makes it + ;; easier to filter some out. + (when diag-type + ;; Include the checker title in the message. + ,@(when title + `((setf (nth 4 ,current-diag-symb) + (concat + (nth 4 ,current-diag-symb) + ,(concat + " (" + (propertize title 'face 'flymake-rest-checker) + ")"))))) + (push (apply #'flymake-make-diagnostic + ,current-diag-symb) + ,diags-symb)) + (with-current-buffer ,source-symb + (flymake-log + :error + "Got invalid buffer position %s or %s in %s" + diag-beg diag-end ,proc-symb)))))))) + (setq ,diags-symb (nreverse ,diags-symb)))) + +;; WARN: I can't seem to make docstring optional and use keys, because +;; the key for the first keyword argument will become the docstring if +;; there's no docstring. +(cl-defmacro flymake-rest-define + (name docstring + &optional &key title command error-parser write-type + source-inplace pre-let pre-check) + "Quickly define a backend function for use with Flymake. +Define a function NAME which is suitable for use with the variable +`flymake-diagnostic-functions'. DOCSTRING if given will become the +docstring of the checker function. Available Variables +------------------- +Within the body of NAME several macro specific variables will be +made available for use with ERROR-PARSER or COMMAND, and other +optional arguments such as PRE-LET. This includes: +* flymake-rest-source + The the buffer where the syntax check originally began. +* flymake-rest-temp-file + A temporary file where the contents of the current buffer were + written (only if WRITE-TYPE is 'file) +* flymake-rest-temp-dir + The dirname of flymake-rest-temp-file. + +Body Execution +-------------- +The overall execution of the generated function NAME first makes use +of (1) WRITE-TYPE, (2) SOURCE-INPLACE, (3) PRE-LET, and (4) PRE-CHECK. +Then a process is created using (4) COMMAND. Once the process finishes +ERROR-PARSER is called (until it returns nil) to get the next +diagnostic which is then provided to `flymake'. TITLE if provided is +used to suffix the message for each diagnostic. + +WRITE-TYPE specifies how the process for a syntax check should recieve +the input. It should one of 'pipe or 'file (defaulting to 'pipe). +When set to 'file a temporary file will ve created, copying the contents +of the `current-buffer'. The variable flymake-rest-temp-file and +flymake-rest-temp-dir will be bound in the body of NAME and provide +access to this temp-file. +When set to 'pipe, all of the `current-buffer' will be passed to the +process on its standard-input stream after it has begun. + +SOURCE-INPLACE determines whether to also create a temporary directory +for a temporary file (when using a WRITE-TYPE of 'file) or whether to +place the temporary file in the same directory as the file being checked. +This can be useful if the syntax checker also resolves imports or packages +and thus needs to be in the same directory. This is disabled by default +meaning the file is placed in folder in the systems temporary directory. + +PRE-LET is a `let*' form that is assigned after any checker agnostic +variables. Place anything you want exposed to everything else in the +checker here. -flymake-rest-source, flymake-rest-temp-file, fmdq-temp-dir, flymake-rest-context. -Within the body of :error-parser and :command, several macro specific variables -are made available. This includes (1) `flymake-rest-source', -(2) `flymake-rest-temp-file', (3) `flymake-rest-temp-dir', (4) `flymake-rest-context'. - -Body Definitions - -The overall execution of the produced function first makes use of (1) -:write-type, (2) :source-inplace, (3) :pre-let, and (3) :pre-check. Next -a process is created using (4) :command. Once the process is finished -:error-parser is called (until it returns nil) to get the next diagnostic -which is then provided to flymake. (5) :title if provided is used to -suffix the messages for each diagnostic. - -:write-type specifies how the process for flymake should recieve the input. -It should be one of 'pipe or 'file (defaulting to 'pipe). When set to file -a temporary file will be created copying the contents of the `current-buffer'. -The variable flymake-rest-temp-file and flymake-rest-temp-dir will be bound in the body -of the rest of the keywords that provide access to the temp-file. When set -to pipe after the process has been started all of the current buffers input -will be passed to the process through standard-input. - -:source-inplace is a boolean that sets flymake-rest-temp-dir to the current working -directory. By default this is nil and the temp-file used for :write-type 'file -will be set to a folder in the systems temporary directory. - -:pre-let is a `let*' form that is assigned after any backend-agnostic let -forms have been setup. - -:pre-check is a Lisp form that will be executed immeadiately before any pending -checker processes are killed and a new process is begun. It can check conditions -to ensure launching the checker program is possible. If something is wrong it -should signal an error. - -:command is a lip form which evaluates to a list of strings that will be used to -start the checker process. It should be suitable for use as the :command argument -to the `make-process' function. - -:error-parser is a lisp-form that should, each time it is evaluated, return the -next diagnostic from the checker output. The result should be a value that can -be passed to the `flymake-make-diagnostic' function. Once there're no more -diagnostics to parse this form should evaluate to nil." +PRE-CHECK is a Lisp form that will be executed immeadiately before any +pending checker processes are killed and a new process is begun. It can +check conditions to ensure launching the checker program is possible. If +something is wrong it should signal an error. + +COMMAND is a Lisp form which evaluates to a list of strings that will be +used to start the checker process. It should be suitable for use as the +:command argument to the `make-process' function. + +ERROR-PARSER is a lisp-form that should, each time it is evaluated, +return the next diagnostic from the checker output. The result should be +a value that can be passed to the `flymake-make-diagnostic' function. Once +there're no more diagnostics to parse this form should evaluate to nil." (declare (indent defun) (doc-string 2)) (unless lexical-binding (error "Need lexical-binding for flymake-rest-define (%s)" name)) - (or (stringp docstring) - (setq defs (cons docstring defs) - docstring nil)) - (dolist (elem '(:command :error-parser)) - (unless (plist-get defs elem) - (error "Missing flymake backend definition `%s'" elem))) - (let* ((write-type (or (eval (plist-get defs :write-type)) 'pipe)) - (source-inplace (plist-get defs :source-inplace)) - (temp-dir-symb (intern "flymake-rest-temp-dir")) - (temp-file-symb (intern "flymake-rest-temp-file")) - (err-symb (intern "flymake-rest-err")) - (diags-symb (intern "diags")) - (proc-symb (intern "proc")) - (source-symb (intern "flymake-rest-source")) - (current-diags-symb (intern "diag")) + (dolist (elem (list (cons 'command command) + (cons 'error-parser error-parser))) + (unless (cdr elem) + (error "Missing flymake backend definition `%s'" (car elem)))) + + (setq write-type (or (eval write-type) 'pipe)) + (setq source-inplace (eval source-inplace)) + + (unless (memq write-type '(file pipe)) + (error "Invalid `:write-type' value `%s'" write-type)) + + (let* ((temp-dir-symb 'flymake-rest-temp-dir) + (temp-file-symb 'flymake-rest-temp-file) + (proc-symb 'proc) + (err-symb 'flymake-rest-err) + (source-symb 'flymake-rest-source) + (diags-symb 'diags) + (current-diag-symb 'diag) (cleanup-form (when (eq write-type 'file) (if source-inplace `((delete-file ,temp-file-symb)) `((delete-directory ,temp-dir-symb t))))) - (not-obsolete-form `((eq ,proc-symb (plist-get (buffer-local-value 'flymake-rest-define--procs ,source-symb) ',name))))) - ;; Sanitise parsed inputs from `defs'. - (unless (memq write-type '(file pipe nil)) - (error "Invalid `:write-type' value `%s'" write-type)) - + (not-obsolete-form + `((eq ,proc-symb + (plist-get (buffer-local-value 'flymake-rest-define--procs + ,source-symb) + ',name))))) `(defun ,name (report-fn &rest _args) ,docstring (let* ((,source-symb (current-buffer)) - (flymake-rest-context nil) ,@(when (eq write-type 'file) - `((,temp-dir-symb - ,@(let ((forms (append (when source-inplace - `((when-let ((dir (or (when-let ((file (buffer-file-name))) - (file-name-directory file)) - default-directory))) - (unless (file-exists-p dir) - (error "Checker needs to be run in the cwd, but the cwd doesn't exist: %s" dir)) - dir))) - '((make-temp-file "flymake-" t))))) - (if (> (length forms) 1) - `((or ,@forms)) - forms))) - (,temp-file-symb - (let ((temporary-file-directory ,temp-dir-symb) - (basename (file-name-nondirectory (or (buffer-file-name) - (buffer-name))))) - (make-temp-file ".flymake_" - nil - (concat "_" basename)))))) - ,@(plist-get defs :pre-let)) - ;; With vars defined, do :pre-check. - ,@(when-let ((pre-check (plist-get defs :pre-check))) + (flymake-rest-define--temp-file + temp-dir-symb temp-file-symb source-inplace)) + ,@pre-let) + ;; With vars defined, do pre-check. + ,@(when pre-check `((condition-case ,err-symb - (progn ,pre-check) + ,pre-check (error ,@cleanup-form (signal (car ,err-symb) (cdr ,err-symb)))))) - ;; Kill any running (obsolete) processes for current checker and buffer. + ;; Kill any running (obsolete) checkers for current checker and buffer. (let ((,proc-symb (plist-get flymake-rest-define--procs ',name))) (when (process-live-p ,proc-symb) - (kill-process ,proc-symb) - (flymake-log :debug "Killing earlier checker process %s" ,proc-symb))) - + (flymake-log :debug "Killing earlier checker process %s" ,proc-symb) + (kill-process ,proc-symb))) ;; Kick-start checker process. (save-restriction (widen) - ;; Write the current file out before starting checker. ,@(when (eq write-type 'file) `((write-region nil nil ,temp-file-symb nil 'silent))) - (let (proc) - (setq proc - (make-process - :name ,(concat (symbol-name name) "-flymake") - :noquery t - :connection-type 'pipe - :buffer (generate-new-buffer ,(concat " *" (symbol-name name) "-flymake*")) - :command - (let ((cmd ,(plist-get defs :command))) - (prog1 cmd - (flymake-log :debug "Checker command is %s" cmd))) - :sentinel - (lambda (,proc-symb _event) - (unless (process-live-p ,proc-symb) - (unwind-protect - (if ,@not-obsolete-form - (with-current-buffer ,source-symb - ;; First read diagnostics from process buffer referencing the source buffer. - (let ((,diags-symb nil) ,current-diags-symb) - ;; Widen the source buffer to ensure `flymake-diag-region' is correct. - (save-restriction - (widen) - (with-current-buffer (process-buffer ,proc-symb) - (goto-char (point-min)) - (save-match-data - (while (setq ,current-diags-symb ,(plist-get defs :error-parser)) - (let* ((diag-beg (nth 1 ,current-diags-symb)) - (diag-end (nth 2 ,current-diags-symb)) - (diag-type (nth 3 ,current-diags-symb))) - (if (and (integer-or-marker-p diag-beg) - (integer-or-marker-p diag-end)) - ;; Skip any diagnostics with a type of nil - ;; This makes it easier to filter some out. - (when diag-type - ;; Include the checker name/title in the message. - ,@(when (plist-get defs :title) - `((setf (nth 4 ,current-diags-symb) - (concat (nth 4 ,current-diags-symb) - ,(concat - " (" - (propertize (plist-get defs :title) - 'face 'flymake-rest-checker) - ")"))))) - - (push (apply #'flymake-make-diagnostic ,current-diags-symb) - ,diags-symb)) - (with-current-buffer ,source-symb - (flymake-log :error "Got invalid buffer position %s or %s in %s" - diag-beg diag-end ,proc-symb)))))))) - ;; Pass reports back to the callback-function when still not-obsolete. - (if ,@not-obsolete-form - (progn - (let ((status (process-exit-status ,proc-symb))) - (when (and (eq (length ,diags-symb) 0) - (not (eq status 0))) - (flymake-log :warning - "Checker gave no diagnostics but had a non-zero exit status %d\nStderr:" status - (with-current-buffer (process-buffer ,proc-symb) - (format "%s" (buffer-substring-no-properties - (point-min) (point-max))))))) - (funcall report-fn (nreverse ,diags-symb))) - ;; In case the check was cancelled after processing began but before it finished. - (flymake-log :warning "Canceling obsolete check %s" ,proc-symb))) - (flymake-log :warning "Canceling obsolete check %s" ,proc-symb))) - ;; Finished linting, cleanup any temp-files and then kill proc buffer. - ,@cleanup-form - (kill-buffer (process-buffer ,proc-symb))))))) + (let (,proc-symb) + (setq + ,proc-symb + (make-process + :name ,(concat (symbol-name name) "-flymake") + :noquery t + :connection-type 'pipe + :buffer (generate-new-buffer + ,(concat " *" (symbol-name name) "-flymake*")) + :command + (prog1 ,command + (flymake-log :debug "Checker command is %s" ,command)) + :sentinel + (lambda (,proc-symb _event) + (unless (process-live-p ,proc-symb) + (unwind-protect + (if ,@not-obsolete-form + (let ((,diags-symb nil) ,current-diag-symb) + (flymake-rest-define--parse-diags + ,title + ,proc-symb + ,diags-symb + ,current-diag-symb + ,source-symb + ,error-parser) + ;; Report diagnostics when still not-obsolete. + (if ,@not-obsolete-form + (progn + (let ((status (process-exit-status ,proc-symb))) + (when (and (eq (length ,diags-symb) 0) + (not (eq status 0))) + (flymake-log + :warning + "Checker gave no diagnostics but had a non-zero \ +exit status %d\nStderr: %s" + status + (with-current-buffer (process-buffer ,proc-symb) + (format "%s" (buffer-substring-no-properties + (point-min) (point-max))))))) + (funcall report-fn ,diags-symb)) + ;; In case the check was cancelled after processing began + ;; but before it finished. + (flymake-log :warning "Canceling obsolete check %s" ,proc-symb))) + (flymake-log :warning "Canceling obsolete check %s" ,proc-symb)) + ;; Finished linting, cleanup any temp-files and then kill + ;; the process buffer. + ,@cleanup-form + (kill-buffer (process-buffer ,proc-symb))))))) ;; Push the new-process to the process to the process alist. (setq flymake-rest-define--procs (plist-put flymake-rest-define--procs ',name ,proc-symb)) ;; If piping, send data to the process. ,@(when (eq write-type 'pipe) - `((process-send-region proc (point-min) (point-max)) - (process-send-eof proc))) + `((process-send-region ,proc-symb (point-min) (point-max)) + (process-send-eof ,proc-symb))) + ;; Return value of syntax-checker is checker function. ,proc-symb)))))) + +;;; `flymake-rest-define-rx' + +(eval-when-compile + (require 'rx)) + +(defconst flymake-rest-parse-rx-constituents + `((file-name ,(lambda (body) + (rx-to-string + `(group-n 1 ,@(or (cdr body) + '((minimal-match + (one-or-more not-newline))))) + t)) + 0 nil) ;; group 1 + (line . ,(rx (group-n 2 (one-or-more digit)))) + (column . ,(rx (group-n 3 (one-or-more digit)))) + (message ,(lambda (body) + (rx-to-string + `(group-n 4 ,@(or (cdr body) + '((minimal-match + (one-or-more not-newline))))) + t)) + 0 nil) + (id ,(lambda (body) + (rx-to-string `(group-n 5 ,@(cdr body)) t)) + 0 nil) + (end-line . ,(rx (group-n 6 (one-or-more digit)))) + (end-column . ,(rx (group-n 7 (one-or-more digit)))))) + +(defmacro flymake-rest-define--parse-rx (regexps) + "`flymake-rest-define' parser using regular expressions. + +This macro generates a parser that for each line of output from the +checker process, matches one or more regular expressions and then +converts the result to a valid flymake diagnostic that can be +passed back to `flymake-make-diagnostic'. + +REGEXPS should be an alist with the car of each entry being the +severity of the diagnostic it matches (as a symbol that will be +turned into a keyword by this macro) and the cdr should be a +sequence of entries that can be interpreted by the `rx' macro. +To simplify matching specific fields in the parsed output several +helper extensions to `rx' have been defined such as file-name or +line. For a list of these see `flymake-rest-parse-rx-constituents'. +The only required fields that MUST be parsed are the line number +and message. If these are ommited the matched diagnostic will be +skipped. + +WARN: You should not try to capture any extra fields outside of +the special ones described above. This is because any extra capture +groups are used to associate the severity of the diagnostic to the +regexp that matched it (as a performance improvement). + +For an example of this macro in action, see `flymake-rest-pycodestyle'." + (unless (> (length regexps) 0) + (error "Must supply at least one regexp for error, warning or note")) + + (let* ((group-count (length flymake-rest-parse-rx-constituents)) + (regexps + ;; To avoid having to rematch each diagnostic more than once we append + ;; a special extra capture group (greater than all the ones above) that + ;; simply matches the empty string. Then we can index the groups after + ;; the ones above and use that to determine the severity of the symbol. + (cl-loop for (severity . regex) in regexps + with count = group-count + do (setq count (1+ count)) + collect (cons `(seq ,@regex (group-n ,count "")) + (intern (concat ":" (symbol-name severity)))))) + (combined-regex + (let ((rx-constituents (append flymake-rest-parse-rx-constituents + (bound-and-true-p rx-constituents) nil))) + (rx-to-string `(or ,@(mapcar #'car regexps)) + 'no-group))) + (severity-seq (mapcar #'cdr regexps))) + ;; Because if this evaluates to nil `flymake-rest-define' thinks there + ;; are no-more diagnostics to be parsed, we wrap it in a loop that exits + ;; the moment we find a match, but otherwise keeps moving through diagnostics + ;; until there actually aren't any more to match. + `(let (res ; file-name + line column message id end-line end-column severity-ix) + (while (and (not res) + (search-forward-regexp ,combined-regex nil t)) + (setq + res + (save-match-data + (save-excursion + (setq ; file-name (match-string 1) + line (match-string 2) + column (match-string 3) + message (match-string 4) + id (match-string 5) + end-line (match-string 6) + end-column (match-string 7) + severity-ix (- (seq-find #'match-string + (number-sequence ,(1+ group-count) + ,(+ group-count (length regexps)))) + ,(1+ group-count))) + (cond + ;; Log an error when any of the required fields are missing. + ,@(cl-loop for it in '(severity-ix line message) + collect + `((not ,it) + (flymake-log :error + ,(format + "Matched diagnostic didn't capture a %s group" + (symbol-name it))) + nil)) + (t + (let ((loc (flymake-diag-region flymake-rest-source + (string-to-number line) + (when column + (string-to-number column)))) + (loc-end (when end-line + (flymake-diag-region flymake-rest-source + (string-to-number end-line) + (when end-column + (string-to-number end-column)))))) + (when loc-end + (setcdr loc (cdr loc-end))) + (list flymake-rest-source + (car loc) + (cdr loc) + (nth severity-ix (quote ,severity-seq)) + (concat + (when id + (concat (propertize id 'face 'flymake-rest-diag-id) " ")) + message))))))))) + res))) + +(cl-defmacro flymake-rest-define-rx + (name docstring + &optional &key title command write-type source-inplace pre-let pre-check regexps) + "`flymake-rest-define' helper using `rx' syntax to parse diagnostics. +This helper macro adapts `flymake-rest-define' to use an error-parser built +from a collections of REGEXPS (see `flymake-rest-define--parse-rx'). + +See `flymake-rest-define' for a description of NAME, DOCSTRING, TITLE, COMMAND, +WRITE-TYPE, SOURCE-INPLACE, PRE-LET, and PRE-CHECK." + (declare (indent defun) (doc-string 2)) + `(flymake-rest-define ,name + ,docstring + :title ,title + :command ,command + :write-type ,write-type + :source-inplace ,source-inplace + :pre-let ,pre-let + :pre-check ,pre-check + :error-parser + (flymake-rest-define--parse-rx ,regexps))) + + +;;; `flymake-rest-define-enumerate' + +(cl-defmacro flymake-rest-define-enumerate + (name docstring + &optional &key title command write-type source-inplace + pre-let pre-check generator enumerate-parser) + "`flymake-rest-define' helper for dealing with serialised diagnostics. +This helper parses a collection of diagnostics using GENERATOR and then +enumerates through it, entry by entry using ENUMERATE-PARSER. This is useful +for linters that produce output such as JSON, to avoid having to reparse the +output again and again. + +The value of the current entry from GENERATOR in ENUMERATE-PARSER will be set to +the variable `it'. ENUMERATE-PARSER should evaluate to a form that can be passed +to `flymake-make-diagnostic'." + (declare (indent defun) (doc-string 2)) + (let ((entries-var 'flymake-rest-entries) + (parsed-var 'flymake-rest-parsed)) + `(flymake-rest-define ,name + ,docstring + :title ,title + :command ,command + :write-type ,write-type + :source-inplace ,source-inplace + :pre-let ,(append `((,entries-var) + (,parsed-var)) + pre-let) + :pre-check ,pre-check + :error-parser + (progn + (unless ,parsed-var + (setq ,entries-var ,generator + ,parsed-var t)) + (let (it res) + ;; While we haven't found a new diagnostic to return, BUT there're + ;; still diagnostics that can be found in the parsed checker output. + (while (and (not res) + (setq it (pop ,entries-var))) + (setq res ,enumerate-parser)) + res))))) + (provide 'flymake-rest-define) ;;; flymake-rest-define.el ends here diff --git a/flymake-rest-parse-enumerate.el b/flymake-rest-parse-enumerate.el deleted file mode 100644 index 6f7f0657df..0000000000 --- a/flymake-rest-parse-enumerate.el +++ /dev/null @@ -1,56 +0,0 @@ -;;; flymakflymake-backend-parse-enumerate!to simplify checker creation -*- lexical-binding: t -*- - -;; Copyright (c) 2021 Mohsin Kaleem - -;; Permission is hereby granted, free of charge, to any person obtaining a copy -;; of this software and associated documentation files (the "Software"), to deal -;; in the Software without restriction, including without limitation the rights -;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -;; copies of the Software, and to permit persons to whom the Software is -;; furnished to do so, subject to the following conditions: - -;; The above copyright notice and this permission notice shall be included in all -;; copies or substantial portions of the Software. - -;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -;; SOFTWARE. - -;;; Commentary: -;; This file defines an error-parser for `flymake-rest-define' which can be used -;; to parse JSON output from the checker into flymake diagnostics. This works by -;; parsing the entire JSON input into a list of diagnostic related data, and then -;; iteratively parsing it into diagnostics. - -;;; Code: - -;;;###autoload -(defmacro flymake-rest-parse-enumerate (gen &rest body) - "Error parser for `flymake-backend-define' which parses all of -the diagnostics at once using GEN and then prepares them one-at-a-time -with BODY. - -The value of the current entry from GEN in BODY will be set to the variable -`it'. BODY should evaluate to a form that can be passed to -`flymake-make-diagnostic'." - (declare (indent 1)) - (let ((context-var (intern "flymake-rest-context"))) - `(progn - (unless (alist-get 'enumerated ,context-var) - (push (cons 'entries ,gen) ,context-var) - (push '(enumerated t) ,context-var)) - (let (it res) - ;; While we haven't found a new diagnostic to return, BUT there're - ;; still diagnostics that can be found in the parsed checker output. - (while (and (not res) - (setq it (pop (alist-get 'entries ,context-var)))) - (setq res ,@body)) - res)))) - -(provide 'flymake-rest-parse-enumerate) - -;;; flymake-rest-parse-enumerate.el ends here diff --git a/flymake-rest-parse-rx.el b/flymake-rest-parse-rx.el deleted file mode 100644 index f2a710ae34..0000000000 --- a/flymake-rest-parse-rx.el +++ /dev/null @@ -1,164 +0,0 @@ -;;; flymake-rest-rest.el --- A macro to simplify checker creation -*- lexical-binding: t -*- - -;; Copyright (c) 2021 Mohsin Kaleem - -;; Permission is hereby granted, free of charge, to any person obtaining a copy -;; of this software and associated documentation files (the "Software"), to deal -;; in the Software without restriction, including without limitation the rights -;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -;; copies of the Software, and to permit persons to whom the Software is -;; furnished to do so, subject to the following conditions: - -;; The above copyright notice and this permission notice shall be included in all -;; copies or substantial portions of the Software. - -;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -;; SOFTWARE. - -;;; Commentary: -;; This file defines an error-parser for `flymake-rest-define' which can be used -;; to parse plaintext output from the checker into flymake diagnostics. This works -;; by defining some regular expressions (one for each severity level of the checker) -;; and then matching each line of the output to a regular expression. Special -;; capture groups have been setup by the parser that should be used by any calling -;; checkers to ensure the correct fields from the output can be parsed. -;; -;; The approach implemented here was heavily inspired by flychecks :error-parsers -;; feature. - -;;; Code: - -(require 'rx) - -(defconst flymake-rest-parse-rx-constituents - `((file-name ,(lambda (body) - (rx-to-string - `(group-n 1 ,@(or (cdr body) - '((minimal-match - (one-or-more not-newline))))) - t)) - 0 nil) ;; group 1 - (line . ,(rx (group-n 2 (one-or-more digit)))) - (column . ,(rx (group-n 3 (one-or-more digit)))) - (message ,(lambda (body) - (rx-to-string - `(group-n 4 ,@(or (cdr body) - '((minimal-match - (one-or-more not-newline))))) - t)) - 0 nil) - (id ,(lambda (body) - (rx-to-string `(group-n 5 ,@(cdr body)) t)) - 0 nil) - (end-line . ,(rx (group-n 6 (one-or-more digit)))) - (end-column . ,(rx (group-n 7 (one-or-more digit)))))) - -;;;###autoload -(defmacro flymake-rest-parse-rx (regexps) - "`flymake-rest-define' parser using regular expressions. - -This macro generates a parser that for each line of output from the -checker process, matches one or more regular expressions and then -converts the result to a valid flymake diagnostic that can be -passed back to `flymake-make-diagnostic'. - -REGEXPS should be an alist with the car of each entry being the -severity of the diagnostic it matches (as a symbol that will be -turned into a keyword by this macro) and the cdr should be a -sequence of entries that can be interpreted by the `rx' macro. -To simplify matching specific fields in the parsed output several -helper extensions to `rx' have been defined such as file-name or -line. For a list of these see `flymake-rest-parse-rx-constituents'. -The only required fields that MUST be parsed are the line number -and message. If these are ommited the matched diagnostic will be -skipped. - -WARN: You should not try to capture any extra fields outside of -the special ones described above. This is because any extra capture -groups are used to associate the severity of the diagnostic to the -regexp that matched it (as a performance improvement). - -For an example of this macro in action, see `flymake-rest-pycodestyle'." - (unless (> (length regexps) 0) - (error "Must supply at least one regexp for error, warning or note")) - - (let* ((group-count (length flymake-rest-parse-rx-constituents)) - (regexps - ;; To avoid having to rematch each diagnostic more than once we append - ;; a special extra capture group (greater than all the ones above) that - ;; simply matches the empty string. Then we can index the groups after - ;; the ones above and use that to determine the severity of the symbol. - (cl-loop for (severity . regex) in regexps - with count = group-count - do (setq count (1+ count)) - collect (cons `(seq ,@regex (group-n ,count "")) - (intern (concat ":" (symbol-name severity)))))) - (combined-regex - (let ((rx-constituents (append flymake-rest-parse-rx-constituents - rx-constituents nil))) - (rx-to-string `(or ,@(mapcar #'car regexps)) - 'no-group))) - (severity-seq (mapcar #'cdr regexps))) - ;; Because if this evaluates to nil `flymake-rest-define' thinks there - ;; are no-more diagnostics to be parsed, we wrap it in a loop that exits - ;; the moment we find a match, but otherwise keeps moving through diagnostics - ;; until there actually aren't any more to match. - `(let (res ; file-name - line column message id end-line end-column severity-ix) - (while (and (not res) - (search-forward-regexp ,combined-regex nil t)) - (setq - res - (save-match-data - (save-excursion - (setq ; file-name (match-string 1) - line (match-string 2) - column (match-string 3) - message (match-string 4) - id (match-string 5) - end-line (match-string 6) - end-column (match-string 7) - severity-ix (- (seq-find #'match-string - (number-sequence ,(1+ group-count) - ,(+ group-count (length regexps)))) - ,(1+ group-count))) - (cond - ;; Log an error when any of the required fields are missing. - ,@(cl-loop for it in '(severity-ix line message) - collect - `((not ,it) - (flymake-log :error - ,(format - "Matched diagnostic didn't capture a %s group" - (symbol-name it))) - nil)) - (t - (let ((loc (flymake-diag-region flymake-rest-source - (string-to-number line) - (when column - (string-to-number column)))) - (loc-end (when end-line - (flymake-diag-region flymake-rest-source - (string-to-number end-line) - (when end-column - (string-to-number end-column)))))) - (when loc-end - (setcdr loc (cdr loc-end))) - (list flymake-rest-source - (car loc) - (cdr loc) - (nth severity-ix (quote ,severity-seq)) - (concat - (when id - (concat (propertize id 'face 'flymake-rest-diag-id) " ")) - message))))))))) - res))) - -(provide 'flymake-rest-parse-rx) - -;;; flymake-rest-parse-rx.el ends here diff --git a/flymake-rest.el b/flymake-rest.el index 7dc1128dc3..f72fe3e0df 100644 --- a/flymake-rest.el +++ b/flymake-rest.el @@ -6,7 +6,7 @@ ;; Created: 15 June 2021 ;; Homepage: https://github.com/mohkale/flymake-rest ;; Keywords: language tools -;; Package-Requires: ((emacs "27.1")) +;; Package-Requires: ((emacs "27.1") (let-alist "1.0")) ;; SPDX-License-Identifier: MIT ;; Version: 1.0.0 @@ -61,7 +61,7 @@ "Id of a diagnostic.") (defun flymake-rest-parse-json (output) - "Helper for `flymake-rest-define' to parse JSON output OUTPUT. + "Helper for `flymake-rest-define' to parse JSON OUTPUT. Adapted from `flycheck-parse-json'. This reads a bunch of JSON-Lines like output from OUTPUT into a list and then returns it."