rubocop 1.88.1 → 1.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +69 -11
  3. data/lib/rubocop/comment_config.rb +35 -19
  4. data/lib/rubocop/config.rb +53 -0
  5. data/lib/rubocop/config_validator.rb +38 -1
  6. data/lib/rubocop/cop/badge.rb +8 -0
  7. data/lib/rubocop/cop/base.rb +19 -12
  8. data/lib/rubocop/cop/bundler.rb +19 -0
  9. data/lib/rubocop/cop/commissioner.rb +13 -11
  10. data/lib/rubocop/cop/corrector.rb +1 -1
  11. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  12. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  13. data/lib/rubocop/cop/gemspec.rb +22 -0
  14. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  15. data/lib/rubocop/cop/generator.rb +4 -0
  16. data/lib/rubocop/cop/layout/class_structure.rb +8 -2
  17. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  18. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  19. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  20. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  21. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  22. data/lib/rubocop/cop/layout/line_length.rb +95 -17
  23. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  24. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  25. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
  26. data/lib/rubocop/cop/layout.rb +114 -0
  27. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  28. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  29. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  30. data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
  31. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  32. data/lib/rubocop/cop/lint/debugger.rb +13 -1
  33. data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
  34. data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
  35. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  36. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  38. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  39. data/lib/rubocop/cop/lint/name_typo.rb +223 -0
  40. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +7 -0
  41. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
  42. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  43. data/lib/rubocop/cop/lint/to_enum_arguments.rb +37 -1
  44. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  45. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  46. data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
  47. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  48. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  49. data/lib/rubocop/cop/lint/void.rb +19 -8
  50. data/lib/rubocop/cop/lint.rb +176 -0
  51. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  52. data/lib/rubocop/cop/metrics.rb +31 -0
  53. data/lib/rubocop/cop/migration/department_name.rb +0 -4
  54. data/lib/rubocop/cop/migration.rb +13 -0
  55. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  56. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  57. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  58. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  59. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  60. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  61. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  62. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  63. data/lib/rubocop/cop/mixin/statement_modifier.rb +43 -7
  64. data/lib/rubocop/cop/mixin.rb +78 -77
  65. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  66. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  67. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  68. data/lib/rubocop/cop/naming.rb +31 -0
  69. data/lib/rubocop/cop/offense.rb +10 -6
  70. data/lib/rubocop/cop/registry.rb +153 -43
  71. data/lib/rubocop/cop/security.rb +19 -0
  72. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  73. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  74. data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
  75. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  76. data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
  77. data/lib/rubocop/cop/style/copyright.rb +6 -1
  78. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  79. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
  80. data/lib/rubocop/cop/style/documentation.rb +47 -0
  81. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  82. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  83. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  84. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  85. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  86. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -52
  87. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  88. data/lib/rubocop/cop/style/lambda.rb +4 -0
  89. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  90. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
  91. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +5 -3
  92. data/lib/rubocop/cop/style/method_def_parentheses.rb +4 -0
  93. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +40 -7
  94. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  95. data/lib/rubocop/cop/style/module_function.rb +4 -2
  96. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  97. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  98. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  99. data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
  100. data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
  101. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  102. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  103. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  104. data/lib/rubocop/cop/style/redundant_format.rb +12 -3
  105. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  106. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  107. data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
  108. data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
  109. data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
  110. data/lib/rubocop/cop/style/static_class.rb +21 -0
  111. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  112. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  113. data/lib/rubocop/cop/style.rb +310 -0
  114. data/lib/rubocop/cop/team.rb +66 -7
  115. data/lib/rubocop/cop/variable_force/variable.rb +18 -6
  116. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  117. data/lib/rubocop/directive_comment.rb +2 -0
  118. data/lib/rubocop/feature_loader.rb +0 -6
  119. data/lib/rubocop/lsp/routes.rb +41 -3
  120. data/lib/rubocop/lsp/runtime.rb +4 -0
  121. data/lib/rubocop/lsp/server.rb +4 -0
  122. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  123. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  124. data/lib/rubocop/project_index_loader.rb +16 -0
  125. data/lib/rubocop/result_cache.rb +6 -4
  126. data/lib/rubocop/rspec/shared_contexts.rb +14 -2
  127. data/lib/rubocop/runner.rb +95 -30
  128. data/lib/rubocop/server/cache.rb +10 -5
  129. data/lib/rubocop/target_ruby.rb +1 -1
  130. data/lib/rubocop/version.rb +1 -1
  131. data/lib/rubocop.rb +17 -626
  132. metadata +18 -3
@@ -73,7 +73,12 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def find_pair_ancestor(node)
76
- node.each_ancestor.find(&:pair_type?)
76
+ node.each_ancestor do |ancestor|
77
+ return ancestor if ancestor.pair_type?
78
+ break if grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
79
+ end
80
+
81
+ nil
77
82
  end
78
83
 
79
84
  def unwrap_block_node(node)
@@ -405,7 +410,7 @@ module RuboCop
405
410
 
406
411
  def find_continuation_node(node)
407
412
  receiver = node.receiver
408
- return receiver.send_node if single_line_block_receiver?(receiver)
413
+ return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
409
414
  return unless receiver.call_type? && receiver.loc.dot
410
415
  return receiver if receiver.receiver.begin_type? && node.block_node.single_line?
411
416
  return unless receiver.loc.dot.line > receiver.receiver.last_line
@@ -417,9 +422,19 @@ module RuboCop
417
422
  receiver.single_line? && receiver.any_block_type?
418
423
  end
419
424
 
425
+ def leftmost_call_on_same_line(node)
426
+ current = node.any_block_type? ? node.send_node : node
427
+ while current.receiver&.call_type? &&
428
+ current.receiver.loc?(:dot) &&
429
+ current.receiver.loc.dot.line == current.loc.dot.line
430
+ current = current.receiver
431
+ end
432
+ current
433
+ end
434
+
420
435
  def handle_descendant_block(node)
421
436
  receiver = node.receiver
422
- return receiver.send_node if single_line_block_receiver?(receiver)
437
+ return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
423
438
 
424
439
  block_node = node.each_descendant(:any_block).first
425
440
  return unless block_node&.multiline?
@@ -49,6 +49,7 @@ module RuboCop
49
49
  class RedundantLineBreak < Base
50
50
  include CheckAssignment
51
51
  include CheckSingleLineSuitability
52
+ include ReparsedEquivalence
52
53
  extend AutoCorrector
53
54
 
54
55
  MSG = 'Redundant line break detected.'
@@ -82,12 +83,88 @@ module RuboCop
82
83
  end
83
84
 
84
85
  def register_offense(node)
86
+ # The exact single-line correction is verified to parse equivalently
87
+ # before the offense is registered, so a join that would change how
88
+ # the code parses is never reported or offered.
89
+ return if verified_by_reparse([node], oversized: :verify).empty?
90
+
85
91
  add_offense(node) do |corrector|
86
92
  corrector.replace(node, to_single_line(node.source).strip)
87
93
  end
88
94
  ignore_node(node)
89
95
  end
90
96
 
97
+ def apply_reparse_correction(corrector, node)
98
+ corrector.replace(node, to_single_line(node.source).strip)
99
+ end
100
+
101
+ # Joining lines shifts the value of any later `__LINE__`, exactly as
102
+ # any other line-removing correction does; neutralize it on both sides
103
+ # with an identifier, which stays valid in every position the keyword
104
+ # can appear in.
105
+ def preprocess_reparsed_source(source)
106
+ source.gsub(/\b__LINE__\b/, '__LINE0__')
107
+ end
108
+
109
+ def normalize_reparsed_ast(node)
110
+ fold_string_concatenation(node)
111
+ end
112
+
113
+ # Joining lines merges split string literals (`"a" \<newline> "b"` into
114
+ # `"ab"`) and turns mixed-quote pairs into `+` concatenation, which
115
+ # changes the tree but not the resulting string. Normalize all string
116
+ # concatenation to a canonical form before comparing: nested
117
+ # concatenations are flattened and adjacent literal parts merged.
118
+ def fold_string_concatenation(node)
119
+ return node unless node.is_a?(::Parser::AST::Node)
120
+
121
+ children = node.children.map { |child| fold_string_concatenation(child) }
122
+ node = node.updated(nil, children)
123
+
124
+ parts = string_concatenation_parts(node)
125
+ return node unless parts
126
+
127
+ merged = merge_string_parts(parts)
128
+ if merged.one? && merged.first.str_type?
129
+ node.updated(:str, merged.first.children)
130
+ else
131
+ node.updated(:dstr, merged)
132
+ end
133
+ end
134
+
135
+ def string_concatenation_parts(node)
136
+ case node.type
137
+ when :dstr
138
+ node.children
139
+ when :send
140
+ parts = [node.receiver, *node.arguments]
141
+ parts if node.method?(:+) && parts.all? { |part| stringish?(part) }
142
+ end
143
+ end
144
+
145
+ def merge_string_parts(parts)
146
+ flattened = parts.flat_map do |part|
147
+ part.is_a?(::Parser::AST::Node) && part.dstr_type? ? part.children : [part]
148
+ end
149
+
150
+ flattened.chunk_while { |left, right| plain_string?(left) && plain_string?(right) }
151
+ .map { |chunk| merge_plain_strings(chunk) }
152
+ end
153
+
154
+ def merge_plain_strings(chunk)
155
+ return chunk.first if chunk.one?
156
+
157
+ chunk.first.updated(:str, [chunk.map { |part| part.children.first }.join])
158
+ end
159
+
160
+ def stringish?(node)
161
+ node.is_a?(::Parser::AST::Node) && %i[str dstr].include?(node.type)
162
+ end
163
+
164
+ def plain_string?(node)
165
+ node.is_a?(::Parser::AST::Node) && node.str_type? && node.children.one?
166
+ end
167
+
91
168
  def offense?(node)
92
169
  return false unless node.multiline? && suitable_as_single_line?(node)
93
170
  return require_backslash?(node) if node.operator_keyword?
@@ -94,10 +94,10 @@ module RuboCop
94
94
  return empty_offenses(node, left, right, EMPTY_MSG)
95
95
  end
96
96
 
97
- start_ok = next_to_newline?(node, left)
97
+ start_ok = next_to_newline?(tokens, left)
98
98
  end_ok = node.single_line? ? false : end_has_own_line?(right)
99
99
 
100
- issue_offenses(node, left, right, start_ok, end_ok)
100
+ issue_offenses(node, tokens, left, right, start_ok, end_ok)
101
101
  end
102
102
  alias on_array_pattern on_array
103
103
 
@@ -117,7 +117,7 @@ module RuboCop
117
117
  elsif style == :space
118
118
  SpaceCorrector.add_space(processed_source, corrector, left, right)
119
119
  else
120
- compact_corrections(corrector, node, left, right)
120
+ compact_corrections(corrector, tokens, left, right)
121
121
  end
122
122
  end
123
123
 
@@ -134,8 +134,8 @@ module RuboCop
134
134
  cop_config['EnforcedStyleForEmptyBrackets']
135
135
  end
136
136
 
137
- def next_to_newline?(node, token)
138
- processed_source.tokens_within(node)[index_for(node, token) + 1].line != token.line
137
+ def next_to_newline?(tokens, token)
138
+ tokens[index_for(tokens, token) + 1].line != token.line
139
139
  end
140
140
 
141
141
  def end_has_own_line?(token)
@@ -145,68 +145,67 @@ module RuboCop
145
145
  !/\S/.match?(processed_source.lines[line][0..col])
146
146
  end
147
147
 
148
- def index_for(node, token)
149
- processed_source.tokens_within(node).index(token)
148
+ def index_for(tokens, token)
149
+ tokens.index(token)
150
150
  end
151
151
 
152
152
  def line_and_column_for(token)
153
153
  [token.line - 1, token.column - 1]
154
154
  end
155
155
 
156
- def issue_offenses(node, left, right, start_ok, end_ok)
156
+ # rubocop:disable Metrics/ParameterLists
157
+ def issue_offenses(node, tokens, left, right, start_ok, end_ok)
157
158
  case style
158
159
  when :no_space
159
- start_ok = next_to_comment?(node, left)
160
+ start_ok = next_to_comment?(tokens, left)
160
161
  no_space_offenses(node, left, right, MSG, start_ok: start_ok, end_ok: end_ok)
161
162
  when :space
162
163
  space_offenses(node, left, right, MSG, start_ok: start_ok, end_ok: end_ok)
163
164
  else
164
- compact_offenses(node, left, right, start_ok, end_ok)
165
+ compact_offenses(node, tokens, left, right, start_ok, end_ok)
165
166
  end
166
167
  end
168
+ # rubocop:enable Metrics/ParameterLists
167
169
 
168
- def next_to_comment?(node, token)
169
- processed_source.tokens_within(node)[index_for(node, token) + 1].comment?
170
+ def next_to_comment?(tokens, token)
171
+ tokens[index_for(tokens, token) + 1].comment?
170
172
  end
171
173
 
172
- def compact_offenses(node, left, right, start_ok, end_ok)
173
- if qualifies_for_compact?(node, left, side: :left)
174
+ # rubocop:disable Metrics/ParameterLists
175
+ def compact_offenses(node, tokens, left, right, start_ok, end_ok)
176
+ if qualifies_for_compact?(tokens, left, side: :left)
174
177
  compact_offense(node, left, side: :left)
175
- elsif !multi_dimensional_array?(node, left, side: :left)
178
+ elsif !multi_dimensional_array?(tokens, left, side: :left)
176
179
  space_offenses(node, left, nil, MSG, start_ok: start_ok, end_ok: true)
177
180
  end
178
- if qualifies_for_compact?(node, right)
181
+
182
+ if qualifies_for_compact?(tokens, right)
179
183
  compact_offense(node, right)
180
- elsif !multi_dimensional_array?(node, right)
184
+ elsif !multi_dimensional_array?(tokens, right)
181
185
  space_offenses(node, nil, right, MSG, start_ok: true, end_ok: end_ok)
182
186
  end
183
187
  end
188
+ # rubocop:enable Metrics/ParameterLists
184
189
 
185
- def qualifies_for_compact?(node, token, side: :right)
190
+ def qualifies_for_compact?(tokens, token, side: :right)
186
191
  if side == :right
187
- multi_dimensional_array?(node, token) && token.space_before?
192
+ multi_dimensional_array?(tokens, token) && token.space_before?
188
193
  else
189
- multi_dimensional_array?(node, token, side: :left) && token.space_after?
194
+ multi_dimensional_array?(tokens, token, side: :left) && token.space_after?
190
195
  end
191
196
  end
192
197
 
193
- def multi_dimensional_array?(node, token, side: :right)
198
+ def multi_dimensional_array?(tokens, token, side: :right)
194
199
  offset = side == :right ? -1 : +1
195
- i = index_for(node, token) + offset
196
- i += offset while processed_source.tokens_within(node)[i].new_line?
200
+ i = index_for(tokens, token) + offset
201
+ i += offset while tokens[i].new_line?
197
202
  if side == :right
198
- processed_source.tokens_within(node)[i].right_bracket?
203
+ tokens[i].right_bracket?
199
204
  else
200
- processed_source.tokens_within(node)[i].left_bracket?
205
+ tokens[i].left_bracket?
201
206
  end
202
207
  end
203
208
 
204
- def next_to_bracket?(token, side: :right)
205
- line_index, col = line_and_column_for(token)
206
- line = processed_source.lines[line_index]
207
- side == :right ? line[col] == ']' : line[col + 2] == '['
208
- end
209
-
210
209
  def compact_offense(node, token, side: :right)
211
210
  if side == :right
212
211
  space_offense(node, token, :left, MSG, NO_SPACE_COMMAND)
@@ -215,14 +214,14 @@ module RuboCop
215
214
  end
216
215
  end
217
216
 
218
- def compact_corrections(corrector, node, left, right)
219
- if multi_dimensional_array?(node, left, side: :left)
217
+ def compact_corrections(corrector, tokens, left, right)
218
+ if multi_dimensional_array?(tokens, left, side: :left)
220
219
  compact(corrector, left, :right)
221
220
  elsif !left.space_after?
222
221
  corrector.insert_after(left.pos, ' ')
223
222
  end
224
223
 
225
- if multi_dimensional_array?(node, right)
224
+ if multi_dimensional_array?(tokens, right)
226
225
  compact(corrector, right, :left)
227
226
  elsif !right.space_before?
228
227
  corrector.insert_before(right.pos, ' ')
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Cops for the `Layout` department. The department's cops are registered for lazy loading
6
+ # and their files are loaded on demand.
7
+ module Layout
8
+ extend LazyLoader
9
+
10
+ register_cop :AccessModifierIndentation, "#{__dir__}/layout/access_modifier_indentation"
11
+ register_cop :ArgumentAlignment, "#{__dir__}/layout/argument_alignment"
12
+ register_cop :ArrayAlignment, "#{__dir__}/layout/array_alignment"
13
+ register_cop :AssignmentIndentation, "#{__dir__}/layout/assignment_indentation"
14
+ register_cop :BeginEndAlignment, "#{__dir__}/layout/begin_end_alignment"
15
+ register_cop :BlockAlignment, "#{__dir__}/layout/block_alignment"
16
+ register_cop :BlockEndNewline, "#{__dir__}/layout/block_end_newline"
17
+ register_cop :CaseIndentation, "#{__dir__}/layout/case_indentation"
18
+ register_cop :ClassStructure, "#{__dir__}/layout/class_structure"
19
+ register_cop :ClosingHeredocIndentation, "#{__dir__}/layout/closing_heredoc_indentation"
20
+ register_cop :ClosingParenthesisIndentation, "#{__dir__}/layout/closing_parenthesis_indentation"
21
+ register_cop :CommentIndentation, "#{__dir__}/layout/comment_indentation"
22
+ register_cop :ConditionPosition, "#{__dir__}/layout/condition_position"
23
+ register_cop :DefEndAlignment, "#{__dir__}/layout/def_end_alignment"
24
+ register_cop :DotPosition, "#{__dir__}/layout/dot_position"
25
+ register_cop :ElseAlignment, "#{__dir__}/layout/else_alignment"
26
+ register_cop :EmptyComment, "#{__dir__}/layout/empty_comment"
27
+ register_cop :EmptyLineAfterGuardClause, "#{__dir__}/layout/empty_line_after_guard_clause"
28
+ register_cop :EmptyLineAfterMagicComment, "#{__dir__}/layout/empty_line_after_magic_comment"
29
+ register_cop :EmptyLineAfterMultilineCondition, "#{__dir__}/layout/empty_line_after_multiline_condition"
30
+ register_cop :EmptyLineBetweenDefs, "#{__dir__}/layout/empty_line_between_defs"
31
+ register_cop :EmptyLinesAfterModuleInclusion, "#{__dir__}/layout/empty_lines_after_module_inclusion"
32
+ register_cop :EmptyLinesAroundAccessModifier, "#{__dir__}/layout/empty_lines_around_access_modifier"
33
+ register_cop :EmptyLinesAroundArguments, "#{__dir__}/layout/empty_lines_around_arguments"
34
+ register_cop :EmptyLinesAroundAttributeAccessor, "#{__dir__}/layout/empty_lines_around_attribute_accessor"
35
+ register_cop :EmptyLinesAroundBeginBody, "#{__dir__}/layout/empty_lines_around_begin_body"
36
+ register_cop :EmptyLinesAroundBlockBody, "#{__dir__}/layout/empty_lines_around_block_body"
37
+ register_cop :EmptyLinesAroundClassBody, "#{__dir__}/layout/empty_lines_around_class_body"
38
+ register_cop :EmptyLinesAroundExceptionHandlingKeywords, "#{__dir__}/layout/empty_lines_around_exception_handling_keywords"
39
+ register_cop :EmptyLinesAroundMethodBody, "#{__dir__}/layout/empty_lines_around_method_body"
40
+ register_cop :EmptyLinesAroundModuleBody, "#{__dir__}/layout/empty_lines_around_module_body"
41
+ register_cop :EmptyLines, "#{__dir__}/layout/empty_lines"
42
+ register_cop :EndAlignment, "#{__dir__}/layout/end_alignment"
43
+ register_cop :EndOfLine, "#{__dir__}/layout/end_of_line"
44
+ register_cop :ExtraSpacing, "#{__dir__}/layout/extra_spacing"
45
+ register_cop :FirstArgumentIndentation, "#{__dir__}/layout/first_argument_indentation"
46
+ register_cop :FirstArrayElementIndentation, "#{__dir__}/layout/first_array_element_indentation"
47
+ register_cop :FirstArrayElementLineBreak, "#{__dir__}/layout/first_array_element_line_break"
48
+ register_cop :FirstHashElementIndentation, "#{__dir__}/layout/first_hash_element_indentation"
49
+ register_cop :FirstHashElementLineBreak, "#{__dir__}/layout/first_hash_element_line_break"
50
+ register_cop :FirstMethodArgumentLineBreak, "#{__dir__}/layout/first_method_argument_line_break"
51
+ register_cop :FirstMethodParameterLineBreak, "#{__dir__}/layout/first_method_parameter_line_break"
52
+ register_cop :FirstParameterIndentation, "#{__dir__}/layout/first_parameter_indentation"
53
+ register_cop :HashAlignment, "#{__dir__}/layout/hash_alignment"
54
+ register_cop :HeredocArgumentClosingParenthesis, "#{__dir__}/layout/heredoc_argument_closing_parenthesis"
55
+ register_cop :HeredocIndentation, "#{__dir__}/layout/heredoc_indentation"
56
+ register_cop :IndentationConsistency, "#{__dir__}/layout/indentation_consistency"
57
+ register_cop :IndentationStyle, "#{__dir__}/layout/indentation_style"
58
+ register_cop :IndentationWidth, "#{__dir__}/layout/indentation_width"
59
+ register_cop :InitialIndentation, "#{__dir__}/layout/initial_indentation"
60
+ register_cop :LeadingCommentSpace, "#{__dir__}/layout/leading_comment_space"
61
+ register_cop :LeadingEmptyLines, "#{__dir__}/layout/leading_empty_lines"
62
+ register_cop :LineContinuationLeadingSpace, "#{__dir__}/layout/line_continuation_leading_space"
63
+ register_cop :LineContinuationSpacing, "#{__dir__}/layout/line_continuation_spacing"
64
+ register_cop :LineEndStringConcatenationIndentation, "#{__dir__}/layout/line_end_string_concatenation_indentation"
65
+ register_cop :LineLength, "#{__dir__}/layout/line_length"
66
+ register_cop :MultilineArrayBraceLayout, "#{__dir__}/layout/multiline_array_brace_layout"
67
+ register_cop :MultilineArrayLineBreaks, "#{__dir__}/layout/multiline_array_line_breaks"
68
+ register_cop :MultilineAssignmentLayout, "#{__dir__}/layout/multiline_assignment_layout"
69
+ register_cop :MultilineBlockLayout, "#{__dir__}/layout/multiline_block_layout"
70
+ register_cop :MultilineHashBraceLayout, "#{__dir__}/layout/multiline_hash_brace_layout"
71
+ register_cop :MultilineHashKeyLineBreaks, "#{__dir__}/layout/multiline_hash_key_line_breaks"
72
+ register_cop :MultilineMethodArgumentLineBreaks, "#{__dir__}/layout/multiline_method_argument_line_breaks"
73
+ register_cop :MultilineMethodCallBraceLayout, "#{__dir__}/layout/multiline_method_call_brace_layout"
74
+ register_cop :MultilineMethodCallIndentation, "#{__dir__}/layout/multiline_method_call_indentation"
75
+ register_cop :MultilineMethodDefinitionBraceLayout, "#{__dir__}/layout/multiline_method_definition_brace_layout"
76
+ register_cop :MultilineMethodParameterLineBreaks, "#{__dir__}/layout/multiline_method_parameter_line_breaks"
77
+ register_cop :MultilineOperationIndentation, "#{__dir__}/layout/multiline_operation_indentation"
78
+ register_cop :ParameterAlignment, "#{__dir__}/layout/parameter_alignment"
79
+ register_cop :RedundantLineBreak, "#{__dir__}/layout/redundant_line_break"
80
+ register_cop :RescueEnsureAlignment, "#{__dir__}/layout/rescue_ensure_alignment"
81
+ register_cop :SingleLineBlockChain, "#{__dir__}/layout/single_line_block_chain"
82
+ register_cop :SpaceAfterColon, "#{__dir__}/layout/space_after_colon"
83
+ register_cop :SpaceAfterComma, "#{__dir__}/layout/space_after_comma"
84
+ register_cop :SpaceAfterMethodName, "#{__dir__}/layout/space_after_method_name"
85
+ register_cop :SpaceAfterNot, "#{__dir__}/layout/space_after_not"
86
+ register_cop :SpaceAfterSemicolon, "#{__dir__}/layout/space_after_semicolon"
87
+ register_cop :SpaceAroundBlockParameters, "#{__dir__}/layout/space_around_block_parameters"
88
+ register_cop :SpaceAroundEqualsInParameterDefault, "#{__dir__}/layout/space_around_equals_in_parameter_default"
89
+ register_cop :SpaceAroundKeyword, "#{__dir__}/layout/space_around_keyword"
90
+ register_cop :SpaceAroundMethodCallOperator, "#{__dir__}/layout/space_around_method_call_operator"
91
+ register_cop :SpaceAroundOperators, "#{__dir__}/layout/space_around_operators"
92
+ register_cop :SpaceBeforeBlockBraces, "#{__dir__}/layout/space_before_block_braces"
93
+ register_cop :SpaceBeforeBrackets, "#{__dir__}/layout/space_before_brackets"
94
+ register_cop :SpaceBeforeComma, "#{__dir__}/layout/space_before_comma"
95
+ register_cop :SpaceBeforeComment, "#{__dir__}/layout/space_before_comment"
96
+ register_cop :SpaceBeforeFirstArg, "#{__dir__}/layout/space_before_first_arg"
97
+ register_cop :SpaceBeforeSemicolon, "#{__dir__}/layout/space_before_semicolon"
98
+ register_cop :SpaceInLambdaLiteral, "#{__dir__}/layout/space_in_lambda_literal"
99
+ register_cop :SpaceInsideArrayPercentLiteral, "#{__dir__}/layout/space_inside_array_percent_literal"
100
+ register_cop :SpaceInsideArrayLiteralBrackets, "#{__dir__}/layout/space_inside_array_literal_brackets"
101
+ register_cop :SpaceInsideBlockBraces, "#{__dir__}/layout/space_inside_block_braces"
102
+ register_cop :SpaceInsideHashLiteralBraces, "#{__dir__}/layout/space_inside_hash_literal_braces"
103
+ register_cop :SpaceInsideParens, "#{__dir__}/layout/space_inside_parens"
104
+ register_cop :SpaceInsidePercentLiteralDelimiters, "#{__dir__}/layout/space_inside_percent_literal_delimiters"
105
+ register_cop :SpaceInsideRangeLiteral, "#{__dir__}/layout/space_inside_range_literal"
106
+ register_cop :SpaceInsideReferenceBrackets, "#{__dir__}/layout/space_inside_reference_brackets"
107
+ register_cop :SpaceInsideStringInterpolation, "#{__dir__}/layout/space_inside_string_interpolation"
108
+ register_cop :TrailingEmptyLines, "#{__dir__}/layout/trailing_empty_lines"
109
+ register_cop :TrailingWhitespace, "#{__dir__}/layout/trailing_whitespace"
110
+
111
+ autoload :EmptyLinesAroundBody, "#{__dir__}/mixin/empty_lines_around_body"
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Extend a department module with this module and call `register_cop` to register
6
+ # the department's cops with the global registry without loading their files until
7
+ # a cop class itself is needed:
8
+ #
9
+ # module RuboCop
10
+ # module Cop
11
+ # # The Foo department.
12
+ # module Foo
13
+ # extend LazyLoader
14
+ #
15
+ # register_cop :BarBaz, "#{__dir__}/foo/bar_baz"
16
+ # end
17
+ # end
18
+ # end
19
+ module LazyLoader
20
+ # Registers a cop for lazy loading. The cop appears in the registry by name right away,
21
+ # but its file is loaded only when the cop class is needed, e.g. when the cop is enabled
22
+ # for an inspection run.
23
+ #
24
+ # The path must be absolute so that loading does not depend on `$LOAD_PATH` and cannot resolve
25
+ # to another RuboCop installation.
26
+ #
27
+ # @param constant_name [Symbol] name of the cop class, e.g. `:BarBaz`
28
+ # @param path [String] absolute path to the file defining the cop class,
29
+ # with or without the `.rb` extension
30
+ def register_cop(constant_name, path)
31
+ raise ArgumentError, "cop path must be absolute: #{path}" unless File.absolute_path?(path)
32
+
33
+ autoload constant_name, path
34
+
35
+ qualified_name = "#{name}::#{constant_name}"
36
+
37
+ Registry.global.lazy_load(Badge.for(qualified_name), qualified_name)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -19,25 +19,27 @@ module RuboCop
19
19
  # x != y # or x = !y
20
20
  #
21
21
  class AmbiguousAssignment < Base
22
+ include CheckAssignment
22
23
  include RangeHelp
23
24
 
24
25
  MSG = 'Suspicious assignment detected. Did you mean `%<op>s`?'
25
26
 
26
- SIMPLE_ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn casgn].freeze
27
-
28
27
  MISTAKES = { '=-' => '-=', '=+' => '+=', '=*' => '*=', '=!' => '!=' }.freeze
29
28
 
30
- def on_asgn(node)
31
- return unless (rhs = node.expression)
29
+ alias on_csend on_send
30
+
31
+ private
32
32
 
33
- range = range_between(node.loc.operator.end_pos - 1, rhs.source_range.begin_pos + 1)
33
+ def check_assignment(node, rhs)
34
+ return unless rhs
35
+ return unless (operator = node.loc.operator)
36
+
37
+ range = range_between(operator.end_pos - 1, rhs.source_range.begin_pos + 1)
34
38
  source = range.source
35
39
  return unless MISTAKES.key?(source)
36
40
 
37
41
  add_offense(range, message: format(MSG, op: MISTAKES[source]))
38
42
  end
39
-
40
- SIMPLE_ASSIGNMENT_TYPES.each { |asgn_type| alias_method :"on_#{asgn_type}", :on_asgn }
41
43
  end
42
44
  end
43
45
  end
@@ -6,6 +6,11 @@ module RuboCop
6
6
  # Checks for ambiguous block association with method
7
7
  # when param passed without parentheses.
8
8
  #
9
+ # This cop also detects `do...end` blocks that are likely intended for
10
+ # an enumerable method in the arguments but actually bind to the outer
11
+ # method call. For example, in `render json: data.map do |x| x end`,
12
+ # Ruby parses the `do...end` block as belonging to `render`, not `map`.
13
+ #
9
14
  # This cop can customize allowed methods with `AllowedMethods`.
10
15
  # By default, there are no allowed methods.
11
16
  #
@@ -20,6 +25,18 @@ module RuboCop
20
25
  # # or (different meaning)
21
26
  # some_method(a) { |val| puts val }
22
27
  #
28
+ # # bad
29
+ # render json: data.map do |item|
30
+ # item.to_h
31
+ # end
32
+ #
33
+ # # good
34
+ # render json: data.map { |item| item.to_h }
35
+ #
36
+ # # good
37
+ # mapped = data.map { |item| item.to_h }
38
+ # render json: mapped
39
+ #
23
40
  # # good
24
41
  # # Operator methods require no disambiguation
25
42
  # foo == bar { |b| b.baz }
@@ -58,6 +75,19 @@ module RuboCop
58
75
  MSG = 'Parenthesize the param `%<param>s` to make sure that the ' \
59
76
  'block will be associated with the `%<method>s` method ' \
60
77
  'call.'
78
+ MSG_DO_END_BLOCK = '`%<inner_method>s` is called without a block because the ' \
79
+ '`do` block binds to `%<outer_method>s`. ' \
80
+ 'Use braces or extract to a variable.'
81
+
82
+ BLOCK_METHODS = %i[
83
+ map collect flat_map collect_concat
84
+ select filter find_all reject
85
+ find detect
86
+ each each_with_object each_with_index
87
+ reduce inject
88
+ sort_by min_by max_by
89
+ group_by filter_map
90
+ ].to_set.freeze
61
91
 
62
92
  def on_send(node)
63
93
  return unless node.arguments?
@@ -74,8 +104,36 @@ module RuboCop
74
104
  end
75
105
  alias on_csend on_send
76
106
 
107
+ def on_block(node)
108
+ return if node.braces?
109
+
110
+ send_node = node.send_node
111
+ block_method_arg = find_ambiguous_block_method(node)
112
+ return unless block_method_arg
113
+
114
+ add_offense(block_method_arg,
115
+ message: format(MSG_DO_END_BLOCK,
116
+ inner_method: block_method_arg.method_name,
117
+ outer_method: send_node.method_name))
118
+ end
119
+ alias on_numblock on_block
120
+ alias on_itblock on_block
121
+
77
122
  private
78
123
 
124
+ def find_ambiguous_block_method(block_node)
125
+ send_node = block_node.send_node
126
+ return unless send_node.send_type? && send_node.arguments?
127
+ return if send_node.parenthesized?
128
+
129
+ block_method_arg = find_block_method_arg(send_node)
130
+ return unless block_method_arg
131
+ return if allowed_method?(block_method_arg.method_name) ||
132
+ matches_allowed_pattern?(block_method_arg.source)
133
+
134
+ block_method_arg
135
+ end
136
+
79
137
  def ambiguous_block_association?(send_node)
80
138
  send_node.last_argument.any_block_type? && !send_node.last_argument.send_node.arguments?
81
139
  end
@@ -92,6 +150,19 @@ module RuboCop
92
150
  format(MSG, param: block_param.source, method: block_param.send_node.source)
93
151
  end
94
152
 
153
+ # Only the call the `do` block could actually have attached to is a candidate:
154
+ # the one whose source ends where the arguments end. This skips calls chained
155
+ # into another call (`foo bar.map.to_a`) and calls buried inside a
156
+ # parenthesized subcall (`foo bar(baz.map)`), which were never block candidates.
157
+ def find_block_method_arg(send_node)
158
+ end_pos = send_node.source_range.end_pos
159
+
160
+ send_node.last_argument.each_node(:call).find do |node|
161
+ node.source_range.end_pos == end_pos &&
162
+ BLOCK_METHODS.include?(node.method_name) && !node.arguments?
163
+ end
164
+ end
165
+
95
166
  def wrap_in_parentheses(corrector, node)
96
167
  range = node.loc.selector.end.join(node.first_argument.source_range.begin)
97
168
 
@@ -209,21 +209,12 @@ module RuboCop
209
209
  def report_cross_file_collision(node, fully_qualified_name, display_name)
210
210
  return unless project_index
211
211
  return unless (declaration = project_index[fully_qualified_name.delete_prefix('::')])
212
- return unless (prior = prior_definition_in_other_file(declaration))
212
+ return unless (prior = prior_definition_in_other_file(declaration.definitions))
213
213
 
214
214
  msg = format(CROSS_FILE_MSG, constant: display_name, path: prior.location.to_file_path)
215
215
 
216
216
  add_offense(node, message: msg)
217
217
  end
218
-
219
- def prior_definition_in_other_file(declaration)
220
- current = processed_source.file_path
221
-
222
- declaration.definitions.find do |definition|
223
- other = definition.location.to_file_path
224
- !File.identical?(other, current)
225
- end
226
- end
227
218
  end
228
219
  end
229
220
  end
@@ -20,6 +20,12 @@ module RuboCop
20
20
  # to prevent conflicting rules. This is because it respects user configurations
21
21
  # that want to enable this cop which is disabled by default.
22
22
  #
23
+ # When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
24
+ # installed, only genuinely ambiguous constants are reported: those
25
+ # that resolve to a different declaration through the surrounding
26
+ # nesting than they would fully qualified. This makes the cop practical
27
+ # to enable without `Only`/`Ignore` lists.
28
+ #
23
29
  # @example
24
30
  # # By default checks every constant
25
31
  #
@@ -60,6 +66,8 @@ module RuboCop
60
66
  # Login
61
67
  #
62
68
  class ConstantResolution < Base
69
+ include ProjectIndexHelp
70
+
63
71
  MSG = 'Fully qualify this constant to avoid possibly ambiguous resolution.'
64
72
 
65
73
  # @!method unqualified_const?(node)
@@ -69,12 +77,29 @@ module RuboCop
69
77
 
70
78
  def on_const(node)
71
79
  return if !unqualified_const?(node) || node.parent&.defined_module || node.loc.nil?
80
+ return if project_index && !ambiguous_resolution?(node)
72
81
 
73
82
  add_offense(node)
74
83
  end
75
84
 
76
85
  private
77
86
 
87
+ # When `AllCops/UseProjectIndex` is enabled, only genuinely ambiguous
88
+ # references are reported: the constant resolves to a different
89
+ # declaration through the lexical nesting than it would fully
90
+ # qualified. Names that resolve identically either way, or that the
91
+ # index cannot resolve at all (e.g. constants from gems), are not
92
+ # reported.
93
+ def ambiguous_resolution?(node)
94
+ name = node.short_name.to_s
95
+ relative = project_index.resolve_constant(name, lexical_nesting_of(node))
96
+ absolute = project_index.resolve_constant(name, [])
97
+
98
+ !relative.nil? && !absolute.nil? && relative.name != absolute.name
99
+ rescue StandardError
100
+ false
101
+ end
102
+
78
103
  def const_name?(name)
79
104
  name = name.to_s
80
105
  (allowed_names.empty? || allowed_names.include?(name)) && !ignored_names.include?(name)