Module: RuboCop::Cop::HashShorthandSyntax
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| Extension / Inclusion / Inheritance Descendants | |
|
Included In:
| |
| Defined in: | lib/rubocop/cop/mixin/hash_shorthand_syntax.rb |
Overview
This module checks for Ruby 3.1’s hash value omission syntax.
Constant Summary
-
DO_NOT_MIX_EXPLICIT_VALUE_MSG =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 12"#{DO_NOT_MIX_MSG_PREFIX} #{EXPLICIT_HASH_VALUE_MSG}" -
DO_NOT_MIX_MSG_PREFIX =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 10'Do not mix explicit and implicit hash values.' -
DO_NOT_MIX_OMIT_VALUE_MSG =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 11"#{DO_NOT_MIX_MSG_PREFIX} #{OMIT_HASH_VALUE_MSG}" -
EXPLICIT_HASH_VALUE_MSG =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 9'Include the hash value.' -
KEYWORDS =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 17
A keyword parameter can be named after a keyword, as in
def foo(if:), but the parameter can only be referenced by an omitted hash value, so{ if: }has no explicit form:{ if: if }is invalid Ruby.%i[ __ENCODING__ __FILE__ __LINE__ BEGIN END alias and begin break case class def do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield ].to_set.freeze
-
OMIT_HASH_VALUE_MSG =
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 8'Omit the hash value.'
Instance Method Summary
- #on_hash_for_mixed_shorthand(hash_node)
- #on_pair(node)
- #brackets?(method_dispatch_node) ⇒ Boolean private
- #breakdown_value_types_of_hash(hash_node) private
-
#def_node_that_require_parentheses(node)
private
rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
- #each_omittable_value_pair(hash_value_type_breakdown, &block) private
- #each_omitted_value_pair(hash_value_type_breakdown, &block) private
- #enforced_shorthand_syntax private
- #find_ancestor_method_dispatch_node(node) private
- #hash_rockets_enforced?(hash_node) ⇒ Boolean private
- #hash_with_mixed_shorthand_syntax?(hash_value_type_breakdown) ⇒ Boolean private
- #hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown) ⇒ Boolean private
- #ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown) ⇒ Boolean private
- #ignore_hash_shorthand_syntax?(pair_node) ⇒ Boolean private
- #ignore_mixed_hash_shorthand_syntax?(hash_node) ⇒ Boolean private
- #keyword_value?(pair_node) ⇒ Boolean private
- #last_expression?(node) ⇒ Boolean private
- #mixed_shorthand_syntax_check(hash_value_type_breakdown) private
- #no_mixed_keys_enforces_hash_rockets?(pairs) ⇒ Boolean private
- #no_mixed_shorthand_syntax_check(hash_value_type_breakdown) private
- #register_offense(node, message, replacement) private
- #require_hash_value?(hash_key_source, node) ⇒ Boolean private
- #require_hash_value_for_around_hash_literal?(node) ⇒ Boolean private
- #requires_parentheses_context?(node) ⇒ Boolean private
- #use_element_of_hash_literal_as_receiver?(ancestor, parent) ⇒ Boolean private
- #use_modifier_form_without_parenthesized_method_call?(ancestor) ⇒ Boolean private
Instance Method Details
#brackets?(method_dispatch_node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 165
def brackets?(method_dispatch_node) method_dispatch_node.method?(:[]) || method_dispatch_node.method?(:[]=) end
#breakdown_value_types_of_hash(hash_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 195
def breakdown_value_types_of_hash(hash_node) hash_node.pairs.group_by do |pair_node| if pair_node.value_omission? :value_omitted elsif require_hash_value?(pair_node.key.source, pair_node) :value_needed else :value_omittable end end end
#def_node_that_require_parentheses(node) (private)
rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 143
def def_node_that_require_parentheses(node) last_pair = node.parent.pairs.last return unless last_pair.key.source == last_pair.value.source return unless (dispatch_node = find_ancestor_method_dispatch_node(node)) return if dispatch_node.assignment_method? return if dispatch_node.parenthesized? return if dispatch_node.parent && parentheses?(dispatch_node.parent) return if last_expression?(dispatch_node) && !requires_parentheses_context?(dispatch_node) def_node = node.each_ancestor(:call, :super, :yield).first DefNode.new(def_node) unless def_node && def_node.arguments.empty? end
#each_omittable_value_pair(hash_value_type_breakdown, &block) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 224
def each_omittable_value_pair(hash_value_type_breakdown, &block) hash_value_type_breakdown[:value_omittable]&.each(&block) end
#each_omitted_value_pair(hash_value_type_breakdown, &block) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 220
def each_omitted_value_pair(hash_value_type_breakdown, &block) hash_value_type_breakdown[:value_omitted]&.each(&block) end
#enforced_shorthand_syntax (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 121
def enforced_shorthand_syntax cop_config.fetch('EnforcedShorthandSyntax', 'always') end
#find_ancestor_method_dispatch_node(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 157
def find_ancestor_method_dispatch_node(node) return unless (ancestor = node.parent.parent) return unless ancestor.type?(:call, :super, :yield) return if brackets?(ancestor) ancestor end
#hash_rockets_enforced?(hash_node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 108
def hash_rockets_enforced?(hash_node) style == :hash_rockets || force_hash_rockets?(hash_node.pairs) || no_mixed_keys_enforces_hash_rockets?(hash_node.pairs) end
#hash_with_mixed_shorthand_syntax?(hash_value_type_breakdown) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 207
def hash_with_mixed_shorthand_syntax?(hash_value_type_breakdown) hash_value_type_breakdown.keys.size > 1 end
#hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 211
def hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown) hash_value_type_breakdown[:value_needed]&.any? end
#ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 215
def ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown) hash_value_type_breakdown.keys == [:value_omittable] && enforced_shorthand_syntax == 'either_consistent' end
#ignore_hash_shorthand_syntax?(pair_node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 102
def ignore_hash_shorthand_syntax?(pair_node) target_ruby_version <= 3.0 || enforced_shorthand_syntax == 'either' || %w[consistent either_consistent].include?(enforced_shorthand_syntax) || !pair_node.parent.hash_type? || hash_rockets_enforced?(pair_node.parent) end
#ignore_mixed_hash_shorthand_syntax?(hash_node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 96
def ignore_mixed_hash_shorthand_syntax?(hash_node) target_ruby_version <= 3.0 || !%w[consistent either_consistent].include?(enforced_shorthand_syntax) || !hash_node.hash_type? || hash_rockets_enforced?(hash_node) end
#keyword_value?(pair_node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 257
def keyword_value?(pair_node) KEYWORDS.include?(pair_node.key.value) end
#last_expression?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 180
def last_expression?(node) return false if node.right_sibling return true unless (assignment_node = node.each_ancestor.find(&:assignment?)) return last_expression?(assignment_node.parent) if assignment_node.parent&.assignment? !assignment_node.right_sibling end
#mixed_shorthand_syntax_check(hash_value_type_breakdown) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 228
def mixed_shorthand_syntax_check(hash_value_type_breakdown) if hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown) each_omitted_value_pair(hash_value_type_breakdown) do |pair_node| next if keyword_value?(pair_node) hash_key_source = pair_node.key.source replacement = "#{hash_key_source}: #{hash_key_source}" register_offense(pair_node, DO_NOT_MIX_EXPLICIT_VALUE_MSG, replacement) end else each_omittable_value_pair(hash_value_type_breakdown) do |pair_node| hash_key_source = pair_node.key.source replacement = "#{hash_key_source}:" register_offense(pair_node, DO_NOT_MIX_OMIT_VALUE_MSG, replacement) end end end
#no_mixed_keys_enforces_hash_rockets?(pairs) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 113
def no_mixed_keys_enforces_hash_rockets?(pairs) case style when :no_mixed_keys then !sym_indices?(pairs) || pairs.first.delimiter == '=>' when :ruby19_no_mixed_keys then !sym_indices?(pairs) else false end end
#no_mixed_shorthand_syntax_check(hash_value_type_breakdown) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 246
def no_mixed_shorthand_syntax_check(hash_value_type_breakdown) return if hash_with_values_that_cant_be_omitted?(hash_value_type_breakdown) return if ignore_explicit_omissible_hash_shorthand_syntax?(hash_value_type_breakdown) each_omittable_value_pair(hash_value_type_breakdown) do |pair_node| hash_key_source = pair_node.key.source replacement = "#{hash_key_source}:" register_offense(pair_node, OMIT_HASH_VALUE_MSG, replacement) end end
#on_hash_for_mixed_shorthand(hash_node)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 41
def on_hash_for_mixed_shorthand(hash_node) return if ignore_mixed_hash_shorthand_syntax?(hash_node) hash_value_type_breakdown = breakdown_value_types_of_hash(hash_node) if hash_with_mixed_shorthand_syntax?(hash_value_type_breakdown) mixed_shorthand_syntax_check(hash_value_type_breakdown) else no_mixed_shorthand_syntax_check(hash_value_type_breakdown) end end
#on_pair(node)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 53
def on_pair(node) return if ignore_hash_shorthand_syntax?(node) hash_key_source = node.key.source if enforced_shorthand_syntax == 'always' return if node.value_omission? || require_hash_value?(hash_key_source, node) = OMIT_HASH_VALUE_MSG replacement = "#{hash_key_source}:" self.config_to_allow_offenses = { 'Enabled' => false } else return if !node.value_omission? || keyword_value?(node) = EXPLICIT_HASH_VALUE_MSG replacement = "#{hash_key_source}: #{hash_key_source}" end register_offense(node, , replacement) end
#register_offense(node, message, replacement) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 76
def register_offense(node, , replacement) # rubocop:disable Metrics/AbcSize add_offense(node.value, message: ) do |corrector| corrector.replace(node, replacement) next unless (def_node = def_node_that_require_parentheses(node)) last_argument = def_node.last_argument if last_argument.nil? || !last_argument.hash_type? next corrector.replace(node, replacement) end white_spaces = range_between(def_node.selector.end_pos, def_node.first_argument.source_range.begin_pos) next if node.parent.braces? corrector.replace(white_spaces, '(') corrector.insert_after(last_argument, ')') if node == last_argument.pairs.last end end
#require_hash_value?(hash_key_source, node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 125
def require_hash_value?(hash_key_source, node) return true if !node.key.sym_type? || require_hash_value_for_around_hash_literal?(node) hash_value = node.value return true unless hash_value.type?(:send, :lvar) hash_key_source != hash_value.source || hash_key_source.end_with?('!', '?') end
#require_hash_value_for_around_hash_literal?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 134
def require_hash_value_for_around_hash_literal?(node) return false unless (method_dispatch_node = find_ancestor_method_dispatch_node(node)) !node.parent.braces? && !use_element_of_hash_literal_as_receiver?(method_dispatch_node, node.parent) && use_modifier_form_without_parenthesized_method_call?(method_dispatch_node) end
#requires_parentheses_context?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 188
def requires_parentheses_context?(node) parent = node.parent return false unless parent parent.type?(:call, :if, :super, :until, :while, :yield) end
#use_element_of_hash_literal_as_receiver?(ancestor, parent) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 169
def use_element_of_hash_literal_as_receiver?(ancestor, parent) # `{value:}.do_something` is a valid syntax. ancestor.send_type? && ancestor.receiver == parent end
#use_modifier_form_without_parenthesized_method_call?(ancestor) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/hash_shorthand_syntax.rb', line 174
def use_modifier_form_without_parenthesized_method_call?(ancestor) return false if ancestor.respond_to?(:parenthesized?) && ancestor.parenthesized? ancestor.ancestors.any? { |node| node.respond_to?(:modifier_form?) && node.modifier_form? } end