Skip to content

Commit 0363212

Browse files
committed
[Fix #49] Add new Rails/RescueFromExceptionsVariableName cop
Ensures that rescued exception variables are named as expected. The `PreferredName` config option specifies the required name of the variable. Its default is `e`, as referenced from `Naming/RescuedExceptionsVariableName`.
1 parent 12b45a5 commit 0363212

File tree

5 files changed

+766
-0
lines changed

5 files changed

+766
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#49](https://github.com/rubocop/rubocop-rails/issues/49): Add new `Rails/RescueFromExceptionsVariableName` cop. ([@anthony-robin][], [@ydakuka][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,12 @@ Rails/RequireDependency:
926926
Enabled: false
927927
VersionAdded: '2.10'
928928

929+
Rails/RescueFromExceptionsVariableName:
930+
Description: 'Use consistent rescued exceptions variables naming.'
931+
Enabled: 'pending'
932+
VersionAdded: '<<next>>'
933+
PreferredName: e
934+
929935
Rails/ResponseParsedBody:
930936
Description: Prefer `response.parsed_body` to custom parsing logic for `response.body`.
931937
Enabled: pending
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Ensures that rescued exception variables are named as expected.
7+
#
8+
# The `PreferredName` config option specifies the required name of the variable.
9+
# Its default is `e`, as referenced from `Naming/RescuedExceptionsVariableName`.
10+
#
11+
# @example PreferredName: e (default)
12+
# # bad
13+
# rescue_from MyException do |exception|
14+
# # do something
15+
# end
16+
#
17+
# # bad
18+
# rescue_from MyException do |_exception|
19+
# # do something
20+
# end
21+
#
22+
# # bad
23+
# rescue_from MyException { |exception| do_something(exception) }
24+
#
25+
# # bad
26+
# rescue_from MyException, with: ->(exception) do
27+
# do_something(exception)
28+
# end
29+
#
30+
# # bad
31+
# rescue_from MyException, with: ->(exception) { do_something(exception) }
32+
#
33+
#
34+
# # good
35+
# rescue_from MyException do |e|
36+
# # do something
37+
# end
38+
#
39+
# # good
40+
# rescue_from MyException do |_e|
41+
# # do something
42+
# end
43+
#
44+
# # good
45+
# rescue_from MyException do |exception, context|
46+
# # do something
47+
# end
48+
#
49+
# # good
50+
# rescue_from MyException { |e| do_something(e) }
51+
#
52+
# # good
53+
# rescue_from MyException, with: ->(e) do
54+
# do_something(e)
55+
# end
56+
#
57+
# # good
58+
# rescue_from MyException, with: ->(e) { do_something(e) }
59+
#
60+
# @example PreferredName: exception
61+
# # bad
62+
# rescue_from MyException do |e|
63+
# # do something
64+
# end
65+
#
66+
# # bad
67+
# rescue_from MyException do |_e|
68+
# # do something
69+
# end
70+
#
71+
# # bad
72+
# rescue_from MyException do |exception, context|
73+
# # do something
74+
# end
75+
#
76+
# # bad
77+
# rescue_from MyException { |e| do_something(e) }
78+
#
79+
# # bad
80+
# rescue_from MyException, with: ->(e) do
81+
# do_something(e)
82+
# end
83+
#
84+
# # bad
85+
# rescue_from MyException, with: ->(e) { do_something(e) }
86+
#
87+
#
88+
# # good
89+
# rescue_from MyException do |exception|
90+
# # do something
91+
# end
92+
#
93+
# # good
94+
# rescue_from MyException do |_exception|
95+
# # do something
96+
# end
97+
#
98+
# # good
99+
# rescue_from MyException { |exception| do_something(exception) }
100+
#
101+
# # good
102+
# rescue_from MyException, with: ->(exception) do
103+
# do_something(exception)
104+
# end
105+
#
106+
# # good
107+
# rescue_from MyException, with: ->(exception) { do_something(exception) }
108+
#
109+
class RescueFromExceptionsVariableName < Base
110+
include ConfigurableEnforcedStyle
111+
extend AutoCorrector
112+
113+
MSG = 'Use `%<preferred>s` instead of `%<current>s`.'
114+
RESTRICT_ON_SEND = %i[rescue_from].freeze
115+
116+
def_node_matcher :rescue_from_block_argument_variable?, <<~PATTERN
117+
(block (send nil? :rescue_from ...) (args (arg $_)) _)
118+
PATTERN
119+
120+
def_node_matcher :rescue_from_with_lambda_variable?, <<~PATTERN
121+
(send nil? :rescue_from ... (hash <(pair (sym :with) (block _ (args (arg $_)) _))>))
122+
PATTERN
123+
124+
def_node_matcher :rescue_from_with_block_variable?, <<~PATTERN
125+
(send nil? :rescue_from ... {(block _ (args (arg $_)) _) (splat (block _ (args (arg $_)) _))})
126+
PATTERN
127+
128+
def on_block(node)
129+
rescue_from_block_argument_variable?(node) do |arg_name|
130+
check_offense(node.first_argument, arg_name)
131+
end
132+
end
133+
alias on_numblock on_block
134+
135+
def on_send(node)
136+
check_rescue_from_variable(node, :rescue_from_with_lambda_variable?)
137+
check_rescue_from_variable(node, :rescue_from_with_block_variable?)
138+
end
139+
140+
private
141+
142+
def check_rescue_from_variable(node, matcher)
143+
send(matcher, node) do |arg_name|
144+
arg_node = node.each_descendant(:args).first.children.first
145+
check_offense(arg_node, arg_name)
146+
end
147+
end
148+
149+
def check_offense(arg_node, arg_name)
150+
preferred_name = preferred_name(arg_name)
151+
return if arg_name.to_s == preferred_name
152+
153+
range = adjusted_range(arg_node)
154+
preferred, current = format_names(arg_node, arg_name, preferred_name)
155+
message = format(MSG, preferred: preferred, current: current)
156+
157+
add_offense(range, message: message) do |corrector|
158+
autocorrect(corrector, range, preferred, arg_node, preferred_name)
159+
end
160+
end
161+
162+
def preferred_name(name)
163+
config_name = cop_config.fetch('PreferredName', 'e')
164+
name.start_with?('_') ? "_#{config_name}" : config_name
165+
end
166+
167+
def adjusted_range(arg_node)
168+
arg_node.source_range.with(
169+
begin_pos: arg_node.source_range.begin_pos - 1,
170+
end_pos: arg_node.source_range.end_pos + 1
171+
)
172+
end
173+
174+
def format_names(arg_node, arg_name, preferred_name)
175+
if arg_node.parent.parent.lambda?
176+
["(#{preferred_name})", "(#{arg_name})"]
177+
else
178+
["|#{preferred_name}|", "|#{arg_name}|"]
179+
end
180+
end
181+
182+
def autocorrect(corrector, range, preferred, arg_node, preferred_name)
183+
corrector.replace(range, preferred)
184+
parent_block = arg_node.ancestors.find(&:block_type?)
185+
186+
parent_block.each_descendant(:lvar).each do |lvar_node|
187+
corrector.replace(lvar_node, preferred_name)
188+
end
189+
end
190+
end
191+
end
192+
end
193+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
require_relative 'rails/render_plain_text'
108108
require_relative 'rails/request_referer'
109109
require_relative 'rails/require_dependency'
110+
require_relative 'rails/rescue_from_exceptions_variable_name'
110111
require_relative 'rails/response_parsed_body'
111112
require_relative 'rails/reversible_migration'
112113
require_relative 'rails/reversible_migration_method_definition'

0 commit comments

Comments
 (0)