rubocop 1.88.0 → 1.89.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 (197) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +72 -12
  3. data/lib/rubocop/comment_config.rb +35 -19
  4. data/lib/rubocop/config.rb +53 -0
  5. data/lib/rubocop/config_validator.rb +38 -1
  6. data/lib/rubocop/cop/badge.rb +8 -0
  7. data/lib/rubocop/cop/base.rb +19 -12
  8. data/lib/rubocop/cop/bundler/gem_comment.rb +3 -1
  9. data/lib/rubocop/cop/bundler.rb +19 -0
  10. data/lib/rubocop/cop/commissioner.rb +13 -11
  11. data/lib/rubocop/cop/corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  13. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  14. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  15. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  16. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  18. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  19. data/lib/rubocop/cop/gemspec.rb +22 -0
  20. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  21. data/lib/rubocop/cop/generator.rb +4 -0
  22. data/lib/rubocop/cop/layout/block_alignment.rb +17 -0
  23. data/lib/rubocop/cop/layout/class_structure.rb +14 -4
  24. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  25. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  26. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  27. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  28. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  29. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  30. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  31. data/lib/rubocop/cop/layout/indentation_width.rb +31 -1
  32. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  33. data/lib/rubocop/cop/layout/line_length.rb +95 -17
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  35. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  36. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  37. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
  38. data/lib/rubocop/cop/layout.rb +114 -0
  39. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  40. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  41. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  42. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  43. data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
  44. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  45. data/lib/rubocop/cop/lint/debugger.rb +13 -1
  46. data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
  47. data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
  48. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  49. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
  50. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  51. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  52. data/lib/rubocop/cop/lint/name_typo.rb +223 -0
  53. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +7 -0
  54. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
  55. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +44 -2
  57. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  58. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +32 -8
  59. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  60. data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
  61. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  62. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  63. data/lib/rubocop/cop/lint/void.rb +19 -8
  64. data/lib/rubocop/cop/lint.rb +176 -0
  65. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  66. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  67. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  68. data/lib/rubocop/cop/metrics.rb +31 -0
  69. data/lib/rubocop/cop/migration/department_name.rb +0 -4
  70. data/lib/rubocop/cop/migration.rb +13 -0
  71. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  72. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  73. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  74. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  75. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  76. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  77. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  78. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  79. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  80. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  81. data/lib/rubocop/cop/mixin/statement_modifier.rb +43 -7
  82. data/lib/rubocop/cop/mixin.rb +78 -77
  83. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  84. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  85. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  86. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  87. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  88. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  89. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  90. data/lib/rubocop/cop/naming.rb +31 -0
  91. data/lib/rubocop/cop/offense.rb +10 -6
  92. data/lib/rubocop/cop/registry.rb +153 -43
  93. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  94. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  95. data/lib/rubocop/cop/security.rb +19 -0
  96. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  97. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  98. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  99. data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
  100. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  101. data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
  102. data/lib/rubocop/cop/style/copyright.rb +6 -1
  103. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  104. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  105. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  106. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
  107. data/lib/rubocop/cop/style/documentation.rb +47 -0
  108. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  109. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  110. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  111. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  112. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  113. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  114. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  115. data/lib/rubocop/cop/style/env_home.rb +4 -0
  116. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  117. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  118. data/lib/rubocop/cop/style/file_null.rb +4 -2
  119. data/lib/rubocop/cop/style/format_string.rb +13 -1
  120. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  121. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  122. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  123. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  124. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -52
  125. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  126. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  127. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  128. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  129. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  130. data/lib/rubocop/cop/style/lambda.rb +11 -1
  131. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  132. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  133. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  134. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
  135. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
  136. data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
  137. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  138. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +40 -7
  139. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  140. data/lib/rubocop/cop/style/module_function.rb +4 -2
  141. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  142. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  143. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  144. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  145. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  146. data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
  147. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  148. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  149. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  150. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  151. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  152. data/lib/rubocop/cop/style/parallel_assignment.rb +11 -2
  153. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  154. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  155. data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
  156. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  157. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  158. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  159. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  160. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  161. data/lib/rubocop/cop/style/redundant_format.rb +40 -3
  162. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  163. data/lib/rubocop/cop/style/redundant_line_continuation.rb +80 -129
  164. data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
  165. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  166. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  167. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  168. data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
  169. data/lib/rubocop/cop/style/semicolon.rb +4 -4
  170. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  171. data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
  172. data/lib/rubocop/cop/style/static_class.rb +21 -0
  173. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  174. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  175. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  176. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  177. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  178. data/lib/rubocop/cop/style.rb +310 -0
  179. data/lib/rubocop/cop/team.rb +66 -7
  180. data/lib/rubocop/cop/variable_force/variable.rb +18 -6
  181. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  182. data/lib/rubocop/directive_comment.rb +2 -0
  183. data/lib/rubocop/feature_loader.rb +0 -6
  184. data/lib/rubocop/lsp/routes.rb +41 -3
  185. data/lib/rubocop/lsp/runtime.rb +4 -0
  186. data/lib/rubocop/lsp/server.rb +4 -0
  187. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  188. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  189. data/lib/rubocop/project_index_loader.rb +16 -0
  190. data/lib/rubocop/result_cache.rb +6 -4
  191. data/lib/rubocop/rspec/shared_contexts.rb +14 -2
  192. data/lib/rubocop/runner.rb +100 -33
  193. data/lib/rubocop/server/cache.rb +10 -5
  194. data/lib/rubocop/target_ruby.rb +1 -1
  195. data/lib/rubocop/version.rb +1 -1
  196. data/lib/rubocop.rb +17 -626
  197. metadata +18 -3
@@ -28,10 +28,10 @@ module RuboCop
28
28
  # # bad
29
29
  # hash = {
30
30
  # key: :value
31
- # }
32
- # and_in_a_method_call({
33
- # no: :difference
34
- # })
31
+ # }
32
+ # in_a_method_call({
33
+ # foo: :bar
34
+ # })
35
35
  # takes_multi_pairs_hash(x: {
36
36
  # a: 1,
37
37
  # b: 2
@@ -42,13 +42,12 @@ module RuboCop
42
42
  # })
43
43
  #
44
44
  # # good
45
- # special_inside_parentheses
46
45
  # hash = {
47
46
  # key: :value
48
47
  # }
49
- # but_in_a_method_call({
50
- # its_like: :this
51
- # })
48
+ # in_a_method_call({
49
+ # foo: :bar
50
+ # })
52
51
  # takes_multi_pairs_hash(x: {
53
52
  # a: 1,
54
53
  # b: 2
@@ -67,17 +66,17 @@ module RuboCop
67
66
  # # bad
68
67
  # hash = {
69
68
  # key: :value
70
- # }
71
- # but_in_a_method_call({
72
- # its_like: :this
73
- # })
69
+ # }
70
+ # in_a_method_call({
71
+ # foo: :bar
72
+ # })
74
73
  #
75
74
  # # good
76
75
  # hash = {
77
76
  # key: :value
78
77
  # }
79
- # and_in_a_method_call({
80
- # no: :difference
78
+ # in_a_method_call({
79
+ # foo: :bar
81
80
  # })
82
81
  #
83
82
  #
@@ -25,6 +25,17 @@ module RuboCop
25
25
  # end
26
26
  # end
27
27
  #
28
+ # @example
29
+ # # bad
30
+ # value = (
31
+ # foo - bar
32
+ # )
33
+ #
34
+ # # good
35
+ # value = (
36
+ # foo - bar
37
+ # )
38
+ #
28
39
  # @example AllowedPatterns: ['^\s*module']
29
40
  # # bad
30
41
  # module A
@@ -95,6 +106,16 @@ module RuboCop
95
106
  check_indentation(node.loc.end, node.children.first)
96
107
  end
97
108
 
109
+ def on_begin(node)
110
+ # Only a parenthesized grouping expression (e.g. `(\n foo\n)`) has
111
+ # explicit delimiters. Indent the body one step from the line
112
+ # the opening parenthesis is on, but only when the closing parenthesis is
113
+ # first on its line (a body on the opening line is skipped downstream).
114
+ return unless parentheses?(node) && begins_its_line?(node.loc.end)
115
+
116
+ check_indentation(opening_line_start(node.loc.begin), node.children.first)
117
+ end
118
+
98
119
  def on_block(node)
99
120
  end_loc = node.loc.end
100
121
 
@@ -185,7 +206,16 @@ module RuboCop
185
206
  def autocorrect(corrector, node)
186
207
  return unless node
187
208
 
188
- AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
209
+ AlignmentCorrector.correct(
210
+ corrector, processed_source, node, @column_delta, tab_indentation: true
211
+ )
212
+ end
213
+
214
+ # Returns a range at the first non-space column of the line the opening parenthesis is on,
215
+ # used as the base to indent the body from.
216
+ def opening_line_start(begin_loc)
217
+ column = begin_loc.source_line =~ /\S/
218
+ source_range(processed_source.buffer, begin_loc.line, column)
189
219
  end
190
220
 
191
221
  def check_members(base, members)
@@ -98,6 +98,24 @@ module RuboCop
98
98
  #
99
99
  # name = 'John' #: String
100
100
  #
101
+ # @example AllowYARDCommentBlockSeparator: false (default)
102
+ #
103
+ # # bad
104
+ #
105
+ # # Copyright (c) Example Corp
106
+ # #-
107
+ # class Client
108
+ # end
109
+ #
110
+ # @example AllowYARDCommentBlockSeparator: true
111
+ #
112
+ # # good
113
+ #
114
+ # # Copyright (c) Example Corp
115
+ # #-
116
+ # class Client
117
+ # end
118
+ #
101
119
  class LeadingCommentSpace < Base
102
120
  include RangeHelp
103
121
  extend AutoCorrector
@@ -113,6 +131,7 @@ module RuboCop
113
131
  next if gemfile_ruby_comment?(comment)
114
132
  next if rbs_inline_annotation?(comment)
115
133
  next if steep_annotation?(comment)
134
+ next if yard_comment_block_separator?(comment)
116
135
 
117
136
  add_offense(comment) do |corrector|
118
137
  expr = comment.source_range
@@ -197,6 +216,14 @@ module RuboCop
197
216
  def steep_annotation?(comment)
198
217
  allow_steep_annotation? && comment.text.start_with?(/#[$:]/)
199
218
  end
219
+
220
+ def allow_yard_comment_block_separator?
221
+ cop_config['AllowYARDCommentBlockSeparator']
222
+ end
223
+
224
+ def yard_comment_block_separator?(comment)
225
+ allow_yard_comment_block_separator? && comment.text.match?(/\A#-\s*\z/)
226
+ end
200
227
  end
201
228
  end
202
229
  end
@@ -65,6 +65,7 @@ module RuboCop
65
65
  include AllowedPattern
66
66
  include RangeHelp
67
67
  include LineLengthHelp
68
+ include EndlessMethodRewriter
68
69
  extend AutoCorrector
69
70
 
70
71
  exclude_limit 'Max'
@@ -92,8 +93,15 @@ module RuboCop
92
93
  alias on_hash on_potential_breakable_node
93
94
  alias on_send on_potential_breakable_node
94
95
  alias on_csend on_potential_breakable_node
95
- alias on_def on_potential_breakable_node
96
- alias on_defs on_potential_breakable_node
96
+
97
+ def on_def(node)
98
+ if node.endless?
99
+ track_endless_method(node)
100
+ else
101
+ check_for_breakable_node(node)
102
+ end
103
+ end
104
+ alias on_defs on_def
97
105
 
98
106
  def on_new_investigation
99
107
  return unless processed_source.raw_source.include?(';')
@@ -111,6 +119,70 @@ module RuboCop
111
119
 
112
120
  attr_accessor :breakable_range
113
121
 
122
+ def track_endless_method(node)
123
+ line_index = node.first_line - processed_source.buffer.first_line
124
+ endless_methods_by_line[line_index] = node
125
+ end
126
+
127
+ def handle_endless_method_line(line, line_index)
128
+ if require_endless_methods?
129
+ return register_required_endless_method_offense(line, line_index)
130
+ end
131
+
132
+ register_endless_method_offense(line, line_index)
133
+ end
134
+
135
+ def require_endless_methods?
136
+ config.cop_enabled?('Style/EndlessMethod') &&
137
+ config.for_cop('Style/EndlessMethod')['EnforcedStyle'] == 'require_always'
138
+ end
139
+
140
+ def register_endless_method_offense(line, line_index)
141
+ message = format(MSG, length: line_length(line), max: max)
142
+ loc = excess_range(nil, line, line_index)
143
+
144
+ add_offense(loc, message: message) do |corrector|
145
+ self.max = line_length(line)
146
+
147
+ correct_to_multiline(corrector, endless_methods_by_line[line_index])
148
+ end
149
+ end
150
+
151
+ def register_required_endless_method_offense(line, line_index)
152
+ node = endless_methods_by_line[line_index]
153
+ loc = excess_range(nil, line, line_index)
154
+
155
+ add_offense(loc, message: format(MSG, length: line_length(line), max: max)) do |corrector|
156
+ self.max = line_length(line)
157
+
158
+ if correctable_endless_method_block?(node)
159
+ correct_endless_method_block_to_multiline(corrector, node)
160
+ end
161
+ end
162
+ end
163
+
164
+ def correctable_endless_method_block?(node)
165
+ block_node = node.body
166
+
167
+ block_node&.type?(:any_block) &&
168
+ block_node.braces? &&
169
+ block_node.single_line? &&
170
+ block_node.body &&
171
+ !receiver_contains_heredoc?(block_node)
172
+ end
173
+
174
+ def correct_endless_method_block_to_multiline(corrector, node)
175
+ block_node = node.body
176
+ block_arguments = block_node.arguments? ? " #{block_node.arguments.source}" : ''
177
+ replacement = [
178
+ "#{block_node.send_node.source} do#{block_arguments}",
179
+ "#{indent(node, offset: 2)}#{block_node.body.source}",
180
+ "#{indent(node)}end"
181
+ ].join("\n")
182
+
183
+ corrector.replace(block_node, replacement)
184
+ end
185
+
114
186
  def check_for_breakable_node(node)
115
187
  breakable_node = extract_breakable_node(node, max)
116
188
  return if breakable_node.nil?
@@ -237,6 +309,10 @@ module RuboCop
237
309
  @breakable_range_by_line_index ||= {}
238
310
  end
239
311
 
312
+ def endless_methods_by_line
313
+ @endless_methods_by_line ||= {}
314
+ end
315
+
240
316
  def breakable_string_delimiters
241
317
  @breakable_string_delimiters ||= {}
242
318
  end
@@ -252,10 +328,15 @@ module RuboCop
252
328
  [max - indentation_difference(line), 0].max
253
329
  end
254
330
 
255
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
331
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
256
332
  def check_line(line, line_index)
257
333
  return if line_length(line) <= max
258
334
  return if allowed_line?(line, line_index)
335
+
336
+ if endless_methods_by_line.key?(line_index)
337
+ return handle_endless_method_line(line, line_index)
338
+ end
339
+
259
340
  if allow_rbs_inline_annotation? && rbs_inline_annotation_on_source_line?(line_index)
260
341
  return
261
342
  end
@@ -267,7 +348,7 @@ module RuboCop
267
348
 
268
349
  register_offense(excess_range(nil, line, line_index), line, line_index)
269
350
  end
270
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
351
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
271
352
 
272
353
  def allowed_line?(line, line_index)
273
354
  matches_allowed_pattern?(line) ||
@@ -313,10 +394,6 @@ module RuboCop
313
394
  end
314
395
  alias max_line_length max
315
396
 
316
- def allow_heredoc?
317
- allowed_heredoc
318
- end
319
-
320
397
  def allowed_heredoc
321
398
  cop_config['AllowHeredoc']
322
399
  end
@@ -344,10 +421,6 @@ module RuboCop
344
421
  end
345
422
  end
346
423
 
347
- def line_in_heredoc?(line_number)
348
- heredocs.any? { |range, _delimiter| range.cover?(line_number) }
349
- end
350
-
351
424
  def receiver_contains_heredoc?(node)
352
425
  return false unless (receiver = node.receiver)
353
426
  return true if receiver.any_str_type? && receiver.heredoc?
@@ -420,11 +493,16 @@ module RuboCop
420
493
  # The maximum allowed length of a string value is:
421
494
  # `Max` - end delimiter (quote) - continuation characters (space and slash)
422
495
  max_length = max - 3
423
- # If the string is on the same line as its parent, offset by the column difference
424
- # (Only apply when on same line to avoid negative offsets for multi-line dstr)
425
- if same_line?(node, node.parent)
426
- max_length -= column_offset_between(node.loc, node.parent.loc)
427
- end
496
+ # Offset by the string's starting column so the broken line actually fits
497
+ # within `Max`. When on the same line as its parent, use the column difference;
498
+ # otherwise the string is indented on its own line, so subtract that indentation.
499
+ # (Without this, an indented string under a multi-line parent never shortens
500
+ # below `Max` and the autocorrect loops, inserting empty `"" \` fragments.)
501
+ max_length -= if same_line?(node, node.parent)
502
+ column_offset_between(node.loc, node.parent.loc)
503
+ else
504
+ node.loc.column
505
+ end
428
506
  node.source[0...(max_length)]
429
507
  end
430
508
  end
@@ -73,7 +73,12 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def find_pair_ancestor(node)
76
- node.each_ancestor.find(&:pair_type?)
76
+ node.each_ancestor do |ancestor|
77
+ return ancestor if ancestor.pair_type?
78
+ break if grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
79
+ end
80
+
81
+ nil
77
82
  end
78
83
 
79
84
  def unwrap_block_node(node)
@@ -405,7 +410,7 @@ module RuboCop
405
410
 
406
411
  def find_continuation_node(node)
407
412
  receiver = node.receiver
408
- return receiver.send_node if single_line_block_receiver?(receiver)
413
+ return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
409
414
  return unless receiver.call_type? && receiver.loc.dot
410
415
  return receiver if receiver.receiver.begin_type? && node.block_node.single_line?
411
416
  return unless receiver.loc.dot.line > receiver.receiver.last_line
@@ -417,9 +422,19 @@ module RuboCop
417
422
  receiver.single_line? && receiver.any_block_type?
418
423
  end
419
424
 
425
+ def leftmost_call_on_same_line(node)
426
+ current = node.any_block_type? ? node.send_node : node
427
+ while current.receiver&.call_type? &&
428
+ current.receiver.loc?(:dot) &&
429
+ current.receiver.loc.dot.line == current.loc.dot.line
430
+ current = current.receiver
431
+ end
432
+ current
433
+ end
434
+
420
435
  def handle_descendant_block(node)
421
436
  receiver = node.receiver
422
- return receiver.send_node if single_line_block_receiver?(receiver)
437
+ return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
423
438
 
424
439
  block_node = node.each_descendant(:any_block).first
425
440
  return unless block_node&.multiline?
@@ -49,6 +49,7 @@ module RuboCop
49
49
  class RedundantLineBreak < Base
50
50
  include CheckAssignment
51
51
  include CheckSingleLineSuitability
52
+ include ReparsedEquivalence
52
53
  extend AutoCorrector
53
54
 
54
55
  MSG = 'Redundant line break detected.'
@@ -82,12 +83,88 @@ module RuboCop
82
83
  end
83
84
 
84
85
  def register_offense(node)
86
+ # The exact single-line correction is verified to parse equivalently
87
+ # before the offense is registered, so a join that would change how
88
+ # the code parses is never reported or offered.
89
+ return if verified_by_reparse([node], oversized: :verify).empty?
90
+
85
91
  add_offense(node) do |corrector|
86
92
  corrector.replace(node, to_single_line(node.source).strip)
87
93
  end
88
94
  ignore_node(node)
89
95
  end
90
96
 
97
+ def apply_reparse_correction(corrector, node)
98
+ corrector.replace(node, to_single_line(node.source).strip)
99
+ end
100
+
101
+ # Joining lines shifts the value of any later `__LINE__`, exactly as
102
+ # any other line-removing correction does; neutralize it on both sides
103
+ # with an identifier, which stays valid in every position the keyword
104
+ # can appear in.
105
+ def preprocess_reparsed_source(source)
106
+ source.gsub(/\b__LINE__\b/, '__LINE0__')
107
+ end
108
+
109
+ def normalize_reparsed_ast(node)
110
+ fold_string_concatenation(node)
111
+ end
112
+
113
+ # Joining lines merges split string literals (`"a" \<newline> "b"` into
114
+ # `"ab"`) and turns mixed-quote pairs into `+` concatenation, which
115
+ # changes the tree but not the resulting string. Normalize all string
116
+ # concatenation to a canonical form before comparing: nested
117
+ # concatenations are flattened and adjacent literal parts merged.
118
+ def fold_string_concatenation(node)
119
+ return node unless node.is_a?(::Parser::AST::Node)
120
+
121
+ children = node.children.map { |child| fold_string_concatenation(child) }
122
+ node = node.updated(nil, children)
123
+
124
+ parts = string_concatenation_parts(node)
125
+ return node unless parts
126
+
127
+ merged = merge_string_parts(parts)
128
+ if merged.one? && merged.first.str_type?
129
+ node.updated(:str, merged.first.children)
130
+ else
131
+ node.updated(:dstr, merged)
132
+ end
133
+ end
134
+
135
+ def string_concatenation_parts(node)
136
+ case node.type
137
+ when :dstr
138
+ node.children
139
+ when :send
140
+ parts = [node.receiver, *node.arguments]
141
+ parts if node.method?(:+) && parts.all? { |part| stringish?(part) }
142
+ end
143
+ end
144
+
145
+ def merge_string_parts(parts)
146
+ flattened = parts.flat_map do |part|
147
+ part.is_a?(::Parser::AST::Node) && part.dstr_type? ? part.children : [part]
148
+ end
149
+
150
+ flattened.chunk_while { |left, right| plain_string?(left) && plain_string?(right) }
151
+ .map { |chunk| merge_plain_strings(chunk) }
152
+ end
153
+
154
+ def merge_plain_strings(chunk)
155
+ return chunk.first if chunk.one?
156
+
157
+ chunk.first.updated(:str, [chunk.map { |part| part.children.first }.join])
158
+ end
159
+
160
+ def stringish?(node)
161
+ node.is_a?(::Parser::AST::Node) && %i[str dstr].include?(node.type)
162
+ end
163
+
164
+ def plain_string?(node)
165
+ node.is_a?(::Parser::AST::Node) && node.str_type? && node.children.one?
166
+ end
167
+
91
168
  def offense?(node)
92
169
  return false unless node.multiline? && suitable_as_single_line?(node)
93
170
  return require_backslash?(node) if node.operator_keyword?
@@ -204,10 +204,14 @@ module RuboCop
204
204
 
205
205
  def autocorrect(corrector, range, right_operand)
206
206
  range_source = range.source
207
+ # Match the operator exactly, not by substring, so compound assignments
208
+ # like `**=` and `/=` are not mistaken for `**` and `/` (which would drop
209
+ # the `=` and silently change the program's behavior).
210
+ operator = range_source.strip
207
211
 
208
- if range_source.include?('**') && !space_around_exponent_operator?
212
+ if operator == '**' && !space_around_exponent_operator?
209
213
  corrector.replace(range, '**')
210
- elsif range_source.include?('/') && !space_around_slash_operator?(right_operand)
214
+ elsif operator == '/' && !space_around_slash_operator?(right_operand)
211
215
  corrector.replace(range, '/')
212
216
  elsif range_source.end_with?("\n")
213
217
  corrector.replace(range, " #{range_source.strip}\n")
@@ -94,10 +94,10 @@ module RuboCop
94
94
  return empty_offenses(node, left, right, EMPTY_MSG)
95
95
  end
96
96
 
97
- start_ok = next_to_newline?(node, left)
97
+ start_ok = next_to_newline?(tokens, left)
98
98
  end_ok = node.single_line? ? false : end_has_own_line?(right)
99
99
 
100
- issue_offenses(node, left, right, start_ok, end_ok)
100
+ issue_offenses(node, tokens, left, right, start_ok, end_ok)
101
101
  end
102
102
  alias on_array_pattern on_array
103
103
 
@@ -117,7 +117,7 @@ module RuboCop
117
117
  elsif style == :space
118
118
  SpaceCorrector.add_space(processed_source, corrector, left, right)
119
119
  else
120
- compact_corrections(corrector, node, left, right)
120
+ compact_corrections(corrector, tokens, left, right)
121
121
  end
122
122
  end
123
123
 
@@ -134,8 +134,8 @@ module RuboCop
134
134
  cop_config['EnforcedStyleForEmptyBrackets']
135
135
  end
136
136
 
137
- def next_to_newline?(node, token)
138
- processed_source.tokens_within(node)[index_for(node, token) + 1].line != token.line
137
+ def next_to_newline?(tokens, token)
138
+ tokens[index_for(tokens, token) + 1].line != token.line
139
139
  end
140
140
 
141
141
  def end_has_own_line?(token)
@@ -145,68 +145,67 @@ module RuboCop
145
145
  !/\S/.match?(processed_source.lines[line][0..col])
146
146
  end
147
147
 
148
- def index_for(node, token)
149
- processed_source.tokens_within(node).index(token)
148
+ def index_for(tokens, token)
149
+ tokens.index(token)
150
150
  end
151
151
 
152
152
  def line_and_column_for(token)
153
153
  [token.line - 1, token.column - 1]
154
154
  end
155
155
 
156
- def issue_offenses(node, left, right, start_ok, end_ok)
156
+ # rubocop:disable Metrics/ParameterLists
157
+ def issue_offenses(node, tokens, left, right, start_ok, end_ok)
157
158
  case style
158
159
  when :no_space
159
- start_ok = next_to_comment?(node, left)
160
+ start_ok = next_to_comment?(tokens, left)
160
161
  no_space_offenses(node, left, right, MSG, start_ok: start_ok, end_ok: end_ok)
161
162
  when :space
162
163
  space_offenses(node, left, right, MSG, start_ok: start_ok, end_ok: end_ok)
163
164
  else
164
- compact_offenses(node, left, right, start_ok, end_ok)
165
+ compact_offenses(node, tokens, left, right, start_ok, end_ok)
165
166
  end
166
167
  end
168
+ # rubocop:enable Metrics/ParameterLists
167
169
 
168
- def next_to_comment?(node, token)
169
- processed_source.tokens_within(node)[index_for(node, token) + 1].comment?
170
+ def next_to_comment?(tokens, token)
171
+ tokens[index_for(tokens, token) + 1].comment?
170
172
  end
171
173
 
172
- def compact_offenses(node, left, right, start_ok, end_ok)
173
- if qualifies_for_compact?(node, left, side: :left)
174
+ # rubocop:disable Metrics/ParameterLists
175
+ def compact_offenses(node, tokens, left, right, start_ok, end_ok)
176
+ if qualifies_for_compact?(tokens, left, side: :left)
174
177
  compact_offense(node, left, side: :left)
175
- elsif !multi_dimensional_array?(node, left, side: :left)
178
+ elsif !multi_dimensional_array?(tokens, left, side: :left)
176
179
  space_offenses(node, left, nil, MSG, start_ok: start_ok, end_ok: true)
177
180
  end
178
- if qualifies_for_compact?(node, right)
181
+
182
+ if qualifies_for_compact?(tokens, right)
179
183
  compact_offense(node, right)
180
- elsif !multi_dimensional_array?(node, right)
184
+ elsif !multi_dimensional_array?(tokens, right)
181
185
  space_offenses(node, nil, right, MSG, start_ok: true, end_ok: end_ok)
182
186
  end
183
187
  end
188
+ # rubocop:enable Metrics/ParameterLists
184
189
 
185
- def qualifies_for_compact?(node, token, side: :right)
190
+ def qualifies_for_compact?(tokens, token, side: :right)
186
191
  if side == :right
187
- multi_dimensional_array?(node, token) && token.space_before?
192
+ multi_dimensional_array?(tokens, token) && token.space_before?
188
193
  else
189
- multi_dimensional_array?(node, token, side: :left) && token.space_after?
194
+ multi_dimensional_array?(tokens, token, side: :left) && token.space_after?
190
195
  end
191
196
  end
192
197
 
193
- def multi_dimensional_array?(node, token, side: :right)
198
+ def multi_dimensional_array?(tokens, token, side: :right)
194
199
  offset = side == :right ? -1 : +1
195
- i = index_for(node, token) + offset
196
- i += offset while processed_source.tokens_within(node)[i].new_line?
200
+ i = index_for(tokens, token) + offset
201
+ i += offset while tokens[i].new_line?
197
202
  if side == :right
198
- processed_source.tokens_within(node)[i].right_bracket?
203
+ tokens[i].right_bracket?
199
204
  else
200
- processed_source.tokens_within(node)[i].left_bracket?
205
+ tokens[i].left_bracket?
201
206
  end
202
207
  end
203
208
 
204
- def next_to_bracket?(token, side: :right)
205
- line_index, col = line_and_column_for(token)
206
- line = processed_source.lines[line_index]
207
- side == :right ? line[col] == ']' : line[col + 2] == '['
208
- end
209
-
210
209
  def compact_offense(node, token, side: :right)
211
210
  if side == :right
212
211
  space_offense(node, token, :left, MSG, NO_SPACE_COMMAND)
@@ -215,14 +214,14 @@ module RuboCop
215
214
  end
216
215
  end
217
216
 
218
- def compact_corrections(corrector, node, left, right)
219
- if multi_dimensional_array?(node, left, side: :left)
217
+ def compact_corrections(corrector, tokens, left, right)
218
+ if multi_dimensional_array?(tokens, left, side: :left)
220
219
  compact(corrector, left, :right)
221
220
  elsif !left.space_after?
222
221
  corrector.insert_after(left.pos, ' ')
223
222
  end
224
223
 
225
- if multi_dimensional_array?(node, right)
224
+ if multi_dimensional_array?(tokens, right)
226
225
  compact(corrector, right, :left)
227
226
  elsif !right.space_before?
228
227
  corrector.insert_before(right.pos, ' ')