rubocop 1.89.0 → 1.91.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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +309 -14
  3. data/config/obsoletion.yml +9 -0
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/cached_data.rb +7 -2
  6. data/lib/rubocop/changed_files.rb +112 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +52 -1
  8. data/lib/rubocop/cli.rb +11 -7
  9. data/lib/rubocop/comment_config/directive_range.rb +34 -0
  10. data/lib/rubocop/comment_config/disable_next.rb +143 -0
  11. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  12. data/lib/rubocop/comment_config.rb +50 -64
  13. data/lib/rubocop/config.rb +33 -5
  14. data/lib/rubocop/config_loader.rb +13 -6
  15. data/lib/rubocop/config_loader_resolver.rb +50 -9
  16. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  17. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  18. data/lib/rubocop/config_obsoletion.rb +1 -1
  19. data/lib/rubocop/config_validator.rb +19 -5
  20. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  21. data/lib/rubocop/cop/badge.rb +1 -1
  22. data/lib/rubocop/cop/base.rb +49 -5
  23. data/lib/rubocop/cop/corrector.rb +3 -3
  24. data/lib/rubocop/cop/correctors.rb +2 -2
  25. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  26. data/lib/rubocop/cop/generator.rb +1 -1
  27. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  28. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  29. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  31. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  32. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  33. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  34. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  35. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  36. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  37. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  38. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  39. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  40. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  41. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  42. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  43. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  44. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  45. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  46. data/lib/rubocop/cop/layout/line_length.rb +20 -3
  47. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  48. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  49. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  50. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  51. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  52. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  53. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  54. data/lib/rubocop/cop/lint/argument_mismatch.rb +99 -0
  55. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  56. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  57. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +165 -15
  58. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  59. data/lib/rubocop/cop/lint/duplicate_methods.rb +113 -15
  60. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  61. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  62. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  63. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  64. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -8
  65. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  66. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  67. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  68. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  69. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  70. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  71. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  72. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  73. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +122 -29
  74. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  75. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  76. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  77. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  78. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  79. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  80. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  81. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  82. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  83. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  84. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  85. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  86. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  87. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  88. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  89. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  90. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  91. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  92. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  93. data/lib/rubocop/cop/lint/void.rb +1 -2
  94. data/lib/rubocop/cop/lint.rb +3 -0
  95. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  96. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  97. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  98. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  99. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  100. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  101. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  102. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +7 -4
  103. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  104. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  105. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  106. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  107. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +62 -6
  108. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  109. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  110. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  111. data/lib/rubocop/cop/mixin.rb +6 -2
  112. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  113. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  114. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  116. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  117. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  118. data/lib/rubocop/cop/offense.rb +27 -4
  119. data/lib/rubocop/cop/registry.rb +43 -11
  120. data/lib/rubocop/cop/severity.rb +12 -0
  121. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  122. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  123. data/lib/rubocop/cop/style/arguments_forwarding.rb +5 -8
  124. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  125. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  126. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  127. data/lib/rubocop/cop/style/case_like_if.rb +5 -7
  128. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  129. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  130. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  131. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  132. data/lib/rubocop/cop/style/constant_visibility.rb +6 -3
  133. data/lib/rubocop/cop/style/copyright.rb +1 -2
  134. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  135. data/lib/rubocop/cop/style/directive_scope.rb +304 -0
  136. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +83 -13
  137. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  138. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  139. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  140. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  141. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  142. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  143. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  144. data/lib/rubocop/cop/style/file_null.rb +2 -2
  145. data/lib/rubocop/cop/style/float_division.rb +10 -16
  146. data/lib/rubocop/cop/style/for.rb +5 -0
  147. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  148. data/lib/rubocop/cop/style/guard_clause.rb +5 -2
  149. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  150. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  151. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  152. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  153. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  154. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  155. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  156. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  157. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  161. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  162. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  163. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  164. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  165. data/lib/rubocop/cop/style/nil_comparison.rb +36 -9
  166. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  167. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  168. data/lib/rubocop/cop/style/not.rb +3 -1
  169. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  170. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  171. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  172. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  173. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  175. data/lib/rubocop/cop/style/redundant_condition.rb +6 -8
  176. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +11 -3
  177. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  178. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  179. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  180. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -5
  181. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  182. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  183. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  184. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  185. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  186. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  187. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  188. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  189. data/lib/rubocop/cop/style/sample.rb +11 -6
  190. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  191. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  192. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  193. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  194. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  195. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  196. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  197. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  198. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  199. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  200. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  201. data/lib/rubocop/cop/style/time_now.rb +41 -0
  202. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  203. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  204. data/lib/rubocop/cop/style.rb +2 -1
  205. data/lib/rubocop/cop/team.rb +11 -3
  206. data/lib/rubocop/cop/util.rb +3 -5
  207. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  208. data/lib/rubocop/cops_documentation_generator.rb +26 -8
  209. data/lib/rubocop/directive_comment.rb +73 -10
  210. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  211. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  212. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  213. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  214. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  215. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  216. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  217. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  218. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  219. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  220. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  221. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  222. data/lib/rubocop/formatter.rb +1 -0
  223. data/lib/rubocop/lockfile.rb +18 -0
  224. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  225. data/lib/rubocop/lsp/routes.rb +48 -19
  226. data/lib/rubocop/magic_comment.rb +27 -0
  227. data/lib/rubocop/mcp/server.rb +1 -2
  228. data/lib/rubocop/options.rb +69 -7
  229. data/lib/rubocop/path_util.rb +2 -3
  230. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  231. data/lib/rubocop/plugin/loader.rb +1 -1
  232. data/lib/rubocop/rake_task.rb +1 -2
  233. data/lib/rubocop/remote_config.rb +4 -4
  234. data/lib/rubocop/result_cache.rb +16 -5
  235. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  236. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  237. data/lib/rubocop/runner.rb +90 -44
  238. data/lib/rubocop/server/cache.rb +1 -2
  239. data/lib/rubocop/server/cli.rb +2 -4
  240. data/lib/rubocop/server.rb +14 -0
  241. data/lib/rubocop/target_finder.rb +4 -1
  242. data/lib/rubocop/todo_audit.rb +111 -0
  243. data/lib/rubocop/unified_diff.rb +257 -0
  244. data/lib/rubocop/version.rb +3 -5
  245. data/lib/rubocop.rb +7 -1
  246. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  247. metadata +18 -6
  248. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -41,6 +41,7 @@ module RuboCop
41
41
 
42
42
  @aligned_comments = aligned_locations(processed_source.comments.map(&:loc))
43
43
  @corrected = Set.new if force_equal_sign_alignment?
44
+ prepare_alignment_data
44
45
 
45
46
  processed_source.tokens.each_cons(2) do |token1, token2|
46
47
  check_tokens(processed_source.ast, token1, token2)
@@ -95,19 +96,120 @@ module RuboCop
95
96
  end_pos = token2.begin_pos - 1
96
97
  return if end_pos <= start_pos
97
98
 
98
- return if allow_for_alignment? && aligned_tok?(token2)
99
+ return if allow_for_alignment? && aligned_tok?(token1, token2)
99
100
 
100
101
  yield range_between(start_pos, end_pos)
101
102
  end
102
103
 
103
- def aligned_tok?(token)
104
+ def aligned_tok?(previous_token, token)
104
105
  if token.comment?
105
106
  @aligned_comments.include?(token.line)
106
107
  else
107
- aligned_with_something?(token.pos)
108
+ aligned_with_something?(token.pos) &&
109
+ (ASSIGNMENT_OR_COMPARISON_TOKENS.include?(token.type) ||
110
+ spacing_varies?(previous_token, token))
108
111
  end
109
112
  end
110
113
 
114
+ def spacing_varies?(previous_token, token)
115
+ by_column, typed = group_profile(token.line)
116
+ column = token.pos.column
117
+ lines, spacings = typed[[column, token.type]]
118
+ entries = by_column[column]
119
+
120
+ if lines.size > 1
121
+ uniform_alignment_allowed?(token, lines, spacings)
122
+ elsif entries.size > 1
123
+ distinct_spacings(entries).size > 1
124
+ else
125
+ nearest_spacing_varies?(previous_token, token)
126
+ end
127
+ end
128
+
129
+ def uniform_alignment_allowed?(token, lines, spacings)
130
+ return @alignment_verdicts[lines] if @alignment_verdicts.key?(lines)
131
+
132
+ @alignment_verdicts[lines] =
133
+ spacings.uniq.size > 1 || aligned_on_other_column?(token, lines)
134
+ end
135
+
136
+ def aligned_on_other_column?(token, lines)
137
+ line_set = Set.new(lines)
138
+ by_column, = group_profile(token.line)
139
+
140
+ by_column.any? do |column, entries|
141
+ next false if column == token.pos.column
142
+
143
+ shared = entries.filter_map { |line, spacing| spacing if line_set.include?(line) }
144
+ shared.uniq.size > 1
145
+ end
146
+ end
147
+
148
+ def distinct_spacings(entries)
149
+ entries.map(&:last).uniq
150
+ end
151
+
152
+ def group_profile(line_number)
153
+ group = alignment_lines(line_number)
154
+
155
+ @group_profiles[group] ||= build_group_profile(group)
156
+ end
157
+
158
+ def build_group_profile(group)
159
+ by_column = Hash.new { |hash, key| hash[key] = [] }
160
+ typed = Hash.new { |hash, key| hash[key] = [[], []] }
161
+
162
+ group.each { |line| record_line_spacings(by_column, typed, line) }
163
+
164
+ [by_column, typed]
165
+ end
166
+
167
+ def record_line_spacings(by_column, typed, line)
168
+ @tokens_by_line[line]&.each_cons(2) do |previous_token, token|
169
+ spacing = token.begin_pos - previous_token.end_pos
170
+ by_column[token.pos.column] << [line, spacing]
171
+ lines, spacings = typed[[token.pos.column, token.type]]
172
+ lines << line
173
+ spacings << spacing
174
+ end
175
+ end
176
+
177
+ def nearest_spacing_varies?(previous_token, token)
178
+ spacing = token.begin_pos - previous_token.end_pos
179
+ line_ranges = [(token.line - 1).downto(1),
180
+ (token.line + 1).upto(processed_source.lines.length)]
181
+
182
+ line_ranges.any? do |line_numbers|
183
+ nearest_spacing_differs?(line_numbers, token, spacing)
184
+ end
185
+ end
186
+
187
+ def nearest_spacing_differs?(line_numbers, token, spacing)
188
+ line_numbers.each do |line|
189
+ break if definition_boundary_lines.include?(line)
190
+ next if aligned_comment_lines.include?(line)
191
+
192
+ other_spacing = spacing_before_token_at(line, token.pos.column)
193
+ return other_spacing != spacing if other_spacing
194
+ end
195
+
196
+ false
197
+ end
198
+
199
+ def spacing_before_token_at(line_number, column)
200
+ tokens = @tokens_by_line[line_number]
201
+ index = tokens&.index { |token| token.pos.column == column }
202
+ return unless index&.positive?
203
+
204
+ tokens[index].begin_pos - tokens[index - 1].end_pos
205
+ end
206
+
207
+ def prepare_alignment_data
208
+ @group_profiles = {}.compare_by_identity
209
+ @alignment_verdicts = {}.compare_by_identity
210
+ @tokens_by_line = processed_source.tokens.group_by(&:line)
211
+ end
212
+
111
213
  def ignored_range?(ast, start_pos)
112
214
  ignored_ranges(ast).any? { |r| r.include?(start_pos) }
113
215
  end
@@ -156,7 +156,8 @@ module RuboCop
156
156
  return unless should_check?(node)
157
157
  return if same_line?(node, node.first_argument)
158
158
  return if enforce_first_argument_with_fixed_indentation? &&
159
- !enable_layout_first_method_argument_line_break?
159
+ (!enable_layout_first_method_argument_line_break? ||
160
+ conflicts_with_fixed_indentation_alignment?(node))
160
161
 
161
162
  indent = base_indentation(node) + configured_indentation_width
162
163
 
@@ -309,6 +310,25 @@ module RuboCop
309
310
  def enable_layout_first_method_argument_line_break?
310
311
  config.cop_enabled?('Layout/FirstMethodArgumentLineBreak')
311
312
  end
313
+
314
+ def conflicts_with_fixed_indentation_alignment?(node)
315
+ return false unless special_inner_call_indentation?(node)
316
+ return false unless argument_alignment_applies?(node)
317
+
318
+ base_indentation(node) != indentation_of_method_line(node)
319
+ end
320
+
321
+ def argument_alignment_applies?(node)
322
+ return false if !node.call_type? || node.method?(:[]=)
323
+
324
+ node.arguments.size >= 2 ||
325
+ (node.first_argument.hash_type? && node.first_argument.pairs.count >= 2)
326
+ end
327
+
328
+ def indentation_of_method_line(node)
329
+ lineno = node.loc.selector ? node.loc.selector.line : node.loc.begin.line
330
+ processed_source.lines[lineno - 1] =~ /\S/
331
+ end
312
332
  end
313
333
  end
314
334
  end
@@ -89,15 +89,16 @@ module RuboCop
89
89
  'in an array, relative to %<base_description>s.'
90
90
 
91
91
  def on_array(node)
92
- return if style != :consistent && enforce_first_argument_with_fixed_indentation?
92
+ return unless node.loc.begin
93
+ return if autocorrect_incompatible_with_other_cops?(node, nil)
93
94
 
94
- check(node, nil) if node.loc.begin
95
+ check(node, nil)
95
96
  end
96
97
 
97
98
  def on_send(node)
98
- return if style != :consistent && enforce_first_argument_with_fixed_indentation?
99
-
100
99
  each_argument_node(node, :array) do |array_node, left_parenthesis|
100
+ next if autocorrect_incompatible_with_other_cops?(array_node, left_parenthesis)
101
+
101
102
  check(array_node, left_parenthesis)
102
103
  end
103
104
  end
@@ -105,6 +106,18 @@ module RuboCop
105
106
 
106
107
  private
107
108
 
109
+ def autocorrect_incompatible_with_other_cops?(array_node, left_parenthesis)
110
+ return false unless enforce_first_argument_with_fixed_indentation?
111
+ return true if style != :consistent
112
+
113
+ # `Layout/ArrayAlignment` does not align single-element arrays.
114
+ return false if array_node.children.size < 2
115
+
116
+ _base_column, indent_base_type =
117
+ indent_base(array_node.loc.begin, array_node.values.first, left_parenthesis)
118
+ indent_base_type != :start_of_line
119
+ end
120
+
108
121
  def autocorrect(corrector, node)
109
122
  AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
110
123
  end
@@ -53,6 +53,7 @@ module RuboCop
53
53
  def on_def(node)
54
54
  return if node.arguments.empty?
55
55
  return if node.arguments.loc.begin.nil?
56
+ return if autocorrect_incompatible_with_other_cops?(node)
56
57
 
57
58
  check(node)
58
59
  end
@@ -60,6 +61,11 @@ module RuboCop
60
61
 
61
62
  private
62
63
 
64
+ def autocorrect_incompatible_with_other_cops?(node)
65
+ node.arguments.size >= 2 &&
66
+ style == :align_parentheses && enforce_parameter_with_fixed_indentation?
67
+ end
68
+
63
69
  def autocorrect(corrector, node)
64
70
  AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
65
71
  end
@@ -95,6 +101,11 @@ module RuboCop
95
101
  base_description: base_description
96
102
  )
97
103
  end
104
+
105
+ def enforce_parameter_with_fixed_indentation?
106
+ parameter_alignment_config = config.for_enabled_cop('Layout/ParameterAlignment')
107
+ parameter_alignment_config['EnforcedStyle'] == 'with_fixed_indentation'
108
+ end
98
109
  end
99
110
  end
100
111
  end
@@ -330,7 +330,11 @@ module RuboCop
330
330
  end
331
331
 
332
332
  def correct_no_value(corrector, key_delta, key)
333
- adjust(corrector, key_delta, key)
333
+ adjust(corrector, clamped_key_delta(key_delta, key), key)
334
+ end
335
+
336
+ def clamped_key_delta(key_delta, key)
337
+ [key_delta, -key.column].max
334
338
  end
335
339
 
336
340
  def correct_key_value(corrector, delta, key, value, separator)
@@ -341,10 +345,7 @@ module RuboCop
341
345
  value_delta = delta[:value] || 0
342
346
  key_delta = delta[:key] || 0
343
347
 
344
- key_column = key.column
345
- key_delta = -key_column if key_delta < -key_column
346
-
347
- adjust(corrector, key_delta, key)
348
+ adjust(corrector, clamped_key_delta(key_delta, key), key)
348
349
  adjust(corrector, separator_delta, separator)
349
350
  adjust(corrector, value_delta, value)
350
351
  end
@@ -400,7 +400,7 @@ module RuboCop
400
400
  if body_node.rescue_type?
401
401
  check_rescue?(body_node)
402
402
  elsif body_node.ensure_type?
403
- block_body, = *body_node # rubocop:disable InternalAffairs/NodeDestructuring
403
+ block_body, = *body_node # rubocop:disable InternalAffairs/NodeDestructuring -- `EnsureNode` has no accessor for the protected body
404
404
  if block_body&.rescue_type?
405
405
  check_rescue?(block_body)
406
406
  else
@@ -440,7 +440,7 @@ module RuboCop
440
440
  begin_pos - indentation
441
441
  end
442
442
 
443
- pos = indentation >= 0 ? ind..begin_pos : begin_pos..ind
443
+ pos = ind <= begin_pos ? ind..begin_pos : begin_pos..ind
444
444
  range_between(pos.begin, pos.end)
445
445
  end
446
446
 
@@ -87,7 +87,7 @@ module RuboCop
87
87
  corrector.replace(range, correction)
88
88
  end
89
89
 
90
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
91
91
  def ignored_literal_ranges(ast)
92
92
  # which lines start inside a string literal?
93
93
  return [] if ast.nil?
@@ -106,7 +106,6 @@ module RuboCop
106
106
  end
107
107
  end
108
108
  end
109
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
109
 
111
110
  def comment_ranges(comments)
112
111
  comments.map(&:source_range)
@@ -104,6 +104,11 @@ module RuboCop
104
104
  alias on_defs on_def
105
105
 
106
106
  def on_new_investigation
107
+ @breakable_range_by_line_index = {}
108
+ @breakable_string_delimiters = {}
109
+ @endless_methods_by_line = {}
110
+ @heredocs = nil
111
+
107
112
  return unless processed_source.raw_source.include?(';')
108
113
 
109
114
  check_for_breakable_semicolons(processed_source)
@@ -288,7 +293,7 @@ module RuboCop
288
293
  source_range = node.source_range
289
294
  relevant_substr = largest_possible_string(node)
290
295
 
291
- if (space_pos = relevant_substr.rindex(/\s/))
296
+ if (space_pos = breakable_space_position(node, relevant_substr))
292
297
  source_range.resize(space_pos + 1)
293
298
  elsif (escape_pos = relevant_substr.rindex(/\\(u[\da-f]{0,4}|x[\da-f]{0,2})?\z/))
294
299
  source_range.resize(escape_pos)
@@ -300,6 +305,19 @@ module RuboCop
300
305
  end
301
306
  end
302
307
 
308
+ def breakable_space_position(node, substr)
309
+ limit = string_content_length(node) - 1
310
+ space_pos = substr.rindex(/\s/)
311
+ space_pos = substr[0, limit].rindex(/\s/) if space_pos && space_pos + 1 > limit
312
+ space_pos
313
+ end
314
+
315
+ def string_content_length(node)
316
+ content_end = node.loc?(:end) ? node.loc.end.begin_pos : node.source_range.end_pos
317
+
318
+ content_end - node.source_range.begin_pos
319
+ end
320
+
303
321
  def breakable_dstr_begin_position(node)
304
322
  source_range = node.source_range
305
323
  source_range.begin_pos if source_range.column < max && source_range.last_column >= max
@@ -328,7 +346,7 @@ module RuboCop
328
346
  [max - indentation_difference(line), 0].max
329
347
  end
330
348
 
331
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
349
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
332
350
  def check_line(line, line_index)
333
351
  return if line_length(line) <= max
334
352
  return if allowed_line?(line, line_index)
@@ -348,7 +366,6 @@ module RuboCop
348
366
 
349
367
  register_offense(excess_range(nil, line, line_index), line, line_index)
350
368
  end
351
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
352
369
 
353
370
  def allowed_line?(line, line_index)
354
371
  matches_allowed_pattern?(line) ||
@@ -71,7 +71,7 @@ module RuboCop
71
71
 
72
72
  BLOCK_TYPES = %i[block numblock itblock].freeze
73
73
 
74
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
74
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
75
  def check_assignment(node, rhs)
76
76
  return if node.send_type? && node.loc.operator&.source != '='
77
77
  return unless rhs
@@ -80,7 +80,6 @@ module RuboCop
80
80
 
81
81
  check_by_enforced_style(node, rhs)
82
82
  end
83
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
84
83
 
85
84
  def check_by_enforced_style(node, rhs)
86
85
  case style
@@ -423,11 +423,11 @@ module RuboCop
423
423
  end
424
424
 
425
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
426
+ current = unwrap_block_node(node)
427
+ while (receiver = unwrap_block_node(current.receiver)) &&
428
+ receiver.call_type? && receiver.loc?(:dot) &&
429
+ receiver.loc.dot.line == current.loc.dot.line
430
+ current = receiver
431
431
  end
432
432
  current
433
433
  end
@@ -91,7 +91,7 @@ module RuboCop
91
91
  )
92
92
  end
93
93
 
94
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
94
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
95
95
  def alignment_source(node, starting_loc)
96
96
  ending_loc =
97
97
  case node.type
@@ -111,7 +111,6 @@ module RuboCop
111
111
 
112
112
  range_between(starting_loc.begin_pos, ending_loc.end_pos).source
113
113
  end
114
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
115
114
 
116
115
  # We will use ancestor or wrapper with access modifier.
117
116
 
@@ -5,7 +5,8 @@ module RuboCop
5
5
  module Layout
6
6
  # Checks the spacing inside and after block parameters pipes. Line breaks
7
7
  # inside parameter pipes are checked by `Layout/MultilineBlockLayout` and
8
- # not by this cop.
8
+ # not by this cop. Spaces inside a lambda's parameter parentheses are left
9
+ # to `Layout/SpaceInsideParens` when that cop enforces a conflicting style.
9
10
  #
10
11
  # @example EnforcedStyleInsidePipes: no_space (default)
11
12
  # # bad
@@ -29,12 +30,12 @@ module RuboCop
29
30
  include RangeHelp
30
31
  extend AutoCorrector
31
32
 
32
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
33
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- checks spacing inside a block parameter list
33
34
  arguments = node.arguments
34
35
 
35
36
  return unless node.arguments? && pipes?(arguments)
36
37
 
37
- check_inside_pipes(arguments)
38
+ check_inside_pipes(arguments) unless conflicting_space_inside_parens_style?(arguments)
38
39
  check_after_closing_pipe(arguments) if node.body
39
40
  check_each_arg(arguments)
40
41
  end
@@ -53,6 +54,16 @@ module RuboCop
53
54
  'EnforcedStyleInsidePipes'
54
55
  end
55
56
 
57
+ def conflicting_space_inside_parens_style?(arguments)
58
+ return false unless arguments.loc.begin.source == '('
59
+
60
+ case config.for_enabled_cop('Layout/SpaceInsideParens')['EnforcedStyle']
61
+ when 'no_space' then style == :space
62
+ when 'space', 'compact' then style == :no_space
63
+ else false
64
+ end
65
+ end
66
+
56
67
  def check_inside_pipes(arguments)
57
68
  case style
58
69
  when :no_space
@@ -129,6 +129,12 @@ module RuboCop
129
129
  check_operator(:class, node.loc.operator, rhs)
130
130
  end
131
131
 
132
+ def on_def(node)
133
+ return unless node.endless?
134
+
135
+ check_operator(:assignment, node.loc.assignment, node.body)
136
+ end
137
+
132
138
  def on_binary(node)
133
139
  rhs = node.rhs
134
140
 
@@ -170,6 +176,7 @@ module RuboCop
170
176
  alias on_or_asgn on_assignment
171
177
  alias on_and_asgn on_assignment
172
178
  alias on_op_asgn on_assignment
179
+ alias on_defs on_def
173
180
 
174
181
  private
175
182
 
@@ -251,7 +258,7 @@ module RuboCop
251
258
  return false unless allow_for_alignment?
252
259
  return false unless with_space.source.start_with?(EXCESSIVE_SPACE)
253
260
 
254
- return !aligned_with_operator?(operator) unless type == :assignment
261
+ return !aligned_with_operator?(operator) unless grouped_alignment_type?(type)
255
262
 
256
263
  token = Token.new(operator, nil, operator.source)
257
264
  align_preceding = aligned_with_preceding_equals_operator(token)
@@ -262,6 +269,10 @@ module RuboCop
262
269
  aligned_with_subsequent_equals_operator(token) != :yes
263
270
  end
264
271
 
272
+ def grouped_alignment_type?(type)
273
+ type == :assignment || (type == :special_asgn && force_equal_sign_alignment?)
274
+ end
275
+
265
276
  def excess_trailing_space?(right_operand, with_space)
266
277
  with_space.source.end_with?(EXCESSIVE_SPACE) &&
267
278
  (!allow_for_alignment? || !aligned_with_something?(right_operand))
@@ -153,7 +153,7 @@ module RuboCop
153
153
  [token.line - 1, token.column - 1]
154
154
  end
155
155
 
156
- # rubocop:disable Metrics/ParameterLists
156
+ # rubocop:disable-next Metrics/ParameterLists
157
157
  def issue_offenses(node, tokens, left, right, start_ok, end_ok)
158
158
  case style
159
159
  when :no_space
@@ -165,13 +165,12 @@ module RuboCop
165
165
  compact_offenses(node, tokens, left, right, start_ok, end_ok)
166
166
  end
167
167
  end
168
- # rubocop:enable Metrics/ParameterLists
169
168
 
170
169
  def next_to_comment?(tokens, token)
171
170
  tokens[index_for(tokens, token) + 1].comment?
172
171
  end
173
172
 
174
- # rubocop:disable Metrics/ParameterLists
173
+ # rubocop:disable-next Metrics/ParameterLists
175
174
  def compact_offenses(node, tokens, left, right, start_ok, end_ok)
176
175
  if qualifies_for_compact?(tokens, left, side: :left)
177
176
  compact_offense(node, left, side: :left)
@@ -185,7 +184,6 @@ module RuboCop
185
184
  space_offenses(node, nil, right, MSG, start_ok: true, end_ok: end_ok)
186
185
  end
187
186
  end
188
- # rubocop:enable Metrics/ParameterLists
189
187
 
190
188
  def qualifies_for_compact?(tokens, token, side: :right)
191
189
  if side == :right
@@ -83,14 +83,13 @@ module RuboCop
83
83
  yield range.end if range.end
84
84
  end
85
85
 
86
- # rubocop:disable Metrics/CyclomaticComplexity
86
+ # rubocop:disable-next Metrics/CyclomaticComplexity
87
87
  def acceptable?(node)
88
88
  node.begin_type? ||
89
89
  node.literal? || rational_literal?(node) ||
90
90
  node.variable? || node.const_type? || node.self_type? ||
91
91
  (node.call_type? && acceptable_call?(node))
92
92
  end
93
- # rubocop:enable Metrics/CyclomaticComplexity
94
93
 
95
94
  def acceptable_call?(node)
96
95
  return true if node.unary_operation?
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for calls that pass the wrong number of positional arguments to a
7
+ # method whose definition is known from the project index.
8
+ #
9
+ # The check is powered by the project-wide index, so it only runs when
10
+ # `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
11
+ # Without the index the cop does nothing.
12
+ #
13
+ # Only calls on constant receivers (`Foo.bar`) are considered, and only
14
+ # when the receiver resolves in the index, its entire ancestry is resolved
15
+ # (so a method inherited from a gem or the standard library never produces
16
+ # an offense), and the called method resolves to a single, unambiguous
17
+ # signature. Calls that forward arguments (`*`, `**`, `...`) are skipped
18
+ # because their argument count is not statically known. Keyword arguments
19
+ # passed to a method that declares no keyword parameters are counted as the
20
+ # single positional hash they collapse into, matching Ruby's semantics.
21
+ #
22
+ # Constructor calls (`Foo.new`) and keyword-argument validation are out of
23
+ # scope: only positional arity of explicitly defined singleton methods is
24
+ # checked. `new` is skipped outright, because `Class#new` is not in the
25
+ # index: any `new` the index does find sits below it in the method
26
+ # resolution order, where Ruby would never reach it.
27
+ #
28
+ # @example
29
+ # # Given the project defines:
30
+ # # class Report
31
+ # # def self.generate(source, format); end
32
+ # # end
33
+ #
34
+ # # bad
35
+ # Report.generate(source)
36
+ #
37
+ # # bad
38
+ # Report.generate(source, :pdf, :extra)
39
+ #
40
+ # # good
41
+ # Report.generate(source, :pdf)
42
+ #
43
+ class ArgumentMismatch < Base
44
+ include ProjectIndexHelp
45
+ include IndexedMethodArity
46
+
47
+ MSG = 'Wrong number of arguments to `%<method>s` (given %<given>d, expected %<expected>s).'
48
+
49
+ def on_send(node)
50
+ return unless checkable_call?(node)
51
+
52
+ shape = resolved_signature_shape(node)
53
+ return unless shape
54
+
55
+ min, max, has_keywords = shape
56
+ given = positional_argument_count(node, has_keywords)
57
+ return if given.nil? || arity_satisfied?(given, min, max)
58
+
59
+ add_offense(node.loc.selector, message: message(node, given, min, max))
60
+ end
61
+ alias on_csend on_send
62
+
63
+ private
64
+
65
+ def checkable_call?(node)
66
+ project_index && node.receiver&.const_type? && !node.method?(:new)
67
+ end
68
+
69
+ def message(node, given, min, max)
70
+ format(MSG, method: node.method_name, given: given, expected: expected_range(min, max))
71
+ end
72
+
73
+ # `[min, max, has_keywords]` for the called singleton method, or nil when
74
+ # the receiver or its ancestry is not resolved.
75
+ def resolved_signature_shape(node)
76
+ declaration = resolve_constant_in_index(node.receiver)
77
+ return nil unless declaration.is_a?(Rubydex::Namespace)
78
+ return nil unless fully_resolved_index_ancestry?(declaration)
79
+
80
+ singleton_signature_shape(declaration, node.method_name)
81
+ rescue StandardError
82
+ nil
83
+ end
84
+
85
+ # A call with an explicit receiver cannot reach a private method, so its
86
+ # signature says nothing about the call. This matters because `Object`
87
+ # closes the singleton ancestry of every constant, and a bare `def` at
88
+ # the top level of any file — what every block-based DSL produces — is a
89
+ # private instance method of `Object`.
90
+ def singleton_signature_shape(declaration, method_name)
91
+ member = indexed_singleton_member(declaration, "#{method_name}()")
92
+ return nil unless member&.public?
93
+
94
+ indexed_member_shape(member)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -106,6 +106,8 @@ module RuboCop
106
106
  def traverse_node(node, &block)
107
107
  # if the node is a block, any assignments are irrelevant
108
108
  return if node.any_block_type?
109
+ # an assignment inside `defined?` is never executed
110
+ return if node.defined_type?
109
111
 
110
112
  result = yield node if ASGN_TYPES.include?(node.type)
111
113