rubocop 1.89.0 → 1.90.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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +74 -7
  3. data/exe/rubocop +1 -1
  4. data/lib/rubocop/cached_data.rb +7 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +31 -0
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/comment_config/directive_range.rb +32 -0
  8. data/lib/rubocop/comment_config/disable_next.rb +97 -0
  9. data/lib/rubocop/comment_config.rb +46 -30
  10. data/lib/rubocop/config.rb +23 -5
  11. data/lib/rubocop/config_loader.rb +1 -2
  12. data/lib/rubocop/config_loader_resolver.rb +1 -2
  13. data/lib/rubocop/config_validator.rb +1 -2
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/base.rb +34 -2
  16. data/lib/rubocop/cop/corrector.rb +3 -3
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  18. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  19. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  20. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  21. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  22. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  23. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  24. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  25. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  26. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  27. data/lib/rubocop/cop/layout/line_length.rb +1 -2
  28. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  29. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  30. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  31. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  32. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  33. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  34. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  35. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  36. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  37. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  38. data/lib/rubocop/cop/lint/duplicate_methods.rb +112 -14
  39. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  40. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  41. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  42. data/lib/rubocop/cop/lint/literal_as_condition.rb +3 -6
  43. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  44. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  45. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  46. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  47. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  48. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  49. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  50. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  51. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  52. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  53. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  54. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  55. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  56. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  57. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  58. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  59. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  60. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  61. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  62. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  63. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  64. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  66. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  67. data/lib/rubocop/cop/lint/void.rb +1 -2
  68. data/lib/rubocop/cop/lint.rb +2 -0
  69. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  70. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  71. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  72. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  73. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  74. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  75. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  76. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  77. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  78. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  79. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  80. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  81. data/lib/rubocop/cop/mixin.rb +4 -0
  82. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  83. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  84. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  85. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  86. data/lib/rubocop/cop/offense.rb +27 -4
  87. data/lib/rubocop/cop/registry.rb +34 -10
  88. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  89. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  90. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  91. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  92. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  93. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  94. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  95. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  96. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  97. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  98. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  99. data/lib/rubocop/cop/style/copyright.rb +1 -2
  100. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  101. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +36 -10
  102. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  103. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  104. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  105. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  106. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  107. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  108. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  109. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  110. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  111. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  112. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  113. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  114. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  115. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  116. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  117. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  118. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  119. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  120. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  121. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  122. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  123. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  124. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  125. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -4
  126. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  127. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  128. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  129. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  130. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  131. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  132. data/lib/rubocop/cop/style/sample.rb +11 -6
  133. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  134. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  135. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  136. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  137. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  138. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  139. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  140. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  141. data/lib/rubocop/cop/style/time_now.rb +41 -0
  142. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  143. data/lib/rubocop/cop/style.rb +2 -0
  144. data/lib/rubocop/cop/team.rb +11 -3
  145. data/lib/rubocop/cop/util.rb +2 -4
  146. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  147. data/lib/rubocop/cops_documentation_generator.rb +3 -6
  148. data/lib/rubocop/directive_comment.rb +37 -3
  149. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  150. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  151. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  152. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  153. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  154. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  155. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  156. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  157. data/lib/rubocop/lockfile.rb +18 -0
  158. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  159. data/lib/rubocop/lsp/routes.rb +48 -19
  160. data/lib/rubocop/mcp/server.rb +1 -2
  161. data/lib/rubocop/options.rb +10 -6
  162. data/lib/rubocop/path_util.rb +1 -2
  163. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  164. data/lib/rubocop/rake_task.rb +1 -2
  165. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  166. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  167. data/lib/rubocop/runner.rb +26 -23
  168. data/lib/rubocop/server/cache.rb +1 -2
  169. data/lib/rubocop/server/cli.rb +2 -4
  170. data/lib/rubocop/server.rb +14 -0
  171. data/lib/rubocop/target_finder.rb +4 -1
  172. data/lib/rubocop/todo_audit.rb +111 -0
  173. data/lib/rubocop/version.rb +3 -5
  174. data/lib/rubocop.rb +3 -0
  175. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  176. metadata +13 -5
@@ -5,6 +5,14 @@ module RuboCop
5
5
  module Layout
6
6
  # Checks for a newline after the final magic comment.
7
7
  #
8
+ # `NumberOfEmptyLines` configures the minimum number of empty lines required. Set it
9
+ # to `2` when using YARD, which otherwise treats the magic comments as documentation
10
+ # for the first module or class in the file.
11
+ #
12
+ # NOTE: `Layout/EmptyLines` has to be disabled for values greater than `1`, as it
13
+ # removes the extra empty lines this cop adds, and autocorrecting with both enabled
14
+ # loops between them.
15
+ #
8
16
  # @example
9
17
  # # good
10
18
  # # frozen_string_literal: true
@@ -20,30 +28,80 @@ module RuboCop
20
28
  # class Person
21
29
  # # Some code
22
30
  # end
31
+ #
32
+ # @example NumberOfEmptyLines: 1 (default)
33
+ # # good
34
+ # # frozen_string_literal: true
35
+ #
36
+ # class Person
37
+ # end
38
+ #
39
+ # @example NumberOfEmptyLines: 2
40
+ # # bad
41
+ # # frozen_string_literal: true
42
+ #
43
+ # class Person
44
+ # end
45
+ #
46
+ # # good
47
+ # # frozen_string_literal: true
48
+ #
49
+ #
50
+ # class Person
51
+ # end
23
52
  class EmptyLineAfterMagicComment < Base
24
53
  include RangeHelp
25
54
  extend AutoCorrector
26
55
 
27
- MSG = 'Add an empty line after magic comments.'
56
+ MSG = 'Expected at least %<expected>d empty %<lines>s after magic comments; found ' \
57
+ '%<actual>d.'
58
+
59
+ def validate_config
60
+ return if number_of_empty_lines.is_a?(Integer) && number_of_empty_lines.positive?
61
+
62
+ raise ValidationError,
63
+ 'The `Layout/EmptyLineAfterMagicComment` cop only accepts a positive ' \
64
+ 'integer for its `NumberOfEmptyLines` configuration parameter, but ' \
65
+ "`#{number_of_empty_lines.inspect}` was given."
66
+ end
28
67
 
29
68
  def on_new_investigation
30
69
  return unless (last_magic_comment = last_magic_comment(processed_source))
31
- return unless (next_line = processed_source[last_magic_comment.loc.line])
32
- return if next_line.strip.empty?
70
+
71
+ actual = empty_lines_after(last_magic_comment)
72
+ # Trailing empty lines are `Layout/TrailingEmptyLines`' responsibility.
73
+ return unless processed_source[last_magic_comment.loc.line + actual]
74
+ return if actual >= number_of_empty_lines
33
75
 
34
76
  offending_range = offending_range(last_magic_comment)
35
77
 
36
- add_offense(offending_range) do |corrector|
37
- corrector.insert_before(offending_range, "\n")
78
+ add_offense(offending_range, message: message(actual)) do |corrector|
79
+ corrector.insert_before(offending_range, "\n" * (number_of_empty_lines - actual))
38
80
  end
39
81
  end
40
82
 
41
83
  private
42
84
 
85
+ def message(actual)
86
+ format(MSG, expected: number_of_empty_lines, actual: actual,
87
+ lines: number_of_empty_lines == 1 ? 'line' : 'lines')
88
+ end
89
+
90
+ def empty_lines_after(last_magic_comment)
91
+ first_line = last_magic_comment.loc.line
92
+ count = 0
93
+ count += 1 while (line = processed_source[first_line + count]) && line.strip.empty?
94
+ count
95
+ end
96
+
43
97
  def offending_range(last_magic_comment)
44
98
  source_range(processed_source.buffer, last_magic_comment.loc.line + 1, 0)
45
99
  end
46
100
 
101
+ def number_of_empty_lines
102
+ cop_config.fetch('NumberOfEmptyLines', 1)
103
+ end
104
+
47
105
  # Find the last magic comment in the source file.
48
106
  #
49
107
  # Take all comments that precede the first line of code (or just take
@@ -123,7 +123,7 @@ module RuboCop
123
123
  AlignmentCorrector.align_end(corrector, processed_source, node, alignment_node(node))
124
124
  end
125
125
 
126
- # rubocop:disable Metrics/CyclomaticComplexity
126
+ # rubocop:disable-next Metrics/CyclomaticComplexity
127
127
  def check_assignment(node, rhs)
128
128
  # If there are method calls chained to the right hand side of the
129
129
  # assignment, we let rhs be the receiver of those method calls before
@@ -139,7 +139,6 @@ module RuboCop
139
139
 
140
140
  check_asgn_alignment(node, rhs)
141
141
  end
142
- # rubocop:enable Metrics/CyclomaticComplexity
143
142
 
144
143
  def check_asgn_alignment(outer_node, inner_node)
145
144
  align_with = {
@@ -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
@@ -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)
@@ -328,7 +328,7 @@ module RuboCop
328
328
  [max - indentation_difference(line), 0].max
329
329
  end
330
330
 
331
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
331
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
332
332
  def check_line(line, line_index)
333
333
  return if line_length(line) <= max
334
334
  return if allowed_line?(line, line_index)
@@ -348,7 +348,6 @@ module RuboCop
348
348
 
349
349
  register_offense(excess_range(nil, line, line_index), line, line_index)
350
350
  end
351
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
352
351
 
353
352
  def allowed_line?(line, line_index)
354
353
  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
 
@@ -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
 
@@ -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,89 @@
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.
25
+ #
26
+ # @example
27
+ # # Given the project defines:
28
+ # # class Report
29
+ # # def self.generate(source, format); end
30
+ # # end
31
+ #
32
+ # # bad
33
+ # Report.generate(source)
34
+ #
35
+ # # bad
36
+ # Report.generate(source, :pdf, :extra)
37
+ #
38
+ # # good
39
+ # Report.generate(source, :pdf)
40
+ #
41
+ class ArgumentMismatch < Base
42
+ include ProjectIndexHelp
43
+ include IndexedMethodArity
44
+
45
+ MSG = 'Wrong number of arguments to `%<method>s` (given %<given>d, expected %<expected>s).'
46
+
47
+ def on_send(node)
48
+ return unless project_index
49
+ return unless node.receiver&.const_type?
50
+
51
+ shape = resolved_signature_shape(node)
52
+ return unless shape
53
+
54
+ min, max, has_keywords = shape
55
+ given = positional_argument_count(node, has_keywords)
56
+ return if given.nil? || arity_satisfied?(given, min, max)
57
+
58
+ add_offense(node.loc.selector, message: message(node, given, min, max))
59
+ end
60
+ alias on_csend on_send
61
+
62
+ private
63
+
64
+ def message(node, given, min, max)
65
+ format(MSG, method: node.method_name, given: given, expected: expected_range(min, max))
66
+ end
67
+
68
+ # `[min, max, has_keywords]` for the called singleton method, or nil when
69
+ # the receiver or its ancestry is not resolved.
70
+ def resolved_signature_shape(node)
71
+ declaration = resolve_constant_in_index(node.receiver)
72
+ return nil unless declaration.is_a?(Rubydex::Namespace)
73
+ return nil unless fully_resolved_index_ancestry?(declaration)
74
+
75
+ singleton_signature_shape(declaration, node.method_name)
76
+ rescue StandardError
77
+ nil
78
+ end
79
+
80
+ def singleton_signature_shape(declaration, method_name)
81
+ member = indexed_singleton_member(declaration, "#{method_name}()")
82
+ return nil unless member
83
+
84
+ indexed_member_shape(member)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -175,10 +175,19 @@ module RuboCop
175
175
  end
176
176
 
177
177
  def ancestor_namespaces(node)
178
- node
179
- .each_ancestor(:class, :module)
180
- .map { |ancestor| ancestor.identifier.short_name }
181
- .reverse
178
+ ancestors = node.each_ancestor(:class, :module).reverse_each
179
+
180
+ ancestors.with_object([]) do |ancestor, namespaces|
181
+ append_namespaces(namespaces, ancestor.identifier)
182
+ end
183
+ end
184
+
185
+ # Compact definitions (e.g. `module A::B`) contribute every path segment,
186
+ # and an absolute one (e.g. `module ::A::B`) discards the enclosing namespaces.
187
+ def append_namespaces(namespaces, identifier)
188
+ namespaces.clear if identifier.absolute?
189
+ namespaces.concat(identifier_namespaces(identifier))
190
+ namespaces << identifier.short_name
182
191
  end
183
192
 
184
193
  def unconditional_definition?(node)
@@ -188,14 +197,11 @@ module RuboCop
188
197
  end
189
198
 
190
199
  def definition_name(node)
191
- identifier = node.identifier
200
+ namespaces = ancestor_namespaces(node)
201
+ append_namespaces(namespaces, node.identifier)
202
+ constant = namespaces.pop
192
203
 
193
- if identifier.namespace&.cbase_type?
194
- fully_qualified_name_for([], identifier.short_name)
195
- else
196
- namespaces = ancestor_namespaces(node) + identifier_namespaces(identifier)
197
- fully_qualified_name_for(namespaces, identifier.short_name)
198
- end
204
+ fully_qualified_name_for(namespaces, constant)
199
205
  end
200
206
 
201
207
  def identifier_namespaces(identifier)