Skip to content

Commit 4356728

Browse files
dvandersluismergify[bot]
authored andcommitted
Fix false negatives for Style/EvalWithLocation for Kernel.eval and when given improper arguments.
Previously if `eval` methods were given any arguments in the file and line spots, the cop would not register an offense. This change allows the cop to detect improper values for file (anything other than `__FILE__`) and line (as before, `__LINE__` with an offset as appropriate).
1 parent 1f7d2d2 commit 4356728

3 files changed

Lines changed: 115 additions & 22 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#9411](https://github.com/rubocop-hq/rubocop/pull/9411): Fix false negatives for `Style/EvalWithLocation` for `Kernel.eval` and when given improper arguments. ([@dvandersluis][])

lib/rubocop/cop/style/eval_with_location.rb

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,15 @@ module Style
3939
class EvalWithLocation < Base
4040
MSG = 'Pass `__FILE__` and `__LINE__` to `%<method_name>s`.'
4141
MSG_EVAL = 'Pass a binding, `__FILE__` and `__LINE__` to `eval`.'
42+
MSG_INCORRECT_FILE = 'Incorrect file for `%<method_name>s`; ' \
43+
'use `%<expected>s` instead of `%<actual>s`.'
4244
MSG_INCORRECT_LINE = 'Incorrect line number for `%<method_name>s`; ' \
4345
'use `%<expected>s` instead of `%<actual>s`.'
4446

4547
RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
4648

47-
def_node_matcher :eval_without_location?, <<~PATTERN
48-
{
49-
(send nil? :eval ${str dstr})
50-
(send nil? :eval ${str dstr} _)
51-
(send nil? :eval ${str dstr} _ #special_file_keyword?)
52-
(send nil? :eval ${str dstr} _ #special_file_keyword? _)
53-
54-
(send _ {:class_eval :module_eval :instance_eval}
55-
${str dstr})
56-
(send _ {:class_eval :module_eval :instance_eval}
57-
${str dstr} #special_file_keyword?)
58-
(send _ {:class_eval :module_eval :instance_eval}
59-
${str dstr} #special_file_keyword? _)
60-
}
49+
def_node_matcher :valid_eval_receiver?, <<~PATTERN
50+
{ nil? (const {nil? cbase} :Kernel) }
6151
PATTERN
6252

6353
def_node_matcher :line_with_offset?, <<~PATTERN
@@ -68,18 +58,31 @@ class EvalWithLocation < Base
6858
PATTERN
6959

7060
def on_send(node)
71-
eval_without_location?(node) do |code|
72-
if with_lineno?(node)
73-
on_with_lineno(node, code)
74-
else
75-
msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
76-
add_offense(node, message: msg)
77-
end
61+
# Classes should not redefine eval, but in case one does, it shouldn't
62+
# register an offense. Only `eval` without a receiver and `Kernel.eval`
63+
# are considered.
64+
return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)
65+
66+
code = node.arguments.first
67+
return unless code.str_type? || code.dstr_type?
68+
69+
file, line = file_and_line(node)
70+
71+
if line
72+
check_file(node, file)
73+
check_line(node, code)
74+
else
75+
register_offense(node)
7876
end
7977
end
8078

8179
private
8280

81+
def register_offense(node)
82+
msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
83+
add_offense(node, message: msg)
84+
end
85+
8386
def special_file_keyword?(node)
8487
node.str_type? &&
8588
node.source == '__FILE__'
@@ -90,6 +93,11 @@ def special_line_keyword?(node)
9093
node.source == '__LINE__'
9194
end
9295

96+
def file_and_line(node)
97+
base = node.method?(:eval) ? 2 : 1
98+
[node.arguments[base], node.arguments[base + 1]]
99+
end
100+
93101
# FIXME: It's a Style/ConditionalAssignment's false positive.
94102
# rubocop:disable Style/ConditionalAssignment
95103
def with_lineno?(node)
@@ -115,7 +123,18 @@ def message_incorrect_line(method_name, actual, sign, line_diff)
115123
expected: expected)
116124
end
117125

118-
def on_with_lineno(node, code)
126+
def check_file(node, file_node)
127+
return true if special_file_keyword?(file_node)
128+
129+
message = format(MSG_INCORRECT_FILE,
130+
method_name: node.method_name,
131+
expected: '__FILE__',
132+
actual: file_node.source)
133+
134+
add_offense(file_node, message: message)
135+
end
136+
137+
def check_line(node, code)
119138
line_node = node.arguments.last
120139
lineno_range = line_node.loc.expression
121140
line_diff = string_first_line(code) - lineno_range.first_line

spec/rubocop/cop/style/eval_with_location_spec.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,30 @@
1212
RUBY
1313
end
1414

15+
it 'registers an offense when using `Kernel.eval` without any arguments' do
16+
expect_offense(<<~RUBY)
17+
Kernel.eval <<-CODE
18+
^^^^^^^^^^^^^^^^^^^ Pass a binding, `__FILE__` and `__LINE__` to `eval`.
19+
do_something
20+
CODE
21+
RUBY
22+
end
23+
24+
it 'registers an offense when using `::Kernel.eval` without any arguments' do
25+
expect_offense(<<~RUBY)
26+
::Kernel.eval <<-CODE
27+
^^^^^^^^^^^^^^^^^^^^^ Pass a binding, `__FILE__` and `__LINE__` to `eval`.
28+
do_something
29+
CODE
30+
RUBY
31+
end
32+
33+
it 'does not register an offense if `eval` is called on another object' do
34+
expect_no_offenses(<<~RUBY)
35+
foo.eval "CODE"
36+
RUBY
37+
end
38+
1539
it 'registers an offense when using `#eval` with `binding` only' do
1640
expect_offense(<<~RUBY)
1741
eval <<-CODE, binding
@@ -120,4 +144,53 @@
120144
__LINE__ - 1
121145
RUBY
122146
end
147+
148+
it 'registers an offense when using `eval` with improper arguments' do
149+
expect_offense(<<~RUBY)
150+
eval <<-CODE, binding, 'foo', 'bar'
151+
^^^^^ Incorrect line number for `eval`; use `__LINE__ + 1` instead of `'bar'`.
152+
^^^^^ Incorrect file for `eval`; use `__FILE__` instead of `'foo'`.
153+
do_something
154+
CODE
155+
RUBY
156+
end
157+
158+
it 'registers an offense when using `instance_eval` with improper arguments' do
159+
expect_offense(<<~RUBY)
160+
instance_eval <<-CODE, 'foo', 'bar'
161+
^^^^^ Incorrect line number for `instance_eval`; use `__LINE__ + 1` instead of `'bar'`.
162+
^^^^^ Incorrect file for `instance_eval`; use `__FILE__` instead of `'foo'`.
163+
do_something
164+
CODE
165+
RUBY
166+
end
167+
168+
it 'registers an offense when using `class_eval` with improper arguments' do
169+
expect_offense(<<~RUBY)
170+
class_eval <<-CODE, 'foo', 'bar'
171+
^^^^^ Incorrect line number for `class_eval`; use `__LINE__ + 1` instead of `'bar'`.
172+
^^^^^ Incorrect file for `class_eval`; use `__FILE__` instead of `'foo'`.
173+
do_something
174+
CODE
175+
RUBY
176+
end
177+
178+
it 'registers an offense when using `module_eval` with improper arguments' do
179+
expect_offense(<<~RUBY)
180+
module_eval <<-CODE, 'foo', 'bar'
181+
^^^^^ Incorrect line number for `module_eval`; use `__LINE__ + 1` instead of `'bar'`.
182+
^^^^^ Incorrect file for `module_eval`; use `__FILE__` instead of `'foo'`.
183+
do_something
184+
CODE
185+
RUBY
186+
end
187+
188+
it 'registers an offense when using correct file argument but incorrect line' do
189+
expect_offense(<<~RUBY)
190+
module_eval <<-CODE, __FILE__, 'bar'
191+
^^^^^ Incorrect line number for `module_eval`; use `__LINE__ + 1` instead of `'bar'`.
192+
do_something
193+
CODE
194+
RUBY
195+
end
123196
end

0 commit comments

Comments
 (0)