From 483f1e834d9008e7b48d7abb3afa84be352014b1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Wed, 27 Sep 2017 02:44:06 +0100 Subject: [PATCH] A couple of Flymake backends for emacs-lisp-mode Loading flymake-elisp.el doesn't setup flymake-mode to turn on automatically, but it affects emacs-lisp-mode-hook so that flymake-diagnostic-functions is setup with a suitable buffer-local value. The variable flymake-diagnostic-funtions in every live emacs-lisp-mode buffer is also adjusted. * lisp/progmodes/flymake.el (top): Require flymake-elisp. * lisp/progmodes/flymake-elisp.el: New file. --- lisp/progmodes/flymake-elisp.el | 176 ++++++++++++++++++++++++++++++++ lisp/progmodes/flymake.el | 12 ++- 2 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 lisp/progmodes/flymake-elisp.el diff --git a/lisp/progmodes/flymake-elisp.el b/lisp/progmodes/flymake-elisp.el new file mode 100644 index 00000000000..bf60f57c82d --- /dev/null +++ b/lisp/progmodes/flymake-elisp.el @@ -0,0 +1,176 @@ +;;; flymake-elisp.el --- Flymake backends for emacs-lisp-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2017 João Távora + +;; Author: João Távora +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Flymake backends for elisp work. + +;;; Code: +(require 'flymake) +(require 'checkdoc) +(eval-when-compile (require 'cl-lib)) +(require 'bytecomp) + +(defun flymake-elisp--checkdoc-1 () + "Do actual work for `flymake-elisp-checkdoc'." + (let (collected) + (cl-letf (((symbol-function 'checkdoc-create-error) + (lambda (text start end &optional unfixable) + (push (list text start end unfixable) collected) + nil))) + (let* ((checkdoc-autofix-flag nil) + (checkdoc-generate-compile-warnings-flag nil) + (buf (generate-new-buffer " *checkdoc-temp*")) + (checkdoc-diagnostic-buffer buf)) + (unwind-protect + (save-excursion + (checkdoc-current-buffer t)) + (kill-buffer buf)))) + collected)) + +(defun flymake-elisp-checkdoc (report-fn) + "A flymake backend for `checkdoc'. +Calls REPORT-FN directly." + (when (derived-mode-p 'emacs-lisp-mode) + (funcall report-fn + (cl-loop for (text start end _unfixable) in + (flymake-elisp--checkdoc-1) + collect + (flymake-make-diagnostic + (current-buffer) + start end :note text))))) + +(defun flymake-elisp--byte-compile-done (report-fn + origin-buffer + output-buffer + temp-file) + (unwind-protect + (with-current-buffer + origin-buffer + (save-excursion + (save-restriction + (widen) + (funcall + report-fn + (ignore-errors + (cl-loop with data = + (with-current-buffer output-buffer + (goto-char (point-min)) + (search-forward ":flymake-elisp-output-start") + (read (point-marker))) + for (string pos _fill level) in data + do (goto-char pos) + for beg = (if (< (point) (point-max)) + (point) + (line-beginning-position)) + for end = (min + (line-end-position) + (or (cdr + (bounds-of-thing-at-point 'sexp)) + (point-max))) + collect (flymake-make-diagnostic + (current-buffer) + (if (= beg end) (1- beg) beg) + end + level + string))))))) + (kill-buffer output-buffer) + (ignore-errors (delete-file temp-file)))) + +(defun flymake-elisp-byte-compile (report-fn) + "A flymake backend for elisp byte compilation. +Spawn an Emacs process that byte-compiles a file representing the +current buffer state and calls REPORT-FN when done." + (interactive (list (lambda (stuff) + (message "aha %s" stuff)))) + (when (derived-mode-p 'emacs-lisp-mode) + (let ((temp-file (make-temp-file "flymake-elisp-byte-compile")) + (origin-buffer (current-buffer))) + (save-restriction + (widen) + (write-region (point-min) (point-max) temp-file nil 'nomessage)) + (let* ((output-buffer (generate-new-buffer " *flymake-elisp-byte-compile*"))) + (make-process + :name "flymake-elisp-byte-compile" + :buffer output-buffer + :command (list (expand-file-name invocation-name invocation-directory) + "-Q" + "--batch" + ;; "--eval" "(setq load-prefer-newer t)" ; for testing + "-L" default-directory + "-l" "flymake-elisp" + "-f" "flymake-elisp--batch-byte-compile" + temp-file) + :connection-type 'pipe + :sentinel + (lambda (proc _event) + (unless (process-live-p proc) + (flymake-elisp--byte-compile-done report-fn + origin-buffer + output-buffer + temp-file)))) + :stderr null-device + :noquery t)))) + +(defun flymake-elisp--batch-byte-compile (&optional file) + "Helper for `flymake-elisp-byte-compile'. +Runs in a batch-mode Emacs. Interactively use variable +`buffer-file-name' for FILE." + (interactive (list buffer-file-name)) + (let* ((file (or file + (car command-line-args-left))) + (dummy-elc-file) + (byte-compile-log-buffer + (generate-new-buffer " *dummy-byte-compile-log-buffer*")) + (byte-compile-dest-file-function + (lambda (source) + (setq dummy-elc-file (make-temp-file (file-name-nondirectory source))))) + (collected)) + (unwind-protect + (cl-letf (((symbol-function 'byte-compile-log-warning) + (lambda (string &optional fill level) + (push (list string byte-compile-last-position fill level) + collected) + t))) + (byte-compile-file file)) + (ignore-errors + (delete-file dummy-elc-file) + (kill-buffer byte-compile-log-buffer))) + (prin1 :flymake-elisp-output-start) + (terpri) + (pp collected))) + +(defun flymake-elisp-setup-backends () + "Setup flymake for elisp work." + (add-to-list (make-local-variable 'flymake-diagnostic-functions) + 'flymake-elisp-checkdoc t) + (add-to-list (make-local-variable 'flymake-diagnostic-functions) + 'flymake-elisp-byte-compile t)) + +(add-hook 'emacs-lisp-mode-hook + 'flymake-elisp-setup-backends) + +(dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (derived-mode-p 'emacs-lisp-mode) + (flymake-elisp-setup-backends)))) + +(provide 'flymake-elisp) +;;; flymake-elisp.el ends here diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 03b319f8715..8c92dc7e53f 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -526,10 +526,15 @@ A backend is disabled if it reported `:panic'.") (flymake-log :warning "Disabled the backend %s due to reports of %s (%s)" backend action explanation)) -(cl-defun flymake--handle-report (backend action &key explanation) - "Handle reports from flymake backend identified by BACKEND." +(cl-defun flymake--handle-report (backend action &key explanation force) + "Handle reports from flymake backend identified by BACKEND. + +BACKEND, ACTION and EXPLANATION conform to the calling convention +described in `flymake-diagnostic-functions' (which see). Optional +FORCE says to handle a report even if it was not expected." (cond - ((not (memq backend flymake--running-backends)) + ((and (not (memq backend flymake--running-backends)) + (not force)) (flymake-error "Ignoring unexpected report from backend %s" backend)) ((eq action :progress) (flymake-log 3 "Backend %s reports progress: %s" backend explanation)) @@ -851,4 +856,5 @@ diagnostics of type `:error' and `:warning'." (provide 'flymake) (require 'flymake-proc) +(require 'flymake-elisp) ;;; flymake.el ends here -- 2.39.2