├── 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 | --------------------------------------------------------------------------------