lib/rubocop/cop/layout/first_array_element_indentation.rb
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks the indentation of the first element in an array literal
# where the opening bracket and the first element are on separate lines.
# The other elements' indentations are handled by `Layout/ArrayAlignment` cop.
#
# This cop will respect `Layout/ArrayAlignment` and will not work when
# `EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArrayAlignment`.
#
# By default, array literals that are arguments in a method call with
# parentheses, and where the opening square bracket of the array is on the
# same line as the opening parenthesis of the method call, shall have
# their first element indented one step (two spaces) more than the
# position inside the opening parenthesis.
#
# Other array literals shall have their first element indented one step
# more than the start of the line where the opening square bracket is.
#
# This default style is called 'special_inside_parentheses'. Alternative
# styles are 'consistent' and 'align_brackets'. Here are examples:
#
# @example EnforcedStyle: special_inside_parentheses (default)
# # The `special_inside_parentheses` style enforces that the first
# # element in an array literal where the opening bracket and first
# # element are on separate lines is indented one step (two spaces) more
# # than the position inside the opening parenthesis.
#
# # bad
# array = [
# :value
# ]
# and_in_a_method_call([
# :no_difference
# ])
#
# # good
# array = [
# :value
# ]
# but_in_a_method_call([
# :its_like_this
# ])
#
# @example EnforcedStyle: consistent
# # The `consistent` style enforces that the first element in an array
# # literal where the opening bracket and the first element are on
# # separate lines is indented the same as an array literal which is not
# # defined inside a method call.
#
# # bad
# # consistent
# array = [
# :value
# ]
# but_in_a_method_call([
# :its_like_this
# ])
#
# # good
# array = [
# :value
# ]
# and_in_a_method_call([
# :no_difference
# ])
#
# @example EnforcedStyle: align_brackets
# # The `align_brackets` style enforces that the opening and closing
# # brackets are indented to the same position.
#
# # bad
# # align_brackets
# and_now_for_something = [
# :completely_different
# ]
#
# # good
# # align_brackets
# and_now_for_something = [
# :completely_different
# ]
class FirstArrayElementIndentation < Base
include Alignment
include ConfigurableEnforcedStyle
include MultilineElementIndentation
extend AutoCorrector
MSG = 'Use %<configured_indentation_width>d spaces for indentation ' \
'in an array, relative to %<base_description>s.'
def on_array(node)
check(node, nil) if node.loc.begin
end
def on_send(node)
return if style != :consistent && enforce_first_argument_with_fixed_indentation?
each_argument_node(node, :array) do |array_node, left_parenthesis|
check(array_node, left_parenthesis)
end
end
alias on_csend on_send
private
def autocorrect(corrector, node)
AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
end
def brace_alignment_style
:align_brackets
end
def check(array_node, left_parenthesis)
return if ignored_node?(array_node)
left_bracket = array_node.loc.begin
first_elem = array_node.values.first
if first_elem
return if same_line?(first_elem, left_bracket)
check_first(first_elem, left_bracket, left_parenthesis, 0)
end
check_right_bracket(array_node.loc.end, first_elem, left_bracket, left_parenthesis)
end
def check_right_bracket(right_bracket, first_elem, left_bracket, left_parenthesis)
# if the right bracket is on the same line as the last value, accept
return if /\S/.match?(right_bracket.source_line[0...right_bracket.column])
expected_column, indent_base_type = indent_base(left_bracket, first_elem,
left_parenthesis)
@column_delta = expected_column - right_bracket.column
return if @column_delta.zero?
msg = message_for_right_bracket(indent_base_type)
add_offense(right_bracket, message: msg) do |corrector|
autocorrect(corrector, right_bracket)
end
end
# Returns the description of what the correct indentation is based on.
def base_description(indent_base_type)
case indent_base_type
when :left_brace_or_bracket
'the position of the opening bracket'
when :first_column_after_left_parenthesis
'the first position after the preceding left parenthesis'
when :parent_hash_key
'the parent hash key'
else
'the start of the line where the left square bracket is'
end
end
def message(base_description)
format(
MSG,
configured_indentation_width: configured_indentation_width,
base_description: base_description
)
end
def message_for_right_bracket(indent_base_type)
case indent_base_type
when :left_brace_or_bracket
'Indent the right bracket the same as the left bracket.'
when :first_column_after_left_parenthesis
'Indent the right bracket the same as the first position ' \
'after the preceding left parenthesis.'
when :parent_hash_key
'Indent the right bracket the same as the parent hash key.' \
else
'Indent the right bracket the same as the start of the line ' \
'where the left bracket is.'
end
end
def enforce_first_argument_with_fixed_indentation?
return false unless array_alignment_config['Enabled']
array_alignment_config['EnforcedStyle'] == 'with_fixed_indentation'
end
def array_alignment_config
config.for_cop('Layout/ArrayAlignment')
end
end
end
end
end