~manuel-uberti/flymake-proselint

30c4baa08db32e73d956c978c81a9f79062c2e1d — Philip Kaludercic 2 years ago fa4b3a3
Generate and parse JSON output

* flymake-proselint.el
(flymake-proselint-sentinel-1): Add new function for translating JSON
objects to Flymake diagnostics.
(flymake-proselint-sentinel): Extract sentinel into a separate function.
(flymake-proselint-backend): Use 'flymake-proselint-sentinel'.
1 files changed, 83 insertions(+), 35 deletions(-)

M flymake-proselint.el
M flymake-proselint.el => flymake-proselint.el +83 -35
@@ 30,54 30,102 @@

;;; Code:

(eval-when-compile
  (require 'subr-x)
  (require 'pcase))
(require 'flymake)

(defun flymake-proselint-sentinel-1 (source data)
  "Handle a successfully parsed DATA from SOURCE.
DATA is a list of error diagnostics that are converted into
Flymake diagnostic objects."
  (let (diags)
    (dolist (err (plist-get data :errors))
      (push (flymake-make-diagnostic
             source
             (plist-get err :start)
             (plist-get err :end)
             (pcase (plist-get err :severity)
               ("warning"	:warning)
               ("suggestion"	:note)
               (_		:error))
             (with-temp-buffer		;create a message
               (insert (plist-get err :message))
               (let ((replacements (plist-get err :replacements)))
                 (cond
                  ((or (eq replacements :null) (null replacements))
                   ;; There are no replacements.
                   )
                  ((stringp replacements)
                   (insert " (Replacement: " replacements ")"))
                  ((listp replacements)
                   (insert " (Replacements: "
                           (mapconcat
                            (lambda (r)
                              (plist-get r :unique))
                            replacements ", ")
                           ")"))))
               (buffer-string)))
            diags))
    diags))

(defvar-local flymake-proselint--flymake-proc nil)

(defun flymake-proselint-sentinel (proc _event)
  "Sentinel on PROC for handling Proselint response.
A successfully parsed message is passed onto the function
`flymake-proselint-sentinel-1' for further handling."
  (pcase (process-status proc)
    ('exit
     (let ((report-fn (process-get proc 'report-fn))
           (source (process-get proc 'source)))
       (unwind-protect
           (with-current-buffer (process-buffer proc)
             (goto-char (point-min))
             (cond
              ((with-current-buffer source
                 (not (eq proc flymake-proselint--flymake-proc)))
               (flymake-log :warning "Canceling obsolete check %s" proc))
              ((= (point-max) (point-min))
               (flymake-log :debug "Empty response"))
              ((condition-case err
                   (let ((response (json-parse-buffer :object-type 'plist
                                                      :array-type 'list)))
                     (if (string= (plist-get response :status) "success")
                         (thread-last
                           (plist-get response :data)
                           (flymake-proselint-sentinel-1 source)
                           (funcall report-fn))
                       (flymake-log :error "Check failed")))
                 (json-parse-error
                  (flymake-log :error "Invalid response: %S" err))))))
         (with-current-buffer source
           (setq flymake-proselint--flymake-proc nil))
         (kill-buffer (process-buffer proc)))))
    ('signal (kill-buffer (process-buffer proc)))))

(defun flymake-proselint-backend (report-fn &rest _args)
  "Flymake backend for Proselint.
REPORT-FN is the flymake reporter function.  See the Info
node (flymake) Backend functions for more details."
  (unless (executable-find "proselint")
    (user-error "Executable proselint not found on PATH"))

  (when (process-live-p flymake-proselint--flymake-proc)
    (kill-process flymake-proselint--flymake-proc))

  (let ((source (current-buffer)))
  (let ((proc (make-process
               :name "proselint-flymake" :noquery t :connection-type 'pipe
               :buffer (generate-new-buffer " *proselint-flymake*")
               :command '("proselint" "--json" "-")
               :sentinel #'flymake-proselint-sentinel)))
    (process-put proc 'source (current-buffer))
    (process-put proc 'report-fn report-fn)
    (setq flymake-proselint--flymake-proc proc)
    (save-restriction
      (widen)
      (setq
       flymake-proselint--flymake-proc
       (make-process
        :name "proselint-flymake" :noquery t :connection-type 'pipe
        :buffer (generate-new-buffer " *proselint-flymake*")
        :command '("proselint" "-")
        :sentinel
        (lambda (proc _event)
          (when (memq (process-status proc) '(exit signal))
            (unwind-protect
                (if (with-current-buffer source (eq proc flymake-proselint--flymake-proc))
                    (with-current-buffer (process-buffer proc)
                      (goto-char (point-min))
                      (cl-loop
                       while (search-forward-regexp
                              "^.+:\\([[:digit:]]+\\):\\([[:digit:]]+\\): \\(.+\\)$"
                              nil t)
                       for msg = (match-string 3)
                       for (beg . end) = (flymake-diag-region
                                          source
                                          (string-to-number (match-string 1))
                                          (string-to-number (match-string 2)))
                       collect (flymake-make-diagnostic source
                                                        beg
                                                        end
                                                        :warning
                                                        msg)
                       into diags
                       finally (funcall report-fn diags)))
                  (flymake-log :warning "Canceling obsolete check %s"
                               proc))
              (kill-buffer (process-buffer proc)))))))
      (process-send-region flymake-proselint--flymake-proc (point-min) (point-max))
      (process-send-eof flymake-proselint--flymake-proc))))
      (process-send-region proc (point-min) (point-max))
      (process-send-eof proc))))

;;;###autoload
(defun flymake-proselint-setup ()