# encoding: utf-8moduleRuboCopmoduleCopmoduleLint# This cop checks whether the end keywords are aligned properly for do# end blocks.## @example## variable = lambda do |i|# i# endclassBlockAlignment<CopincludeCheckAssignmentMSG='`end` at %d, %d is not aligned with `%s` at %d, %d%s'defon_block(node)returnifignored_node?(node)check_block_alignment(node,node)enddefon_and(node)returnifignored_node?(node)_left,right=*nodereturnunlessright.type==:blockcheck_block_alignment(node,right)ignore_node(right)endalias_method:on_or,:on_anddefon_op_asgn(node)variable,_op,args=*nodecheck_assignment(variable,args)enddefon_send(node)_receiver,_method,*args=*nodecheck_assignment(node,args.last)enddefon_masgn(node)variables,args=*nodecheck_assignment(variables,args)endprivatedefcheck_assignment(begin_node,other_node)returnunlessother_nodeblock_node=find_block_node(other_node)returnunlessblock_node.type==:block# If the block is an argument in a function call, align end with# the block itself, and not with the function.ifbegin_node.type==:send_receiver,method,*_args=*begin_nodebegin_node=block_nodeifmethod.to_s=~/^\w+$/end# Align with the expression that is on the same line# where the block is definedifbegin_node.type!=:mlhs&&block_is_on_next_line?(begin_node,block_node)returnendreturnifignored_node?(block_node)ignore_node(block_node)check_block_alignment(begin_node,block_node)enddeffind_block_node(node)while[:send,:lvasgn].include?(node.type)n=casenode.typewhen:sendfind_block_or_send_node(node)||breakwhen:lvasgn_variable,value=*nodevalueendnode=nifnendnodeenddeffind_block_or_send_node(send_node)receiver,_method,args=*send_node[receiver,args].finddo|subnode|subnode&&[:block,:send].include?(subnode.type)endenddefcheck_block_alignment(start_node,block_node)end_loc=block_node.loc.endreturnunlessbegins_its_line?(end_loc)start_loc=start_node.loc.expressionreturnunlessstart_loc.column!=end_loc.columndo_loc=block_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_lineadd_offense(block_node,end_loc,format(MSG,end_loc.line,end_loc.column,start_loc.source.lines.to_a.first.chomp,start_loc.line,start_loc.column,alt_start_msg(match,start_loc,do_loc,indentation_of_do_line)))enddefalt_start_msg(match,start_loc,do_loc,indentation_of_do_line)ifstart_loc.line==do_loc.line&&start_loc.column==indentation_of_do_line''else" or `#{match[0]}` at #{do_loc.line}, #{indentation_of_do_line}"endenddefmessageenddefblock_is_on_next_line?(begin_node,block_node)begin_node.loc.line!=block_node.loc.lineenddefautocorrect(node)ancestor_node=ancestor_on_same_line(node)source=node.loc.expression.source_bufferlambdado|corrector|start_col=(ancestor_node||node).loc.expression.columnstarting_position_of_block_end=node.loc.end.begin_posend_col=node.loc.end.columnifend_col<start_coldelta=start_col-end_colcorrector.insert_before(node.loc.end,' '*delta)elsifend_col>start_colrange_start=starting_position_of_block_end+start_col-end_colrange=Parser::Source::Range.new(source,range_start,starting_position_of_block_end)corrector.remove(range)endendenddefancestor_on_same_line(node)node.ancestors.reverse.finddo|ancestor|nextunlessancestor.loc.respond_to?(:line)ancestor.loc.line==node.loc.lineendendendendendend