# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleLayout# Checks the indentation of the first argument in a method call.# Arguments after the first one are checked by `Layout/ArgumentAlignment`,# not by this cop.## For indenting the first parameter of method _definitions_, check out# `Layout/FirstParameterIndentation`.## This cop will respect `Layout/ArgumentAlignment` and will not work when# `EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArgumentAlignment`.## @example## # bad# some_method(# first_param,# second_param)## foo = some_method(# first_param,# second_param)## foo = some_method(nested_call(# nested_first_param),# second_param)## foo = some_method(# nested_call(# nested_first_param),# second_param)## some_method nested_call(# nested_first_param),# second_param## @example EnforcedStyle: special_for_inner_method_call_in_parentheses (default)# # Same as `special_for_inner_method_call` except that the special rule# # only applies if the outer method call encloses its arguments in# # parentheses.## # good# some_method(# first_param,# second_param)## foo = some_method(# first_param,# second_param)## foo = some_method(nested_call(# nested_first_param),# second_param)## foo = some_method(# nested_call(# nested_first_param),# second_param)## some_method nested_call(# nested_first_param),# second_param## @example EnforcedStyle: consistent# # The first argument should always be indented one step more than the# # preceding line.## # good# some_method(# first_param,# second_param)## foo = some_method(# first_param,# second_param)## foo = some_method(nested_call(# nested_first_param),# second_param)## foo = some_method(# nested_call(# nested_first_param),# second_param)## some_method nested_call(# nested_first_param),# second_param## @example EnforcedStyle: consistent_relative_to_receiver# # The first argument should always be indented one level relative to# # the parent that is receiving the argument## # good# some_method(# first_param,# second_param)## foo = some_method(# first_param,# second_param)## foo = some_method(nested_call(# nested_first_param),# second_param)## foo = some_method(# nested_call(# nested_first_param),# second_param)## some_method nested_call(# nested_first_param),# second_params## @example EnforcedStyle: special_for_inner_method_call# # The first argument should normally be indented one step more than# # the preceding line, but if it's an argument for a method call that# # is itself an argument in a method call, then the inner argument# # should be indented relative to the inner method.## # good# some_method(# first_param,# second_param)## foo = some_method(# first_param,# second_param)## foo = some_method(nested_call(# nested_first_param),# second_param)## foo = some_method(# nested_call(# nested_first_param),# second_param)## some_method nested_call(# nested_first_param),# second_param#classFirstArgumentIndentation<BaseincludeAlignmentincludeConfigurableEnforcedStyleincludeRangeHelpextendAutoCorrectorMSG='Indent the first argument one step more than %<base>s.'defon_send(node)returnunlessshould_check?(node)returnifsame_line?(node,node.first_argument)returnifstyle!=:consistent&&enforce_first_argument_with_fixed_indentation?&&!enable_layout_first_method_argument_line_break?indent=base_indentation(node)+configured_indentation_widthcheck_alignment([node.first_argument],indent)endaliason_csendon_sendaliason_superon_sendprivatedefshould_check?(node)node.arguments?&&!bare_operator?(node)&&!node.setter_method?enddefautocorrect(corrector,node)AlignmentCorrector.correct(corrector,processed_source,node,column_delta)enddefbare_operator?(node)node.operator_method?&&!node.dot?enddefmessage(arg_node)return'Bad indentation of the first argument.'unlessarg_nodesend_node=arg_node.parenttext=base_range(send_node,arg_node).source.stripbase=if!text.include?("\n")&&special_inner_call_indentation?(send_node)"`#{text}`"elsifcomment_line?(text.lines.reverse_each.first)'the start of the previous line (not counting the comment)'else'the start of the previous line'endformat(MSG,base: base)enddefbase_indentation(node)ifspecial_inner_call_indentation?(node)column_of(base_range(node,node.first_argument))elseprevious_code_line(node.first_argument.first_line)=~/\S/endenddefspecial_inner_call_indentation?(node)returnfalseifstyle==:consistentreturntrueifstyle==:consistent_relative_to_receiverparent=node.parentreturnfalseunlesseligible_method_call?(parent)returnfalseif!parent.parenthesized?&&style==:special_for_inner_method_call_in_parentheses# The node must begin inside the parent, otherwise node is the first# part of a chained method call.node.source_range.begin_pos>parent.source_range.begin_posend# @!method eligible_method_call?(node)def_node_matcher:eligible_method_call?,<<~PATTERN
(send _ !:[]= ...)
PATTERNdefbase_range(send_node,arg_node)parent=send_node.parentstart_node=ifparent&.type?(:splat,:kwsplat)send_node.parentelsesend_nodeendrange_between(start_node.source_range.begin_pos,arg_node.source_range.begin_pos)end# Returns the column of the given range. For single line ranges, this# is simple. For ranges with line breaks, we look a the last code line.defcolumn_of(range)source=range.source.stripifsource.include?("\n")previous_code_line(range.line+source.count("\n")+1)=~/\S/elsedisplay_column(range)endend# Takes the line number of a given code line and returns a string# containing the previous line that's not a comment line or a blank# line.defprevious_code_line(line_number)line=''whileline.blank?||comment_lines.include?(line_number)line_number-=1line=processed_source.lines[line_number-1]endlineenddefcomment_lines@comment_lines||=processed_source.comments.select{|c|begins_its_line?(c.source_range)}.map{|c|c.loc.line}enddefon_new_investigation@comment_lines=nilenddefenforce_first_argument_with_fixed_indentation?argument_alignment_config=config.for_enabled_cop('Layout/ArgumentAlignment')argument_alignment_config['EnforcedStyle']=='with_fixed_indentation'enddefenable_layout_first_method_argument_line_break?config.cop_enabled?('Layout/FirstMethodArgumentLineBreak')endendendendend