# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# This cop 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## @example EnforcedStyle: ruby19 (default)# # bad# {:a => 2}# {b: 1, :c => 2}## # good# {a: 2, b: 1}# {:c => 2, 'd' => 2} # acceptable since 'd' isn't a symbol# {d: 1, 'e' => 2} # technically not forbidden## @example EnforcedStyle: hash_rockets# # bad# {a: 1, b: 2}# {c: 1, 'd' => 5}## # good# {:a => 1, :b => 2}## @example EnforcedStyle: no_mixed_keys# # bad# {:a => 1, b: 2}# {c: 1, 'd' => 2}## # good# {:a => 1, :b => 2}# {c: 1, d: 2}## @example EnforcedStyle: ruby19_no_mixed_keys# # bad# {:a => 1, :b => 2}# {c: 2, 'd' => 3} # should just use hash rockets## # good# {a: 1, b: 2}# {:c => 3, 'd' => 4}classHashSyntax<CopincludeConfigurableEnforcedStyleincludeRangeHelpMSG_19='Use the new Ruby 1.9 hash syntax.'.freezeMSG_NO_MIXED_KEYS="Don't mix styles in the same hash.".freezeMSG_HASH_ROCKETS='Use hash rockets syntax.'.freezedefon_hash(node)pairs=node.pairsreturnifpairs.empty?ifstyle==:hash_rockets||force_hash_rockets?(pairs)hash_rockets_check(pairs)elsifstyle==:ruby19_no_mixed_keysruby19_no_mixed_keys_check(pairs)elsifstyle==:no_mixed_keysno_mixed_keys_check(pairs)elseruby19_check(pairs)endenddefruby19_check(pairs)check(pairs,'=>',MSG_19)ifsym_indices?(pairs)enddefhash_rockets_check(pairs)check(pairs,':',MSG_HASH_ROCKETS)enddefruby19_no_mixed_keys_check(pairs)ifforce_hash_rockets?(pairs)check(pairs,':',MSG_HASH_ROCKETS)elsifsym_indices?(pairs)check(pairs,'=>',MSG_19)elsecheck(pairs,':',MSG_NO_MIXED_KEYS)endenddefno_mixed_keys_check(pairs)if!sym_indices?(pairs)check(pairs,':',MSG_NO_MIXED_KEYS)elsecheck(pairs,pairs.first.inverse_delimiter,MSG_NO_MIXED_KEYS)endenddefautocorrect(node)lambdado|corrector|ifstyle==:hash_rockets||force_hash_rockets?(node.parent.pairs)autocorrect_hash_rockets(corrector,node)elsifstyle==:ruby19_no_mixed_keys||style==:no_mixed_keysautocorrect_no_mixed_keys(corrector,node)elseautocorrect_ruby19(corrector,node)endendenddefalternative_stylecasestylewhen:hash_rocketsthen:ruby19when:ruby19,:ruby19_no_mixed_keysthen:hash_rocketsendendprivatedefsym_indices?(pairs)pairs.all?{|p|word_symbol_pair?(p)}enddefword_symbol_pair?(pair)returnfalseunlesspair.key.sym_type?acceptable_19_syntax_symbol?(pair.key.source)enddefacceptable_19_syntax_symbol?(sym_name)sym_name.sub!(/\A:/,'')ifcop_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 }).returnfalseunlesssym_name=~/[\p{Alnum}"']\z/end# Most hash keys can be matched against a simple regex.returntrueifsym_name=~/\A[_a-z]\w*[?!]?\z/i# For more complicated hash keys, let the parser validate the syntax.parse("{ #{sym_name}: :foo }").valid_syntax?enddefcheck(pairs,delim,msg)pairs.eachdo|pair|ifpair.delimiter==delimlocation=pair.source_range.begin.join(pair.loc.operator)add_offense(pair,location: location,message: msg)doopposite_style_detectedendelsecorrect_style_detectedendendenddefautocorrect_ruby19(corrector,pair_node)key=pair_node.keyop=pair_node.loc.operatorrange=range_between(key.source_range.begin_pos,op.end_pos)range=range_with_surrounding_space(range: range,side: :right)space=argument_without_space?(pair_node.parent)?' ':''corrector.replace(range,range.source.sub(/^:(.*\S)\s*=>\s*$/,space.to_s+'\1: '))enddefargument_without_space?(node)node.argument?&&node.loc.expression.begin_pos==node.parent.loc.selector.end_posenddefautocorrect_hash_rockets(corrector,pair_node)key=pair_node.key.source_rangeop=pair_node.loc.operatorcorrector.insert_after(key,pair_node.inverse_delimiter(true))corrector.insert_before(key,':')corrector.remove(range_with_surrounding_space(range: op))enddefautocorrect_no_mixed_keys(corrector,pair_node)ifpair_node.colon?autocorrect_hash_rockets(corrector,pair_node)elseautocorrect_ruby19(corrector,pair_node)endenddefforce_hash_rockets?(pairs)cop_config['UseHashRocketsWithSymbolValues']&&pairs.map(&:value).any?(&:sym_type?)endendendendend