Skip to content

Commit 6f20945

Browse files
committed
[Fix #454] Fix false positives for Performance/BigDecimalWithNumericArgument
Fixes #454. This PR fixes false positives for `Performance/BigDecimalWithNumericArgument` when using BigDecimal 3.1+. The bad and good examples for this cop are reversed between BigDecimal 3.0 and 3.1. Since BigDecimal 3.1 is the default gem in Ruby 3.1, this PR reverses the bad and good examples when targeting Ruby 3.1+. So, the goal of this PR is to address many cases where the meaning was entirely reversed. To avoid introducing excessive complexity related to version dependencies, detection is disabled for Ruby 3.0 and below. Ideally, a solution that prioritizes the BigDecimal version would be best in the future, but this PR does not take that into account.
1 parent b52d821 commit 6f20945

File tree

2 files changed

+153
-140
lines changed

2 files changed

+153
-140
lines changed

lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,48 @@
33
module RuboCop
44
module Cop
55
module Performance
6-
# Identifies places where numeric argument to BigDecimal should be
7-
# converted to string. Initializing from String is faster
8-
# than from Numeric for BigDecimal.
6+
# Identifies places where string argument to `BigDecimal` should be
7+
# converted to numeric. Initializing from Integer is faster
8+
# than from String for BigDecimal.
99
#
1010
# @example
1111
# # bad
12-
# BigDecimal(1, 2)
13-
# 4.to_d(6)
14-
# BigDecimal(1.2, 3, exception: true)
15-
# 4.5.to_d(6, exception: true)
16-
#
17-
# # good
1812
# BigDecimal('1', 2)
1913
# BigDecimal('4', 6)
2014
# BigDecimal('1.2', 3, exception: true)
2115
# BigDecimal('4.5', 6, exception: true)
2216
#
17+
# # good
18+
# BigDecimal(1, 2)
19+
# 4.to_d(6)
20+
# BigDecimal(1.2, 3, exception: true)
21+
# 4.5.to_d(6, exception: true)
22+
#
2323
class BigDecimalWithNumericArgument < Base
2424
extend AutoCorrector
25+
extend TargetRubyVersion
26+
27+
minimum_target_ruby_version 3.1
2528

26-
MSG = 'Convert numeric literal to string and pass it to `BigDecimal`.'
29+
MSG = 'Convert string literal to numeric and pass it to `BigDecimal`.'
2730
RESTRICT_ON_SEND = %i[BigDecimal to_d].freeze
2831

2932
def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
30-
(send nil? :BigDecimal $numeric_type? ...)
33+
(send nil? :BigDecimal $str_type? ...)
3134
PATTERN
3235

3336
def_node_matcher :to_d?, <<~PATTERN
34-
(send [!nil? $numeric_type?] :to_d ...)
37+
(send [!nil? $str_type?] :to_d ...)
3538
PATTERN
3639

3740
def on_send(node)
38-
if (numeric = big_decimal_with_numeric_argument?(node))
39-
add_offense(numeric.source_range) do |corrector|
40-
corrector.wrap(numeric, "'", "'")
41+
if (string = big_decimal_with_numeric_argument?(node))
42+
add_offense(string.source_range) do |corrector|
43+
corrector.replace(string, string.value)
4144
end
42-
elsif (numeric_to_d = to_d?(node))
43-
add_offense(numeric_to_d.source_range) do |corrector|
44-
big_decimal_args = node.arguments.map(&:source).unshift("'#{numeric_to_d.source}'").join(', ')
45+
elsif (string_to_d = to_d?(node))
46+
add_offense(string_to_d.source_range) do |corrector|
47+
big_decimal_args = node.arguments.map(&:source).unshift(string_to_d.value).join(', ')
4548

4649
corrector.replace(node, "BigDecimal(#{big_decimal_args})")
4750
end
Lines changed: 132 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,139 @@
11
# frozen_string_literal: true
22

33
RSpec.describe RuboCop::Cop::Performance::BigDecimalWithNumericArgument, :config do
4-
it 'registers an offense and corrects when using `BigDecimal` with integer' do
5-
expect_offense(<<~RUBY)
6-
BigDecimal(1)
7-
^ Convert numeric literal to string and pass it to `BigDecimal`.
8-
RUBY
9-
10-
expect_correction(<<~RUBY)
11-
BigDecimal('1')
12-
RUBY
4+
context 'when Ruby >= 3.1', :ruby31 do
5+
it 'registers an offense and corrects when using `BigDecimal` with string' do
6+
expect_offense(<<~RUBY)
7+
BigDecimal('1')
8+
^^^ Convert string literal to numeric and pass it to `BigDecimal`.
9+
RUBY
10+
11+
expect_correction(<<~RUBY)
12+
BigDecimal(1)
13+
RUBY
14+
end
15+
16+
it 'registers an offense and corrects when using `String#to_d`' do
17+
expect_offense(<<~RUBY)
18+
'1'.to_d
19+
^^^ Convert string literal to numeric and pass it to `BigDecimal`.
20+
RUBY
21+
22+
expect_correction(<<~RUBY)
23+
BigDecimal(1)
24+
RUBY
25+
end
26+
27+
it 'registers an offense and corrects when using `BigDecimal` with float string' do
28+
expect_offense(<<~RUBY)
29+
BigDecimal('1.5', exception: true)
30+
^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
31+
RUBY
32+
33+
expect_correction(<<~RUBY)
34+
BigDecimal(1.5, exception: true)
35+
RUBY
36+
end
37+
38+
it 'registers an offense and corrects when using float `String#to_d`' do
39+
expect_offense(<<~RUBY)
40+
'1.5'.to_d(exception: true)
41+
^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
42+
RUBY
43+
44+
expect_correction(<<~RUBY)
45+
BigDecimal(1.5, exception: true)
46+
RUBY
47+
end
48+
49+
it 'registers an offense when using `BigDecimal` with float string and precision' do
50+
expect_offense(<<~RUBY)
51+
BigDecimal('3.14', 1)
52+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
53+
RUBY
54+
55+
expect_correction(<<~RUBY)
56+
BigDecimal(3.14, 1)
57+
RUBY
58+
end
59+
60+
it 'registers an offense when using float `String#to_d` with precision' do
61+
expect_offense(<<~RUBY)
62+
'3.14'.to_d(1)
63+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
64+
RUBY
65+
66+
expect_correction(<<~RUBY)
67+
BigDecimal(3.14, 1)
68+
RUBY
69+
end
70+
71+
it 'registers an offense when using `BigDecimal` with float string and non-literal precision' do
72+
expect_offense(<<~RUBY)
73+
precision = 1
74+
BigDecimal('3.14', precision)
75+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
76+
RUBY
77+
78+
expect_correction(<<~RUBY)
79+
precision = 1
80+
BigDecimal(3.14, precision)
81+
RUBY
82+
end
83+
84+
it 'registers an offense when using float `String#to_d` with non-literal precision' do
85+
expect_offense(<<~RUBY)
86+
precision = 1
87+
'3.14'.to_d(precision)
88+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
89+
RUBY
90+
91+
expect_correction(<<~RUBY)
92+
precision = 1
93+
BigDecimal(3.14, precision)
94+
RUBY
95+
end
96+
97+
it 'registers an offense when using `BigDecimal` with float string, precision, and a keyword argument' do
98+
expect_offense(<<~RUBY)
99+
BigDecimal('3.14', 1, exception: true)
100+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
101+
RUBY
102+
103+
expect_correction(<<~RUBY)
104+
BigDecimal(3.14, 1, exception: true)
105+
RUBY
106+
end
107+
108+
it 'registers an offense when using float `String#to_d` with precision and a keyword argument' do
109+
expect_offense(<<~RUBY)
110+
'3.14'.to_d(1, exception: true)
111+
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
112+
RUBY
113+
114+
expect_correction(<<~RUBY)
115+
BigDecimal(3.14, 1, exception: true)
116+
RUBY
117+
end
118+
119+
it 'does not register an offense when using `BigDecimal` with integer' do
120+
expect_no_offenses(<<~RUBY)
121+
BigDecimal(1)
122+
RUBY
123+
end
124+
125+
it 'does not register an offense when using `Integer#to_d`' do
126+
expect_no_offenses(<<~RUBY)
127+
1.to_d
128+
RUBY
129+
end
13130
end
14131

15-
it 'registers an offense and corrects when using `Integer#to_d`' do
16-
expect_offense(<<~RUBY)
17-
1.to_d
18-
^ Convert numeric literal to string and pass it to `BigDecimal`.
19-
RUBY
20-
21-
expect_correction(<<~RUBY)
22-
BigDecimal('1')
23-
RUBY
24-
end
25-
26-
it 'registers an offense and corrects when using `BigDecimal` with float' do
27-
expect_offense(<<~RUBY)
28-
BigDecimal(1.5, exception: true)
29-
^^^ Convert numeric literal to string and pass it to `BigDecimal`.
30-
RUBY
31-
32-
expect_correction(<<~RUBY)
33-
BigDecimal('1.5', exception: true)
34-
RUBY
35-
end
36-
37-
it 'registers an offense and corrects when using `Float#to_d`' do
38-
expect_offense(<<~RUBY)
39-
1.5.to_d(exception: true)
40-
^^^ Convert numeric literal to string and pass it to `BigDecimal`.
41-
RUBY
42-
43-
expect_correction(<<~RUBY)
44-
BigDecimal('1.5', exception: true)
45-
RUBY
46-
end
47-
48-
it 'registers an offense when using `BigDecimal` with float and precision' do
49-
expect_offense(<<~RUBY)
50-
BigDecimal(3.14, 1)
51-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
52-
RUBY
53-
54-
expect_correction(<<~RUBY)
55-
BigDecimal('3.14', 1)
56-
RUBY
57-
end
58-
59-
it 'registers an offense when using `Float#to_d` with precision' do
60-
expect_offense(<<~RUBY)
61-
3.14.to_d(1)
62-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
63-
RUBY
64-
65-
expect_correction(<<~RUBY)
66-
BigDecimal('3.14', 1)
67-
RUBY
68-
end
69-
70-
it 'registers an offense when using `BigDecimal` with float and non-literal precision' do
71-
expect_offense(<<~RUBY)
72-
precision = 1
73-
BigDecimal(3.14, precision)
74-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
75-
RUBY
76-
77-
expect_correction(<<~RUBY)
78-
precision = 1
79-
BigDecimal('3.14', precision)
80-
RUBY
81-
end
82-
83-
it 'registers an offense when using `Float#to_d` with non-literal precision' do
84-
expect_offense(<<~RUBY)
85-
precision = 1
86-
3.14.to_d(precision)
87-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
88-
RUBY
89-
90-
expect_correction(<<~RUBY)
91-
precision = 1
92-
BigDecimal('3.14', precision)
93-
RUBY
94-
end
95-
96-
it 'registers an offense when using `BigDecimal` with float, precision, and a keyword argument' do
97-
expect_offense(<<~RUBY)
98-
BigDecimal(3.14, 1, exception: true)
99-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
100-
RUBY
101-
102-
expect_correction(<<~RUBY)
103-
BigDecimal('3.14', 1, exception: true)
104-
RUBY
105-
end
106-
107-
it 'registers an offense when using `Float#to_d` with precision and a keyword argument' do
108-
expect_offense(<<~RUBY)
109-
3.14.to_d(1, exception: true)
110-
^^^^ Convert numeric literal to string and pass it to `BigDecimal`.
111-
RUBY
112-
113-
expect_correction(<<~RUBY)
114-
BigDecimal('3.14', 1, exception: true)
115-
RUBY
116-
end
117-
118-
it 'does not register an offense when using `BigDecimal` with string' do
119-
expect_no_offenses(<<~RUBY)
120-
BigDecimal('1')
121-
RUBY
122-
end
123-
124-
it 'does not register an offense when using `String#to_d`' do
125-
expect_no_offenses(<<~RUBY)
126-
'1'.to_d
127-
RUBY
132+
context 'when Ruby <= 3.0', :ruby30, unsupported_on: :prism do
133+
it 'does not register an offense and corrects when using `BigDecimal` with string' do
134+
expect_no_offenses(<<~RUBY)
135+
BigDecimal('1')
136+
RUBY
137+
end
128138
end
129139
end

0 commit comments

Comments
 (0)