Commit 3ad712eb authored by Lele Gaifax's avatar Lele Gaifax Committed by João Távora

Add a Flymake backend for Python (bug#28808)

Implement new Flymake backend with related customizable settings.

* lisp/progmodes/python.el (python-flymake-command)
(python-flymake-command-output-pattern)
(python-flymake-msg-alist): New defcustom.
(python--flymake-parse-output): New function, able to parse
python-flymake-command output accordingly to
python-flymake-command-output-pattern.
(python-flymake): New function implementing the backend
interface using python--flymake-parse-output for the real
work.
(python-mode): Add python-flymake to flymake-diagnostic-functions.
parent 8db2b3a7
......@@ -5141,6 +5141,138 @@ returned as is."
"Return non-nil if REGEXP is valid."
(ignore-errors (string-match regexp "") t))
;;; Flymake integration
(defgroup python-flymake nil
"Integration between Python and Flymake."
:group 'python
:link '(custom-group-link :tag "Flymake" flymake)
:version "26.1")
(defcustom python-flymake-command '("pyflakes")
"The external tool that will be used to perform the syntax check.
This is a non empty list of strings, the checker tool possibly followed by
required arguments. Once launched it will receive the Python source to be
checked as its standard input.
To use `flake8' you would set this to (\"flake8\" \"-\")."
:group 'python-flymake
:type '(repeat string))
;; The default regexp accomodates for older pyflakes, which did not
;; report the column number, and at the same time it's compatible with
;; flake8 output, although it may be redefined to explicitly match the
;; TYPE
(defcustom python-flymake-command-output-pattern
(list
"^\\(?:<?stdin>?\\):\\(?1:[0-9]+\\):\\(?:\\(?2:[0-9]+\\):\\)? \\(?3:.*\\)$"
1 2 nil 3)
"Specify how to parse the output of `python-flymake-command'.
The value has the form (REGEXP LINE COLUMN TYPE MESSAGE): if
REGEXP matches, the LINE'th subexpression gives the line number,
the COLUMN'th subexpression gives the column number on that line,
the TYPE'th subexpression gives the type of the message and the
MESSAGE'th gives the message text itself.
If COLUMN or TYPE are nil or that index didn't match, that
information is not present on the matched line and a default will
be used."
:group 'python-flymake
:type '(list regexp
(integer :tag "Line's index")
(choice
(const :tag "No column" nil)
(integer :tag "Column's index"))
(choice
(const :tag "No type" nil)
(integer :tag "Type's index"))
(integer :tag "Message's index")))
(defcustom python-flymake-msg-alist
'(("\\(^redefinition\\|.*unused.*\\|used$\\)" . :warning))
"Alist used to associate messages to their types.
Each element should be a cons-cell (REGEXP . TYPE), where TYPE must be
one defined in the variable `flymake-diagnostic-types-alist'.
For example, when using `flake8' a possible configuration could be:
((\"\\(^redefinition\\|.*unused.*\\|used$\\)\" . :warning)
(\"^E999\" . :error)
(\"^[EW][0-9]+\" . :note))
By default messages are considered errors."
:group 'python-flymake
:type `(alist :key-type (regexp)
:value-type (symbol)))
(defvar-local python--flymake-proc nil)
(defun python--flymake-parse-output (source proc report-fn)
"Collect diagnostics parsing checker tool's output line by line."
(let ((rx (nth 0 python-flymake-command-output-pattern))
(lineidx (nth 1 python-flymake-command-output-pattern))
(colidx (nth 2 python-flymake-command-output-pattern))
(typeidx (nth 3 python-flymake-command-output-pattern))
(msgidx (nth 4 python-flymake-command-output-pattern)))
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(cl-loop
while (search-forward-regexp rx nil t)
for msg = (match-string msgidx)
for (beg . end) = (flymake-diag-region
source
(string-to-number
(match-string lineidx))
(and colidx
(match-string colidx)
(string-to-number
(match-string colidx))))
for type = (or (and typeidx
(match-string typeidx)
(assoc-default
(match-string typeidx)
python-flymake-msg-alist
#'string-match))
(assoc-default msg
python-flymake-msg-alist
#'string-match)
:error)
collect (flymake-make-diagnostic
source beg end type msg)
into diags
finally (funcall report-fn diags)))))
(defun python-flymake (report-fn &rest _args)
"Flymake backend for Python.
This backend uses `python-flymake-command' (which see) to launch a process
that is passed the current buffer's content via stdin.
REPORT-FN is Flymake's callback function."
(unless (executable-find (car python-flymake-command))
(error "Cannot find a suitable checker"))
(when (process-live-p python--flymake-proc)
(kill-process python--flymake-proc))
(let ((source (current-buffer)))
(save-restriction
(widen)
(setq python--flymake-proc
(make-process
:name "python-flymake"
:noquery t
:connection-type 'pipe
:buffer (generate-new-buffer " *python-flymake*")
:command python-flymake-command
:sentinel
(lambda (proc _event)
(when (eq 'exit (process-status proc))
(unwind-protect
(when (with-current-buffer source
(eq proc python--flymake-proc))
(python--flymake-parse-output source proc report-fn))
(kill-buffer (process-buffer proc)))))))
(process-send-region python--flymake-proc (point-min) (point-max))
(process-send-eof python--flymake-proc))))
(defun python-electric-pair-string-delimiter ()
(when (and electric-pair-mode
......@@ -5255,7 +5387,9 @@ returned as is."
(make-local-variable 'python-shell-internal-buffer)
(when python-indent-guess-indent-offset
(python-indent-guess-indent-offset)))
(python-indent-guess-indent-offset))
(add-hook 'flymake-diagnostic-functions #'python-flymake nil t))
(provide 'python)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment