├── README.md
└── rubocop.el
/README.md:
--------------------------------------------------------------------------------
1 | # RuboCop.el
2 |
3 | ## Synopsis
4 |
5 | A simple Emacs interface for [RuboCop](https://github.com/rubocop-hq/rubocop).
6 |
7 | It doesn't aim to compete with general-purpose packages providing lint integration, but rather to provide the simplest way to leverage the essential RuboCop functionality like:
8 |
9 | * checking code style
10 | * auto-formatting code
11 | * auto-correcting code
12 |
13 | Most of the package's commands are meant to be used on demand (when needed), but
14 | you can also enable automatic code correction on save.
15 |
16 | ## Installation
17 |
18 | Please, note that the current version of `RuboCop.el` requires `RuboCop` 0.9.0 or later.
19 |
20 | ### MELPA
21 |
22 | If you're an `package.el` user,
23 | you can install rubocop.el from the [MELPA](http://melpa.org/) and
24 | [MELPA Stable](http://stable.melpa.org/) repositories.
25 |
26 | ### Manual
27 |
28 | Just drop `rubocop.el` somewhere in your `load-path`. I
29 | favour the folder `~/.emacs.d/vendor`:
30 |
31 | ```lisp
32 | (add-to-list 'load-path "~/.emacs.d/vendor")
33 | (require 'rubocop)
34 | ```
35 |
36 | ## Usage
37 |
38 | Command | Description | RuboCop mode binding
39 | ------------------------------------------------|---------------------------------------------------------|--------------------
40 | M-x rubocop-check-project | Runs RuboCop on the entire project | `C-c C-r p`
41 | M-x rubocop-check-directory | Prompts from a directory on which to run RuboCop | `C-c C-r d`
42 | M-x rubocop-check-current-file | Runs RuboCop on the currently visited file | `C-c C-r f`
43 | M-x rubocop-autocorrect-project | Runs auto-correct on the entire project | `C-c C-r P`
44 | M-x rubocop-autocorrect-directory | Prompts for a directory on which to run auto-correct | `C-c C-r D`
45 | M-x rubocop-autocorrect-current-file | Runs auto-correct on the currently visited file. | `C-c C-r F`
46 | M-x rubocop-format-project | Runs format on the entire project | `C-c C-r X`
47 | M-x rubocop-format-directory | Prompts for a directory on which to run format | `C-c C-r y`
48 | M-x rubocop-format-current-file | Runs format on the currently visited file. | `C-c C-r x`
49 |
50 |
51 | If you use them often you might want to enable `rubocop-mode` which will added some keybindings for them:
52 |
53 | ```lisp
54 | (add-hook 'ruby-mode-hook #'rubocop-mode)
55 | ```
56 |
57 | By default `rubocop-mode` uses the prefix `C-c C-r` for its commands, but you can change this if you wish:
58 |
59 | ``` emacs-lisp
60 | (setq rubocop-keymap-prefix (kbd "C-c C-x"))
61 | ```
62 |
63 | ## Configuration
64 |
65 | There are a couple of configuration variables that you can use to adjust RuboCop.el's behavior.
66 |
67 | The variable `rubocop-autocorrect-on-save` controls whether to auto-correct automatically files on save when
68 | `rubocop-mode` is active. It's disabled by default, but you can easily change this:
69 |
70 | ``` emacs-lisp
71 | (setq rubocop-autocorrect-on-save t)
72 | ```
73 |
74 | Alternatively you can enable only automatic code formatting on save (effectively that's a subset of
75 | the full auto-correct):
76 |
77 | ``` emacs-lisp
78 | (setq rubocop-format-on-save t)
79 | ```
80 |
81 | **Note:** Generally you shouldn't enable `rubocop-format-on-save` if `rubocop-autocorrect-on-save` is enabled.
82 |
83 | You can change the shell command used by `rubocop-check-*` commands via `rubocop-check-command`:
84 |
85 | ``` emacs-lisp
86 | ;; let's run only lint cops
87 | (setq rubocop-check-command "rubocop --lint --format emacs")
88 | ```
89 |
90 | You can change the shell command used by `rubocop-autocorrect-*` commands via `rubocop-autocorrect-command`:
91 |
92 | ``` emacs-lisp
93 | ;; let's run all auto-corrections possible (by default it's only the safe ones)
94 | (setq rubocop-autocorrect-command "rubocop -A --format emacs")
95 | ```
96 |
97 | You can change the shell command used by `rubocop-format-*` commands via `rubocop-format-command`.
98 |
99 | You can run rubocop inside a chroot via schroot by setting:
100 |
101 | ``` emacs-lisp
102 | (setq rubocop-run-in-chroot t)
103 | ```
104 |
105 | ## Alternatives
106 |
107 | [Flycheck](https://www.flycheck.org) and Flymake (Emacs built-in) provide more sophisticated integration with various lint tools, including RuboCop.
108 |
109 | There's also [rubocopfmt](https://github.com/jimeh/rubocopfmt.el), which provides functionality similar to RuboCop.el, but is focused exclusively on the auto-correction side of things.
110 |
111 | ## Known issues
112 |
113 | Check out the project's
114 | [issue list](https://github.com/rubocop-hq/rubocop-emacs/issues?sort=created&direction=desc&state=open)
115 | a list of unresolved issues. By the way - feel free to fix any of them
116 | and send me a pull request. :-)
117 |
118 | ## Contributors
119 |
120 | Here's a [list](https://github.com/rubocop-hq/rubocop-emacs/contributors) of all the people who have contributed to the
121 | development of RuboCop.el.
122 |
123 | ## Bugs & Improvements
124 |
125 | Bug reports and suggestions for improvements are always
126 | welcome. GitHub pull requests are even better! :-)
127 |
128 | Cheers,
129 | [Bozhidar](http://twitter.com/bbatsov)
130 |
--------------------------------------------------------------------------------
/rubocop.el:
--------------------------------------------------------------------------------
1 | ;;; rubocop.el --- An Emacs interface for RuboCop -*- lexical-binding: t -*-
2 |
3 | ;; Copyright © 2011-2021 Bozhidar Batsov
4 |
5 | ;; Author: Bozhidar Batsov
6 | ;; URL: https://github.com/rubocop/rubocop-emacs
7 | ;; Version: 0.7.0-snapshot
8 | ;; Keywords: project, convenience
9 | ;; Package-Requires: ((emacs "24"))
10 |
11 | ;; This file is NOT part of GNU Emacs.
12 |
13 | ;;; License:
14 |
15 | ;; This program is free software; you can redistribute it and/or modify
16 | ;; it under the terms of the GNU General Public License as published by
17 | ;; the Free Software Foundation; either version 3, or (at your option)
18 | ;; any later version.
19 | ;;
20 | ;; This program is distributed in the hope that it will be useful,
21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | ;; GNU General Public License for more details.
24 | ;;
25 | ;; You should have received a copy of the GNU General Public License
26 | ;; along with GNU Emacs; see the file COPYING. If not, write to the
27 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 | ;; Boston, MA 02110-1301, USA.
29 |
30 | ;;; Commentary:
31 | ;;
32 | ;; This library allows the user to easily invoke RuboCop to get feedback
33 | ;; about stylistic issues in Ruby code. It also gives access to RuboCop
34 | ;; auto-correction functionality.
35 | ;;
36 | ;;; Code:
37 |
38 | (require 'tramp)
39 |
40 | (defgroup rubocop nil
41 | "An Emacs interface for RuboCop."
42 | :group 'tools
43 | :group 'convenience
44 | :prefix "rubocop-"
45 | :link '(url-link :tag "GitHub" "https://github.com/rubocop/rubocop-emacs"))
46 |
47 | (defcustom rubocop-project-root-files
48 | '(".projectile" ".git" ".hg" ".bzr" "_darcs" "Gemfile")
49 | "A list of files considered to mark the root of a project."
50 | :type '(repeat string))
51 |
52 | (defcustom rubocop-check-command
53 | "rubocop --format emacs"
54 | "The command used to run RuboCop checks."
55 | :type 'string)
56 |
57 | (defcustom rubocop-autocorrect-command
58 | "rubocop -a --format emacs"
59 | "The command used to run RuboCop's autocorrection."
60 | :type 'string)
61 |
62 | (defcustom rubocop-format-command
63 | "rubocop -x --format emacs"
64 | "The command used to run RuboCop's code formatting.
65 | It's basically auto-correction limited to layout cops."
66 | :type 'string
67 | :package-version '(rubocop . "0.6.0"))
68 |
69 | (defcustom rubocop-extensions
70 | '()
71 | "A list of extensions to be loaded by RuboCop."
72 | :type '(repeat string))
73 |
74 | (defcustom rubocop-keymap-prefix (kbd "C-c C-r")
75 | "RuboCop keymap prefix."
76 | :group 'rubocop
77 | :type 'string)
78 |
79 | (defcustom rubocop-autocorrect-on-save nil
80 | "Runs `rubocop-autocorrect-current-file' automatically on save."
81 | :group 'rubocop
82 | :type 'boolean
83 | :package-version '(rubocop . "0.6.0"))
84 |
85 | (defcustom rubocop-format-on-save nil
86 | "Runs `rubocop-format-current-file' automatically on save."
87 | :group 'rubocop
88 | :type 'boolean
89 | :package-version '(rubocop . "0.7.0"))
90 |
91 | (defcustom rubocop-prefer-system-executable nil
92 | "Runs rubocop with the system executable even if inside a bundled project."
93 | :group 'rubocop
94 | :type 'boolean)
95 |
96 | (defcustom rubocop-run-in-chroot nil
97 | "Runs rubocop inside a chroot via schroot setting the cwd to the project's root."
98 | :group 'rubocop
99 | :type 'boolean
100 | :package-version '(rubocop . "0.7.0"))
101 |
102 | (defun rubocop-local-file-name (file-name)
103 | "Retrieve local filename if FILE-NAME is opened via TRAMP."
104 | (cond ((tramp-tramp-file-p file-name)
105 | (tramp-file-name-localname (tramp-dissect-file-name file-name)))
106 | (t
107 | file-name)))
108 |
109 | (defun rubocop-project-root (&optional no-error)
110 | "Retrieve the root directory of a project if available.
111 |
112 | When NO-ERROR is non-nil returns nil instead of raise an error."
113 | (or
114 | (car
115 | (mapcar #'expand-file-name
116 | (delq nil
117 | (mapcar
118 | (lambda (f) (locate-dominating-file default-directory f))
119 | rubocop-project-root-files))))
120 | (if no-error
121 | nil
122 | (error "You're not into a project"))))
123 |
124 | (defun rubocop-buffer-name (file-or-dir)
125 | "Generate a name for the RuboCop buffer from FILE-OR-DIR."
126 | (concat "*RuboCop " file-or-dir "*"))
127 |
128 | (defun rubocop-build-requires ()
129 | "Build RuboCop requires from `rubocop-extensions'."
130 | (if rubocop-extensions
131 | (concat
132 | " "
133 | (mapconcat
134 | (lambda (ext)
135 | (format "--require %s" ext))
136 | rubocop-extensions
137 | " ")
138 | " ")
139 | ""))
140 |
141 | (defun rubocop-build-command (command path)
142 | "Build the full command to be run based on COMMAND and PATH.
143 | The command will be prefixed with `bundle exec` if RuboCop is bundled."
144 | (concat
145 | (if rubocop-run-in-chroot (format "schroot -d %s -- " (rubocop-project-root)))
146 | (if (and (not rubocop-prefer-system-executable) (rubocop-bundled-p)) "bundle exec " "")
147 | command
148 | (rubocop-build-requires)
149 | " "
150 | path))
151 |
152 | (defun rubocop--dir-command (command &optional directory)
153 | "Run COMMAND on DIRECTORY (if present).
154 | Alternatively prompt user for directory."
155 | (rubocop-ensure-installed)
156 | (let ((directory
157 | (or directory
158 | (read-directory-name "Select directory: "))))
159 | ;; make sure we run RuboCop from a project's root if the command is executed within a project
160 | (let ((default-directory (or (rubocop-project-root 'no-error) default-directory)))
161 | (compilation-start
162 | (rubocop-build-command command (rubocop-local-file-name directory))
163 | 'compilation-mode
164 | (lambda (arg) (message arg) (rubocop-buffer-name directory))))))
165 |
166 | ;;;###autoload
167 | (defun rubocop-check-project ()
168 | "Run check on current project."
169 | (interactive)
170 | (rubocop-check-directory (rubocop-project-root)))
171 |
172 | ;;;###autoload
173 | (defun rubocop-autocorrect-project ()
174 | "Run autocorrect on current project."
175 | (interactive)
176 | (rubocop-autocorrect-directory (rubocop-project-root)))
177 |
178 | ;;;###autoload
179 | (defun rubocop-format-project ()
180 | "Run format on current project."
181 | (interactive)
182 | (rubocop-format-directory (rubocop-project-root)))
183 |
184 | ;;;###autoload
185 | (defun rubocop-check-directory (&optional directory)
186 | "Run check on DIRECTORY if present.
187 | Alternatively prompt user for directory."
188 | (interactive)
189 | (rubocop--dir-command rubocop-check-command directory))
190 |
191 | ;;;###autoload
192 | (defun rubocop-autocorrect-directory (&optional directory)
193 | "Run autocorrect on DIRECTORY if present.
194 | Alternatively prompt user for directory."
195 | (interactive)
196 | (rubocop--dir-command rubocop-autocorrect-command directory))
197 |
198 | (defun rubocop-format-directory (&optional directory)
199 | "Run format on DIRECTORY if present.
200 | Alternatively prompt user for directory."
201 | (interactive)
202 | (rubocop--dir-command rubocop-format-command directory))
203 |
204 | (defun rubocop--file-command (command)
205 | "Run COMMAND on currently visited file."
206 | (rubocop-ensure-installed)
207 | (let ((file-name (buffer-file-name (current-buffer))))
208 | (if file-name
209 | ;; make sure we run RuboCop from a project's root if the command is executed within a project
210 | (let ((default-directory (or (rubocop-project-root 'no-error) default-directory)))
211 | (compilation-start
212 | (rubocop-build-command command (rubocop-local-file-name file-name))
213 | 'compilation-mode
214 | (lambda (_arg) (rubocop-buffer-name file-name))))
215 | (error "Buffer is not visiting a file"))))
216 |
217 | ;;;###autoload
218 | (defun rubocop-check-current-file ()
219 | "Run check on current file."
220 | (interactive)
221 | (rubocop--file-command rubocop-check-command))
222 |
223 | ;;;###autoload
224 | (defun rubocop-autocorrect-current-file ()
225 | "Run autocorrect on current file."
226 | (interactive)
227 | (rubocop--file-command rubocop-autocorrect-command))
228 |
229 | (defun rubocop-autocorrect-current-file-silent ()
230 | "This command is used by the minor mode to auto-correct on save.
231 | See also `rubocop-autocorrect-on-save'."
232 | (when rubocop-autocorrect-on-save
233 | (save-window-excursion (rubocop-autocorrect-current-file))))
234 |
235 | ;;;###autoload
236 | (defun rubocop-format-current-file ()
237 | "Run format on current file."
238 | (interactive)
239 | (rubocop--file-command rubocop-format-command))
240 |
241 | (defun rubocop-format-current-file-silent ()
242 | "This command is used by the minor mode to format on save.
243 | See also `rubocop-format-on-save' and `rubocop-autocorrect-on-save'."
244 | (when (and rubocop-format-on-save (not rubocop-autocorrect-on-save))
245 | (save-window-excursion (rubocop-format-current-file))))
246 |
247 | (defun rubocop-bundled-p ()
248 | "Check if RuboCop has been bundled."
249 | (let ((gemfile-lock (expand-file-name "Gemfile.lock" (rubocop-project-root))))
250 | (when (file-exists-p gemfile-lock)
251 | (with-temp-buffer
252 | (insert-file-contents gemfile-lock)
253 | (re-search-forward "rubocop" nil t)))))
254 |
255 | (defun rubocop-ensure-installed ()
256 | "Check if RuboCop is installed."
257 | (unless (or (executable-find "rubocop") (rubocop-bundled-p))
258 | (error "RuboCop is not installed")))
259 |
260 | ;;; Minor mode
261 | (defvar rubocop-mode-map
262 | (let ((map (make-sparse-keymap)))
263 | (let ((prefix-map (make-sparse-keymap)))
264 | (define-key prefix-map (kbd "p") #'rubocop-check-project)
265 | (define-key prefix-map (kbd "d") #'rubocop-check-directory)
266 | (define-key prefix-map (kbd "f") #'rubocop-check-current-file)
267 | (define-key prefix-map (kbd "P") #'rubocop-autocorrect-project)
268 | (define-key prefix-map (kbd "D") #'rubocop-autocorrect-directory)
269 | (define-key prefix-map (kbd "F") #'rubocop-autocorrect-current-file)
270 | (define-key prefix-map (kbd "X") #'rubocop-format-project)
271 | (define-key prefix-map (kbd "y") #'rubocop-format-directory)
272 | (define-key prefix-map (kbd "x") #'rubocop-format-current-file)
273 |
274 | (define-key map rubocop-keymap-prefix prefix-map))
275 | map)
276 | "Keymap for RuboCop mode.")
277 |
278 | ;;;###autoload
279 | (define-minor-mode rubocop-mode
280 | "Minor mode to interface with RuboCop."
281 | :lighter " RuboCop"
282 | :keymap rubocop-mode-map
283 | :group 'rubocop
284 | (if rubocop-mode
285 | ;; on mode enable
286 | (progn
287 | (add-hook 'before-save-hook 'rubocop-autocorrect-current-file-silent nil t)
288 | (add-hook 'before-save-hook 'rubocop-format-current-file-silent nil t))
289 | ;; on mode disable
290 | (remove-hook 'before-save-hook 'rubocop-autocorrect-current-file-silent t)
291 | (remove-hook 'before-save-hook 'rubocop-format-current-file-silent t)))
292 |
293 | (provide 'rubocop)
294 |
295 | ;;; rubocop.el ends here
296 |
--------------------------------------------------------------------------------