diff --git a/CHANGELOG.md b/CHANGELOG.md index cc78dc733a..18410688ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -312,3 +312,4 @@ [@ghiculescu]: https://github.com/ghiculescu [@mfbmina]: https://github.com/mfbmina [@mvz]: https://github.com/mvz +[@leoarnold]: https://github.com/leoarnold diff --git a/changelog/.gitkeep b/changelog/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog/new_add_to_d_to_BigDecimalWithNumericArgument.md b/changelog/new_add_to_d_to_BigDecimalWithNumericArgument.md new file mode 100644 index 0000000000..e4055bae19 --- /dev/null +++ b/changelog/new_add_to_d_to_BigDecimalWithNumericArgument.md @@ -0,0 +1 @@ +* [#269](https://github.com/rubocop/rubocop-performance/pull/269): Add `#to_d` support to `BigDecimalWithNumericArgument`. ([@leoarnold][]) diff --git a/config/default.yml b/config/default.yml index 446b24a760..2eb89b12a2 100644 --- a/config/default.yml +++ b/config/default.yml @@ -17,7 +17,7 @@ Performance/ArraySemiInfiniteRangeSlice: VersionAdded: '1.9' Performance/BigDecimalWithNumericArgument: - Description: 'Convert numeric argument to string before passing to BigDecimal.' + Description: 'Convert numeric literal to string and pass it to `BigDecimal`.' Enabled: 'pending' VersionAdded: '1.7' diff --git a/docs/modules/ROOT/pages/cops_performance.adoc b/docs/modules/ROOT/pages/cops_performance.adoc index 234d8fbe0a..a6ead830f5 100644 --- a/docs/modules/ROOT/pages/cops_performance.adoc +++ b/docs/modules/ROOT/pages/cops_performance.adoc @@ -99,7 +99,9 @@ than from Numeric for BigDecimal. ---- # bad BigDecimal(1, 2) +1.to_d(2) BigDecimal(1.2, 3, exception: true) +1.2.to_d(3, exception: true) # good BigDecimal('1', 2) diff --git a/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb b/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb index 1248544cf2..af64cb7c84 100644 --- a/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +++ b/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb @@ -10,7 +10,9 @@ module Performance # @example # # bad # BigDecimal(1, 2) + # 1.to_d(2) # BigDecimal(1.2, 3, exception: true) + # 1.2.to_d(3, exception: true) # # # good # BigDecimal('1', 2) @@ -19,27 +21,34 @@ module Performance class BigDecimalWithNumericArgument < Base extend AutoCorrector - MSG = 'Convert numeric argument to string before passing to `BigDecimal`.' - RESTRICT_ON_SEND = %i[BigDecimal].freeze + MSG = 'Convert numeric literal to string and pass it to `BigDecimal`.' + RESTRICT_ON_SEND = %i[BigDecimal to_d].freeze def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN (send nil? :BigDecimal $numeric_type? ...) PATTERN + def_node_matcher :to_d?, <<~PATTERN + (send [!nil? $numeric_type?] :to_d ...) + PATTERN + def on_send(node) - return unless (numeric = big_decimal_with_numeric_argument?(node)) - return if numeric.float_type? && specifies_precision?(node) + if (numeric = big_decimal_with_numeric_argument?(node)) + add_offense(numeric.source_range) do |corrector| + corrector.wrap(numeric, "'", "'") + end + elsif (numeric_to_d = to_d?(node)) + add_offense(numeric_to_d.source_range) do |corrector| + big_decimal_args = node + .arguments + .map(&:source) + .unshift("'#{numeric_to_d.source}'") + .join(', ') - add_offense(numeric.source_range) do |corrector| - corrector.wrap(numeric, "'", "'") + corrector.replace(node, "BigDecimal(#{big_decimal_args})") + end end end - - private - - def specifies_precision?(node) - node.arguments.size > 1 && !node.arguments[1].hash_type? - end end end end diff --git a/spec/rubocop/cop/performance/big_decimal_with_numeric_argument_spec.rb b/spec/rubocop/cop/performance/big_decimal_with_numeric_argument_spec.rb index 95255823cc..2fc9ba4fdd 100644 --- a/spec/rubocop/cop/performance/big_decimal_with_numeric_argument_spec.rb +++ b/spec/rubocop/cop/performance/big_decimal_with_numeric_argument_spec.rb @@ -4,7 +4,18 @@ it 'registers an offense and corrects when using `BigDecimal` with integer' do expect_offense(<<~RUBY) BigDecimal(1) - ^ Convert numeric argument to string before passing to `BigDecimal`. + ^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('1') + RUBY + end + + it 'registers an offense and corrects when using `Integer#to_d`' do + expect_offense(<<~RUBY) + 1.to_d + ^ Convert numeric literal to string and pass it to `BigDecimal`. RUBY expect_correction(<<~RUBY) @@ -15,7 +26,7 @@ it 'registers an offense and corrects when using `BigDecimal` with float' do expect_offense(<<~RUBY) BigDecimal(1.5, exception: true) - ^^^ Convert numeric argument to string before passing to `BigDecimal`. + ^^^ Convert numeric literal to string and pass it to `BigDecimal`. RUBY expect_correction(<<~RUBY) @@ -23,22 +34,84 @@ RUBY end - it 'does not register an offense when using `BigDecimal` with float and precision' do - expect_no_offenses(<<~RUBY) + it 'registers an offense and corrects when using `Float#to_d`' do + expect_offense(<<~RUBY) + 1.5.to_d(exception: true) + ^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('1.5', exception: true) + RUBY + end + + it 'registers an offense when using `BigDecimal` with float and precision' do + expect_offense(<<~RUBY) BigDecimal(3.14, 1) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('3.14', 1) RUBY end - it 'does not register an offense when using `BigDecimal` with float and non-literal precision' do - expect_no_offenses(<<~RUBY) + it 'registers an offense when using `Float#to_d` with precision' do + expect_offense(<<~RUBY) + 3.14.to_d(1) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('3.14', 1) + RUBY + end + + it 'registers an offense when using `BigDecimal` with float and non-literal precision' do + expect_offense(<<~RUBY) precision = 1 BigDecimal(3.14, precision) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + precision = 1 + BigDecimal('3.14', precision) RUBY end - it 'does not register an offense when using `BigDecimal` with float, precision, and a keyword argument' do - expect_no_offenses(<<~RUBY) + it 'registers an offense when using `Float#to_d` with non-literal precision' do + expect_offense(<<~RUBY) + precision = 1 + 3.14.to_d(precision) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + precision = 1 + BigDecimal('3.14', precision) + RUBY + end + + it 'registers an offense when using `BigDecimal` with float, precision, and a keyword argument' do + expect_offense(<<~RUBY) BigDecimal(3.14, 1, exception: true) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('3.14', 1, exception: true) + RUBY + end + + it 'registers an offense when using `Float#to_d` with precision and a keyword argument' do + expect_offense(<<~RUBY) + 3.14.to_d(1, exception: true) + ^^^^ Convert numeric literal to string and pass it to `BigDecimal`. + RUBY + + expect_correction(<<~RUBY) + BigDecimal('3.14', 1, exception: true) RUBY end @@ -47,4 +120,10 @@ BigDecimal('1') RUBY end + + it 'does not register an offense when using `String#to_d`' do + expect_no_offenses(<<~RUBY) + '1'.to_d + RUBY + end end