Skip to content

Commit 8eaea82

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 4d0e655 commit 8eaea82

File tree

5 files changed

+765
-0
lines changed

5 files changed

+765
-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: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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+
115+
def_node_matcher :rescue_from_block_argument_variable?, <<~PATTERN
116+
(block (send nil? :rescue_from ...) (args (arg $_)) _)
117+
PATTERN
118+
119+
def_node_matcher :rescue_from_with_lambda_variable?, <<~PATTERN
120+
(send nil? :rescue_from ... (hash <(pair (sym :with) (block _ (args (arg $_)) _))>))
121+
PATTERN
122+
123+
def_node_matcher :rescue_from_with_block_variable?, <<~PATTERN
124+
(send nil? :rescue_from ... {(block _ (args (arg $_)) _) (splat (block _ (args (arg $_)) _))})
125+
PATTERN
126+
127+
def on_block(node)
128+
rescue_from_block_argument_variable?(node) do |arg_name|
129+
check_offense(node.first_argument, arg_name)
130+
end
131+
end
132+
alias on_numblock on_block
133+
134+
def on_send(node)
135+
check_rescue_from_variable(node, :rescue_from_with_lambda_variable?)
136+
check_rescue_from_variable(node, :rescue_from_with_block_variable?)
137+
end
138+
139+
private
140+
141+
def check_rescue_from_variable(node, matcher)
142+
send(matcher, node) do |arg_name|
143+
arg_node = node.each_descendant(:args).first.children.first
144+
check_offense(arg_node, arg_name)
145+
end
146+
end
147+
148+
def check_offense(arg_node, arg_name)
149+
preferred_name = preferred_name(arg_name)
150+
return if arg_name.to_s == preferred_name
151+
152+
range = adjusted_range(arg_node)
153+
preferred, current = format_names(arg_node, arg_name, preferred_name)
154+
message = format(MSG, preferred: preferred, current: current)
155+
156+
add_offense(range, message: message) do |corrector|
157+
autocorrect(corrector, range, preferred, arg_node, preferred_name)
158+
end
159+
end
160+
161+
def preferred_name(name)
162+
config_name = cop_config.fetch('PreferredName', 'e')
163+
name.start_with?('_') ? "_#{config_name}" : config_name
164+
end
165+
166+
def adjusted_range(arg_node)
167+
arg_node.source_range.with(
168+
begin_pos: arg_node.source_range.begin_pos - 1,
169+
end_pos: arg_node.source_range.end_pos + 1
170+
)
171+
end
172+
173+
def format_names(arg_node, arg_name, preferred_name)
174+
if arg_node.parent.parent.lambda?
175+
["(#{preferred_name})", "(#{arg_name})"]
176+
else
177+
["|#{preferred_name}|", "|#{arg_name}|"]
178+
end
179+
end
180+
181+
def autocorrect(corrector, range, preferred, arg_node, preferred_name)
182+
corrector.replace(range, preferred)
183+
parent_block = arg_node.ancestors.find(&:block_type?)
184+
185+
parent_block.each_descendant(:lvar).each do |lvar_node|
186+
corrector.replace(lvar_node, preferred_name)
187+
end
188+
end
189+
end
190+
end
191+
end
192+
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)