From 09944d499a361044d81eb6afaa29ffda885b2076 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Wed, 22 Nov 2017 01:04:27 +0200 Subject: [PATCH] Add Rubocop Flymake backend * lisp/progmodes/ruby-mode.el (ruby-flymake-command): Inline the value. There are no known substitutes. (ruby-flymake): Rename to `ruby-flymake-simple' and simplify the docstring. (ruby-flymake-use-rubocop-if-available): New option. (ruby--rubocop-flymake-proc): New variable. (ruby-rubocop-config): New option. (ruby-flymake-rubocop, ruby-flymake-auto): New functions. (ruby-mode): Use `ruby-flymake-auto'. --- lisp/progmodes/ruby-mode.el | 118 ++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 18 deletions(-) diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el index da08bb0788e..18ed6ee5e79 100644 --- a/lisp/progmodes/ruby-mode.el +++ b/lisp/progmodes/ruby-mode.el @@ -2254,23 +2254,12 @@ See `font-lock-syntax-table'.") (ruby-match-expression-expansion limit))))) ;;; Flymake support -(defcustom ruby-flymake-command '("ruby" "-w" "-c") - "External tool used to check Ruby source code. -This is a non empty list of strings, the checker tool possibly -followed by required arguments. Once launched it will receive -the Ruby source to be checked as its standard input." - :group 'ruby - :type '(repeat string)) - (defvar-local ruby--flymake-proc nil) -(defun ruby-flymake (report-fn &rest _args) - "Ruby backend for Flymake. Launches -`ruby-flymake-command' (which see) and passes to its standard -input the contents of the current buffer. The output of this -command is analyzed for error and warning messages." - (unless (executable-find (car ruby-flymake-command)) - (error "Cannot find a suitable checker")) +(defun ruby-flymake-simple (report-fn &rest _args) + "`ruby -wc' backend for Flymake." + (unless (executable-find "ruby") + (error "Cannot find the ruby executable")) (when (process-live-p ruby--flymake-proc) (kill-process ruby--flymake-proc)) @@ -2281,9 +2270,9 @@ command is analyzed for error and warning messages." (setq ruby--flymake-proc (make-process - :name "ruby-flymake" :noquery t :connection-type 'pipe + :name "ruby-flymake-simple" :noquery t :connection-type 'pipe :buffer (generate-new-buffer " *ruby-flymake*") - :command ruby-flymake-command + :command '("ruby" "-w" "-c") :sentinel (lambda (proc _event) (when (eq 'exit (process-status proc)) @@ -2315,6 +2304,99 @@ command is analyzed for error and warning messages." (process-send-region ruby--flymake-proc (point-min) (point-max)) (process-send-eof ruby--flymake-proc)))) +(defcustom ruby-flymake-use-rubocop-if-available t + "Non-nil to use the Rubocop Flymake backend. +Only takes effect if Rubocop is installed." + :type 'boolean + :group 'ruby + :safe 'booleanp) + +(defvar-local ruby--rubocop-flymake-proc nil) + +(defcustom ruby-rubocop-config ".rubocop.yml" + "Configuration file for `ruby-flymake-rubocop'." + :type 'string + :group 'ruby + :safe 'stringp) + +(defun ruby-flymake-rubocop (report-fn &rest _args) + "Rubocop backend for Flymake." + (unless (executable-find "rubocop") + (error "Cannot find the rubocop executable")) + + (when (process-live-p ruby--rubocop-flymake-proc) + (kill-process ruby--rubocop-flymake-proc)) + + (let ((source (current-buffer)) + (command (list "rubocop" "--stdin" buffer-file-name "--format" "emacs" + "--cache" "false" ; Work around a bug in old version. + "--display-cop-names")) + config-dir) + (when buffer-file-name + (setq config-dir (locate-dominating-file buffer-file-name + ruby-rubocop-config)) + (when config-dir + (setq command (append command (list "--config" + (expand-file-name ruby-rubocop-config + config-dir))))) + (save-restriction + (widen) + (setq + ruby--rubocop-flymake-proc + (make-process + :name "rubocop-flymake" :noquery t :connection-type 'pipe + :buffer (generate-new-buffer " *rubocop-flymake*") + :command command + :sentinel + (lambda (proc _event) + (when (eq 'exit (process-status proc)) + (unwind-protect + (if (with-current-buffer source (eq proc ruby--rubocop-flymake-proc)) + (with-current-buffer (process-buffer proc) + ;; Finding the executable is no guarantee of + ;; rubocop working, especially in the presence + ;; of rbenv shims (which cross ruby versions). + (unless (zerop (process-exit-status proc)) + (flymake-log :warning "Rubocop returned non-zero status: %s" + (buffer-string))) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp + "^\\(?:.*.rb\\|-\\):\\([0-9]+\\):\\([0-9]<<+\\): \\(.*\\)$" + 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))) + for type = (cond + ((string-match "^[EF]: " msg) + :error) + ((string-match "^W: " msg) + :warning) + (t :note)) + collect (flymake-make-diagnostic source + beg + end + type + (substring msg 3)) + into diags + finally (funcall report-fn diags))) + (flymake-log :debug "Canceling obsolete check %s" + proc)) + (kill-buffer (process-buffer proc))))))) + (process-send-region ruby--rubocop-flymake-proc (point-min) (point-max)) + (process-send-eof ruby--rubocop-flymake-proc))))) + +(defun ruby-flymake-auto (report-fn &rest args) + (apply + (if (and ruby-flymake-use-rubocop-if-available + (executable-find "rubocop")) + #'ruby-flymake-rubocop + #'ruby-flymake-simple) + report-fn + args)) + ;;;###autoload (define-derived-mode ruby-mode prog-mode "Ruby" "Major mode for editing Ruby code." @@ -2327,7 +2409,7 @@ command is analyzed for error and warning messages." (add-hook 'after-save-hook 'ruby-mode-set-encoding nil 'local) (add-hook 'electric-indent-functions 'ruby--electric-indent-p nil 'local) - (add-hook 'flymake-diagnostic-functions 'ruby-flymake nil 'local) + (add-hook 'flymake-diagnostic-functions 'ruby-flymake-auto nil 'local) (setq-local font-lock-defaults '((ruby-font-lock-keywords) nil nil)) (setq-local font-lock-keywords ruby-font-lock-keywords) -- 2.39.2