Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New features

* [#123](https://github.com/rubocop-hq/rubocop-rails/pull/123): Add new `Rails/ApplicationController` and `Rails/ApplicationMailer` cops. ([@eugeneius][])
* [#139](https://github.com/rubocop-hq/rubocop-rails/pull/139): Add new `Rails/RescueFromExceptionsVariableName` cop. ([@anthony-robin][])

### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ Rails/RequestReferer:
- referer
- referrer

Rails/RescueFromExceptionsVariableName:
Description: 'Use consistent rescued exceptions variables naming.'
Enabled: true
VersionAdded: '2.4'
PreferredName: e

Rails/ReversibleMigration:
Description: 'Checks whether the change method of the migration file is reversible.'
StyleGuide: 'https://rails.rubystyle.guide#reversible-migration'
Expand Down
98 changes: 98 additions & 0 deletions lib/rubocop/cop/rails/rescue_from_exceptions_variable_name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# This cop makes sure that rescued exceptions variables are named as
# expected.
#
# The `PreferredName` config option takes a `String`. It represents
# the required name of the variable. Its default is `e` that is read
# from `Naming/RescuedExceptionsVariableName` cop in the main Rubocop
# repository.
Comment on lines +9 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of:

The PreferredName config option sets the preferred name for the exception variable, defaults to e.

#
# @example PreferredName: e (default)
# # bad
# rescue_from MyException do |exception|
# # do something
# end
#
# # good
# rescue_from MyException do |e|
# # do something
# end
#
# # good
# rescue_from MyException do |_e|
# # do something
# end
#
# @example PreferredName: exception
# # bad
# rescue_from MyException do |e|
# # do something
# end
#
# # good
# rescue_from MyException do |exception|
# # do something
# end
#
# # good
# rescue_from MyException do |_exception|
# # do something
# end
#
class RescueFromExceptionsVariableName < Cop
include ConfigurableEnforcedStyle

MSG = 'Use `%<preferred>s` instead of `%<bad>s`.'

def on_block(node)
name = variable_name(node)
return unless name
return if preferred_name(name).to_sym == name

add_offense(node, location: offense_range(node))
end

def autocorrect(node)
lambda do |corrector|
offending_name = variable_name(node)
preferred_name = preferred_name(offending_name)
corrector.replace(offense_range(node), "|#{preferred_name}|")
end
end
Comment on lines +59 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new interface for auto-correction, see https://docs.rubocop.org/rubocop/1.22/development.html#auto-correct


private

def offense_range(resbody)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this would better be expressed with node pattern. Do you need any guidance with that?
Docs: https://docs.rubocop.org/rubocop-ast/node_pattern.html

variable = resbody.node_parts[1]
variable.loc.expression
end

def preferred_name(variable_name)
preferred_name = cop_config.fetch('PreferredName', 'e')
if variable_name.to_s.start_with?('_')
"_#{preferred_name}"
else
preferred_name
end
end

def variable_name(node)
asgn_node = node.node_parts[1]
return unless asgn_node

asgn_node.children.last&.source&.to_sym
end

def message(node)
offending_name = variable_name(node)
preferred_name = preferred_name(offending_name)
format(MSG, preferred: preferred_name, bad: offending_name)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
require_relative 'rails/refute_methods'
require_relative 'rails/relative_date_constant'
require_relative 'rails/request_referer'
require_relative 'rails/rescue_from_exceptions_variable_name'
require_relative 'rails/reversible_migration'
require_relative 'rails/safe_navigation'
require_relative 'rails/save_bang'
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
* [Rails/RefuteMethods](cops_rails.md#railsrefutemethods)
* [Rails/RelativeDateConstant](cops_rails.md#railsrelativedateconstant)
* [Rails/RequestReferer](cops_rails.md#railsrequestreferer)
* [Rails/RescueFromExceptionsVariableName](cops_rails.md#railsrescuefromexceptionsvariablename)
* [Rails/ReversibleMigration](cops_rails.md#railsreversiblemigration)
* [Rails/SafeNavigation](cops_rails.md#railssafenavigation)
* [Rails/SaveBang](cops_rails.md#railssavebang)
Expand Down
59 changes: 59 additions & 0 deletions manual/cops_rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -1913,6 +1913,65 @@ Name | Default value | Configurable values
--- | --- | ---
EnforcedStyle | `referer` | `referer`, `referrer`

## Rails/RescueFromExceptionsVariableName

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
--- | --- | --- | --- | ---
Enabled | Yes | Yes | 2.4 | -

This cop makes sure that rescued exceptions variables are named as
expected.

The `PreferredName` config option takes a `String`. It represents
the required name of the variable. Its default is `e` that is read
from `Naming/RescuedExceptionsVariableName` cop in the main Rubocop
repository.

### Examples

#### PreferredName: e (default)

```ruby
# bad
rescue_from MyException do |exception|
# do something
end

# good
rescue_from MyException do |e|
# do something
end

# good
rescue_from MyException do |_e|
# do something
end
```
#### PreferredName: exception

```ruby
# bad
rescue_from MyException do |e|
# do something
end

# good
rescue_from MyException do |exception|
# do something
end

# good
rescue_from MyException do |_exception|
# do something
end
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
PreferredName | `e` | String

## Rails/ReversibleMigration

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
166 changes: 166 additions & 0 deletions spec/rubocop/cop/rails/rescue_from_exceptions_variable_name_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::RescueFromExceptionsVariableName, :config do
subject(:cop) { described_class.new(config) }

it 'does not register an offense without variable' do
expect_no_offenses(<<~RUBY)
rescue_from MyException do
# do something
end
RUBY
end

context 'with default config' do
context 'with default variable' do
it 'does not register an offense with a single rescued exception' do
expect_no_offenses(<<~RUBY)
rescue_from MyException do |e|
# do something
end
RUBY
end

it 'does not register an offense with multiple rescued exceptions' do
expect_no_offenses(<<~RUBY)
rescue_from MyException, MyOtherException do |e|
# do something
end
RUBY
end

it 'does not register an offense with underscored prefix variable' do
expect_no_offenses(<<~RUBY)
rescue_from MyException do |_e|
# do something
end
RUBY
end

it 'does not register an offense using splat operator' do
expect_no_offenses(<<~RUBY)
rescue_from *handled do |e|
# do something
end
RUBY
end
end

context 'when using another variable' do
it 'registers an offense with a single rescued exception' do
expect_offense(<<~RUBY)
rescue_from MyException do |exception|
^^^^^^^^^^^ Use `e` instead of `exception`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from MyException do |e|
# do something
end
RUBY
end

it 'registers an offense with multiple rescued exceptions' do
expect_offense(<<~RUBY)
rescue_from MyException, MyOtherException do |exception|
^^^^^^^^^^^ Use `e` instead of `exception`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from MyException, MyOtherException do |e|
# do something
end
RUBY
end

it 'registers an offense with underscored prefix variable' do
expect_offense(<<~RUBY)
rescue_from MyException do |_exception|
^^^^^^^^^^^^ Use `_e` instead of `_exception`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from MyException do |_e|
# do something
end
RUBY
end

it 'registers an offense using splat operator' do
expect_offense(<<~RUBY)
rescue_from *handled do |exception|
^^^^^^^^^^^ Use `e` instead of `exception`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from *handled do |e|
# do something
end
RUBY
end
end
end

context 'with the `PreferredName` setup' do
let(:cop_config) do
{
'PreferredName' => 'exception'
}
end

it 'does not register an offense when using the preferred name' do
expect_no_offenses(<<~RUBY)
rescue_from MyException do |exception|
# do something
end
RUBY
end

it 'does not register an offense when using the preferred name with' \
'multiple rescued exceptions' do
expect_no_offenses(<<~RUBY)
rescue_from MyException, MyOtherException do |exception|
# do something
end
RUBY
end

it 'registers an offense when using another name' do
expect_offense(<<~RUBY)
rescue_from MyException do |e|
^^^ Use `exception` instead of `e`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from MyException do |exception|
# do something
end
RUBY
end

it 'registers an offense with underscored prefix variable' do
expect_offense(<<~RUBY)
rescue_from MyException do |_e|
^^^^ Use `_exception` instead of `_e`.
# do something
end
RUBY

expect_correction(<<~RUBY)
rescue_from MyException do |_exception|
# do something
end
RUBY
end
end
end