Skip to content

Commit 100cdfe

Browse files
authored
Merge pull request #469 from koic/fix_false_positives_for_performance_big_decimal_with_numeric_argument_cop
[Fix #468] Fix false positives for `Performance/BigDecimalWithNumericArgument`
2 parents f74a890 + 6bb06b2 commit 100cdfe

File tree

3 files changed

+91
-69
lines changed

3 files changed

+91
-69
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#468](https://github.com/rubocop/rubocop-performance/issues/468): Fix false positives for `Performance/BigDecimalWithNumericArgument` when using float argument for `BigDecimal`. ([@koic][])

lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,74 @@
33
module RuboCop
44
module Cop
55
module Performance
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.
6+
# Identifies places where a float argument to BigDecimal should be converted to a string.
7+
# Initializing from String is faster than from Float for BigDecimal.
8+
#
9+
# Also identifies places where an integer string argument to BigDecimal should be converted to
10+
# an integer. Initializing from Integer is faster than from String for BigDecimal.
911
#
1012
# @example
1113
# # bad
12-
# BigDecimal('1', 2)
13-
# BigDecimal('4', 6)
14+
# BigDecimal(1.2, 3, exception: true)
15+
# 4.5.to_d(6, exception: true)
16+
#
17+
# # good
1418
# BigDecimal('1.2', 3, exception: true)
1519
# BigDecimal('4.5', 6, exception: true)
1620
#
21+
# # bad
22+
# BigDecimal('1', 2)
23+
# BigDecimal('4', 6)
24+
#
1725
# # good
1826
# BigDecimal(1, 2)
1927
# 4.to_d(6)
20-
# BigDecimal(1.2, 3, exception: true)
21-
# 4.5.to_d(6, exception: true)
2228
#
2329
class BigDecimalWithNumericArgument < Base
2430
extend AutoCorrector
2531
extend TargetRubyVersion
2632

2733
minimum_target_ruby_version 3.1
2834

29-
MSG = 'Convert string literal to numeric and pass it to `BigDecimal`.'
35+
MSG_FROM_FLOAT_TO_STRING = 'Convert float literal to string and pass it to `BigDecimal`.'
36+
MSG_FROM_INTEGER_TO_STRING = 'Convert string literal to integer and pass it to `BigDecimal`.'
3037
RESTRICT_ON_SEND = %i[BigDecimal to_d].freeze
3138

32-
def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
33-
(send nil? :BigDecimal $str_type? ...)
39+
def_node_matcher :big_decimal_with_numeric_argument, <<~PATTERN
40+
(send nil? :BigDecimal ${float_type? str_type?} ...)
3441
PATTERN
3542

36-
def_node_matcher :to_d?, <<~PATTERN
37-
(send [!nil? $str_type?] :to_d ...)
43+
def_node_matcher :to_d, <<~PATTERN
44+
(send [!nil? ${float_type? str_type?}] :to_d ...)
3845
PATTERN
3946

47+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
4048
def on_send(node)
41-
if (string = big_decimal_with_numeric_argument?(node))
42-
add_offense(string.source_range) do |corrector|
43-
corrector.replace(string, string.value)
49+
if (numeric = big_decimal_with_numeric_argument(node))
50+
if numeric.numeric_type?
51+
add_offense(numeric, message: MSG_FROM_FLOAT_TO_STRING) do |corrector|
52+
corrector.wrap(numeric, "'", "'")
53+
end
54+
elsif numeric.value.match?(/\A\d+\z/)
55+
add_offense(numeric, message: MSG_FROM_INTEGER_TO_STRING) do |corrector|
56+
corrector.replace(numeric, numeric.value)
57+
end
4458
end
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(', ')
59+
elsif (numeric_to_d = to_d(node))
60+
if numeric_to_d.numeric_type?
61+
add_offense(numeric_to_d, message: MSG_FROM_FLOAT_TO_STRING) do |corrector|
62+
big_decimal_args = node.arguments.map(&:source).unshift("'#{numeric_to_d.source}'").join(', ')
4863

49-
corrector.replace(node, "BigDecimal(#{big_decimal_args})")
64+
corrector.replace(node, "BigDecimal(#{big_decimal_args})")
65+
end
66+
elsif numeric_to_d.value.match?(/\A\d+\z/)
67+
add_offense(numeric_to_d, message: MSG_FROM_INTEGER_TO_STRING) do |corrector|
68+
corrector.replace(node, "#{numeric_to_d.value}.to_d")
69+
end
5070
end
5171
end
5272
end
73+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
5374
end
5475
end
5576
end

spec/rubocop/cop/performance/big_decimal_with_numeric_argument_spec.rb

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,128 +2,128 @@
22

33
RSpec.describe RuboCop::Cop::Performance::BigDecimalWithNumericArgument, :config do
44
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`.
5+
it 'does not register an offense and corrects when using `BigDecimal` with integer' do
6+
expect_no_offenses(<<~RUBY)
7+
BigDecimal(1)
98
RUBY
9+
end
1010

11-
expect_correction(<<~RUBY)
12-
BigDecimal(1)
11+
it 'does not register an offense and corrects when using `Integer#to_d` for integer' do
12+
expect_no_offenses(<<~RUBY)
13+
1.to_d
1314
RUBY
1415
end
1516

16-
it 'registers an offense and corrects when using `String#to_d`' do
17+
it 'registers an offense and corrects when using `BigDecimal` with float' do
1718
expect_offense(<<~RUBY)
18-
'1'.to_d
19-
^^^ Convert string literal to numeric and pass it to `BigDecimal`.
19+
BigDecimal(1.5, exception: true)
20+
^^^ Convert float literal to string and pass it to `BigDecimal`.
2021
RUBY
2122

2223
expect_correction(<<~RUBY)
23-
BigDecimal(1)
24+
BigDecimal('1.5', exception: true)
2425
RUBY
2526
end
2627

27-
it 'registers an offense and corrects when using `BigDecimal` with float string' do
28+
it 'registers an offense and corrects when using `Float#to_d`' do
2829
expect_offense(<<~RUBY)
29-
BigDecimal('1.5', exception: true)
30-
^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
30+
1.5.to_d(exception: true)
31+
^^^ Convert float literal to string and pass it to `BigDecimal`.
3132
RUBY
3233

3334
expect_correction(<<~RUBY)
34-
BigDecimal(1.5, exception: true)
35+
BigDecimal('1.5', exception: true)
3536
RUBY
3637
end
3738

38-
it 'registers an offense and corrects when using float `String#to_d`' do
39+
it 'registers an offense when using `BigDecimal` with float and precision' do
3940
expect_offense(<<~RUBY)
40-
'1.5'.to_d(exception: true)
41-
^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
41+
BigDecimal(3.14, 1)
42+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
4243
RUBY
4344

4445
expect_correction(<<~RUBY)
45-
BigDecimal(1.5, exception: true)
46+
BigDecimal('3.14', 1)
4647
RUBY
4748
end
4849

49-
it 'registers an offense when using `BigDecimal` with float string and precision' do
50+
it 'registers an offense when using `Float#to_d` with precision' do
5051
expect_offense(<<~RUBY)
51-
BigDecimal('3.14', 1)
52-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
52+
3.14.to_d(1)
53+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
5354
RUBY
5455

5556
expect_correction(<<~RUBY)
56-
BigDecimal(3.14, 1)
57+
BigDecimal('3.14', 1)
5758
RUBY
5859
end
5960

60-
it 'registers an offense when using float `String#to_d` with precision' do
61+
it 'registers an offense when using `BigDecimal` with float and non-literal precision' do
6162
expect_offense(<<~RUBY)
62-
'3.14'.to_d(1)
63-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
63+
precision = 1
64+
BigDecimal(3.14, precision)
65+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
6466
RUBY
6567

6668
expect_correction(<<~RUBY)
67-
BigDecimal(3.14, 1)
69+
precision = 1
70+
BigDecimal('3.14', precision)
6871
RUBY
6972
end
7073

71-
it 'registers an offense when using `BigDecimal` with float string and non-literal precision' do
74+
it 'registers an offense when using `Float#to_d` with non-literal precision' do
7275
expect_offense(<<~RUBY)
7376
precision = 1
74-
BigDecimal('3.14', precision)
75-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
77+
3.14.to_d(precision)
78+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
7679
RUBY
7780

7881
expect_correction(<<~RUBY)
7982
precision = 1
80-
BigDecimal(3.14, precision)
83+
BigDecimal('3.14', precision)
8184
RUBY
8285
end
8386

84-
it 'registers an offense when using float `String#to_d` with non-literal precision' do
87+
it 'registers an offense when using `BigDecimal` with float, precision, and a keyword argument' do
8588
expect_offense(<<~RUBY)
86-
precision = 1
87-
'3.14'.to_d(precision)
88-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
89+
BigDecimal(3.14, 1, exception: true)
90+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
8991
RUBY
9092

9193
expect_correction(<<~RUBY)
92-
precision = 1
93-
BigDecimal(3.14, precision)
94+
BigDecimal('3.14', 1, exception: true)
9495
RUBY
9596
end
9697

97-
it 'registers an offense when using `BigDecimal` with float string, precision, and a keyword argument' do
98+
it 'registers an offense when using `Float#to_d` with precision and a keyword argument' do
9899
expect_offense(<<~RUBY)
99-
BigDecimal('3.14', 1, exception: true)
100-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
100+
3.14.to_d(1, exception: true)
101+
^^^^ Convert float literal to string and pass it to `BigDecimal`.
101102
RUBY
102103

103104
expect_correction(<<~RUBY)
104-
BigDecimal(3.14, 1, exception: true)
105+
BigDecimal('3.14', 1, exception: true)
105106
RUBY
106107
end
107108

108-
it 'registers an offense when using float `String#to_d` with precision and a keyword argument' do
109+
it 'registers an offense when using `BigDecimal` with integer string' do
109110
expect_offense(<<~RUBY)
110-
'3.14'.to_d(1, exception: true)
111-
^^^^^^ Convert string literal to numeric and pass it to `BigDecimal`.
111+
BigDecimal('1')
112+
^^^ Convert string literal to integer and pass it to `BigDecimal`.
112113
RUBY
113114

114115
expect_correction(<<~RUBY)
115-
BigDecimal(3.14, 1, exception: true)
116+
BigDecimal(1)
116117
RUBY
117118
end
118119

119-
it 'does not register an offense when using `BigDecimal` with integer' do
120-
expect_no_offenses(<<~RUBY)
121-
BigDecimal(1)
120+
it 'registers an offense when using `String#to_d` for integer string' do
121+
expect_offense(<<~RUBY)
122+
'1'.to_d
123+
^^^ Convert string literal to integer and pass it to `BigDecimal`.
122124
RUBY
123-
end
124125

125-
it 'does not register an offense when using `Integer#to_d`' do
126-
expect_no_offenses(<<~RUBY)
126+
expect_correction(<<~RUBY)
127127
1.to_d
128128
RUBY
129129
end

0 commit comments

Comments
 (0)