rubocop 1.13.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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