2020-03-09 13:42:32 +05:30
|
|
|
module RuboCop
|
|
|
|
module Cop
|
|
|
|
module Gitlab
|
|
|
|
class KeysFirstAndValuesFirst < RuboCop::Cop::Cop
|
|
|
|
FIRST_PATTERN = /\Afirst\z/.freeze
|
|
|
|
|
|
|
|
def message(used_method)
|
|
|
|
<<~MSG
|
|
|
|
Don't use `.keys.first` and `.values.first`.
|
|
|
|
Instead use `.each_key.first` and `.each_value.first` (or `.first.first` and `first.second`)
|
|
|
|
|
|
|
|
This will reduce memory usage and execution time.
|
|
|
|
MSG
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_send(node)
|
|
|
|
if find_on_keys_or_values?(node)
|
|
|
|
add_offense(node, location: :selector, message: message(node.method_name))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def autocorrect(node)
|
|
|
|
lambda do |corrector|
|
|
|
|
replace_with = if node.descendants.first.method_name == :values
|
|
|
|
'.each_value'
|
|
|
|
elsif node.descendants.first.method_name == :keys
|
|
|
|
'.each_key'
|
|
|
|
else
|
2020-04-08 14:13:33 +05:30
|
|
|
throw("Expect '.values.first' or '.keys.first', but get #{node.descendants.first.method_name}.first") # rubocop:disable Cop/BanCatchThrow
|
2020-03-09 13:42:32 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
upto_including_keys_or_values = node.descendants.first.source_range
|
|
|
|
before_keys_or_values = node.descendants[1].source_range
|
|
|
|
range_to_replace = node.source_range
|
|
|
|
.with(begin_pos: before_keys_or_values.end_pos,
|
|
|
|
end_pos: upto_including_keys_or_values.end_pos)
|
|
|
|
corrector.replace(range_to_replace, replace_with)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_on_keys_or_values?(node)
|
|
|
|
chained_on_node = node.descendants.first
|
|
|
|
node.method_name.to_s =~ FIRST_PATTERN &&
|
|
|
|
chained_on_node.is_a?(RuboCop::AST::SendNode) &&
|
|
|
|
[:keys, :values].include?(chained_on_node.method_name) &&
|
|
|
|
node.descendants[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
def method_name_for_node(node)
|
|
|
|
children[1].to_s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|