class RuboCop::Cop::Layout::ExtraSpacing
some_object.method(arg) # this is some comment
another_object.method(arg) # this is another comment
object.method(arg) # this is a comment
# good with either AllowBeforeTrailingComments or AllowForAlignment
object.method(arg) # this is a comment
# good even if AllowBeforeTrailingComments is false or not set
object.method(arg) # this is a comment
# good only if AllowBeforeTrailingComments is true
website = “github.com/rubocop/rubocop”
set_app(“RuboCop”)
# bad for any configuration
puts “rubocop” if debug
website += “/rubocop/rubocop” unless cond
# Some comment and an empty line
name = “RuboCop”
# good if AllowForAlignment is true
@example
Checks for extra/unnecessary whitespace.
def align_column(asgn_token)
def align_column(asgn_token) # if we removed unneeded spaces from the beginning of this =, # what column would it end from? line = processed_source.lines[asgn_token.line - 1] leading = line[0...asgn_token.column] spaces = leading.size - (leading =~ / *\Z/) asgn_token.pos.last_column - spaces + 1 end
def align_equal_sign(corrector, token, align_to)
def align_equal_sign(corrector, token, align_to) return unless @corrected.add?(token) diff = align_to - token.pos.last_column if diff.positive? corrector.insert_before(token.pos, ' ' * diff) elsif diff.negative? corrector.remove_preceding(token.pos, -diff) end end
def align_equal_signs(range, corrector)
def align_equal_signs(range, corrector) lines = all_relevant_assignment_lines(range.line) tokens = assignment_tokens.select { |t| lines.include?(t.line) } columns = tokens.map { |t| align_column(t) } align_to = columns.max tokens.each { |token| align_equal_sign(corrector, token, align_to) } end
def aligned_locations(locs) # rubocop:disable Metrics/AbcSize
def aligned_locations(locs) # rubocop:disable Metrics/AbcSize return [] if locs.empty? aligned = Set[locs.first.line, locs.last.line] locs.each_cons(3) do |before, loc, after| col = loc.column aligned << loc.line if col == before.column || col == after.column end # if locs.size > 2 and the size of variable `aligned` # has not increased from its initial value, there are not aligned lines. return [] if locs.size > 2 && aligned.size == 2 aligned end
def aligned_tok?(token)
def aligned_tok?(token) if token.comment? @aligned_comments.include?(token.line) else aligned_with_something?(token.pos) end end
def all_relevant_assignment_lines(line_number)
def all_relevant_assignment_lines(line_number) last_line_number = processed_source.lines.size ( relevant_assignment_lines(line_number.downto(1)) + relevant_assignment_lines(line_number.upto(last_line_number)) ) .uniq .sort end
def allow_for_trailing_comments?
def allow_for_trailing_comments? cop_config['AllowBeforeTrailingComments'] end
def check_assignment(token)
def check_assignment(token) return unless aligned_with_preceding_assignment(token) == :no message = format(MSG_UNALIGNED_ASGN, location: 'preceding') add_offense(token.pos, message: message) do |corrector| align_equal_signs(token.pos, corrector) end end
def check_other(token1, token2, ast)
def check_other(token1, token2, ast) return false if allow_for_trailing_comments? && token2.text.start_with?('#') extra_space_range(token1, token2) do |range| next if ignored_range?(ast, range.begin_pos) add_offense(range, message: MSG_UNNECESSARY) { |corrector| corrector.remove(range) } end end
def check_tokens(ast, token1, token2)
def check_tokens(ast, token1, token2) return if token2.type == :tNL if force_equal_sign_alignment? && assignment_tokens.include?(token2) check_assignment(token2) else check_other(token1, token2, ast) end end
def extra_space_range(token1, token2)
def extra_space_range(token1, token2) return if token1.line != token2.line start_pos = token1.end_pos end_pos = token2.begin_pos - 1 return if end_pos <= start_pos return if allow_for_alignment? && aligned_tok?(token2) yield range_between(start_pos, end_pos) end
def force_equal_sign_alignment?
def force_equal_sign_alignment? cop_config['ForceEqualSignAlignment'] end
def ignored_range?(ast, start_pos)
def ignored_range?(ast, start_pos) ignored_ranges(ast).any? { |r| r.include?(start_pos) } end
def ignored_ranges(ast)
extra spaces between the keys and values in a multiline hash,
Returns an array of ranges that should not be reported. It's the
def ignored_ranges(ast) return [] unless ast @ignored_ranges ||= begin ranges = [] on_node(:pair, ast) do |pair| next if pair.parent.single_line? key, value = *pair ranges << (key.source_range.end_pos...value.source_range.begin_pos) end ranges end end
def on_new_investigation
def on_new_investigation return if processed_source.blank? @aligned_comments = aligned_locations(processed_source.comments.map(&:loc)) @corrected = Set.new if force_equal_sign_alignment? processed_source.tokens.each_cons(2) do |token1, token2| check_tokens(processed_source.ast, token1, token2) end end