Module: RuboCop::Cop::TrailingComma

Includes:
ConfigurableEnforcedStyle
Included in:
Style::TrailingCommaInArguments, Style::TrailingCommaInLiteral
Defined in:
lib/rubocop/cop/mixin/trailing_comma.rb

Overview

Common methods shared by Style/TrailingCommaInArguments and Style/TrailingCommaInLiteral

Constant Summary collapse

MSG =
'%s comma after the last %s'.freeze

Instance Method Summary collapse

Methods included from ConfigurableEnforcedStyle

#alternative_style, #alternative_styles, #ambiguous_style_detected, #correct_style_detected, #detected_style, #detected_style=, #no_acceptable_style!, #no_acceptable_style?, #opposite_style_detected, #style, #style_detected, #supported_styles, #unexpected_style_detected

Instance Method Details

#autocorrect(range) ⇒ Object



149
150
151
152
153
154
155
156
157
158
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 149

def autocorrect(range)
  return unless range

  lambda do |corrector|
    case range.source
    when ',' then corrector.remove(range)
    else          corrector.insert_after(range, ',')
    end
  end
end

#autocorrect_range(item) ⇒ Object



136
137
138
139
140
141
142
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 136

def autocorrect_range(item)
  expr = item.source_range
  ix = expr.source.rindex("\n") || 0
  ix += expr.source[ix..-1] =~ /\S/

  range_between(expr.begin_pos + ix, expr.end_pos)
end

#avoid_autocorrect?(_) ⇒ Boolean

By default, there’s no reason to avoid auto-correct.

Returns:

  • (Boolean)


145
146
147
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 145

def avoid_autocorrect?(_)
  false
end

#avoid_comma(kind, comma_begin_pos, extra_info) ⇒ Object



116
117
118
119
120
121
122
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 116

def avoid_comma(kind, comma_begin_pos, extra_info)
  range = range_between(comma_begin_pos, comma_begin_pos + 1)
  article = kind =~ /array/ ? 'an' : 'a'
  add_offense(range, range,
              format(MSG, 'Avoid', format(kind, article)) +
              "#{extra_info}.")
end

#brackets?(node) ⇒ Boolean

Returns true if the node has round/square/curly brackets.

Returns:

  • (Boolean)


70
71
72
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 70

def brackets?(node)
  node.loc.end
end

#check(node, items, kind, begin_pos, end_pos) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 16

def check(node, items, kind, begin_pos, end_pos)
  after_last_item = range_between(begin_pos, end_pos)

  return if heredoc?(after_last_item.source)

  comma_offset = after_last_item.source =~ /,/

  if comma_offset && !inside_comment?(after_last_item, comma_offset)
    check_comma(node, kind, after_last_item.begin_pos + comma_offset)
  elsif should_have_comma?(style, node)
    put_comma(node, items, kind)
  end
end

#check_comma(node, kind, comma_pos) ⇒ Object



30
31
32
33
34
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 30

def check_comma(node, kind, comma_pos)
  return if should_have_comma?(style, node)

  avoid_comma(kind, comma_pos, extra_avoid_comma_info)
end

#elements(node) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 90

def elements(node)
  return node.children unless node.send_type?

  _receiver, _method_name, *args = *node
  args.flat_map do |a|
    # For each argument, if it is a multi-line hash without braces,
    # then promote the hash elements to method arguments
    # for the purpose of determining multi-line-ness.
    if a.hash_type? && a.multiline? && !a.braces?
      a.children
    else
      a
    end
  end
end

#extra_avoid_comma_infoObject



36
37
38
39
40
41
42
43
44
45
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 36

def extra_avoid_comma_info
  case style
  when :comma
    ', unless each item is on its own line'
  when :consistent_comma
    ', unless items are split onto multiple lines'
  else
    ''
  end
end

#heredoc?(source_after_last_item) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 65

def heredoc?(source_after_last_item)
  source_after_last_item =~ /\w/
end

#inside_comment?(range, comma_offset) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
61
62
63
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 58

def inside_comment?(range, comma_offset)
  processed_source.comments.any? do |comment|
    comment_offset = comment.loc.expression.begin_pos - range.begin_pos
    comment_offset >= 0 && comment_offset < comma_offset
  end
end

#multiline?(node) ⇒ Boolean

Returns true if the round/square/curly brackets of the given node are on different lines, and each item within is on its own line, and the closing bracket is on its own line.

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 77

def multiline?(node)
  # No need to process anything if the whole node is not multiline
  # Without the 2nd check, Foo.new({}) is considered multiline, which
  # it should not be. Essentially, if there are no elements, the
  # expression can not be multiline.
  return false unless node.multiline?

  items = elements(node).map(&:source_range)
  return false if items.empty?
  items << node.loc.begin << node.loc.end
  (items.map(&:first_line) + items.map(&:last_line)).uniq.size > 1
end

#no_elements_on_same_line?(node) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
109
110
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 106

def no_elements_on_same_line?(node)
  items = elements(node).map(&:source_range)
  items << node.loc.end
  items.each_cons(2).none? { |a, b| on_same_line?(a, b) }
end

#on_same_line?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 112

def on_same_line?(a, b)
  a.last_line == b.line
end

#put_comma(node, items, kind) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 124

def put_comma(node, items, kind)
  return if avoid_autocorrect?(elements(node))

  last_item = items.last
  return if last_item.block_pass_type?

  range = autocorrect_range(last_item)

  add_offense(range, range,
              format(MSG, 'Put a', format(kind, 'a multiline') + '.'))
end

#should_have_comma?(style, node) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
50
51
52
53
54
55
56
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 47

def should_have_comma?(style, node)
  case style
  when :comma
    multiline?(node) && no_elements_on_same_line?(node)
  when :consistent_comma
    multiline?(node)
  else
    false
  end
end

#style_parameter_nameObject



12
13
14
# File 'lib/rubocop/cop/mixin/trailing_comma.rb', line 12

def style_parameter_name
  'EnforcedStyleForMultiline'
end