rubocop 1.84.2 → 1.86.1

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +91 -15
  3. data/config/obsoletion.yml +5 -0
  4. data/lib/rubocop/cache_config.rb +1 -1
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +1 -1
  6. data/lib/rubocop/cli/command/mcp.rb +19 -0
  7. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  8. data/lib/rubocop/cli/command/show_docs_url.rb +1 -1
  9. data/lib/rubocop/cli.rb +6 -3
  10. data/lib/rubocop/config.rb +14 -10
  11. data/lib/rubocop/config_finder.rb +1 -1
  12. data/lib/rubocop/config_loader_resolver.rb +2 -1
  13. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  14. data/lib/rubocop/config_store.rb +1 -1
  15. data/lib/rubocop/config_validator.rb +1 -1
  16. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  17. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  18. data/lib/rubocop/cop/documentation.rb +2 -3
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  20. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  23. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  26. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  27. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  28. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  29. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  30. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  31. data/lib/rubocop/cop/layout/end_alignment.rb +6 -3
  32. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  33. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  34. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  35. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  36. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  37. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  38. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  40. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  42. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  43. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  44. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  45. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  46. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  47. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  48. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  49. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  50. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  51. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  52. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  53. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  54. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  56. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  57. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  58. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  59. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  60. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  61. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  62. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  63. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  64. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  66. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  67. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +22 -7
  68. data/lib/rubocop/cop/lint/void.rb +32 -12
  69. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  70. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  71. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  72. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  73. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  74. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  75. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  76. data/lib/rubocop/cop/registry.rb +20 -13
  77. data/lib/rubocop/cop/security/eval.rb +15 -2
  78. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  79. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  80. data/lib/rubocop/cop/style/alias.rb +4 -1
  81. data/lib/rubocop/cop/style/and_or.rb +1 -0
  82. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  83. data/lib/rubocop/cop/style/array_join.rb +4 -2
  84. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  85. data/lib/rubocop/cop/style/attr.rb +5 -2
  86. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  87. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  88. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  89. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  90. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  91. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  92. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  93. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  94. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  95. data/lib/rubocop/cop/style/copyright.rb +1 -1
  96. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  97. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  98. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  99. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  100. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  101. data/lib/rubocop/cop/style/encoding.rb +7 -1
  102. data/lib/rubocop/cop/style/end_block.rb +3 -1
  103. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  104. data/lib/rubocop/cop/style/file_open.rb +84 -0
  105. data/lib/rubocop/cop/style/for.rb +3 -0
  106. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  107. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  108. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  109. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  110. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  111. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  112. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  113. data/lib/rubocop/cop/style/if_inside_else.rb +1 -5
  114. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  115. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  116. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  117. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  118. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  119. data/lib/rubocop/cop/style/map_join.rb +123 -0
  120. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  121. data/lib/rubocop/cop/style/module_member_existence_check.rb +1 -11
  122. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  123. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  124. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  125. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  126. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  127. data/lib/rubocop/cop/style/not.rb +2 -0
  128. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  129. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  130. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  131. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  132. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  133. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  134. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  135. data/lib/rubocop/cop/style/proc.rb +3 -2
  136. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  137. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  138. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  139. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  140. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  141. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  142. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  143. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  144. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  145. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  146. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  147. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  148. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  149. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  150. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  151. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  152. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  153. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  154. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  155. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  156. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  157. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  158. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  159. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  160. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  161. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  162. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  163. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  164. data/lib/rubocop/directive_comment.rb +2 -1
  165. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  166. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  167. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  168. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  169. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  170. data/lib/rubocop/formatter.rb +22 -21
  171. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  172. data/lib/rubocop/lsp/routes.rb +10 -3
  173. data/lib/rubocop/mcp/server.rb +200 -0
  174. data/lib/rubocop/options.rb +10 -1
  175. data/lib/rubocop/path_util.rb +14 -2
  176. data/lib/rubocop/plugin/loader.rb +1 -1
  177. data/lib/rubocop/result_cache.rb +22 -10
  178. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  179. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  180. data/lib/rubocop/runner.rb +8 -3
  181. data/lib/rubocop/server/cache.rb +5 -7
  182. data/lib/rubocop/server/core.rb +2 -0
  183. data/lib/rubocop/target_finder.rb +1 -1
  184. data/lib/rubocop/target_ruby.rb +18 -12
  185. data/lib/rubocop/version.rb +2 -2
  186. data/lib/rubocop.rb +14 -0
  187. metadata +22 -5
@@ -60,6 +60,11 @@ module RuboCop
60
60
  END_OF_HEREDOC_LINE = 1
61
61
  SIMPLE_DIRECTIVE_COMMENT_PATTERN = /\A# *:nocov:\z/.freeze
62
62
 
63
+ # @!method guard_clause_branch?(node)
64
+ def_node_matcher :guard_clause_branch?, <<~PATTERN
65
+ {(send nil? {:raise :fail} ...) return break next}
66
+ PATTERN
67
+
63
68
  def on_if(node)
64
69
  return if correct_style?(node)
65
70
  return if multiple_statements_on_line?(node)
@@ -97,14 +102,16 @@ module RuboCop
97
102
  end
98
103
 
99
104
  def correct_style?(node)
100
- !contains_guard_clause?(node) ||
105
+ !node.if_branch&.guard_clause? ||
101
106
  next_line_rescue_or_ensure?(node) ||
102
107
  next_sibling_parent_empty_or_else?(node) ||
103
108
  next_sibling_empty_or_guard_clause?(node)
104
109
  end
105
110
 
106
111
  def contains_guard_clause?(node)
107
- node.if_branch&.guard_clause?
112
+ return false unless (branch = node.if_branch)
113
+
114
+ branch.guard_clause? || guard_clause_branch?(branch)
108
115
  end
109
116
 
110
117
  def next_line_empty_or_allowed_directive_comment?(line)
@@ -185,7 +185,7 @@ module RuboCop
185
185
  end
186
186
 
187
187
  def empty_line_between_macros
188
- cop_config.fetch('DefLikeMacros', []).map(&:to_sym)
188
+ @empty_line_between_macros ||= cop_config.fetch('DefLikeMacros', []).map(&:to_sym).freeze
189
189
  end
190
190
 
191
191
  def macro_candidate?(node)
@@ -66,6 +66,7 @@ module RuboCop
66
66
  extend AutoCorrector
67
67
 
68
68
  MSG = 'Add an empty line after attribute accessor.'
69
+ RESTRICT_ON_SEND = %i[attr_reader attr_writer attr_accessor attr].freeze
69
70
 
70
71
  def on_send(node)
71
72
  return unless node.attribute_accessor?
@@ -7,15 +7,25 @@ module RuboCop
7
7
  # the configuration.
8
8
  #
9
9
  # @example EnforcedStyle: no_empty_lines (default)
10
- # # good
10
+ # # bad
11
+ # foo do |bar|
12
+ #
13
+ # # ...
14
+ #
15
+ # end
11
16
  #
17
+ # # good
12
18
  # foo do |bar|
13
19
  # # ...
14
20
  # end
15
21
  #
16
22
  # @example EnforcedStyle: empty_lines
17
- # # good
23
+ # # bad
24
+ # foo do |bar|
25
+ # # ...
26
+ # end
18
27
  #
28
+ # # good
19
29
  # foo do |bar|
20
30
  #
21
31
  # # ...
@@ -7,8 +7,16 @@ module RuboCop
7
7
  # the configuration.
8
8
  #
9
9
  # @example EnforcedStyle: no_empty_lines (default)
10
- # # good
10
+ # # bad
11
+ # class Foo
12
+ #
13
+ # def bar
14
+ # # ...
15
+ # end
16
+ #
17
+ # end
11
18
  #
19
+ # # good
12
20
  # class Foo
13
21
  # def bar
14
22
  # # ...
@@ -16,8 +24,14 @@ module RuboCop
16
24
  # end
17
25
  #
18
26
  # @example EnforcedStyle: empty_lines
19
- # # good
27
+ # # bad
28
+ # class Foo
29
+ # def bar
30
+ # # ...
31
+ # end
32
+ # end
20
33
  #
34
+ # # good
21
35
  # class Foo
22
36
  #
23
37
  # def bar
@@ -7,8 +7,16 @@ module RuboCop
7
7
  # the configuration.
8
8
  #
9
9
  # @example EnforcedStyle: no_empty_lines (default)
10
- # # good
10
+ # # bad
11
+ # module Foo
12
+ #
13
+ # def bar
14
+ # # ...
15
+ # end
16
+ #
17
+ # end
11
18
  #
19
+ # # good
12
20
  # module Foo
13
21
  # def bar
14
22
  # # ...
@@ -16,8 +24,14 @@ module RuboCop
16
24
  # end
17
25
  #
18
26
  # @example EnforcedStyle: empty_lines
19
- # # good
27
+ # # bad
28
+ # module Foo
29
+ # def bar
30
+ # # ...
31
+ # end
32
+ # end
20
33
  #
34
+ # # good
21
35
  # module Foo
22
36
  #
23
37
  # def bar
@@ -123,20 +123,23 @@ 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
127
  def check_assignment(node, rhs)
127
128
  # If there are method calls chained to the right hand side of the
128
129
  # assignment, we let rhs be the receiver of those method calls before
129
130
  # we check if it's an if/unless/while/until.
130
131
  return unless (rhs = first_part_of_call_chain(rhs))
131
132
 
132
- # If `rhs` is a `begin` node, find the first non-`begin` child.
133
- rhs = rhs.child_nodes.first while rhs.begin_type?
133
+ # If `rhs` is a `begin` node or a logical operator,
134
+ # unwrap to find the leading conditional.
135
+ rhs = rhs.child_nodes.first while rhs&.type?(:begin, :or, :and)
134
136
 
135
- return unless rhs.conditional?
137
+ return unless rhs&.conditional?
136
138
  return if rhs.if_type? && rhs.ternary?
137
139
 
138
140
  check_asgn_alignment(node, rhs)
139
141
  end
142
+ # rubocop:enable Metrics/CyclomaticComplexity
140
143
 
141
144
  def check_asgn_alignment(outer_node, inner_node)
142
145
  align_with = {
@@ -135,7 +135,13 @@ module RuboCop
135
135
  private
136
136
 
137
137
  def autocorrect(corrector, node)
138
- AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
138
+ line_range = if !node.is_a?(AST::Node) || node.value.first_line <= node.key.first_line
139
+ node
140
+ else
141
+ processed_source.buffer.line_range(node.loc.line)
142
+ end
143
+
144
+ AlignmentCorrector.correct(corrector, processed_source, line_range, @column_delta)
139
145
  end
140
146
 
141
147
  def brace_alignment_style
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Check that the keys, separators, and values of a multi-line hash
6
+ # Checks that the keys, separators, and values of a multi-line hash
7
7
  # literal are aligned according to configuration. The configuration
8
8
  # options are:
9
9
  #
@@ -473,7 +473,7 @@ module RuboCop
473
473
  end
474
474
 
475
475
  def block_body_indentation_base(node, end_loc)
476
- if dot_on_new_line?(node)
476
+ if style == :relative_to_receiver && dot_on_new_line?(node)
477
477
  node.send_node.loc.dot
478
478
  else
479
479
  end_loc
@@ -406,9 +406,11 @@ module RuboCop
406
406
  end
407
407
 
408
408
  def string_delimiter(node)
409
- delimiter = node.loc.begin
410
- delimiter ||= node.parent.loc.begin if node.parent&.dstr_type? && node.parent.loc?(:begin)
411
- delimiter = delimiter&.source
409
+ delimiter = if node.loc?(:begin)
410
+ node.loc.begin
411
+ elsif node.parent&.dstr_type? && node.parent.loc?(:begin)
412
+ node.parent.loc.begin
413
+ end&.source
412
414
 
413
415
  delimiter if %w[' "].include?(delimiter)
414
416
  end
@@ -69,14 +69,18 @@ module RuboCop
69
69
  SAME_LINE_OFFENSE = 'Right hand side of multi-line assignment is not ' \
70
70
  'on the same line as the assignment operator `=`.'
71
71
 
72
+ BLOCK_TYPES = %i[block numblock itblock].freeze
73
+
74
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
72
75
  def check_assignment(node, rhs)
73
76
  return if node.send_type? && node.loc.operator&.source != '='
74
77
  return unless rhs
75
78
  return unless supported_types.include?(rhs.type)
76
- return if rhs.single_line?
79
+ return if rhs.single_line? && (!rhs.block_type? || same_line?(node, rhs.loc.begin))
77
80
 
78
81
  check_by_enforced_style(node, rhs)
79
82
  end
83
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
80
84
 
81
85
  def check_by_enforced_style(node, rhs)
82
86
  case style
@@ -109,7 +113,10 @@ module RuboCop
109
113
  private
110
114
 
111
115
  def supported_types
112
- @supported_types ||= cop_config['SupportedTypes'].map(&:to_sym)
116
+ @supported_types ||= cop_config['SupportedTypes'].flat_map do |type|
117
+ sym = type.to_sym
118
+ sym == :block ? BLOCK_TYPES : sym
119
+ end
113
120
  end
114
121
  end
115
122
  end
@@ -128,12 +128,15 @@ module RuboCop
128
128
  if hash_pair_indented?(node, pair_ancestor, given_style)
129
129
  return check_hash_pair_indented_style(rhs, pair_ancestor)
130
130
  end
131
-
132
- return false if !pair_ancestor && not_for_this_cop?(node)
131
+ return false if skip_for_context?(node, pair_ancestor)
133
132
 
134
133
  check_regular_indentation(node, lhs, rhs, given_style)
135
134
  end
136
135
 
136
+ def skip_for_context?(node, pair_ancestor)
137
+ pair_ancestor ? inside_multiline_chain_arg?(node) : not_for_this_cop?(node)
138
+ end
139
+
137
140
  def hash_pair_aligned?(pair_ancestor, given_style)
138
141
  pair_ancestor && given_style == :aligned
139
142
  end
@@ -152,7 +155,10 @@ module RuboCop
152
155
  end
153
156
 
154
157
  def check_hash_pair_indentation(node, lhs, rhs)
155
- @base = find_hash_pair_alignment_base(node) || lhs.source_range
158
+ @base = find_hash_pair_alignment_base(node)
159
+ return false if !@base && inside_multiline_chain_arg?(node)
160
+
161
+ @base ||= lhs.source_range
156
162
  return if aligned_with_first_line_dot?(node, rhs)
157
163
 
158
164
  calculate_column_delta_offense(rhs, @base.column)
@@ -166,6 +172,25 @@ module RuboCop
166
172
  first_call.loc.dot.join(first_call.loc.selector)
167
173
  end
168
174
 
175
+ def inside_multiline_chain_arg?(node)
176
+ enclosing_call = find_enclosing_chain_call(node)
177
+ return false unless enclosing_call
178
+
179
+ !same_line?(enclosing_call.loc.selector, enclosing_call.receiver.source_range)
180
+ end
181
+
182
+ def find_enclosing_chain_call(node)
183
+ hash_ancestor = find_pair_ancestor(node).parent
184
+ enclosing_call = hash_ancestor.parent
185
+ return unless hash_arg_in_chain?(enclosing_call, hash_ancestor)
186
+
187
+ enclosing_call
188
+ end
189
+
190
+ def hash_arg_in_chain?(call, hash_node)
191
+ call&.call_type? && call.receiver != hash_node && call.loc?(:dot)
192
+ end
193
+
169
194
  def aligned_with_first_line_dot?(node, rhs)
170
195
  return false unless rhs.source.start_with?('.', '&.')
171
196
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Check that the parameters on a multi-line method call or definition are aligned.
6
+ # Checks that the parameters on a multi-line method call or definition are aligned.
7
7
  #
8
8
  # To set the alignment of the first argument, use the
9
9
  # `Layout/FirstParameterIndentation` cop.
@@ -114,7 +114,7 @@ module RuboCop
114
114
 
115
115
  def other_cop_takes_precedence?(node)
116
116
  single_line_block_chain_enabled? && any_descendant?(node, :any_block) do |block_node|
117
- block_node.parent.send_type? && block_node.parent.loc.dot && !block_node.multiline?
117
+ block_node.parent.send_type? && block_node.parent.loc.dot && block_node.single_line?
118
118
  end
119
119
  end
120
120
 
@@ -29,7 +29,7 @@ module RuboCop
29
29
  include RangeHelp
30
30
  extend AutoCorrector
31
31
 
32
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
32
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
33
33
  arguments = node.arguments
34
34
 
35
35
  return unless node.arguments? && pipes?(arguments)
@@ -47,9 +47,11 @@ module RuboCop
47
47
  check(node, [:operator].freeze) if node.keyword?
48
48
  end
49
49
 
50
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
50
+ def on_block(node)
51
51
  check(node, %i[begin end].freeze)
52
52
  end
53
+ alias on_numblock on_block
54
+ alias on_itblock on_block
53
55
 
54
56
  def on_break(node)
55
57
  check(node, [:keyword].freeze)
@@ -26,6 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG_REQUIRE_SPACE = 'Use a space between `->` and `(` in lambda literals.'
28
28
  MSG_REQUIRE_NO_SPACE = 'Do not use spaces between `->` and `(` in lambda literals.'
29
+ RESTRICT_ON_SEND = %i[lambda].freeze
29
30
 
30
31
  def on_send(node)
31
32
  return unless arrow_lambda_with_args?(node)
@@ -6,8 +6,11 @@ module RuboCop
6
6
  # Checks for constant reassignments.
7
7
  #
8
8
  # Emulates Ruby's runtime warning "already initialized constant X"
9
- # when a constant is reassigned in the same file and namespace using the
10
- # `NAME = value` syntax.
9
+ # when a constant is reassigned in the same file and namespace.
10
+ #
11
+ # The cop tracks constants defined via `NAME = value` syntax as well as
12
+ # class/module keyword definitions. It detects reassignment when a constant
13
+ # is first defined one way and then redefined using the `NAME = value` syntax.
11
14
  #
12
15
  # The cop cannot catch all offenses, like, for example, when a constant
13
16
  # is reassigned in another file, or when using metaprogramming (`Module#const_set`).
@@ -36,6 +39,14 @@ module RuboCop
36
39
  # X = :bar
37
40
  # end
38
41
  #
42
+ # # bad
43
+ # class FooError < StandardError; end
44
+ # FooError = Class.new(RuntimeError)
45
+ #
46
+ # # bad
47
+ # module M; end
48
+ # M = 1
49
+ #
39
50
  # # good - keep only one assignment
40
51
  # X = :bar
41
52
  #
@@ -69,16 +80,30 @@ module RuboCop
69
80
 
70
81
  # @!method remove_constant(node)
71
82
  def_node_matcher :remove_constant, <<~PATTERN
72
- (send _ :remove_const
83
+ (send {nil? self} :remove_const
73
84
  ({sym str} $_))
74
85
  PATTERN
75
86
 
87
+ def on_class(node)
88
+ return unless unconditional_definition?(node)
89
+
90
+ constant_definitions[definition_name(node)] ||= :class
91
+ end
92
+
93
+ def on_module(node)
94
+ return unless unconditional_definition?(node)
95
+
96
+ constant_definitions[definition_name(node)] ||= :module
97
+ end
98
+
76
99
  def on_casgn(node)
77
100
  return unless fixed_constant_path?(node)
78
101
  return unless simple_assignment?(node)
79
- return if constant_names.add?(fully_qualified_constant_name(node))
80
102
 
81
- add_offense(node, message: format(MSG, constant: node.name))
103
+ name = fully_qualified_constant_name(node)
104
+ return constant_definitions[name] = :casgn unless constant_definitions.key?(name)
105
+
106
+ add_offense(node, message: format(MSG, constant: constant_display_name(node)))
82
107
  end
83
108
 
84
109
  def on_send(node)
@@ -90,7 +115,7 @@ module RuboCop
90
115
 
91
116
  return if namespaces.none?
92
117
 
93
- constant_names.delete(fully_qualified_name_for(namespaces, constant))
118
+ constant_definitions.delete(fully_qualified_name_for(namespaces, constant))
94
119
  end
95
120
 
96
121
  private
@@ -104,7 +129,7 @@ module RuboCop
104
129
  return true if ancestor.type?(:module, :class)
105
130
 
106
131
  ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
107
- freeze_method?(ancestor)
132
+ ancestor.type?(:masgn, :mlhs) || freeze_method?(ancestor)
108
133
  end
109
134
  end
110
135
 
@@ -128,6 +153,10 @@ module RuboCop
128
153
  ['', *namespaces, constant].join('::')
129
154
  end
130
155
 
156
+ def constant_display_name(node)
157
+ [*constant_namespaces(node), node.name].join('::')
158
+ end
159
+
131
160
  def constant_namespaces(node)
132
161
  node.each_path.select(&:const_type?).map(&:short_name)
133
162
  end
@@ -139,8 +168,29 @@ module RuboCop
139
168
  .reverse
140
169
  end
141
170
 
142
- def constant_names
143
- @constant_names ||= Set.new
171
+ def unconditional_definition?(node)
172
+ node.each_ancestor.all? do |ancestor|
173
+ ancestor.type?(:begin, :module, :class)
174
+ end
175
+ end
176
+
177
+ def definition_name(node)
178
+ identifier = node.identifier
179
+
180
+ if identifier.namespace&.cbase_type?
181
+ fully_qualified_name_for([], identifier.short_name)
182
+ else
183
+ namespaces = ancestor_namespaces(node) + identifier_namespaces(identifier)
184
+ fully_qualified_name_for(namespaces, identifier.short_name)
185
+ end
186
+ end
187
+
188
+ def identifier_namespaces(identifier)
189
+ identifier.each_path.select(&:const_type?).map(&:short_name)
190
+ end
191
+
192
+ def constant_definitions
193
+ @constant_definitions ||= {}
144
194
  end
145
195
  end
146
196
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Check that certain constants are fully qualified.
6
+ # Checks that certain constants are fully qualified.
7
7
  #
8
8
  # This is not enabled by default because it would mark a lot of offenses
9
9
  # unnecessarily.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks unexpected overrides of the `Data` built-in methods
7
+ # via `Data.define`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # Bad = Data.define(:members, :clone, :to_s)
12
+ # b = Bad.new(members: [], clone: true, to_s: 'bad')
13
+ # b.members #=> [] (overriding `Data#members`)
14
+ # b.clone #=> true (overriding `Object#clone`)
15
+ # b.to_s #=> "bad" (overriding `Data#to_s`)
16
+ #
17
+ # # good
18
+ # Good = Data.define(:id, :name)
19
+ # g = Good.new(id: 1, name: "foo")
20
+ # g.members #=> [:id, :name]
21
+ # g.clone #=> #<data Good id=1, name="foo">
22
+ #
23
+ class DataDefineOverride < Base
24
+ MSG = '`%<member_name>s` member overrides `Data#%<method_name>s` and it may be unexpected.'
25
+ RESTRICT_ON_SEND = %i[define].freeze
26
+
27
+ # This is based on `Data.define.instance_methods.sort` in Ruby 4.0.0.
28
+ DATA_METHOD_NAMES = %i[
29
+ ! != !~ <=> == === __id__ __send__ class clone deconstruct deconstruct_keys
30
+ define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash
31
+ inspect instance_eval instance_exec instance_of? instance_variable_defined?
32
+ instance_variable_get instance_variable_set instance_variables is_a? itself kind_of?
33
+ members method methods nil? object_id private_methods protected_methods
34
+ public_method public_methods public_send remove_instance_variable respond_to? send
35
+ singleton_class singleton_method singleton_methods tap then to_enum to_h to_s with
36
+ yield_self
37
+ ].freeze
38
+ MEMBER_NAME_TYPES = %i[sym str].freeze
39
+
40
+ # @!method data_define(node)
41
+ def_node_matcher :data_define, <<~PATTERN
42
+ (send
43
+ (const {nil? cbase} :Data) :define ...)
44
+ PATTERN
45
+
46
+ def on_send(node)
47
+ return unless data_define(node)
48
+
49
+ node.arguments.each do |arg|
50
+ next unless MEMBER_NAME_TYPES.include?(arg.type)
51
+
52
+ member_name = arg.value
53
+
54
+ next unless DATA_METHOD_NAMES.include?(member_name.to_sym)
55
+
56
+ message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s)
57
+ add_offense(arg, message: message)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -197,6 +197,13 @@ module RuboCop
197
197
  # @!method sym_name(node)
198
198
  def_node_matcher :sym_name, '(sym $_name)'
199
199
 
200
+ # @!method class_or_module_new_block?(node)
201
+ def_node_matcher :class_or_module_new_block?, <<~PATTERN
202
+ (block
203
+ (send (const _ {:Class :Module}) :new ...)
204
+ ...)
205
+ PATTERN
206
+
200
207
  def on_send(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
201
208
  name, original_name = alias_method?(node)
202
209
 
@@ -233,9 +240,12 @@ module RuboCop
233
240
 
234
241
  def check_self_receiver(node, name)
235
242
  enclosing = node.parent_module_name
236
- return unless enclosing
237
-
238
- found_method(node, "#{enclosing}.#{name}")
243
+ if enclosing
244
+ found_method(node, "#{enclosing}.#{name}")
245
+ elsif (anon_block = anonymous_class_block(node))
246
+ scope = qualified_name(anon_block.parent_module_name, nil, 'Object')
247
+ found_method(node, "#{scope}.#{name}", scope_id: anon_block_scope_id(anon_block))
248
+ end
239
249
  end
240
250
 
241
251
  def inside_condition?(node)
@@ -274,16 +284,50 @@ module RuboCop
274
284
  end
275
285
 
276
286
  def found_instance_method(node, name)
277
- return found_sclass_method(node, name) unless (scope = node.parent_module_name)
287
+ if (scope = node.parent_module_name)
288
+ found_method(node, "#{humanize_scope(scope)}#{name}")
289
+ elsif (anon_block = anonymous_class_block(node))
290
+ base = qualified_name(anon_block.parent_module_name, nil, 'Object')
291
+ scope = node.each_ancestor(:sclass).any? ? "#<Class:#{base}>" : base
292
+ found_method(
293
+ node, "#{humanize_scope(scope)}#{name}", scope_id: anon_block_scope_id(anon_block)
294
+ )
295
+ else
296
+ found_sclass_method(node, name)
297
+ end
298
+ end
278
299
 
279
- # Humanize the scope
300
+ def humanize_scope(scope)
280
301
  scope = scope.sub(
281
302
  /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
282
303
  '\k<name>.'
283
304
  )
284
- scope << '#' unless scope.end_with?('.')
305
+ scope.end_with?('.') ? scope : "#{scope}#"
306
+ end
307
+
308
+ def anonymous_class_block(node)
309
+ first_block = node.each_ancestor(:block).first
310
+ return unless class_or_module_new_block?(first_block)
311
+ return if first_block.parent&.type?(:lvasgn)
312
+ return if node.each_ancestor(:sclass).any? { |s| !s.children.first.self_type? }
313
+
314
+ first_block
315
+ end
316
+
317
+ def anon_block_scope_id(anon_block)
318
+ parent = anon_block.parent
319
+ return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)
320
+
321
+ if (receiver = named_receiver(parent))
322
+ "#{receiver.source}.#{parent.method_name}"
323
+ elsif !parent.begin_type? || parent.parent&.any_block_type?
324
+ source_location(anon_block)
325
+ end
326
+ end
285
327
 
286
- found_method(node, "#{scope}#{name}")
328
+ def named_receiver(node)
329
+ receiver = node.receiver
330
+ receiver unless class_or_module_new_block?(receiver)
287
331
  end
288
332
 
289
333
  def found_sclass_method(node, name)
@@ -296,8 +340,10 @@ module RuboCop
296
340
  found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
297
341
  end
298
342
 
299
- def found_method(node, method_name)
343
+ # rubocop:disable Metrics/AbcSize
344
+ def found_method(node, method_name, scope_id: nil)
300
345
  key = method_key(node, method_name)
346
+ key = "#{key}@#{scope_id}" if scope_id
301
347
  scope = node.each_ancestor(:rescue, :ensure).first&.type
302
348
 
303
349
  if @definitions.key?(key)
@@ -314,6 +360,7 @@ module RuboCop
314
360
  @definitions[key] = node
315
361
  end
316
362
  end
363
+ # rubocop:enable Metrics/AbcSize
317
364
 
318
365
  def method_key(node, method_name)
319
366
  if (ancestor_def = node.each_ancestor(:any_def).first)