lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb
# frozen_string_literal: true module RuboCop module Cop module Performance # Identifies places where a float argument to BigDecimal should be converted to a string. # Initializing from String is faster than from Float for BigDecimal. # # Also identifies places where an integer string argument to BigDecimal should be converted to # an integer. Initializing from Integer is faster than from String for BigDecimal. # # @example # # bad # BigDecimal(1.2, 3, exception: true) # 4.5.to_d(6, exception: true) # # # good # BigDecimal('1.2', 3, exception: true) # BigDecimal('4.5', 6, exception: true) # # # bad # BigDecimal('1', 2) # BigDecimal('4', 6) # # # good # BigDecimal(1, 2) # 4.to_d(6) # class BigDecimalWithNumericArgument < Base extend AutoCorrector extend TargetRubyVersion minimum_target_ruby_version 3.1 MSG_FROM_FLOAT_TO_STRING = 'Convert float literal to string and pass it to `BigDecimal`.' MSG_FROM_INTEGER_TO_STRING = 'Convert string literal to integer 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 ${float_type? str_type?} ...) PATTERN def_node_matcher :to_d, <<~PATTERN (send [!nil? ${float_type? str_type?}] :to_d ...) PATTERN # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength def on_send(node) if (numeric = big_decimal_with_numeric_argument(node)) if numeric.numeric_type? add_offense(numeric, message: MSG_FROM_FLOAT_TO_STRING) do |corrector| corrector.wrap(numeric, "'", "'") end elsif numeric.value.match?(/\A\d+\z/) add_offense(numeric, message: MSG_FROM_INTEGER_TO_STRING) do |corrector| corrector.replace(numeric, numeric.value) end end elsif (numeric_to_d = to_d(node)) if numeric_to_d.numeric_type? add_offense(numeric_to_d, message: MSG_FROM_FLOAT_TO_STRING) do |corrector| big_decimal_args = node.arguments.map(&:source).unshift("'#{numeric_to_d.source}'").join(', ') corrector.replace(node, "BigDecimal(#{big_decimal_args})") end elsif numeric_to_d.value.match?(/\A\d+\z/) add_offense(numeric_to_d, message: MSG_FROM_INTEGER_TO_STRING) do |corrector| corrector.replace(node, "#{numeric_to_d.value}.to_d") end end end end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength end end end end