Class: RuboCop::Cop::Style::HashSyntax
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
::RuboCop::Cop::AutoCorrector,
::RuboCop::Cop::Base,
::RuboCop::ExcludeLimit,
NodePattern::Macros,
RuboCop::AST::Sexp
|
|
|
Instance Chain:
|
|
| Inherits: |
RuboCop::Cop::Base
|
| Defined in: | lib/rubocop/cop/style/hash_syntax.rb |
Overview
Checks hash literal syntax.
It can enforce either the use of the class hash rocket syntax or the use of the newer Ruby 1.9 syntax (when applicable).
A separate offense is registered for each problematic pair.
The supported styles are:
-
ruby19 - forces use of the 1.9 syntax (e.g.
{a: 1}) when hashes have all symbols for keys -
hash_rockets - forces use of hash rockets for all hashes
-
no_mixed_keys - simply checks for hashes with mixed syntaxes
-
ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed syntax hashes
This cop has EnforcedShorthandSyntax option.
It can enforce either the use of the explicit hash value syntax or
the use of Ruby 3.1’s hash value shorthand syntax.
The supported styles are:
-
always - forces use of the 3.1 syntax (e.g.
foo:) -
never - forces use of explicit hash literal value
-
either - accepts both shorthand and explicit use of hash literal value
-
consistent - forces use of the 3.1 syntax only if all values can be omitted in the hash
-
either_consistent - accepts both shorthand and explicit use of hash literal value, but they must be consistent
Constant Summary
-
MSG_19 =
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 140'Use the new Ruby 1.9 hash syntax.' -
MSG_HASH_ROCKETS =
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 142'Use hash rockets syntax.' -
MSG_NO_MIXED_KEYS =
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 141"Don't mix styles in the same hash." -
NO_MIXED_KEYS_STYLES =
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 143%i[ruby19_no_mixed_keys no_mixed_keys].freeze
::RuboCop::Cop::Base - Inherited
DEPARTMENT_SEVERITIES, EMPTY_OFFENSES, RESTRICT_ON_SEND
::RuboCop::Cop::ConfigurableEnforcedStyle - Included
::RuboCop::Cop::HashShorthandSyntax - Included
DO_NOT_MIX_EXPLICIT_VALUE_MSG, DO_NOT_MIX_MSG_PREFIX, DO_NOT_MIX_OMIT_VALUE_MSG, EXPLICIT_HASH_VALUE_MSG, KEYWORDS, OMIT_HASH_VALUE_MSG
::RuboCop::Cop::RangeHelp - Included
Class Attribute Summary
::RuboCop::Cop::AutoCorrector - Extended
::RuboCop::Cop::Base - Inherited
| .gem_requirements, .lint?, | |
| .support_autocorrect? | Returns if class supports autocorrect. |
| .support_multiple_source? | Override if your cop should be called repeatedly for multiple investigations Between calls to |
Class Method Summary
-
.autocorrect_incompatible_with
Omitting the last hash value of a call without parentheses adds the parentheses, which
Style/MethodCallWithArgsParenthesesandStyle/SuperWithArgsParentheseswould add again in the same pass.
::RuboCop::Cop::Base - Inherited
| .autocorrect_incompatible_with | List of cops that should not try to autocorrect at the same time as this cop. |
| .badge | Naming. |
| .callbacks_needed, .cop_name, .department, | |
| .documentation_url | Returns a url to view this cops documentation online. |
| .exclude_from_registry | Call for abstract Cop classes. |
| .inherited, | |
| .joining_forces | Override and return the Force class(es) you need to join. |
| .match? | Returns true if the cop name or the cop namespace matches any of the given names. |
| .new, | |
| .requires_gem | Register a version requirement for the given gem name. |
| .restrict_on_send | Reserved for Commissioner. |
::RuboCop::ExcludeLimit - Extended
| exclude_limit | Sets up a configuration option to have an exclude limit tracked. |
| transform | |
Instance Attribute Summary
::RuboCop::Cop::ConfigurableEnforcedStyle - Included
::RuboCop::Cop::Base - Inherited
| #active_support_extensions_enabled?, #always_autocorrect?, #config, #config_to_allow_offenses, #config_to_allow_offenses=, #contextual_autocorrect?, #disabled_autocorrect?, | |
| #preview? | Whether the user opted in to unstable behavior, with |
| #processed_source, #project_index, #string_literals_frozen_by_default?, #target_satisfies_all_gem_version_requirements? | |
::RuboCop::Cop::AutocorrectLogic - Included
| #autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?, | |
| #skipped_unsafe_correction_with_disable_uncorrectable? | Whether a correction exists but is skipped because it is unsafe in a safe autocorrect run, while |
| #contextual_autocorrect_held_back? | When LSP is enabled or the |
Instance Method Summary
- #alternative_style
- #hash_rockets_check(pairs)
- #no_mixed_keys_check(pairs)
- #on_hash(node)
- #ruby19_check(pairs)
- #ruby19_no_mixed_keys_check(pairs)
-
#acceptable_19_syntax_symbol?(sym_name) ⇒ Boolean
private
rubocop:disable-next Metrics/CyclomaticComplexity.
- #argument_without_space?(node) ⇒ Boolean private
- #autocorrect(corrector, node) private
- #autocorrect_hash_rockets(corrector, pair_node) private
- #autocorrect_no_mixed_keys(corrector, pair_node) private
- #autocorrect_ruby19(corrector, pair_node) private
- #check(pairs, delim, msg) private
- #force_hash_rockets?(pairs) ⇒ Boolean private
- #operator_range(pair_node) private
- #range_for_autocorrect_ruby19(pair_node) private
- #sym_indices?(pairs) ⇒ Boolean private
- #word_symbol_pair?(pair) ⇒ Boolean private
::RuboCop::Cop::RangeHelp - Included
| #add_range, | |
| #arguments_range | A range containing the first to the last argument of a method call or method definition. |
| #column_offset_between, | |
| #contents_range | A range containing only the contents of a literal with delimiters (e.g. |
| #directions, | |
| #effective_column | Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value. |
| #final_pos | rubocop:disable-next Metrics/ParameterLists. |
| #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range | |
::RuboCop::Cop::HashShorthandSyntax - Included
::RuboCop::Cop::ConfigurableEnforcedStyle - Included
::RuboCop::Cop::Base - Inherited
| #add_global_offense | Adds an offense that has no particular location. |
| #add_offense | Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense. |
| #begin_investigation | Called before any investigation. |
| #callbacks_needed | — grouped under the Commissioner heading above. |
| #cop_config | Configuration Helpers. |
| #cop_name, #excluded_file?, | |
| #external_dependency_checksum | This method should be overridden when a cop’s behavior depends on state that lives outside of these locations: |
| #inspect, | |
| #message | Gets called if no message is specified when calling |
| #name | Alias for Base#cop_name. |
| #offenses, | |
| #on_investigation_end | Called after all on_… |
| #on_new_investigation | Called before all on_… |
| #on_other_file | Called instead of all on_… |
| #parse | There should be very limited reasons for a Cop to do it’s own parsing. |
| #parser_engine, | |
| #ready | Called between investigations. |
| #relevant_file?, | |
| #target_gem_version | Returns the version of a gem in the target, by default its locked version (i.e. from Gemfile.lock or gems.locked). |
| #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction, | |
| #callback_argument | Reserved for Cop::Cop. |
| #complete_investigation | Called to complete an investigation. |
| #correct, #covering_disabled_range, #current_corrector, | |
| #current_offense_locations | Reserved for Commissioner: |
| #current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, | |
| #enabled_lines? | A multi-line offense is suppressed by a directive on any line of its range, not only its first line, matching the intuition that the directive is attached to the offending code. |
| #file_name_matches_any?, #find_message, #find_severity, #matches_absolute_include_pattern?, #new_offense, #range_for_original, #range_from_node_or_range, | |
| #reset_investigation | Actually private methods. |
| #suppression_reason | The |
| #use_corrector | |
::RuboCop::Cop::AutocorrectLogic - Included
::RuboCop::Cop::IgnoredNode - Included
Constructor Details
This class inherits a constructor from RuboCop::Cop::Base
Class Method Details
.autocorrect_incompatible_with
Omitting the last hash value of a call without parentheses adds the parentheses,
which Style/MethodCallWithArgsParentheses and Style/SuperWithArgsParentheses
would add again in the same pass.
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 148
def self.autocorrect_incompatible_with [Style::MethodCallWithArgsParentheses, Style::SuperWithArgsParentheses] end
Instance Method Details
#acceptable_19_syntax_symbol?(sym_name) ⇒ Boolean (private)
rubocop:disable-next Metrics/CyclomaticComplexity
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 228
def acceptable_19_syntax_symbol?(sym_name) sym_name.delete_prefix!(':') if cop_config['PreferHashRocketsForNonAlnumEndingSymbols'] && # Prefer { :production? => false } over { production?: false } and # similarly for other non-alnum final characters (except quotes, # to prefer { "x y": 1 } over { :"x y" => 1 }). !/[\p{Alnum}"']\z/.match?(sym_name) return false end # Most hash keys can be matched against a simple regex. return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name) return false if target_ruby_version <= 2.1 (sym_name.start_with?("'") && sym_name.end_with?("'")) || (sym_name.start_with?('"') && sym_name.end_with?('"')) end
#alternative_style
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 196
def alternative_style case style when :hash_rockets :ruby19 when :ruby19, :ruby19_no_mixed_keys :hash_rockets end end
#argument_without_space?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 288
def argument_without_space?(node) return false if !node.argument? || !node.parent.loc.selector node.source_range.begin_pos == node.parent.loc.selector.end_pos end
#autocorrect(corrector, node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 207
def autocorrect(corrector, node) if style == :hash_rockets || force_hash_rockets?(node.parent.pairs) autocorrect_hash_rockets(corrector, node) elsif NO_MIXED_KEYS_STYLES.include?(style) autocorrect_no_mixed_keys(corrector, node) else autocorrect_ruby19(corrector, node) end end
#autocorrect_hash_rockets(corrector, pair_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 294
def autocorrect_hash_rockets(corrector, pair_node) key_with_hash_rocket = ":#{pair_node.key.source}#{pair_node.inverse_delimiter(true)}" key_with_hash_rocket += pair_node.key.source if pair_node.value_omission? corrector.replace(pair_node.key, key_with_hash_rocket) corrector.remove(operator_range(pair_node)) end
#autocorrect_no_mixed_keys(corrector, pair_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 307
def autocorrect_no_mixed_keys(corrector, pair_node) if pair_node.colon? autocorrect_hash_rockets(corrector, pair_node) else autocorrect_ruby19(corrector, pair_node) end end
#autocorrect_ruby19(corrector, pair_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 265
def autocorrect_ruby19(corrector, pair_node) range = range_for_autocorrect_ruby19(pair_node) space = argument_without_space?(pair_node.parent) ? ' ' : '' corrector.replace(range, range.source.sub(/^:(.*\S)\s*=>\s*$/, "#{space}\\1: ")) hash_node = pair_node.parent return unless hash_node.parent&.return_type? && !hash_node.braces? # This runs once per pair, but the hash must only be wrapped once. return unless pair_node.equal?(hash_node.pairs.first) corrector.wrap(hash_node, '{', '}') end
#check(pairs, delim, msg) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 248
def check(pairs, delim, msg) pairs.each do |pair| if pair.delimiter == delim next if pair.value_omission? && keyword_value?(pair) location = pair.source_range.begin.join(pair.loc.operator) add_offense(location, message: msg) do |corrector| autocorrect(corrector, pair) opposite_style_detected end else correct_style_detected end end end
#force_hash_rockets?(pairs) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 315
def force_hash_rockets?(pairs) cop_config['UseHashRocketsWithSymbolValues'] && pairs.map(&:value).any?(&:sym_type?) end
#hash_rockets_check(pairs)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 174
def hash_rockets_check(pairs) check(pairs, ':', MSG_HASH_ROCKETS) end
#no_mixed_keys_check(pairs)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 188
def no_mixed_keys_check(pairs) if sym_indices?(pairs) check(pairs, pairs.first.inverse_delimiter, MSG_NO_MIXED_KEYS) else check(pairs, ':', MSG_NO_MIXED_KEYS) end end
#on_hash(node)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 152
def on_hash(node) pairs = node.pairs return if pairs.empty? on_hash_for_mixed_shorthand(node) if style == :hash_rockets || force_hash_rockets?(pairs) hash_rockets_check(pairs) elsif style == :ruby19_no_mixed_keys ruby19_no_mixed_keys_check(pairs) elsif style == :no_mixed_keys no_mixed_keys_check(pairs) else ruby19_check(pairs) end end
#operator_range(pair_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 301
def operator_range(pair_node) op = pair_node.loc.operator range_with_surrounding_space(op, newlines: !pair_node.value_omission?) end
#range_for_autocorrect_ruby19(pair_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 280
def range_for_autocorrect_ruby19(pair_node) key = pair_node.key.source_range operator = pair_node.loc.operator range = key.join(operator) range_with_surrounding_space(range, side: :right) end
#ruby19_check(pairs)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 170
def ruby19_check(pairs) check(pairs, '=>', MSG_19) if sym_indices?(pairs) end
#ruby19_no_mixed_keys_check(pairs)
[ GitHub ]# File 'lib/rubocop/cop/style/hash_syntax.rb', line 178
def ruby19_no_mixed_keys_check(pairs) if force_hash_rockets?(pairs) check(pairs, ':', MSG_HASH_ROCKETS) elsif sym_indices?(pairs) check(pairs, '=>', MSG_19) else check(pairs, ':', MSG_NO_MIXED_KEYS) end end
#sym_indices?(pairs) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 217
def sym_indices?(pairs) pairs.all? { |p| word_symbol_pair?(p) } end
#word_symbol_pair?(pair) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/hash_syntax.rb', line 221
def word_symbol_pair?(pair) return false unless pair.key.any_sym_type? acceptable_19_syntax_symbol?(pair.key.source) end