class RuboCop::Cop::Style::MultilineOperationIndentation
end
something
b
if a +
# bad
@example
binary operations that span more than one line.
This cops checks the indentation of the right hand side operand in
def assignment_rhs?(node)
def assignment_rhs?(node) node.ancestors.unshift(node).each_cons(2).any? do |child, parent| return true if parent.asgn_rhs.equal?(child) grandparent = parent.parent return true if grandparent && grandparent.masgn_type? && grandparent.asgn_rhs.equal?(parent) end end
def check(range, node, lhs, rhs)
def check(range, node, lhs, rhs) if range incorrect_style_detected(range, node, lhs, rhs) else correct_style_detected end end
def check_and_or(node)
def check_and_or(node) lhs, rhs = *node range = offending_range(node, lhs, rhs.loc.expression, style) check(range, node, lhs, rhs.loc.expression) end
def correct_indentation(node)
def correct_indentation(node) multiplier = kw_node_with_special_indentation(node) ? 2 : 1 configured_indentation_width * multiplier end
def grouped_expression?(node)
def grouped_expression?(node) node.type == :begin && node.loc.respond_to?(:begin) && node.loc.begin end
def incorrect_style_detected(range, node, lhs, rhs)
def incorrect_style_detected(range, node, lhs, rhs) add_offense(range, range, message(node, lhs, rhs)) do if offending_range(node, lhs, rhs, alternative_style) unrecognized_style_detected else opposite_style_detected end end end
def indentation(node)
def indentation(node) node.loc.expression.source_line =~ /\S/ end
def inside_arg_list_parentheses?(node, ancestor)
def inside_arg_list_parentheses?(node, ancestor) a = ancestor.loc return false unless ancestor.type == :send && a.begin && a.begin.is?('(') n = node.loc.expression n.begin_pos > a.begin.begin_pos && n.end_pos < a.end.end_pos end
def kw_node_with_special_indentation(node)
def kw_node_with_special_indentation(node) node.each_ancestor.find do |a| next unless a.loc.respond_to?(:keyword) case a.type when :if, :while, :until then condition, = *a when :for then _, collection, = *a end if condition || collection within_node?(node, condition || collection) end end end
def left_hand_side(receiver)
b c { block }. <-- b is indented relative to a
a.
for indentation of all lines following the first. For example:
In a chain of method calls, we regard the top send node as the base
def left_hand_side(receiver) lhs = receiver while lhs.parent && lhs.parent.type == :send _receiver, method_name, *_args = *lhs.parent break if operator?(method_name) lhs = lhs.parent end lhs end
def message(node, lhs, rhs)
def message(node, lhs, rhs) what = operation_description(node) if should_align?(node, style) "Align the operands of #{what} spanning multiple lines." else used_indentation = rhs.column - indentation(lhs) "Use #{correct_indentation(node)} (not #{used_indentation}) " \ "spaces for indenting #{what} spanning multiple lines." end end
def not_for_this_cop?(node)
def not_for_this_cop?(node) node.each_ancestor.any? do |ancestor| grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor) end end
def offending_range(node, lhs, rhs, given_style)
def offending_range(node, lhs, rhs, given_style) return false unless begins_its_line?(rhs) return false if lhs.loc.line == rhs.line # Needed for unary op. return false if not_for_this_cop?(node) correct_column = if should_align?(node, given_style) lhs.loc.column else indentation(lhs) + correct_indentation(node) end @column_delta = correct_column - rhs.column rhs if @column_delta != 0 end
def on_and(node)
def on_and(node) check_and_or(node) end
def on_or(node)
def on_or(node) check_and_or(node) end
def on_send(node)
def on_send(node) receiver, method_name, *_args = *node return unless receiver return if method_name == :[] # Don't check parameters inside []. lhs = left_hand_side(receiver) rhs = right_hand_side(node) range = offending_range(node, lhs, rhs, style) check(range, node, lhs, rhs) end
def operation_description(node)
def operation_description(node) ancestor = kw_node_with_special_indentation(node) if ancestor kw = ancestor.loc.keyword.source kind = kw == 'for' ? 'collection' : 'condition' article = kw =~ /^[iu]/ ? 'an' : 'a' "a #{kind} in #{article} `#{kw}` statement" else 'an expression' + (assignment_rhs?(node) ? ' in an assignment' : '') end end
def right_hand_side(send_node)
def right_hand_side(send_node) _, method_name, *args = *send_node if operator?(method_name) && args.any? args.first.loc.expression else dot = send_node.loc.dot selector = send_node.loc.selector if dot && selector && dot.line == selector.line dot.join(selector) elsif selector selector elsif dot.line == send_node.loc.begin.line # lambda.(args) dot.join(send_node.loc.begin) end end end
def should_align?(node, given_style)
def should_align?(node, given_style) given_style == :aligned && (kw_node_with_special_indentation(node) || assignment_rhs?(node)) end