rubocop 1.91.0 → 1.92.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 (263) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +4 -1
  4. data/lib/rubocop/cached_data.rb +47 -7
  5. data/lib/rubocop/changed_files.rb +35 -13
  6. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +2 -2
  8. data/lib/rubocop/cli/command/explain.rb +34 -0
  9. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  10. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  11. data/lib/rubocop/cli.rb +3 -2
  12. data/lib/rubocop/comment_config.rb +17 -8
  13. data/lib/rubocop/config_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +42 -21
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  22. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  23. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  25. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  26. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  27. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  28. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  29. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  30. data/lib/rubocop/cop/documentation.rb +1 -1
  31. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  32. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  33. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  34. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  35. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  36. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  37. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  38. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  39. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  40. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  41. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  42. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  43. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  44. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  46. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  47. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  48. data/lib/rubocop/cop/layout/end_alignment.rb +19 -17
  49. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  50. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  51. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  52. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  53. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  54. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  55. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  56. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  57. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  58. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  59. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  60. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  62. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  63. data/lib/rubocop/cop/layout/line_length.rb +153 -21
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  65. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  66. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  67. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  69. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  70. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  71. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  72. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  73. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  74. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  75. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  76. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  77. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  78. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  79. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  80. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  81. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  82. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  83. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  84. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  85. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  86. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  87. data/lib/rubocop/cop/lint/loop.rb +24 -2
  88. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  89. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  90. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  91. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  92. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  93. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  94. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  95. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  96. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  97. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  98. data/lib/rubocop/cop/lint/void.rb +8 -1
  99. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  100. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  101. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  102. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  103. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  104. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  105. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  106. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  107. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  108. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  109. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +24 -5
  110. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  111. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  112. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  113. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  114. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  115. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  116. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  117. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  118. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  119. data/lib/rubocop/cop/mixin/statement_modifier.rb +25 -4
  120. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  121. data/lib/rubocop/cop/mixin.rb +2 -0
  122. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  123. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  124. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  125. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  126. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  127. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  128. data/lib/rubocop/cop/offense.rb +59 -3
  129. data/lib/rubocop/cop/registry.rb +1 -1
  130. data/lib/rubocop/cop/security/json_load.rb +16 -4
  131. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  132. data/lib/rubocop/cop/style/alias.rb +18 -5
  133. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  134. data/lib/rubocop/cop/style/and_or.rb +6 -2
  135. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  136. data/lib/rubocop/cop/style/attr.rb +3 -4
  137. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  138. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  139. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  140. data/lib/rubocop/cop/style/class_and_module_children.rb +56 -16
  141. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  142. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  143. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  144. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  145. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  146. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  147. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  148. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  149. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  150. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  151. data/lib/rubocop/cop/style/endless_method.rb +71 -6
  152. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  153. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  154. data/lib/rubocop/cop/style/float_division.rb +22 -2
  155. data/lib/rubocop/cop/style/for.rb +53 -2
  156. data/lib/rubocop/cop/style/format_string.rb +28 -6
  157. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  158. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  160. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  162. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  163. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  164. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  166. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  167. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  168. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  169. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  170. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  171. data/lib/rubocop/cop/style/lambda.rb +34 -1
  172. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  173. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  174. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  175. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  176. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  179. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  180. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  181. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  182. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  183. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  184. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  185. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  186. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  187. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  188. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  189. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  190. data/lib/rubocop/cop/style/next.rb +24 -10
  191. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  192. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  193. data/lib/rubocop/cop/style/not.rb +10 -5
  194. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  195. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  196. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  197. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  198. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  199. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  200. data/lib/rubocop/cop/style/redundant_begin.rb +22 -4
  201. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  202. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  203. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  204. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  205. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  206. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  207. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  208. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  209. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  210. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  211. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  212. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  213. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  214. data/lib/rubocop/cop/style/require_order.rb +25 -8
  215. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  216. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  217. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  218. data/lib/rubocop/cop/style/sample.rb +9 -8
  219. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  220. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  221. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  222. data/lib/rubocop/cop/style/single_line_methods.rb +21 -4
  223. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  224. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  226. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  227. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  228. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  229. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  230. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  231. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  232. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  233. data/lib/rubocop/cop/team.rb +10 -5
  234. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  235. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  236. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  237. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  238. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  239. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  240. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  241. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  242. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  243. data/lib/rubocop/lsp/routes.rb +23 -23
  244. data/lib/rubocop/lsp/runtime.rb +47 -11
  245. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  246. data/lib/rubocop/lsp.rb +5 -2
  247. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  248. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  249. data/lib/rubocop/mcp/scope.rb +65 -0
  250. data/lib/rubocop/mcp/server.rb +184 -76
  251. data/lib/rubocop/mcp/source_files.rb +35 -0
  252. data/lib/rubocop/offense_limit.rb +30 -0
  253. data/lib/rubocop/options.rb +45 -10
  254. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  255. data/lib/rubocop/remote_config.rb +4 -1
  256. data/lib/rubocop/result_cache.rb +20 -13
  257. data/lib/rubocop/runner.rb +109 -41
  258. data/lib/rubocop/server/cache.rb +6 -6
  259. data/lib/rubocop/target_ruby.rb +21 -9
  260. data/lib/rubocop/util.rb +69 -0
  261. data/lib/rubocop/version.rb +1 -1
  262. data/lib/rubocop.rb +5 -0
  263. metadata +16 -5
@@ -216,14 +216,8 @@ module RuboCop
216
216
  message = find_message(range_to_pass, message)
217
217
 
218
218
  status, corrector = enabled_lines?(range) ? correct(range, &block) : :disabled
219
- justification = suppression_reason(range) if status == :disabled
220
219
 
221
- # Since this range may be generated from Ruby code embedded in some
222
- # template file, we convert it to location info in the original file.
223
- range = range_for_original(range)
224
-
225
- current_offenses << Offense.new(severity, range, message, name, status, corrector,
226
- justification: justification)
220
+ current_offenses << new_offense(range, severity, message, status, corrector)
227
221
  end
228
222
 
229
223
  # This method should be overridden when a cop's behavior depends
@@ -271,10 +265,21 @@ module RuboCop
271
265
  @config.target_ruby_version
272
266
  end
273
267
 
274
- # Returns a gems locked versions (i.e. from Gemfile.lock or gems.locked)
275
- # @returns [Gem::Version | nil] The locked gem version, or nil if the gem is not present.
268
+ # Returns the version of a gem in the target, by default its locked version
269
+ # (i.e. from Gemfile.lock or gems.locked).
270
+ #
271
+ # The requirements declared with `requires_gem` are checked against the version
272
+ # this method returns, so an extension that lets users state the version of its
273
+ # gem in `AllCops` (e.g. `TargetFrameworkVersion`) can override it for that gem,
274
+ # returning a `Gem::Version`, and fall back to `super` for any other gem.
275
+ #
276
+ # @param [String] gem_name
277
+ # @return [Gem::Version, nil] The gem version, or nil if the gem is not present.
278
+ #
279
+ # @api public
276
280
  def target_gem_version(gem_name)
277
- @config.gem_versions_in_target && @config.gem_versions_in_target[gem_name]
281
+ gem_versions = @config.gem_versions_in_target
282
+ gem_versions && gem_versions[gem_name]
278
283
  end
279
284
 
280
285
  def parser_engine
@@ -382,6 +387,12 @@ module RuboCop
382
387
  cop_config.fetch('AutoCorrect', 'always') == 'contextual'
383
388
  end
384
389
 
390
+ # @api private
391
+ def disabled_autocorrect?
392
+ # `false` is the same as `'disabled'` for backward compatibility.
393
+ ['disabled', false].include?(cop_config['AutoCorrect'])
394
+ end
395
+
385
396
  def inspect # :nodoc:
386
397
  "#<#{self.class.name}:#{object_id} @config=#{@config} @options=#{@options}>"
387
398
  end
@@ -394,6 +405,22 @@ module RuboCop
394
405
  @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
395
406
  end
396
407
 
408
+ # @return [Offense]
409
+ def new_offense(range, severity, message, status, corrector)
410
+ justification = suppression_reason(range) if status == :disabled
411
+
412
+ # Since this range may be generated from Ruby code embedded in some
413
+ # template file, we convert it to location info in the original file.
414
+ # A cop configured with `AutoCorrect: false` reports no edits, since
415
+ # nothing that honors the configuration applies them. The corrector
416
+ # stays, so an editor can still offer it as a quick fix to choose.
417
+ corrections = [] if corrector && disabled_autocorrect?
418
+
419
+ Offense.new(severity, range_for_original(range), message, name, status, corrector,
420
+ justification: justification, correction_safe: safe_autocorrect?,
421
+ corrections: corrections)
422
+ end
423
+
397
424
  ### Reserved for Cop::Cop
398
425
 
399
426
  def callback_argument(range)
@@ -465,7 +492,8 @@ module RuboCop
465
492
  # The unsafe correction will not run, so the offense is
466
493
  # uncorrectable for this run and gets a todo comment.
467
494
  attempt_correction(range, nil)
468
- elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?))
495
+ elsif corrector && (always_autocorrect? ||
496
+ (contextual_autocorrect? && !contextual_autocorrect_held_back?))
469
497
  :uncorrected
470
498
  else
471
499
  :unsupported
@@ -601,17 +629,10 @@ module RuboCop
601
629
  end
602
630
 
603
631
  def target_satisfies_all_gem_version_requirements?
604
- gem_requirements = self.class.gem_requirements
605
- return true if gem_requirements.empty?
606
-
607
- gem_requirements.all? do |gem_name, version_req|
608
- all_gem_versions_in_target = @config.gem_versions_in_target
609
- next false unless all_gem_versions_in_target
610
-
611
- gem_version_in_target = all_gem_versions_in_target[gem_name]
612
- next false unless gem_version_in_target
632
+ self.class.gem_requirements.all? do |gem_name, version_requirement|
633
+ gem_version = target_gem_version(gem_name)
613
634
 
614
- version_req.satisfied_by?(gem_version_in_target)
635
+ gem_version && version_requirement.satisfied_by?(gem_version)
615
636
  end
616
637
  end
617
638
  end
@@ -81,7 +81,6 @@ module RuboCop
81
81
  # gem 'bar', '< 2.1'
82
82
  #
83
83
  class GemComment < Base
84
- include DefNode
85
84
  include GemDeclaration
86
85
 
87
86
  MSG = 'Missing gem description comment.'
@@ -51,14 +51,6 @@ module RuboCop
51
51
  end
52
52
  end
53
53
 
54
- private
55
-
56
- def previous_declaration(node)
57
- declarations = gem_declarations(processed_source.ast)
58
- node_index = declarations.map(&:location).find_index(node.location)
59
- declarations.to_a[node_index - 1]
60
- end
61
-
62
54
  # @!method gem_declarations(node)
63
55
  def_node_search :gem_declarations, <<~PATTERN
64
56
  (:send nil? :gem str ...)
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Reads the documentation comment a cop carries above its class definition
6
+ # and splits it into the sections worth showing on their own.
7
+ #
8
+ # YARD is a development dependency, so the comment is read straight from the
9
+ # source rather than through a doclet.
10
+ # @api private
11
+ class CopDocumentation
12
+ TAG = /^@(\w+)\s*(.*)$/.freeze
13
+
14
+ def initialize(cop_class)
15
+ @cop_class = cop_class
16
+ end
17
+
18
+ # @return [Array<Array(String, Array<String>)>] title and body per section
19
+ def sections
20
+ comment = comment_lines
21
+ return [] if comment.empty?
22
+
23
+ prose, tagged = split_on_first_tag(comment)
24
+
25
+ [['Details', strip_blank(prose)], *tagged_sections(tagged)].reject { |_, b| b.empty? }
26
+ end
27
+
28
+ private
29
+
30
+ def comment_lines
31
+ path, line = source_location
32
+ return [] unless path && File.file?(path)
33
+
34
+ lines = File.readlines(path, chomp: true)
35
+ collect_comment_above(lines, line - 2)
36
+ end
37
+
38
+ def source_location
39
+ name = @cop_class.name
40
+ return [] unless name
41
+
42
+ Module.const_source_location(name) || []
43
+ rescue NameError
44
+ []
45
+ end
46
+
47
+ # Walks up from the class definition for as long as the lines are comments.
48
+ def collect_comment_above(lines, index)
49
+ block = []
50
+ while index >= 0 && lines[index].match?(/^\s*#/)
51
+ block.unshift(lines[index].sub(/^\s*# ?/, ''))
52
+ index -= 1
53
+ end
54
+ block
55
+ end
56
+
57
+ def split_on_first_tag(comment)
58
+ first_tag = comment.index { |line| line.match?(TAG) }
59
+ return [comment, []] unless first_tag
60
+
61
+ [comment[0...first_tag], comment[first_tag..]]
62
+ end
63
+
64
+ def tagged_sections(lines)
65
+ lines.slice_before { |line| line.match?(TAG) }.map do |(head, *body)|
66
+ tag, argument = head.match(TAG).captures
67
+ [section_title(tag, argument), strip_blank(body)]
68
+ end
69
+ end
70
+
71
+ def section_title(tag, argument)
72
+ title = tag.capitalize
73
+ argument.empty? ? title : "#{title}: #{argument}"
74
+ end
75
+
76
+ def strip_blank(lines)
77
+ lines.drop_while(&:empty?).reverse.drop_while(&:empty?).reverse
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Explains what a single cop does: its description, how it is configured,
6
+ # whether it corrects, and the examples from its own documentation.
7
+ #
8
+ # Rendered as plain text, which is what `--explain` prints and what the MCP
9
+ # server hands to an agent.
10
+ # @api private
11
+ class CopExplanation
12
+ # Keys that describe the cop to RuboCop itself rather than something a
13
+ # user would set, so they are reported in their own section or not at all.
14
+ METADATA_KEYS = %w[
15
+ Description StyleGuide Reference References Enabled Safe SafeAutoCorrect
16
+ AutoCorrect VersionAdded VersionChanged Include Exclude Details Preview
17
+ ].freeze
18
+
19
+ def initialize(cop_class, config)
20
+ @cop_class = cop_class
21
+ @config = config
22
+ @cop_config = config.for_cop(cop_class.cop_name)
23
+ end
24
+
25
+ def to_s
26
+ lines.join("\n")
27
+ end
28
+
29
+ private
30
+
31
+ def lines
32
+ description = description_lines
33
+
34
+ [
35
+ @cop_class.cop_name,
36
+ *section(nil, description),
37
+ *section('Properties', property_lines),
38
+ *documentation_sections(description).flat_map { |title, body| section(title, body) },
39
+ *section('Configuration', configuration_lines),
40
+ *section('References', reference_lines)
41
+ ]
42
+ end
43
+
44
+ def section(title, body)
45
+ return [] if body.empty?
46
+
47
+ heading = title ? [title, ''] : []
48
+ ['', *heading, *body.map { |line| line.empty? ? '' : " #{line}" }]
49
+ end
50
+
51
+ def description_lines
52
+ Array(@cop_config['Description']).flat_map { |line| line.split("\n") }
53
+ end
54
+
55
+ def property_lines
56
+ [
57
+ "Enabled: #{@cop_config.fetch('Enabled', true)}",
58
+ "Safe: #{@cop_config.fetch('Safe', true)}",
59
+ "Autocorrect: #{autocorrect_description}",
60
+ *version_lines,
61
+ *scope_lines
62
+ ]
63
+ end
64
+
65
+ # Which files the cop looks at. A cop that only runs on Gemfiles
66
+ # explains an absent offense better than anything else here does.
67
+ def scope_lines
68
+ [
69
+ *patterns_line('Applies to: ', @cop_config['Include']),
70
+ *patterns_line('Excludes: ', @cop_config['Exclude'])
71
+ ]
72
+ end
73
+
74
+ def patterns_line(label, patterns)
75
+ patterns = Array(patterns).compact
76
+ return [] if patterns.empty?
77
+
78
+ ["#{label}#{patterns.map { |pattern| readable_pattern(pattern) }.join(', ')}"]
79
+ end
80
+
81
+ # `Exclude` patterns are absolutized against the config that set them,
82
+ # which makes for a wall of identical prefixes. A user config can also
83
+ # hold a regexp rather than a glob.
84
+ def readable_pattern(pattern)
85
+ pattern.is_a?(String) ? PathUtil.smart_path(pattern) : pattern.inspect
86
+ end
87
+
88
+ def autocorrect_description
89
+ return 'not supported' unless @cop_class.support_autocorrect?
90
+
91
+ # Asks the cop, so `AutoCorrect` is read the way the cop reads it.
92
+ cop = @cop_class.new(@config)
93
+ return 'disabled' unless cop.always_autocorrect? || cop.contextual_autocorrect?
94
+
95
+ description = cop.safe_autocorrect? ? 'safe, applied by -a' : 'unsafe, applied by -A only'
96
+ # Contextual corrections wait until the code is finished, which an
97
+ # editor or an agent mid-edit cannot promise, so they have to ask.
98
+ description += ', but through LSP or MCP only on request' if cop.contextual_autocorrect?
99
+ description
100
+ end
101
+
102
+ def version_lines
103
+ lines = []
104
+ lines << "Added: #{@cop_config['VersionAdded']}" if @cop_config['VersionAdded']
105
+ lines << "Changed: #{@cop_config['VersionChanged']}" if @cop_config['VersionChanged']
106
+ lines
107
+ end
108
+
109
+ def configuration_lines
110
+ (@cop_config.keys - METADATA_KEYS).sort.map do |key|
111
+ "#{key}: #{format_value(@cop_config[key])}"
112
+ end
113
+ end
114
+
115
+ def format_value(value)
116
+ value.is_a?(Array) ? value.join(', ') : value.to_s
117
+ end
118
+
119
+ def reference_lines
120
+ annotator = MessageAnnotator.new(@config, @cop_class.cop_name, @cop_config, {})
121
+
122
+ [*annotator.urls, Documentation.url_for(@cop_class, @config)].compact
123
+ end
124
+
125
+ # The comment above the class opens by restating the description, which
126
+ # is already printed above, so that opening is dropped.
127
+ def documentation_sections(description)
128
+ CopDocumentation.new(@cop_class).sections.filter_map do |title, body|
129
+ body -= description if title == 'Details'
130
+ body = strip_blank(body)
131
+ [title, body] unless body.empty?
132
+ end
133
+ end
134
+
135
+ def strip_blank(lines)
136
+ lines.drop_while(&:empty?).reverse.drop_while(&:empty?).reverse
137
+ end
138
+ end
139
+ end
140
+ end
@@ -109,8 +109,20 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def inside_string_ranges(node)
112
- return [] unless node.is_a?(Parser::AST::Node)
112
+ return source_string_ranges unless node.is_a?(Parser::AST::Node)
113
113
 
114
+ string_ranges(node)
115
+ end
116
+
117
+ def source_string_ranges
118
+ return [] unless processed_source.ast
119
+ return @source_string_ranges if @source_string_ranges_source.equal?(processed_source)
120
+
121
+ @source_string_ranges_source = processed_source
122
+ @source_string_ranges = string_ranges(processed_source.ast)
123
+ end
124
+
125
+ def string_ranges(node)
114
126
  node.each_node(:any_str).filter_map { |n| inside_string_range(n) }
115
127
  end
116
128
 
@@ -17,6 +17,7 @@ module RuboCop
17
17
 
18
18
  def call(corrector)
19
19
  corrector.replace(offending_range, correction)
20
+ corrector.replace(block_node.loc.end, closing_keyword) if block_node.braces?
20
21
  end
21
22
 
22
23
  private
@@ -24,13 +25,42 @@ module RuboCop
24
25
  attr_reader :block_node, :collection_node, :argument_node
25
26
 
26
27
  def correction
27
- if block_node.arguments?
28
- format(CORRECTION_WITH_ARGUMENTS,
29
- collection: collection_node.source,
30
- variables: argument_node.children.map(&:source).join(', '))
31
- else
32
- format(CORRECTION_WITHOUT_ARGUMENTS, enumerable: collection_node.source)
33
- end
28
+ keyword = if block_node.arguments?
29
+ format(
30
+ CORRECTION_WITH_ARGUMENTS,
31
+ collection: collection_node.source,
32
+ variables: loop_variables
33
+ )
34
+ else
35
+ format(CORRECTION_WITHOUT_ARGUMENTS, enumerable: collection_node.source)
36
+ end
37
+
38
+ padding_needed?(offending_range.end_pos) ? "#{keyword} " : keyword
39
+ end
40
+
41
+ def loop_variables
42
+ variables = argument_node.children.map { |argument| loop_variable(argument) }.join(', ')
43
+
44
+ argument_node.source.match?(/,\s*\|\z/) ? "#{variables}," : variables
45
+ end
46
+
47
+ def loop_variable(argument)
48
+ return argument.source unless argument.mlhs_type?
49
+
50
+ variables = argument.children.map { |child| loop_variable(child) }.join(', ')
51
+ lone_target = argument.children.one? && !argument.children.first.restarg_type?
52
+
53
+ lone_target ? "(#{variables},)" : "(#{variables})"
54
+ end
55
+
56
+ def closing_keyword
57
+ padding_needed?(block_node.loc.end.begin_pos - 1) ? ' end' : 'end'
58
+ end
59
+
60
+ def padding_needed?(position)
61
+ character = block_node.source_range.source_buffer.source[position]
62
+
63
+ !character.nil? && !character.match?(/\s/)
34
64
  end
35
65
 
36
66
  def offending_range
@@ -12,7 +12,7 @@ module RuboCop
12
12
  when :no_empty_lines
13
13
  corrector.remove(range)
14
14
  when :empty_lines
15
- corrector.insert_before(range, "\n")
15
+ corrector.insert_before(range.begin, "\n")
16
16
  end
17
17
  end
18
18
 
@@ -29,6 +29,10 @@ module RuboCop
29
29
  insert_arguments(corrector)
30
30
  end
31
31
 
32
+ def replace_delimiters?
33
+ !block_node.braces? && arg_to_unparenthesized_call?
34
+ end
35
+
32
36
  private
33
37
 
34
38
  attr_reader :block_node, :method, :arguments
@@ -72,12 +76,12 @@ module RuboCop
72
76
  end
73
77
 
74
78
  def remove_trailing_whitespace(corrector)
75
- size = block_begin.begin_pos - arguments.source_range.end_pos - 1
76
- corrector.remove_preceding(block_begin, size) if size.positive?
79
+ whitespace = arguments.source_range.end.join(block_begin.begin)
80
+ corrector.replace(whitespace, ' ') if whitespace.size > 1
77
81
  end
78
82
 
79
83
  def replace_delimiters(corrector)
80
- return if block_node.braces? || !arg_to_unparenthesized_call?
84
+ return unless replace_delimiters?
81
85
 
82
86
  corrector.insert_after(block_begin, ' ') unless separating_space?
83
87
 
@@ -90,7 +94,7 @@ module RuboCop
90
94
  # `;`; joining everything with `,` would turn them into extra parameters
91
95
  # and change the lambda's arity.
92
96
  regular, shadow = arguments.children.partition { |arg| !arg.shadowarg_type? }
93
- arg_string = regular.map(&:source).join(', ')
97
+ arg_string = regular.map { |arg| argument_source(arg) }.join(', ')
94
98
  arg_string += "; #{shadow.map(&:source).join(', ')}" unless shadow.empty?
95
99
  arg_string
96
100
  end
@@ -140,6 +144,23 @@ module RuboCop
140
144
  def separating_space?
141
145
  block_begin.source_buffer.source[block_begin.begin_pos + 2].match?(/\s/)
142
146
  end
147
+
148
+ def argument_source(argument)
149
+ return argument.source unless argument.type?(:optarg, :kwoptarg)
150
+ return argument.source unless operator_expression?(argument.default_value)
151
+
152
+ default_value = argument.default_value
153
+ name = argument.source_range.begin.join(default_value.source_range.begin).source
154
+
155
+ "#{name}(#{default_value.source})"
156
+ end
157
+
158
+ def operator_expression?(node)
159
+ return true if node.type?(:range, :if, :and, :or)
160
+ return false unless node.call_type?
161
+
162
+ node.operator_method? && !node.method?(:[])
163
+ end
143
164
  end
144
165
  end
145
166
  end
@@ -27,6 +27,7 @@ module RuboCop
27
27
  # corrected. The user must handle the delicate decision of where to
28
28
  # put the comment.
29
29
  return if new_line_needed_before_closing_brace?(node)
30
+ return if nested_closing_bracket_moved_before_comment?
30
31
 
31
32
  end_range = last_element_range_with_trailing_comma(node).end
32
33
 
@@ -43,6 +44,39 @@ module RuboCop
43
44
  corrector.insert_before(node.loc.end, "\n")
44
45
  end
45
46
 
47
+ def nested_closing_bracket_moved_before_comment?
48
+ last_element = children(node).last
49
+ return false unless (comment = processed_source.comment_at_line(last_element.last_line - 1))
50
+ return false unless (nested = nested_literal_with_closing_bracket_on_own_line(last_element))
51
+
52
+ own_line_comment?(comment) || !(nested.chained? || nested.argument?)
53
+ end
54
+
55
+ def nested_literal_with_closing_bracket_on_own_line(last_element)
56
+ last_line = last_element.last_line
57
+
58
+ last_element.each_node.find do |descendant|
59
+ same_kind_as_node?(descendant) && closing_bracket_on_own_line?(descendant, last_line)
60
+ end
61
+ end
62
+
63
+ def same_kind_as_node?(descendant)
64
+ descendant.type == node.type || (descendant.call_type? && node.call_type?)
65
+ end
66
+
67
+ def closing_bracket_on_own_line?(descendant, line)
68
+ return false unless descendant.loc?(:end) && descendant.loc.end.is?('}', ']', ')')
69
+
70
+ closing_bracket = descendant.loc.end
71
+ return false if closing_bracket.line != line
72
+
73
+ closing_bracket.source_line[0...closing_bracket.column].strip.empty?
74
+ end
75
+
76
+ def own_line_comment?(comment)
77
+ comment.source_range.source_line.lstrip.start_with?('#')
78
+ end
79
+
46
80
  def correct_next_line_brace(corrector, end_range)
47
81
  corrector.remove(range_with_surrounding_space(node.loc.end, side: :left))
48
82
  corrector.insert_before(end_range, content_if_comment_present(corrector, node))
@@ -21,14 +21,55 @@ module RuboCop
21
21
  corrector.insert_after(node.loc.end, ' ')
22
22
  end
23
23
 
24
+ def heredoc_strands_code_after_close_paren?(node)
25
+ return false unless heredoc_body_before_close_paren?(node)
26
+
27
+ after_paren = text_after_close_paren(node)
28
+
29
+ !after_paren.empty? && !after_paren.start_with?('.', '&.', ',')
30
+ end
31
+
24
32
  private
25
33
 
26
34
  # When the line above `)` ends with a comment and a chained call follows `)`,
27
35
  # crossing the newline would pull the chain into the comment. Preserve the newline.
36
+ # When a heredoc body comes before `)`, a chained call has to start on the line
37
+ # after the terminator instead, since the terminator must stay alone on its line.
28
38
  def remove_close_paren(corrector, node, buffer)
29
- newlines = !comment_above_close_paren_swallows_chain?(node, buffer)
30
- corrector.remove(range_with_surrounding_space(range: node.loc.end, buffer: buffer,
31
- side: :left, newlines: newlines))
39
+ if heredoc_above_close_paren_swallows_chain?(node)
40
+ range = range_with_surrounding_space(range: node.loc.end, buffer: buffer, side: :left)
41
+
42
+ corrector.replace(range, "\n")
43
+ else
44
+ newlines = !comment_above_close_paren_swallows_chain?(node, buffer)
45
+ range = range_with_surrounding_space(
46
+ range: node.loc.end, buffer: buffer, side: :left, newlines: newlines
47
+ )
48
+
49
+ corrector.remove(range)
50
+ end
51
+ end
52
+
53
+ def heredoc_above_close_paren_swallows_chain?(node)
54
+ return false unless heredoc_body_before_close_paren?(node)
55
+
56
+ text_after_close_paren(node).start_with?('.', '&.')
57
+ end
58
+
59
+ def heredoc_body_before_close_paren?(node)
60
+ return false unless (last_child = node.children.last)
61
+
62
+ close_paren_pos = node.loc.end.begin_pos
63
+
64
+ last_child.each_node(:any_str).any? do |str_node|
65
+ str_node.heredoc? && str_node.loc.heredoc_end.end_pos <= close_paren_pos
66
+ end
67
+ end
68
+
69
+ def text_after_close_paren(node)
70
+ close_paren = node.loc.end
71
+
72
+ close_paren.source_line[(close_paren.column + 1)..].to_s.lstrip
32
73
  end
33
74
 
34
75
  def comment_above_close_paren_swallows_chain?(node, buffer)
@@ -46,12 +87,8 @@ module RuboCop
46
87
  end
47
88
 
48
89
  def chained_after_close_paren?(node)
49
- close_paren = node.loc.end
50
- line_text = close_paren.source_line
51
- after_paren = line_text[(close_paren.column + 1)..]
52
- return false if after_paren.nil?
90
+ trimmed = text_after_close_paren(node)
53
91
 
54
- trimmed = after_paren.lstrip
55
92
  !trimmed.empty? && !trimmed.start_with?('#')
56
93
  end
57
94
 
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # @api private
15
15
  def url_for(cop_class, config = nil)
16
16
  base = department_to_basename(cop_class.department)
17
- fragment = cop_class.cop_name.downcase.gsub(/[^a-z]/, '')
17
+ fragment = cop_class.cop_name.downcase.delete('/')
18
18
  base_url = base_url_for(cop_class, config)
19
19
  extension = extension_for(cop_class, config)
20
20
 
@@ -109,12 +109,6 @@ module RuboCop
109
109
  end
110
110
  end
111
111
 
112
- def match_block_variable_name?(receiver_name)
113
- gem_specification(processed_source.ast) do |block_variable_name|
114
- return block_variable_name == receiver_name
115
- end
116
- end
117
-
118
112
  def add_dependency_method?(method_name)
119
113
  ADD_DEPENDENCY_METHODS.include?(method_name)
120
114
  end
@@ -80,12 +80,6 @@ module RuboCop
80
80
 
81
81
  private
82
82
 
83
- def previous_declaration(node)
84
- declarations = dependency_declarations(processed_source.ast)
85
- node_index = declarations.find_index(node)
86
- declarations.to_a[node_index - 1]
87
- end
88
-
89
83
  def get_dependency_name(node)
90
84
  node.method_name
91
85
  end
@@ -201,11 +201,8 @@ module RuboCop
201
201
  def add_newline?(node)
202
202
  # Determine if a blank line should be inserted before the new directive
203
203
  # in order to spread out pattern matchers
204
- return false if node.sibling_index&.zero?
205
- return false unless node.parent
206
-
207
- prev_sibling = node.parent.child_nodes[node.sibling_index - 1]
208
- return false unless prev_sibling && pattern_matcher?(prev_sibling)
204
+ prev_sibling = node.left_sibling
205
+ return false unless prev_sibling.is_a?(AST::Node) && pattern_matcher?(prev_sibling)
209
206
 
210
207
  node.loc.line == last_line(prev_sibling) + 1
211
208
  end