]> git.eshelyaron.com Git - emacs.git/commitdiff
Add Rubocop Flymake backend
authorDmitry Gutov <dgutov@yandex.ru>
Tue, 21 Nov 2017 23:04:27 +0000 (01:04 +0200)
committerDmitry Gutov <dgutov@yandex.ru>
Tue, 21 Nov 2017 23:53:37 +0000 (01:53 +0200)
* 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

index da08bb0788e1c3eb07ce68d8d7466a50af6e11a2..18ed6ee5e7991ada2805d4b56c16ac752a7e8ca9 100644 (file)
@@ -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)