rubocop 1.13.0 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/config/default.yml +68 -8
  4. data/lib/rubocop.rb +9 -0
  5. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -3
  6. data/lib/rubocop/cop/bundler/gem_version.rb +99 -0
  7. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  8. data/lib/rubocop/cop/layout/argument_alignment.rb +29 -11
  9. data/lib/rubocop/cop/layout/case_indentation.rb +57 -9
  10. data/lib/rubocop/cop/layout/dot_position.rb +7 -1
  11. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +13 -15
  12. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +12 -0
  13. data/lib/rubocop/cop/layout/hash_alignment.rb +34 -9
  14. data/lib/rubocop/cop/layout/indentation_width.rb +13 -2
  15. data/lib/rubocop/cop/layout/redundant_line_break.rb +24 -10
  16. data/lib/rubocop/cop/layout/single_line_block_chain.rb +53 -0
  17. data/lib/rubocop/cop/layout/space_around_keyword.rb +28 -0
  18. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -0
  19. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +83 -39
  20. data/lib/rubocop/cop/lint/empty_block.rb +18 -2
  21. data/lib/rubocop/cop/lint/empty_in_pattern.rb +62 -0
  22. data/lib/rubocop/cop/lint/literal_as_condition.rb +13 -1
  23. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +32 -17
  24. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  25. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +105 -74
  26. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +5 -0
  27. data/lib/rubocop/cop/lint/symbol_conversion.rb +2 -12
  28. data/lib/rubocop/cop/lint/unreachable_loop.rb +12 -2
  29. data/lib/rubocop/cop/lint/unused_block_argument.rb +7 -1
  30. data/lib/rubocop/cop/lint/void.rb +1 -1
  31. data/lib/rubocop/cop/migration/department_name.rb +3 -1
  32. data/lib/rubocop/cop/mixin/check_line_breakable.rb +19 -3
  33. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +6 -0
  34. data/lib/rubocop/cop/mixin/gem_declaration.rb +13 -0
  35. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +14 -3
  36. data/lib/rubocop/cop/mixin/string_literals_help.rb +3 -5
  37. data/lib/rubocop/cop/mixin/symbol_help.rb +13 -0
  38. data/lib/rubocop/cop/style/class_and_module_children.rb +17 -5
  39. data/lib/rubocop/cop/style/empty_literal.rb +8 -1
  40. data/lib/rubocop/cop/style/hash_each_methods.rb +18 -1
  41. data/lib/rubocop/cop/style/identical_conditional_branches.rb +58 -8
  42. data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -4
  43. data/lib/rubocop/cop/style/in_pattern_then.rb +56 -0
  44. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -1
  45. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +62 -0
  46. data/lib/rubocop/cop/style/multiline_when_then.rb +2 -11
  47. data/lib/rubocop/cop/style/negated_if_else_condition.rb +17 -9
  48. data/lib/rubocop/cop/style/nil_lambda.rb +29 -12
  49. data/lib/rubocop/cop/style/quoted_symbols.rb +110 -0
  50. data/lib/rubocop/cop/style/raise_args.rb +2 -0
  51. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  52. data/lib/rubocop/cop/style/redundant_self.rb +24 -2
  53. data/lib/rubocop/cop/style/regexp_literal.rb +9 -1
  54. data/lib/rubocop/cop/style/single_line_methods.rb +8 -3
  55. data/lib/rubocop/cop/style/sole_nested_conditional.rb +14 -5
  56. data/lib/rubocop/cop/style/string_literals.rb +1 -0
  57. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +1 -0
  58. data/lib/rubocop/cop/style/top_level_method_definition.rb +83 -0
  59. data/lib/rubocop/cop/style/trivial_accessors.rb +65 -0
  60. data/lib/rubocop/cop/style/when_then.rb +6 -2
  61. data/lib/rubocop/cop/variable_force/branch.rb +15 -0
  62. data/lib/rubocop/directive_comment.rb +58 -6
  63. data/lib/rubocop/formatter/junit_formatter.rb +21 -6
  64. data/lib/rubocop/options.rb +14 -20
  65. data/lib/rubocop/rake_task.rb +1 -1
  66. data/lib/rubocop/remote_config.rb +10 -2
  67. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  68. data/lib/rubocop/target_finder.rb +9 -2
  69. data/lib/rubocop/target_ruby.rb +1 -1
  70. data/lib/rubocop/version.rb +1 -1
  71. metadata +18 -9
@@ -97,7 +97,7 @@ module RuboCop
97
97
 
98
98
  def handle_as_symbol(node)
99
99
  to_method_symbol(node) do |receiver, sym_node, to_method|
100
- next if receiver.nil?
100
+ next if receiver.nil? || !node.arguments.one?
101
101
 
102
102
  message = format(
103
103
  MSG,
@@ -25,11 +25,12 @@ module RuboCop
25
25
  #
26
26
  # # good
27
27
  # x += 1
28
- class RedundantCopDisableDirective < Base
28
+ class RedundantCopDisableDirective < Base # rubocop:todo Metrics/ClassLength
29
29
  include RangeHelp
30
30
  extend AutoCorrector
31
31
 
32
32
  COP_NAME = 'Lint/RedundantCopDisableDirective'
33
+ DEPARTMENT_MARKER = 'DEPARTMENT'
33
34
 
34
35
  attr_accessor :offenses_to_check
35
36
 
@@ -41,12 +42,9 @@ module RuboCop
41
42
  def on_new_investigation
42
43
  return unless offenses_to_check
43
44
 
44
- cop_disabled_line_ranges = processed_source.disabled_line_ranges
45
-
46
45
  redundant_cops = Hash.new { |h, k| h[k] = Set.new }
47
46
 
48
- each_redundant_disable(cop_disabled_line_ranges,
49
- offenses_to_check) do |comment, redundant_cop|
47
+ each_redundant_disable do |comment, redundant_cop|
50
48
  redundant_cops[comment].add(redundant_cop)
51
49
  end
52
50
 
@@ -56,20 +54,31 @@ module RuboCop
56
54
 
57
55
  private
58
56
 
57
+ def cop_disabled_line_ranges
58
+ processed_source.disabled_line_ranges
59
+ end
60
+
61
+ def disabled_ranges
62
+ cop_disabled_line_ranges[COP_NAME] || [0..0]
63
+ end
64
+
59
65
  def previous_line_blank?(range)
60
66
  processed_source.buffer.source_line(range.line - 1).blank?
61
67
  end
62
68
 
63
- def comment_range_with_surrounding_space(range)
64
- if previous_line_blank?(range) &&
65
- processed_source.comment_config.comment_only_line?(range.line)
69
+ def comment_range_with_surrounding_space(directive_comment_range, line_comment_range)
70
+ if previous_line_blank?(directive_comment_range) &&
71
+ processed_source.comment_config.comment_only_line?(directive_comment_range.line) &&
72
+ directive_comment_range.begin_pos == line_comment_range.begin_pos
66
73
  # When the previous line is blank, it should be retained
67
- range_with_surrounding_space(range: range, side: :right)
74
+ range_with_surrounding_space(range: directive_comment_range, side: :right)
68
75
  else
69
76
  # Eat the entire comment, the preceding space, and the preceding
70
77
  # newline if there is one.
71
- original_begin = range.begin_pos
72
- range = range_with_surrounding_space(range: range, side: :left, newlines: true)
78
+ original_begin = directive_comment_range.begin_pos
79
+ range = range_with_surrounding_space(
80
+ range: directive_comment_range, side: :left, newlines: true
81
+ )
73
82
 
74
83
  range_with_surrounding_space(range: range,
75
84
  side: :right,
@@ -94,32 +103,34 @@ module RuboCop
94
103
  range_with_surrounding_space(range: range, side: :right, newlines: false)
95
104
  end
96
105
 
97
- def each_redundant_disable(cop_disabled_line_ranges, offenses,
98
- &block)
99
- disabled_ranges = cop_disabled_line_ranges[COP_NAME] || [0..0]
100
-
106
+ def each_redundant_disable(&block)
101
107
  cop_disabled_line_ranges.each do |cop, line_ranges|
102
- each_already_disabled(line_ranges, disabled_ranges) { |comment| yield comment, cop }
103
-
104
- each_line_range(line_ranges, disabled_ranges, offenses, cop, &block)
108
+ each_already_disabled(cop, line_ranges, &block)
109
+ each_line_range(cop, line_ranges, &block)
105
110
  end
106
111
  end
107
112
 
108
- def each_line_range(line_ranges, disabled_ranges, offenses,
109
- cop)
110
- line_ranges.each_with_index do |line_range, ix|
111
- comment = processed_source.comment_at_line(line_range.begin)
112
- next if ignore_offense?(disabled_ranges, line_range)
113
+ def each_line_range(cop, line_ranges)
114
+ line_ranges.each_with_index do |line_range, line_range_index|
115
+ next if ignore_offense?(line_range)
113
116
 
114
- redundant_cop = find_redundant(comment, offenses, cop, line_range, line_ranges[ix + 1])
115
- yield comment, redundant_cop if redundant_cop
117
+ comment = processed_source.comment_at_line(line_range.begin)
118
+ redundant = if all_disabled?(comment)
119
+ find_redundant_all(line_range, line_ranges[line_range_index + 1])
120
+ elsif department_disabled?(cop, comment)
121
+ find_redundant_department(cop, line_range)
122
+ else
123
+ find_redundant_cop(cop, line_range)
124
+ end
125
+
126
+ yield comment, redundant if redundant
116
127
  end
117
128
  end
118
129
 
119
- def each_already_disabled(line_ranges, disabled_ranges)
130
+ def each_already_disabled(cop, line_ranges)
120
131
  line_ranges.each_cons(2) do |previous_range, range|
121
- next if ignore_offense?(disabled_ranges, range)
122
- next if previous_range.end != range.begin
132
+ next if ignore_offense?(range)
133
+ next unless followed_ranges?(previous_range, range)
123
134
 
124
135
  # If a cop is disabled in a range that begins on the same line as
125
136
  # the end of the previous range, it means that the cop was
@@ -130,42 +141,56 @@ module RuboCop
130
141
  # Comments disabling all cops don't count since it's reasonable
131
142
  # to disable a few select cops first and then all cops further
132
143
  # down in the code.
133
- yield comment if comment && !all_disabled?(comment)
144
+ yield comment, cop if comment && !all_disabled?(comment)
134
145
  end
135
146
  end
136
147
 
137
- # rubocop:todo Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
138
- def find_redundant(comment, offenses, cop, line_range, next_line_range)
139
- if all_disabled?(comment)
140
- # If there's a disable all comment followed by a comment
141
- # specifically disabling `cop`, we don't report the `all`
142
- # comment. If the disable all comment is truly redundant, we will
143
- # detect that when examining the comments of another cop, and we
144
- # get the full line range for the disable all.
145
- if (next_line_range.nil? || line_range.last != next_line_range.first) &&
146
- offenses.none? { |o| line_range.cover?(o.line) }
147
- 'all'
148
- end
149
- else
150
- cop_offenses = offenses.select { |o| o.cop_name == cop }
151
- cop if cop_offenses.none? { |o| line_range.cover?(o.line) }
152
- end
148
+ def find_redundant_cop(cop, range)
149
+ cop_offenses = offenses_to_check.select { |offense| offense.cop_name == cop }
150
+ cop if range_with_offense?(range, cop_offenses)
151
+ end
152
+
153
+ def find_redundant_all(range, next_range)
154
+ # If there's a disable all comment followed by a comment
155
+ # specifically disabling `cop`, we don't report the `all`
156
+ # comment. If the disable all comment is truly redundant, we will
157
+ # detect that when examining the comments of another cop, and we
158
+ # get the full line range for the disable all.
159
+ has_no_next_range = next_range.nil? || !followed_ranges?(range, next_range)
160
+ 'all' if has_no_next_range && range_with_offense?(range)
161
+ end
162
+
163
+ def find_redundant_department(cop, range)
164
+ department = cop.split('/').first
165
+ offenses = offenses_to_check.select { |offense| offense.cop_name.start_with?(department) }
166
+ add_department_marker(department) if range_with_offense?(range, offenses)
167
+ end
168
+
169
+ def followed_ranges?(range, next_range)
170
+ range.end == next_range.begin
171
+ end
172
+
173
+ def range_with_offense?(range, offenses = offenses_to_check)
174
+ offenses.none? { |offense| range.cover?(offense.line) }
153
175
  end
154
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
155
176
 
156
177
  def all_disabled?(comment)
157
- /rubocop\s*:\s*(?:disable|todo)\s+all\b/.match?(comment.text)
178
+ DirectiveComment.new(comment).disabled_all?
158
179
  end
159
180
 
160
- def ignore_offense?(disabled_ranges, line_range)
181
+ def ignore_offense?(line_range)
161
182
  disabled_ranges.any? do |range|
162
183
  range.cover?(line_range.min) && range.cover?(line_range.max)
163
184
  end
164
185
  end
165
186
 
187
+ def department_disabled?(cop, comment)
188
+ directive = DirectiveComment.new(comment)
189
+ directive.in_directive_department?(cop) && !directive.overridden_by_department?(cop)
190
+ end
191
+
166
192
  def directive_count(comment)
167
- _, cops_string = DirectiveComment.new(comment).match_captures
168
- cops_string.split(/,\s*/).size
193
+ DirectiveComment.new(comment).directive_count
169
194
  end
170
195
 
171
196
  def add_offenses(redundant_cops)
@@ -179,14 +204,11 @@ module RuboCop
179
204
  end
180
205
 
181
206
  def add_offense_for_entire_comment(comment, cops)
182
- location = comment.loc.expression
183
- cop_list = cops.sort.map { |c| describe(c) }
184
-
185
- add_offense(
186
- location,
187
- message: "Unnecessary disabling of #{cop_list.join(', ')}."
188
- ) do |corrector|
189
- range = comment_range_with_surrounding_space(location)
207
+ location = DirectiveComment.new(comment).range
208
+ cop_names = cops.sort.map { |c| describe(c) }.join(', ')
209
+
210
+ add_offense(location, message: message(cop_names)) do |corrector|
211
+ range = comment_range_with_surrounding_space(location, comment.loc.expression)
190
212
  corrector.remove(range)
191
213
  end
192
214
  end
@@ -197,10 +219,8 @@ module RuboCop
197
219
  ranges = cop_ranges.map { |_, r| r }
198
220
 
199
221
  cop_ranges.each do |cop, range|
200
- add_offense(
201
- range,
202
- message: "Unnecessary disabling of #{describe(cop)}."
203
- ) do |corrector|
222
+ cop_name = describe(cop)
223
+ add_offense(range, message: message(cop_name)) do |corrector|
204
224
  range = directive_range_in_list(range, ranges)
205
225
  corrector.remove(range)
206
226
  end
@@ -208,6 +228,7 @@ module RuboCop
208
228
  end
209
229
 
210
230
  def cop_range(comment, cop)
231
+ cop = remove_department_marker(cop)
211
232
  matching_range(comment.loc.expression, cop) ||
212
233
  matching_range(comment.loc.expression, Badge.parse(cop).cop_name) ||
213
234
  raise("Couldn't find #{cop} in comment: #{comment.text}")
@@ -230,18 +251,16 @@ module RuboCop
230
251
  end
231
252
 
232
253
  def describe(cop)
233
- if cop == 'all'
234
- 'all cops'
235
- elsif all_cop_names.include?(cop)
236
- "`#{cop}`"
237
- else
238
- similar = NameSimilarity.find_similar_name(cop, all_cop_names)
239
- if similar
240
- "`#{cop}` (did you mean `#{similar}`?)"
241
- else
242
- "`#{cop}` (unknown cop)"
243
- end
244
- end
254
+ return 'all cops' if cop == 'all'
255
+ return "`#{remove_department_marker(cop)}` department" if department_marker?(cop)
256
+ return "`#{cop}`" if all_cop_names.include?(cop)
257
+
258
+ similar = NameSimilarity.find_similar_name(cop, all_cop_names)
259
+ similar ? "`#{cop}` (did you mean `#{similar}`?)" : "`#{cop}` (unknown cop)"
260
+ end
261
+
262
+ def message(cop_names)
263
+ "Unnecessary disabling of #{cop_names}."
245
264
  end
246
265
 
247
266
  def all_cop_names
@@ -252,6 +271,18 @@ module RuboCop
252
271
  line = range.source_buffer.source_line(range.last_line)
253
272
  (line =~ /\s*\z/) == range.last_column
254
273
  end
274
+
275
+ def department_marker?(department)
276
+ department.start_with?(DEPARTMENT_MARKER)
277
+ end
278
+
279
+ def remove_department_marker(department)
280
+ department.gsub(DEPARTMENT_MARKER, '')
281
+ end
282
+
283
+ def add_department_marker(department)
284
+ DEPARTMENT_MARKER + department
285
+ end
255
286
  end
256
287
  end
257
288
  end
@@ -54,6 +54,7 @@ module RuboCop
54
54
  directive = DirectiveComment.new(comment)
55
55
 
56
56
  cop_names.each do |name|
57
+ name = name.split('/').first if department?(directive, name)
57
58
  add_offense(
58
59
  range_of_offense(comment, name),
59
60
  message: format(MSG, cop: all_or_name(name))
@@ -119,6 +120,10 @@ module RuboCop
119
120
  def all_or_name(name)
120
121
  name == 'all' ? 'all cops' : name
121
122
  end
123
+
124
+ def department?(directive, name)
125
+ directive.in_directive_department?(name) && !directive.overridden_by_department?(name)
126
+ end
122
127
  end
123
128
  end
124
129
  end
@@ -66,6 +66,7 @@ module RuboCop
66
66
  class SymbolConversion < Base
67
67
  extend AutoCorrector
68
68
  include ConfigurableEnforcedStyle
69
+ include SymbolHelp
69
70
 
70
71
  MSG = 'Unnecessary symbol conversion; use `%<correction>s` instead.'
71
72
  MSG_CONSISTENCY = 'Symbol hash key should be quoted for consistency; ' \
@@ -138,10 +139,6 @@ module RuboCop
138
139
  node.parent&.array_type? && node.parent&.percent_literal?
139
140
  end
140
141
 
141
- def hash_key?(node)
142
- node.parent&.pair_type? && node == node.parent.child_nodes.first
143
- end
144
-
145
142
  def correct_hash_key(node)
146
143
  # Although some operators can be converted to symbols normally
147
144
  # (ie. `:==`), these are not accepted as hash keys and will
@@ -167,7 +164,7 @@ module RuboCop
167
164
  next if requires_quotes?(key)
168
165
  next if properly_quoted?(key.source, %("#{key.value}"))
169
166
 
170
- correction = "#{quote_type}#{key.value}#{quote_type}"
167
+ correction = %("#{key.value}")
171
168
  register_offense(
172
169
  key,
173
170
  correction: correction,
@@ -175,13 +172,6 @@ module RuboCop
175
172
  )
176
173
  end
177
174
  end
178
-
179
- def quote_type
180
- # Use the `Style/StringLiterals` configuration for quoting symbols
181
- return '"' unless config.for_cop('Style/StringLiterals')['Enabled']
182
-
183
- config.for_cop('Style/StringLiterals')['EnforcedStyle'] == 'single_quotes' ? "'" : '"'
184
- end
185
175
  end
186
176
  end
187
177
  end
@@ -87,6 +87,7 @@ module RuboCop
87
87
  include IgnoredPattern
88
88
 
89
89
  MSG = 'This loop will have at most one iteration.'
90
+ CONTINUE_KEYWORDS = %i[next redo].freeze
90
91
 
91
92
  def on_while(node)
92
93
  check(node)
@@ -116,7 +117,10 @@ module RuboCop
116
117
  break_statement = statements.find { |statement| break_statement?(statement) }
117
118
  return unless break_statement
118
119
 
119
- add_offense(node) unless preceded_by_continue_statement?(break_statement)
120
+ unless preceded_by_continue_statement?(break_statement) ||
121
+ conditional_continue_keyword?(break_statement)
122
+ add_offense(node)
123
+ end
120
124
  end
121
125
 
122
126
  def statements(node)
@@ -177,9 +181,15 @@ module RuboCop
177
181
  break_statement.left_siblings.any? do |sibling|
178
182
  next if sibling.loop_keyword? || loop_method?(sibling)
179
183
 
180
- sibling.each_descendant(:next, :redo).any?
184
+ sibling.each_descendant(*CONTINUE_KEYWORDS).any?
181
185
  end
182
186
  end
187
+
188
+ def conditional_continue_keyword?(break_statement)
189
+ or_node = break_statement.each_descendant(:or).to_a.last
190
+
191
+ or_node && CONTINUE_KEYWORDS.include?(or_node.rhs.type)
192
+ end
183
193
  end
184
194
  end
185
195
  end
@@ -67,11 +67,17 @@ module RuboCop
67
67
  end
68
68
 
69
69
  def check_argument(variable)
70
- return if allowed_block?(variable) || allowed_keyword_argument?(variable)
70
+ return if allowed_block?(variable) ||
71
+ allowed_keyword_argument?(variable) ||
72
+ used_block_local?(variable)
71
73
 
72
74
  super
73
75
  end
74
76
 
77
+ def used_block_local?(variable)
78
+ variable.explicit_block_local_variable? && !variable.assignments.empty?
79
+ end
80
+
75
81
  def allowed_block?(variable)
76
82
  !variable.block_argument? || (ignore_empty_blocks? && empty_block?(variable))
77
83
  end
@@ -104,7 +104,7 @@ module RuboCop
104
104
  end
105
105
 
106
106
  def check_literal(node)
107
- return if !node.literal? || node.xstr_type?
107
+ return if !node.literal? || node.xstr_type? || node.range_type?
108
108
 
109
109
  add_offense(node, message: format(LIT_MSG, lit: node.source))
110
110
  end
@@ -61,7 +61,9 @@ module RuboCop
61
61
  end
62
62
 
63
63
  def valid_content_token?(content_token)
64
- /\W+/.match?(content_token) || DISABLING_COPS_CONTENT_TOKEN.match?(content_token)
64
+ /\W+/.match?(content_token) ||
65
+ DISABLING_COPS_CONTENT_TOKEN.match?(content_token) ||
66
+ Registry.global.department?(content_token)
65
67
  end
66
68
 
67
69
  def contain_unexpected_character_for_department_name?(name)
@@ -70,17 +70,33 @@ module RuboCop
70
70
  def extract_first_element_over_column_limit(node, elements, max)
71
71
  line = node.first_line
72
72
 
73
- # If the first argument is a hash pair but the method is not parenthesized,
74
- # the argument cannot be moved to another line because it cause a syntax error.
75
- elements.shift if node.send_type? && !node.parenthesized? && elements.first.pair_type?
73
+ # If a `send` node is not parenthesized, don't move the first element, because it
74
+ # can result in changed behavior or a syntax error.
75
+ elements = elements.drop(1) if node.send_type? && !node.parenthesized?
76
76
 
77
77
  i = 0
78
78
  i += 1 while within_column_limit?(elements[i], max, line)
79
+ i = shift_elements_for_heredoc_arg(node, elements, i)
80
+
81
+ return if i.nil?
79
82
  return elements.first if i.zero?
80
83
 
81
84
  elements[i - 1]
82
85
  end
83
86
 
87
+ # @api private
88
+ # If a send node contains a heredoc argument, splitting cannot happen
89
+ # after the heredoc or else it will cause a syntax error.
90
+ def shift_elements_for_heredoc_arg(node, elements, index)
91
+ return index unless node.send_type?
92
+
93
+ heredoc_index = elements.index { |arg| (arg.str_type? || arg.dstr_type?) && arg.heredoc? }
94
+ return index unless heredoc_index
95
+ return nil if heredoc_index.zero?
96
+
97
+ heredoc_index >= index ? index : heredoc_index + 1
98
+ end
99
+
84
100
  # @api private
85
101
  def within_column_limit?(element, max, line)
86
102
  element && element.loc.column <= max && element.loc.line == line