diff options
Diffstat (limited to 'rubocop/cop/gitlab/keys_first_and_values_first.rb')
-rw-r--r-- | rubocop/cop/gitlab/keys_first_and_values_first.rb | 57 |
1 files changed, 57 insertions, 0 deletions
diff --git a/rubocop/cop/gitlab/keys_first_and_values_first.rb b/rubocop/cop/gitlab/keys_first_and_values_first.rb new file mode 100644 index 00000000000..e9bf266cdd7 --- /dev/null +++ b/rubocop/cop/gitlab/keys_first_and_values_first.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +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 + throw("Expect '.values.first' or '.keys.first', but get #{node.descendants.first.method_name}.first") # rubocop:disable Cop/BanCatchThrow + 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 |