Refactor rubocopfmt.el to use rubocop directly

Having the rubocopfmt Ruby gem as an external dependency is becoming
tedious. So lets parse rubocop's output and perform the diffing
ourselves within Emacs.

This effectively marks the end of the rubocopfmt Ruby gem, as I no
longer need it, and will not maintain it moving forward.
This commit is contained in:
2018-05-19 13:06:05 +01:00
parent 54e023fd40
commit 9d0aef2f82

View File

@@ -37,39 +37,131 @@
;;; Code:
(defgroup rubocopfmt nil
"Minor mode for formatting Ruby buffers with rubocopfmt."
"Minor mode for formatting Ruby buffers with rubocop."
:group 'languages
:link '(url-link "https://github.com/jimeh/rubocopfmt.el"))
(defcustom rubocopfmt-command "rubocopfmt"
"The 'rubocopfmt' command."
(defcustom rubocopfmt-rubocop-command "rubocop"
"Name of rubocop executable."
:type 'string
:group 'rubocopfmt)
(defcustom rubocopfmt-disabled-cops
'("Lint/Debugger" ; Don't remove debugger calls.
"Lint/UnusedBlockArgument" ; Don't rename unused block arguments.
"Lint/UnusedMethodArgument" ; Don't rename unused method arguments.
"Style/EmptyMethod" ; Don't remove blank line in empty methods.
)
"A list of RuboCop Cops to disable during auto-correction.
These cops are disabled because they cause confusion during
interactive use within a text-editor."
:type '(repeat string)
:group 'rubocopfmt)
(defcustom rubocopfmt-show-errors t
"Display errors in echo area."
:type 'boolean
:group 'rubocopfmt)
;;;###autoload
(defun rubocopfmt ()
"Format the current buffer with rubocopfmt."
"Format the current buffer with rubocop."
(interactive)
(let ((patchbuf (get-buffer-create "*Rubocopfmt patch*"))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8)
(rubocopfmt-args
(list "--diff-format" "rcs"
"--interactive"
"--src-file" (file-truename buffer-file-name))))
(let* ((coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8)
(tmpfile (make-temp-file "rubocopfmt" nil ".rb"))
(resultbuf (get-buffer-create "*Rubocopfmt result*"))
(patchbuf (get-buffer-create "*Rubocopfmt patch*"))
(buffer-file (file-truename buffer-file-name))
(src-dir (file-name-directory buffer-file))
(src-file (file-name-nondirectory buffer-file))
(fmt-command rubocopfmt-rubocop-command)
(fmt-args (list "--stdin" src-file
"--auto-correct"
"--format" "emacs")))
(if (rubocopfmt--bundled-path-p src-dir)
(setq fmt-command "bundle"
fmt-args (append (list "exec" rubocopfmt-rubocop-command)
fmt-args)))
(if rubocopfmt-disabled-cops
(setq fmt-args (append fmt-args (list "--except"
(combine-and-quote-strings
rubocopfmt-disabled-cops ",")))))
(unwind-protect
(save-restriction
(widen)
(write-region nil nil tmpfile)
(with-current-buffer resultbuf (erase-buffer))
(with-current-buffer patchbuf (erase-buffer))
(message "Calling rubocopfmt: %s %s"
rubocopfmt-command rubocopfmt-args)
(apply #'call-process-region (point-min) (point-max)
rubocopfmt-command nil patchbuf nil rubocopfmt-args)
(let ((current-directory src-dir))
(message "Calling rubocop from directory \"%s\": %s %s"
src-dir fmt-command (mapconcat 'identity fmt-args " "))
(apply #'call-process-region (point-min) (point-max)
fmt-command nil resultbuf nil fmt-args)
(if (rubocopfmt--parse-result resultbuf tmpfile)
(call-process-region (point-min) (point-max) "diff"
nil patchbuf nil "-n" "-" tmpfile)))
(if (= (buffer-size patchbuf) 0)
(message "Buffer is already rubocopfmted")
(progn
(rubocopfmt--apply-rcs-patch patchbuf)
(message "Applied rubocopfmt")))))
(kill-buffer patchbuf)))
(rubocopfmt--apply-rcs-patch patchbuf)
(message "Applied rubocopfmt")))
(delete-file tmpfile)
(kill-buffer resultbuf)
(kill-buffer patchbuf))))
(defun rubocopfmt--parse-result (resultbuf tmpfile)
"Parse Rubocop result in RESULTBUF and write corrections to TMPFILE."
(let ((split 0))
(with-current-buffer resultbuf
(goto-char (point-min))
;; Only find the separator when RuboCop has printed complaints.
(setq split (search-forward "\n====================\n" nil t))
;; If no RuboCop complaints were printed, we need to find the separator at
;; the beginning of the buffer. This separation helps prevent false
;; positive separator matches.
(unless split
(setq split (search-forward "====================\n" nil t)))
(if split
(when (> split 22)
(goto-char (point-min))
(when (search-forward "[Corrected]" split t)
(write-region split (point-max) tmpfile)
t))
(rubocopfmt--display-error resultbuf)
nil))))
(defun rubocopfmt--display-error (resultbuf)
"Display contents of RESULTBUF if rubocop-show-errors is t."
(if rubocopfmt-show-errors
(with-current-buffer resultbuf
(message (buffer-string)))))
(defun rubocopfmt--bundled-path-p (directory)
"Check if there is a Gemfile in DIRECTORY, or any parent directory."
(rubocopfmt--file-search-upward directory "Gemfile"))
(defun rubocopfmt--file-search-upward (directory file)
"Search DIRECTORY for FILE and return its full path if found, or NIL if not.
If FILE is not found in DIRECTORY, the parent of DIRECTORY will be searched."
(let ((parent-dir (file-truename (concat (file-name-directory directory) "../")))
(current-path (if (not (string= (substring directory (- (length directory) 1)) "/"))
(concat directory "/" file)
(concat directory file))))
(if (file-exists-p current-path)
current-path
(when (and (not (string= (file-truename directory) parent-dir))
(< (length parent-dir) (length (file-truename directory))))
(rubocopfmt--file-search-upward parent-dir file)))))
(defun rubocopfmt--apply-rcs-patch (patch-buffer)
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."