# 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'"## @good# {a: 2, b: 1}# {:c => 2, 'd' => 2} # acceptable since 'd' isn't a symbol# {d: 1, 'e' => 2} # technically not forbidden## @bad# {:a => 2}# {b: 1, :c => 2}## @example# "EnforcedStyle => 'hash_rockets'"## @good# {:a => 1, :b => 2}## @bad# {a: 1, b: 2}# {c: 1, 'd' => 5}## @example# "EnforcedStyle => 'no_mixed_keys'"## @good# {:a => 1, :b => 2}# {c: 1, d: 2}## @bad# {:a => 1, b: 2}# {c: 1, 'd' => 2}## @example# "EnforcedStyle => 'ruby19_no_mixed_keys'"## @good# {a: 1, b: 2}# {:c => 3, 'd' => 4}## @bad# {:a => 1, :b => 2}# {c: 2, 'd' => 3} # should just use hash rocketsclassHashSyntax<CopincludeConfigurableEnforcedStyleMSG_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)returnifnode.pairs.empty?@force_hash_rockets=force_hash_rockets?(node.pairs)ifstyle==:hash_rockets||@force_hash_rocketshash_rockets_check(node.pairs)elsifstyle==:ruby19_no_mixed_keysruby19_no_mixed_keys_check(node.pairs)elsifstyle==:no_mixed_keysno_mixed_keys_check(node.pairs)elseruby19_check(node.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)if@force_hash_rocketscheck(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_rocketsautocorrect_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==delimadd_offense(pair,pair.source_range.begin.join(pair.loc.operator),msg)doopposite_style_detectedendelsecorrect_style_detectedendendenddefautocorrect_ruby19(corrector,node)key=node.children.first.source_rangeop=node.loc.operatorrange=range_between(key.begin_pos,op.end_pos)range=range_with_surrounding_space(range,:right)corrector.replace(range,range.source.sub(/^:(.*\S)\s*=>\s*$/,'\1: '))enddefautocorrect_hash_rockets(corrector,node)key=node.children.first.source_rangeop=node.loc.operatorcorrector.insert_after(key,node.inverse_delimiter(true))corrector.insert_before(key,':')corrector.remove(range_with_surrounding_space(op))enddefautocorrect_no_mixed_keys(corrector,node)ifnode.colon?autocorrect_hash_rockets(corrector,node)elseautocorrect_ruby19(corrector,node)endenddefforce_hash_rockets?(pairs)@force_hash_rockets||=begincop_config['UseHashRocketsWithSymbolValues']&&pairs.map(&:value).any?(&:sym_type?)endendendendendend