class RuboCop::Cop::Lint::RedundantTypeConversion
foo.to_json
foo.inspect
# good
foo.to_json.to_s
foo.inspect.to_s
# bad - chaining a conversion to a method that is expected to return the same type
foo.to_s
# good
foo.to_s.to_s
# bad - chaining the same conversion
Integer(var, exception: false).to_i
# in this case, ‘Integer()` could return `nil`
# good - chaining to a type constructor with exceptions suppressed
Integer(var)
# good
Integer(var).to_i
# bad
Set.new
{}
[]
1i
12r
8.5
42
:sym
“text”
# good
Set.new.to_set
{}.to_h
[].to_a
1i.to_c
12r.to_r
8.5.to_f
42.to_i
:sym.to_sym
“text”.to_s
# bad
@example
`foo.to_json.to_s`).
expected to return a specific type regardless of receiver (eg. `foo.inspect.to_s` and
The cop can also register an offense for chaining conversion methods on methods that are
In all cases, chaining one same `to_*` conversion methods listed above is redundant.
* `to_set` when called on `Set.new` or `Set[]`.
* `to_h` when called on a hash literal, or with `Hash.new`, `Hash()` or `Hash[]`.
* `to_a` when called on an array literal, or with `Array.new`, `Array()` or `Array[]`.
* `to_c` when called on a complex literal of with `Complex()`.
* `to_r` when called on a rational literal or with `Rational()`.
* `to_f` when called on a float literal of with `Float()`.
* `to_i` when called on an integer literal or with `Integer()`.
* `to_sym` when called on a symbol literal or interpolated symbol.
or with `String.new` or `String()`.
* `to_s` when called on a string literal, interpolated string, heredoc,
Specifically, these cases are detected for each conversion method:
`String()`, `Integer()`, `Float()`, BigDecimal(), `Rational()`, `Complex()`, and `Array()`.
on object literals, class constructors, class `[]` methods, and the `Kernel` methods
is returned, making the call unnecessary. The cop detects conversion methods called
When one of these methods is called on an object of the same type, that object
`to_a`, `to_h`, and `to_set`.
Checks for redundant uses of `to_s`, `to_sym`, `to_i`, `to_f`, `to_d`, `to_r`, `to_c`,
def chained_conversion?(node, receiver)
def chained_conversion?(node, receiver) return false unless receiver&.call_type? receiver.method?(node.method_name) end
def chained_to_typed_method?(node, receiver)
def chained_to_typed_method?(node, receiver) return false unless receiver&.call_type? TYPED_METHODS.fetch(node.method_name, []).include?(receiver.method_name) end
def constructor?(node, receiver)
def constructor?(node, receiver) matcher = CONSTRUCTOR_MAPPING[node.method_name] return false unless matcher public_send(matcher, receiver) && !constructor_suppresses_exceptions?(receiver) end
def constructor_suppresses_exceptions?(receiver)
def constructor_suppresses_exceptions?(receiver) # If the constructor suppresses exceptions with `exception: false`, it is possible # it could return `nil`, and therefore a chained conversion is not redundant. receiver.arguments.any? { |arg| exception_false_keyword_argument?(arg) } end
def find_receiver(node)
def find_receiver(node) receiver = node.receiver return unless receiver while receiver.begin_type? break unless receiver.children.one? receiver = receiver.children.first end receiver end
def hash_or_set_with_block?(node)
def hash_or_set_with_block?(node) return false if !node.method?(:to_h) && !node.method?(:to_set) node.parent&.any_block_type? || node.last_argument&.block_pass_type? end
def literal_receiver?(node, receiver)
def literal_receiver?(node, receiver) return false unless receiver receiver.type?(*LITERAL_NODE_TYPES[node.method_name]) end
def on_send(node)
def on_send(node) return if hash_or_set_with_block?(node) receiver = find_receiver(node) return unless literal_receiver?(node, receiver) || constructor?(node, receiver) || chained_conversion?(node, receiver) || chained_to_typed_method?(node, receiver) message = format(MSG, method: node.method_name) add_offense(node.loc.selector, message: message) do |corrector| corrector.remove(node.loc.dot.join(node.loc.selector)) end end