rubocop 0.67.2 → 0.68.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +86 -233
  4. data/exe/rubocop +0 -12
  5. data/lib/rubocop.rb +13 -30
  6. data/lib/rubocop/ast/builder.rb +4 -0
  7. data/lib/rubocop/ast/node/alias_node.rb +24 -0
  8. data/lib/rubocop/ast/node/class_node.rb +31 -0
  9. data/lib/rubocop/ast/node/module_node.rb +24 -0
  10. data/lib/rubocop/ast/node/range_node.rb +7 -0
  11. data/lib/rubocop/ast/node/resbody_node.rb +12 -0
  12. data/lib/rubocop/ast/node/self_class_node.rb +24 -0
  13. data/lib/rubocop/cli.rb +40 -4
  14. data/lib/rubocop/config.rb +9 -7
  15. data/lib/rubocop/config_loader.rb +48 -7
  16. data/lib/rubocop/config_loader_resolver.rb +5 -4
  17. data/lib/rubocop/cop/commissioner.rb +24 -0
  18. data/lib/rubocop/cop/correctors/unused_arg_corrector.rb +18 -6
  19. data/lib/rubocop/cop/internal_affairs/node_destructuring.rb +12 -14
  20. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +9 -20
  21. data/lib/rubocop/cop/layout/align_arguments.rb +93 -0
  22. data/lib/rubocop/cop/layout/align_parameters.rb +57 -33
  23. data/lib/rubocop/cop/layout/class_structure.rb +5 -5
  24. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -8
  25. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +3 -6
  26. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +1 -2
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +1 -0
  28. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +292 -0
  29. data/lib/rubocop/cop/layout/{first_parameter_indentation.rb → indent_first_argument.rb} +11 -12
  30. data/lib/rubocop/cop/layout/{indent_array.rb → indent_first_array_element.rb} +2 -2
  31. data/lib/rubocop/cop/layout/{indent_hash.rb → indent_first_hash_element.rb} +2 -2
  32. data/lib/rubocop/cop/layout/indent_first_parameter.rb +96 -0
  33. data/lib/rubocop/cop/layout/indentation_width.rb +4 -16
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -4
  35. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -16
  36. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -2
  37. data/lib/rubocop/cop/lint/duplicate_methods.rb +6 -8
  38. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +4 -8
  39. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +157 -0
  40. data/lib/rubocop/cop/lint/inherit_exception.rb +3 -4
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +18 -1
  42. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +3 -5
  43. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +25 -5
  44. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -6
  45. data/lib/rubocop/cop/lint/useless_setter_call.rb +1 -2
  46. data/lib/rubocop/cop/message_annotator.rb +1 -0
  47. data/lib/rubocop/cop/metrics/line_length.rb +139 -28
  48. data/lib/rubocop/cop/metrics/perceived_complexity.rb +3 -4
  49. data/lib/rubocop/cop/mixin/check_line_breakable.rb +190 -0
  50. data/lib/rubocop/cop/mixin/{array_hash_indentation.rb → multiline_element_indentation.rb} +3 -2
  51. data/lib/rubocop/cop/mixin/too_many_lines.rb +3 -7
  52. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +33 -4
  53. data/lib/rubocop/cop/rails/active_record_override.rb +23 -8
  54. data/lib/rubocop/cop/rails/delegate.rb +5 -8
  55. data/lib/rubocop/cop/rails/environment_comparison.rb +5 -3
  56. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  57. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +3 -3
  58. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  59. data/lib/rubocop/cop/rails/skips_model_validations.rb +6 -7
  60. data/lib/rubocop/cop/rails/time_zone.rb +3 -10
  61. data/lib/rubocop/cop/rails/validation.rb +3 -0
  62. data/lib/rubocop/cop/registry.rb +3 -3
  63. data/lib/rubocop/cop/style/alias.rb +13 -7
  64. data/lib/rubocop/cop/style/block_delimiters.rb +20 -0
  65. data/lib/rubocop/cop/style/class_and_module_children.rb +19 -21
  66. data/lib/rubocop/cop/style/class_methods.rb +16 -24
  67. data/lib/rubocop/cop/style/conditional_assignment.rb +20 -49
  68. data/lib/rubocop/cop/style/documentation.rb +3 -7
  69. data/lib/rubocop/cop/style/format_string.rb +18 -21
  70. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  71. data/lib/rubocop/cop/style/inverse_methods.rb +4 -0
  72. data/lib/rubocop/cop/style/lambda.rb +12 -8
  73. data/lib/rubocop/cop/style/mixin_grouping.rb +8 -10
  74. data/lib/rubocop/cop/style/module_function.rb +2 -3
  75. data/lib/rubocop/cop/style/next.rb +10 -14
  76. data/lib/rubocop/cop/style/one_line_conditional.rb +5 -3
  77. data/lib/rubocop/cop/style/optional_arguments.rb +1 -4
  78. data/lib/rubocop/cop/style/random_with_offset.rb +44 -47
  79. data/lib/rubocop/cop/style/redundant_return.rb +6 -14
  80. data/lib/rubocop/cop/style/redundant_sort_by.rb +1 -1
  81. data/lib/rubocop/cop/style/safe_navigation.rb +3 -0
  82. data/lib/rubocop/cop/style/struct_inheritance.rb +2 -3
  83. data/lib/rubocop/cop/style/symbol_proc.rb +20 -40
  84. data/lib/rubocop/cop/style/unless_else.rb +1 -2
  85. data/lib/rubocop/cop/style/yoda_condition.rb +8 -7
  86. data/lib/rubocop/cop/util.rb +2 -4
  87. data/lib/rubocop/file_finder.rb +5 -10
  88. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -0
  89. data/lib/rubocop/node_pattern.rb +304 -170
  90. data/lib/rubocop/options.rb +4 -1
  91. data/lib/rubocop/rspec/shared_contexts.rb +3 -0
  92. data/lib/rubocop/version.rb +1 -1
  93. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  94. metadata +26 -50
  95. data/lib/rubocop/cop/performance/caller.rb +0 -69
  96. data/lib/rubocop/cop/performance/case_when_splat.rb +0 -177
  97. data/lib/rubocop/cop/performance/casecmp.rb +0 -108
  98. data/lib/rubocop/cop/performance/chain_array_allocation.rb +0 -78
  99. data/lib/rubocop/cop/performance/compare_with_block.rb +0 -122
  100. data/lib/rubocop/cop/performance/count.rb +0 -102
  101. data/lib/rubocop/cop/performance/detect.rb +0 -110
  102. data/lib/rubocop/cop/performance/double_start_end_with.rb +0 -94
  103. data/lib/rubocop/cop/performance/end_with.rb +0 -56
  104. data/lib/rubocop/cop/performance/fixed_size.rb +0 -97
  105. data/lib/rubocop/cop/performance/flat_map.rb +0 -78
  106. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +0 -99
  107. data/lib/rubocop/cop/performance/open_struct.rb +0 -46
  108. data/lib/rubocop/cop/performance/range_include.rb +0 -50
  109. data/lib/rubocop/cop/performance/redundant_block_call.rb +0 -93
  110. data/lib/rubocop/cop/performance/redundant_match.rb +0 -56
  111. data/lib/rubocop/cop/performance/redundant_merge.rb +0 -183
  112. data/lib/rubocop/cop/performance/regexp_match.rb +0 -265
  113. data/lib/rubocop/cop/performance/reverse_each.rb +0 -42
  114. data/lib/rubocop/cop/performance/size.rb +0 -77
  115. data/lib/rubocop/cop/performance/start_with.rb +0 -59
  116. data/lib/rubocop/cop/performance/string_replacement.rb +0 -173
  117. data/lib/rubocop/cop/performance/times_map.rb +0 -71
  118. data/lib/rubocop/cop/performance/unfreeze_string.rb +0 -50
  119. data/lib/rubocop/cop/performance/uri_default_parser.rb +0 -47
@@ -49,16 +49,14 @@ module RuboCop
49
49
  return_node.each_ancestor(:block, :def, :defs) do |node|
50
50
  break if scoped_node?(node)
51
51
 
52
- send_node, args_node, _body_node = *node
53
-
54
52
  # if a proc is passed to `Module#define_method` or
55
53
  # `Object#define_singleton_method`, `return` will not cause a
56
54
  # non-local exit error
57
- break if define_method?(send_node)
55
+ break if define_method?(node.send_node)
58
56
 
59
- next if args_node.children.empty?
57
+ next unless node.arguments?
60
58
 
61
- if chained_send?(send_node)
59
+ if chained_send?(node.send_node)
62
60
  add_offense(return_node, location: :keyword)
63
61
  break
64
62
  end
@@ -6,7 +6,11 @@ module RuboCop
6
6
  # This cop checks for underscore-prefixed variables that are actually
7
7
  # used.
8
8
  #
9
- # @example
9
+ # Since block keyword arguments cannot be arbitrarily named at call
10
+ # sites, the `AllowKeywordBlockArguments` will allow use of underscore-
11
+ # prefixed block keyword arguments.
12
+ #
13
+ # @example AllowKeywordBlockArguments: false (default)
10
14
  #
11
15
  # # bad
12
16
  #
@@ -14,7 +18,9 @@ module RuboCop
14
18
  # do_something(_num)
15
19
  # end
16
20
  #
17
- # @example
21
+ # query(:sales) do |_id:, revenue:, cost:|
22
+ # {_id: _id, profit: revenue - cost}
23
+ # end
18
24
  #
19
25
  # # good
20
26
  #
@@ -22,13 +28,18 @@ module RuboCop
22
28
  # do_something(num)
23
29
  # end
24
30
  #
25
- # @example
31
+ # [1, 2, 3].each do |_num|
32
+ # do_something # not using `_num`
33
+ # end
34
+ #
35
+ # @example AllowKeywordBlockArguments: true
26
36
  #
27
37
  # # good
28
38
  #
29
- # [1, 2, 3].each do |_num|
30
- # do_something # not using `_num`
39
+ # query(:sales) do |_id:, revenue:, cost:|
40
+ # {_id: _id, profit: revenue - cost}
31
41
  # end
42
+ #
32
43
  class UnderscorePrefixedVariableName < Cop
33
44
  MSG = 'Do not use prefix `_` for a variable that is used.'.freeze
34
45
 
@@ -45,6 +56,7 @@ module RuboCop
45
56
  def check_variable(variable)
46
57
  return unless variable.should_be_unused?
47
58
  return if variable.references.none?(&:explicit?)
59
+ return if allowed_keyword_block_argument?(variable)
48
60
 
49
61
  node = variable.declaration_node
50
62
 
@@ -56,6 +68,14 @@ module RuboCop
56
68
 
57
69
  add_offense(nil, location: location)
58
70
  end
71
+
72
+ private
73
+
74
+ def allowed_keyword_block_argument?(variable)
75
+ variable.block_argument? &&
76
+ variable.keyword_argument? &&
77
+ cop_config['AllowKeywordBlockArguments']
78
+ end
59
79
  end
60
80
  end
61
81
  end
@@ -111,10 +111,7 @@ module RuboCop
111
111
 
112
112
  def collect_variable_like_names(scope)
113
113
  names = scope.each_node.with_object(Set.new) do |node, set|
114
- if variable_like_method_invocation?(node)
115
- _receiver, method_name, = *node
116
- set << method_name
117
- end
114
+ set << node.method_name if variable_like_method_invocation?(node)
118
115
  end
119
116
 
120
117
  variable_names = scope.variables.each_value.map(&:name)
@@ -124,8 +121,7 @@ module RuboCop
124
121
  def variable_like_method_invocation?(node)
125
122
  return false unless node.send_type?
126
123
 
127
- receiver, _method_name, *args = *node
128
- receiver.nil? && args.empty?
124
+ node.receiver.nil? && !node.arguments?
129
125
  end
130
126
  end
131
127
  end
@@ -155,8 +155,7 @@ module RuboCop
155
155
  return true if node.literal?
156
156
  return false unless node.send_type?
157
157
 
158
- _receiver, method = *node
159
- method == :new
158
+ node.method_name == :new
160
159
  end
161
160
  end
162
161
  end
@@ -106,6 +106,7 @@ module RuboCop
106
106
  return true if debug?
107
107
  return false if options[:display_cop_names] == false
108
108
  return true if options[:display_cop_names]
109
+ return false if options[:format] == 'json'
109
110
 
110
111
  config.for_all_cops['DisplayCopNames']
111
112
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ # rubocop:disable Metrics/ClassLength
5
6
  module RuboCop
6
7
  module Cop
7
8
  module Metrics
@@ -9,22 +10,95 @@ module RuboCop
9
10
  # The maximum length is configurable.
10
11
  # The tab size is configured in the `IndentationWidth`
11
12
  # of the `Layout/Tab` cop.
13
+ #
14
+ # This cop has some autocorrection capabilities.
15
+ # It can programmatically shorten certain long lines by
16
+ # inserting line breaks into expressions that can be safely
17
+ # split across lines. These include arrays, hashes, and
18
+ # method calls with argument lists.
19
+ #
20
+ # If autocorrection is enabled, the following Layout cops
21
+ # are recommended to further format the broken lines.
22
+ #
23
+ # - AlignArray
24
+ # - AlignHash
25
+ # - AlignParameters
26
+ # - ClosingParenthesisIndentation
27
+ # - IndentFirstArgument
28
+ # - IndentFirstArrayElement
29
+ # - IndentFirstHashElement
30
+ # - IndentFirstParameter
31
+ # - MultilineArrayLineBreaks
32
+ # - MultilineHashBraceLayout
33
+ # - MultilineHashKeyLineBreaks
34
+ # - MultilineMethodArgumentLineBreaks
35
+ #
36
+ # Together, these cops will pretty print hashes, arrays,
37
+ # method calls, etc. For example, let's say the max columns
38
+ # is 25:
39
+ #
40
+ # @example
41
+ #
42
+ # # bad
43
+ # {foo: "0000000000", bar: "0000000000", baz: "0000000000"}
44
+ #
45
+ # # good
46
+ # {foo: "0000000000",
47
+ # bar: "0000000000", baz: "0000000000"}
48
+ #
49
+ # # good (with recommended cops enabled)
50
+ # {
51
+ # foo: "0000000000",
52
+ # bar: "0000000000",
53
+ # baz: "0000000000",
54
+ # }
12
55
  class LineLength < Cop
56
+ include CheckLineBreakable
13
57
  include ConfigurableMax
14
58
  include IgnoredPattern
15
59
  include RangeHelp
16
60
 
17
61
  MSG = 'Line is too long. [%<length>d/%<max>d]'.freeze
18
62
 
19
- def investigate(processed_source)
20
- heredocs = extract_heredocs(processed_source.ast) if allow_heredoc?
21
- processed_source.lines.each_with_index do |line, index|
22
- check_line(line, index, heredocs)
63
+ def on_potential_breakable_node(node)
64
+ check_for_breakable_node(node)
65
+ end
66
+ alias on_array on_potential_breakable_node
67
+ alias on_hash on_potential_breakable_node
68
+ alias on_send on_potential_breakable_node
69
+
70
+ def investigate_post_walk(processed_source)
71
+ processed_source.lines.each_with_index do |line, line_index|
72
+ check_line(line, line_index)
73
+ end
74
+ end
75
+
76
+ def autocorrect(range)
77
+ return if range.nil?
78
+
79
+ lambda do |corrector|
80
+ corrector.insert_before(range, "\n")
23
81
  end
24
82
  end
25
83
 
26
84
  private
27
85
 
86
+ def check_for_breakable_node(node)
87
+ breakable_node = extract_breakable_node(node, max)
88
+ return if breakable_node.nil?
89
+
90
+ line_index = breakable_node.first_line - 1
91
+ breakable_nodes_by_line_index[line_index] = breakable_node
92
+ end
93
+
94
+ def breakable_nodes_by_line_index
95
+ @breakable_nodes_by_line_index ||= {}
96
+ end
97
+
98
+ def heredocs
99
+ @heredocs ||= extract_heredocs(processed_source.ast)
100
+ end
101
+
28
102
  def tab_indentation_width
29
103
  config.for_cop('Layout/Tab')['IndentationWidth']
30
104
  end
@@ -39,49 +113,72 @@ module RuboCop
39
113
  line.length + indentation_difference(line)
40
114
  end
41
115
 
42
- def highligh_start(line)
116
+ def highlight_start(line)
43
117
  max - indentation_difference(line)
44
118
  end
45
119
 
46
- def check_line(line, index, heredocs)
120
+ def check_line(line, line_index)
47
121
  return if line_length(line) <= max
48
- return if ignored_line?(line, index, heredocs)
122
+ return if ignored_line?(line, line_index)
49
123
 
50
- if ignore_cop_directives? && directive_on_source_line?(index)
51
- return check_directive_line(line, index)
124
+ if ignore_cop_directives? && directive_on_source_line?(line_index)
125
+ return check_directive_line(line, line_index)
52
126
  end
53
- return check_uri_line(line, index) if allow_uri?
127
+ return check_uri_line(line, line_index) if allow_uri?
54
128
 
55
129
  register_offense(
56
130
  source_range(
57
- processed_source.buffer, index,
58
- highligh_start(line)...line_length(line)
131
+ processed_source.buffer, line_index,
132
+ highlight_start(line)...line_length(line)
59
133
  ),
60
- line
134
+ line,
135
+ line_index
61
136
  )
62
137
  end
63
138
 
64
- def ignored_line?(line, index, heredocs)
139
+ def ignored_line?(line, line_index)
65
140
  matches_ignored_pattern?(line) ||
66
- heredocs && line_in_permitted_heredoc?(heredocs, index.succ)
141
+ heredocs && line_in_permitted_heredoc?(line_index.succ)
67
142
  end
68
143
 
69
- def register_offense(loc, line)
144
+ def register_offense(loc, line, line_index)
70
145
  message = format(MSG, length: line_length(line), max: max)
71
146
 
72
- add_offense(nil, location: loc, message: message) do
147
+ breakable_range = breakable_range(line, line_index)
148
+ add_offense(breakable_range, location: loc, message: message) do
73
149
  self.max = line_length(line)
74
150
  end
75
151
  end
76
152
 
77
- def excess_range(uri_range, line, index)
153
+ def breakable_range(line, line_index)
154
+ return if line_in_heredoc?(line_index + 1)
155
+
156
+ semicolon_range = breakable_semicolon_range(line, line_index)
157
+ return semicolon_range if semicolon_range
158
+
159
+ breakable_node = breakable_nodes_by_line_index[line_index]
160
+ return breakable_node.source_range if breakable_node
161
+ end
162
+
163
+ def breakable_semicolon_range(line, line_index)
164
+ semicolon_separated_parts = line.split(';')
165
+ return if semicolon_separated_parts.length <= 1
166
+
167
+ column = semicolon_separated_parts.first.length + 1
168
+ range = source_range(processed_source.buffer, line_index, column, 1)
169
+ return if processed_source.commented?(range)
170
+
171
+ range
172
+ end
173
+
174
+ def excess_range(uri_range, line, line_index)
78
175
  excessive_position = if uri_range && uri_range.begin < max
79
176
  uri_range.end
80
177
  else
81
- highligh_start(line)
178
+ highlight_start(line)
82
179
  end
83
180
 
84
- source_range(processed_source.buffer, index + 1,
181
+ source_range(processed_source.buffer, line_index + 1,
85
182
  excessive_position...(line_length(line)))
86
183
  end
87
184
 
@@ -107,13 +204,21 @@ module RuboCop
107
204
  end
108
205
  end
109
206
 
110
- def line_in_permitted_heredoc?(heredocs, line_number)
207
+ def line_in_permitted_heredoc?(line_number)
208
+ return false unless allowed_heredoc
209
+
111
210
  heredocs.any? do |range, delimiter|
112
211
  range.cover?(line_number) &&
113
212
  (allowed_heredoc == true || allowed_heredoc.include?(delimiter))
114
213
  end
115
214
  end
116
215
 
216
+ def line_in_heredoc?(line_number)
217
+ heredocs.any? do |range, _delimiter|
218
+ range.cover?(line_number)
219
+ end
220
+ end
221
+
117
222
  def allow_uri?
118
223
  cop_config['AllowURI']
119
224
  end
@@ -161,22 +266,23 @@ module RuboCop
161
266
  URI::DEFAULT_PARSER.make_regexp(cop_config['URISchemes'])
162
267
  end
163
268
 
164
- def check_directive_line(line, index)
269
+ def check_directive_line(line, line_index)
165
270
  return if line_length_without_directive(line) <= max
166
271
 
167
272
  range = max..(line_length_without_directive(line) - 1)
168
273
  register_offense(
169
274
  source_range(
170
275
  processed_source.buffer,
171
- index + 1,
276
+ line_index + 1,
172
277
  range
173
278
  ),
174
- line
279
+ line,
280
+ line_index
175
281
  )
176
282
  end
177
283
 
178
- def directive_on_source_line?(index)
179
- source_line_number = index + processed_source.buffer.first_line
284
+ def directive_on_source_line?(line_index)
285
+ source_line_number = line_index + processed_source.buffer.first_line
180
286
  comment =
181
287
  processed_source
182
288
  .comments
@@ -192,13 +298,18 @@ module RuboCop
192
298
  before_comment.rstrip.length
193
299
  end
194
300
 
195
- def check_uri_line(line, index)
301
+ def check_uri_line(line, line_index)
196
302
  uri_range = find_excessive_uri_range(line)
197
303
  return if uri_range && allowed_uri_position?(line, uri_range)
198
304
 
199
- register_offense(excess_range(uri_range, line, index), line)
305
+ register_offense(
306
+ excess_range(uri_range, line, line_index),
307
+ line,
308
+ line_index
309
+ )
200
310
  end
201
311
  end
202
312
  end
203
313
  end
204
314
  end
315
+ # rubocop:enable Metrics/ClassLength
@@ -39,16 +39,15 @@ module RuboCop
39
39
  def complexity_score_for(node)
40
40
  case node.type
41
41
  when :case
42
- expression, *whens, _else = *node
43
42
  # If cond is nil, that means each when has an expression that
44
43
  # evaluates to true or false. It's just an alternative to
45
44
  # if/elsif/elsif... so the when nodes count.
46
- if expression.nil?
47
- whens.length
45
+ if node.condition.nil?
46
+ node.when_branches.length
48
47
  else
49
48
  # Otherwise, the case node gets 0.8 complexity points and each
50
49
  # when gets 0.2.
51
- (0.8 + 0.2 * whens.length).round
50
+ (0.8 + 0.2 * node.when_branches.length).round
52
51
  end
53
52
  when :if
54
53
  node.else? && !node.elsif? ? 2 : 1
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This mixin detects collections that are safe to "break"
6
+ # by inserting new lines. This is useful for breaking
7
+ # up long lines.
8
+ #
9
+ # Let's look at hashes as an example:
10
+ #
11
+ # We know hash keys are safe to break across lines. We can add
12
+ # linebreaks into hashes on lines longer than the specified maximum.
13
+ # Then in further passes cops can clean up the multi-line hash.
14
+ # For example, say the maximum line length is as indicated below:
15
+ #
16
+ # |
17
+ # v
18
+ # {foo: "0000000000", bar: "0000000000", baz: "0000000000"}
19
+ #
20
+ # In a LineLength autocorrection pass, a line is added before
21
+ # the first key that exceeds the column limit:
22
+ #
23
+ # {foo: "0000000000", bar: "0000000000",
24
+ # baz: "0000000000"}
25
+ #
26
+ # In a MultilineHashKeyLineBreaks pass, lines are inserted
27
+ # before all keys:
28
+ #
29
+ # {foo: "0000000000",
30
+ # bar: "0000000000",
31
+ # baz: "0000000000"}
32
+ #
33
+ # Then in future passes FirstHashElementLineBreak,
34
+ # MultilineHashBraceLayout, and TrailingCommaInHashLiteral will
35
+ # manipulate as well until we get:
36
+ #
37
+ # {
38
+ # foo: "0000000000",
39
+ # bar: "0000000000",
40
+ # baz: "0000000000",
41
+ # }
42
+ #
43
+ # (Note: Passes may not happen exactly in this sequence.)
44
+ module CheckLineBreakable
45
+ def extract_breakable_node(node, max)
46
+ if node.send_type?
47
+ args = process_args(node.arguments)
48
+ return extract_breakable_node_from_elements(node, args, max)
49
+ elsif node.array_type? || node.hash_type?
50
+ return extract_breakable_node_from_elements(node, node.children, max)
51
+ end
52
+ nil
53
+ end
54
+
55
+ private
56
+
57
+ def extract_breakable_node_from_elements(node, elements, max)
58
+ return unless breakable_collection?(node, elements)
59
+ return if safe_to_ignore?(node)
60
+
61
+ line = processed_source.lines[node.first_line - 1]
62
+ return if processed_source.commented?(node.loc.begin)
63
+ return if line.length <= max
64
+
65
+ extract_first_element_over_column_limit(node, elements, max)
66
+ end
67
+
68
+ def extract_first_element_over_column_limit(node, elements, max)
69
+ line = node.first_line
70
+ i = 0
71
+ i += 1 while within_column_limit?(elements[i], max, line)
72
+ return elements.first if i.zero?
73
+
74
+ elements[i - 1]
75
+ end
76
+
77
+ def within_column_limit?(element, max, line)
78
+ element && element.loc.column < max && element.loc.line == line
79
+ end
80
+
81
+ def safe_to_ignore?(node)
82
+ return true unless max
83
+ return true if already_on_multiple_lines?(node)
84
+
85
+ # If there's a containing breakable collection on the same
86
+ # line, we let that one get broken first. In a separate pass,
87
+ # this one might get broken as well, but to avoid conflicting
88
+ # or redundant edits, we only mark one offense at a time.
89
+ return true if contained_by_breakable_collection_on_same_line?(node)
90
+
91
+ if contained_by_multiline_collection_that_could_be_broken_up?(node)
92
+ return true
93
+ end
94
+
95
+ false
96
+ end
97
+
98
+ def breakable_collection?(node, elements)
99
+ # For simplicity we only want to insert breaks in normal
100
+ # hashes wrapped in a set of curly braces like {foo: 1}.
101
+ # That is, not a kwargs hash. For method calls, this ensures
102
+ # the method call is made with parens.
103
+ starts_with_bracket = node.loc.begin
104
+
105
+ # If the call has a second argument, we can insert a line
106
+ # break before the second argument and the rest of the
107
+ # argument will get auto-formatted onto separate lines
108
+ # by other cops.
109
+ has_second_element = elements.length >= 2
110
+
111
+ starts_with_bracket && has_second_element
112
+ end
113
+
114
+ def contained_by_breakable_collection_on_same_line?(node)
115
+ node.each_ancestor.find do |ancestor|
116
+ # Ignore ancestors on different lines.
117
+ break if ancestor.first_line != node.first_line
118
+
119
+ if ancestor.hash_type? || ancestor.array_type?
120
+ elements = ancestor.children
121
+ elsif ancestor.send_type?
122
+ elements = process_args(ancestor.arguments)
123
+ else
124
+ next
125
+ end
126
+
127
+ return true if breakable_collection?(ancestor, elements)
128
+ end
129
+
130
+ false
131
+ end
132
+
133
+ def contained_by_multiline_collection_that_could_be_broken_up?(node)
134
+ node.each_ancestor.find do |ancestor|
135
+ if ancestor.hash_type? || ancestor.array_type?
136
+ if breakable_collection?(ancestor, ancestor.children)
137
+ return children_could_be_broken_up?(ancestor.children)
138
+ end
139
+ end
140
+
141
+ next unless ancestor.send_type?
142
+
143
+ args = process_args(ancestor.arguments)
144
+ if breakable_collection?(ancestor, args)
145
+ return children_could_be_broken_up?(args)
146
+ end
147
+ end
148
+
149
+ false
150
+ end
151
+
152
+ def children_could_be_broken_up?(children)
153
+ return false if all_on_same_line?(children)
154
+
155
+ last_seen_line = -1
156
+ children.each do |child|
157
+ return true if last_seen_line >= child.first_line
158
+
159
+ last_seen_line = child.last_line
160
+ end
161
+ false
162
+ end
163
+
164
+ def all_on_same_line?(nodes)
165
+ return true if nodes.empty?
166
+
167
+ nodes.first.first_line == nodes.last.last_line
168
+ end
169
+
170
+ def process_args(args)
171
+ # If there is a trailing hash arg without explicit braces, like this:
172
+ #
173
+ # method(1, 'key1' => value1, 'key2' => value2)
174
+ #
175
+ # ...then each key/value pair is treated as a method 'argument'
176
+ # when determining where line breaks should appear.
177
+ if (last_arg = args.last)
178
+ if last_arg.hash_type? && !last_arg.braces?
179
+ args = args.concat(args.pop.children)
180
+ end
181
+ end
182
+ args
183
+ end
184
+
185
+ def already_on_multiple_lines?(node)
186
+ node.first_line != node.last_line
187
+ end
188
+ end
189
+ end
190
+ end