# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleLayout# This cop checks whether the end keywords are aligned properly for do# end blocks.## Three modes are supported through the `EnforcedStyleAlignWith`# configuration parameter:## `start_of_block` : the `end` shall be aligned with the# start of the line where the `do` appeared.## `start_of_line` : the `end` shall be aligned with the# start of the line where the expression started.## `either` (which is the default) : the `end` is allowed to be in either# location. The autofixer will default to `start_of_line`.## @example EnforcedStyleAlignWith: either (default)# # bad## foo.bar# .each do# baz# end## # good## variable = lambda do |i|# i# end## @example EnforcedStyleAlignWith: start_of_block# # bad## foo.bar# .each do# baz# end## # good## foo.bar# .each do# baz# end## @example EnforcedStyleAlignWith: start_of_line# # bad## foo.bar# .each do# baz# end## # good## foo.bar# .each do# baz# endclassBlockAlignment<CopincludeConfigurableEnforcedStyleincludeRangeHelpMSG='%<current>s is not aligned with %<prefer>s%<alt_prefer>s.'.freezedef_node_matcher:block_end_align_target?,<<-PATTERN
{assignment?
splat
and
or
(send _ :<< ...)
(send equal?(%1) !:[] ...)}
PATTERNdefon_block(node)check_block_alignment(start_for_block_node(node),node)enddefstyle_parameter_name'EnforcedStyleAlignWith'enddefautocorrect(node)ancestor_node=start_for_block_node(node)start_col=compute_start_col(ancestor_node,node)loc_end=node.loc.enddelta=start_col-loc_end.columnifdelta>0add_space_before(loc_end,delta)elsifdelta<0remove_space_before(loc_end.begin_pos,-delta)endendprivatedefstart_for_block_node(block_node)# Which node should we align the 'end' with?result=block_end_align_target(block_node)# In offense message, we want to show the assignment LHS rather than# the entire assignmentresult,=*resultwhileresult.op_asgn_type?||result.masgn_type?resultenddefblock_end_align_target(node)lineage=[node,*node.ancestors]target=lineage.each_cons(2)do|current,parent|breakcurrentifend_align_target?(current,parent)endtarget||lineage.lastenddefend_align_target?(node,parent)disqualified_parent?(parent,node)||!block_end_align_target?(parent,node)enddefdisqualified_parent?(parent,node)parent&&parent.loc&&parent.first_line!=node.first_line&&!parent.masgn_type?enddefcheck_block_alignment(start_node,block_node)end_loc=block_node.loc.endreturnunlessbegins_its_line?(end_loc)start_loc=start_node.source_rangereturnunlessstart_loc.column!=end_loc.column||style==:start_of_blockdo_source_line_column=compute_do_source_line_column(block_node,end_loc)returnunlessdo_source_line_columnregister_offense(block_node,start_loc,end_loc,do_source_line_column)enddefregister_offense(block_node,start_loc,end_loc,do_source_line_column)error_source_line_column=ifstyle==:start_of_blockdo_source_line_columnelseloc_to_source_line_column(start_loc)endmessage=format_message(start_loc,end_loc,do_source_line_column,error_source_line_column)add_offense(block_node,location: end_loc,message: message)enddefformat_message(start_loc,end_loc,do_source_line_column,error_source_line_column)format(MSG,current: format_source_line_column(loc_to_source_line_column(end_loc)),prefer: format_source_line_column(error_source_line_column),alt_prefer: alt_start_msg(start_loc,do_source_line_column))enddefcompute_do_source_line_column(node,end_loc)do_loc=node.loc.begin# Actually it's either do or {.# We've found that "end" is not aligned with the start node (which# can be a block, a variable assignment, etc). But we also allow# the "end" to be aligned with the start of the line where the "do"# is, which is a style some people use in multi-line chains of# blocks.match=/\S.*/.match(do_loc.source_line)indentation_of_do_line=match.begin(0)returnunlessend_loc.column!=indentation_of_do_line||style==:start_of_line{source: match[0],line: do_loc.line,column: indentation_of_do_line}enddefloc_to_source_line_column(loc){source: loc.source.lines.to_a.first.chomp,line: loc.line,column: loc.column}enddefalt_start_msg(start_loc,source_line_column)ifstyle!=:either''elsifstart_loc.line==source_line_column[:line]&&start_loc.column==source_line_column[:column]''else' or '+format_source_line_column(source_line_column)endenddefformat_source_line_column(source_line_column)"`#{source_line_column[:source]}` at #{source_line_column[:line]}, "\"#{source_line_column[:column]}"enddefcompute_start_col(ancestor_node,node)ifstyle==:start_of_blockdo_loc=node.loc.beginreturndo_loc.source_line=~/\S/end(ancestor_node||node).source_range.columnenddefadd_space_before(loc,delta)->(corrector){corrector.insert_before(loc,' '*delta)}enddefremove_space_before(end_pos,delta)range=range_between(end_pos-delta,end_pos)->(corrector){corrector.remove(range)}endendendendend