# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleLayout# Check that the keys, separators, and values of a multi-line hash# literal are aligned according to configuration. The configuration# options are:## - key (left align keys)# - separator (align hash rockets and colons, right align keys)# - table (left align keys, hash rockets, and values)## The treatment of hashes passed as the last argument to a method call# can also be configured. The options are:## - always_inspect# - always_ignore# - ignore_implicit (without curly braces)# - ignore_explicit (with curly braces)## @example## # EnforcedHashRocketStyle: key (default)# # EnforcedColonStyle: key (default)## # good# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }## # bad# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }## @example## # EnforcedHashRocketStyle: separator# # EnforcedColonStyle: separator## #good# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }## #bad# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }# {# :foo => bar,# :ba => baz# }## @example## # EnforcedHashRocketStyle: table# # EnforcedColonStyle: table## #good# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }## #bad# {# foo: bar,# ba: baz# }# {# :foo => bar,# :ba => baz# }classAlignHash<CopincludeHashAlignmentMSG='Align the elements of a hash literal if they span more than '\'one line.'.freezedefon_send(node)returnifdouble_splat?(node)last_argument=node.last_argumentreturnunlesslast_argument.hash_type?&&ignore_hash_argument?(last_argument)ignore_node(last_argument)enddefon_hash(node)returnifignored_node?(node)returnifnode.pairs.empty?||node.single_line?returnunlessalignment_for_hash_rockets.checkable_layout?(node)&&alignment_for_colons.checkable_layout?(node)check_pairs(node)endprivateattr_accessor:column_deltasdefdouble_splat?(node)node.children.last.is_a?(Symbol)enddefcheck_pairs(node)first_pair=node.pairs.firstself.column_deltas=alignment_for(first_pair).deltas_for_first_pair(first_pair,node)add_offense(first_pair,:expression)unlessgood_alignment?node.children.eachdo|current|self.column_deltas=alignment_for(current).deltas(first_pair,current)add_offense(current,:expression)unlessgood_alignment?endenddefignore_hash_argument?(node)casecop_config['EnforcedLastArgumentHashStyle']when'always_inspect'thenfalsewhen'always_ignore'thentruewhen'ignore_explicit'thennode.braces?when'ignore_implicit'then!node.braces?endenddefalignment_for(pair)ifpair.hash_rocket?alignment_for_hash_rocketselsealignment_for_colonsendenddefalignment_for_hash_rockets@alignment_for_hash_rockets||=new_alignment('EnforcedHashRocketStyle')enddefalignment_for_colons@alignment_for_colons||=new_alignment('EnforcedColonStyle')enddefautocorrect(node)# We can't use the instance variable inside the lambda. That would# just give each lambda the same reference and they would all get the# last value of each. A local variable fixes the problem.key_delta=column_deltas[:key]||0if!node.valuecorrect_no_value(key_delta,node.source_range)elsecorrect_key_value(key_delta,node.key.source_range,node.value.source_range,node.loc.operator)endenddefcorrect_no_value(key_delta,key)->(corrector){adjust(corrector,key_delta,key)}enddefcorrect_key_value(key_delta,key,value,separator)# We can't use the instance variable inside the lambda. That would# just give each lambda the same reference and they would all get the# last value of each. Some local variables fix the problem.separator_delta=column_deltas[:separator]||0value_delta=column_deltas[:value]||0key_column=key.columnkey_delta=-key_columnifkey_delta<-key_columnlambdado|corrector|adjust(corrector,key_delta,key)adjust(corrector,separator_delta,separator)adjust(corrector,value_delta,value)endenddefnew_alignment(key)casecop_config[key]when'key'thenKeyAlignment.newwhen'table'thenTableAlignment.newwhen'separator'thenSeparatorAlignment.newelseraise"Unknown #{key}: #{cop_config[key]}"endenddefadjust(corrector,delta,range)ifdelta>0corrector.insert_before(range,' '*delta)elsifdelta<0range=range_between(range.begin_pos-delta.abs,range.begin_pos)corrector.remove(range)endenddefgood_alignment?column_deltas.values.all?(&:zero?)endendendendend