rubocop 1.91.0 → 1.92.2

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 (278) 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.rb +0 -4
  14. data/lib/rubocop/config_loader.rb +6 -3
  15. data/lib/rubocop/config_loader_resolver.rb +37 -12
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  17. data/lib/rubocop/config_obsoletion.rb +9 -0
  18. data/lib/rubocop/config_validator.rb +11 -0
  19. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  20. data/lib/rubocop/cop/badge.rb +1 -1
  21. data/lib/rubocop/cop/base.rb +42 -21
  22. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  23. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  24. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  25. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  26. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  27. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  28. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  29. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  30. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  31. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  32. data/lib/rubocop/cop/documentation.rb +1 -1
  33. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  34. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  35. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  36. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  37. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  38. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  39. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  40. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  41. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  42. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  43. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  44. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  45. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  46. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  47. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  49. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  50. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  51. data/lib/rubocop/cop/layout/end_alignment.rb +21 -25
  52. data/lib/rubocop/cop/layout/extra_spacing.rb +21 -6
  53. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  54. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  55. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  56. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  57. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  58. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  59. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  60. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  61. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  62. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  63. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  64. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  65. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  66. data/lib/rubocop/cop/layout/line_length.rb +176 -26
  67. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  68. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  69. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  70. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  71. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  72. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  73. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  74. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +16 -3
  75. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  76. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  77. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  78. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  79. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  80. data/lib/rubocop/cop/layout/space_before_block_braces.rb +16 -1
  81. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  82. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +32 -9
  83. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  84. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  85. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  86. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  87. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  88. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  89. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  90. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  91. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  92. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  93. data/lib/rubocop/cop/lint/loop.rb +24 -2
  94. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  95. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  96. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  97. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  98. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  99. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  100. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +39 -9
  101. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  102. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  103. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  104. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  105. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  106. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  107. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  108. data/lib/rubocop/cop/lint/void.rb +8 -1
  109. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  110. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  111. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  112. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  113. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  114. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  115. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  116. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  117. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  118. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  119. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  120. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +41 -6
  121. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  122. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  123. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  124. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  125. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  126. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  127. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  128. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  129. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  130. data/lib/rubocop/cop/mixin/statement_modifier.rb +39 -5
  131. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  132. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  133. data/lib/rubocop/cop/mixin.rb +2 -0
  134. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  135. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  136. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  137. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  138. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  139. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  140. data/lib/rubocop/cop/offense.rb +59 -3
  141. data/lib/rubocop/cop/registry.rb +1 -1
  142. data/lib/rubocop/cop/security/json_load.rb +16 -4
  143. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  144. data/lib/rubocop/cop/style/alias.rb +18 -5
  145. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  146. data/lib/rubocop/cop/style/and_or.rb +37 -10
  147. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  148. data/lib/rubocop/cop/style/attr.rb +3 -4
  149. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  150. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  151. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  152. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  153. data/lib/rubocop/cop/style/class_and_module_children.rb +57 -16
  154. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  155. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  156. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  157. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  158. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  159. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  160. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  161. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  162. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  163. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  164. data/lib/rubocop/cop/style/endless_method.rb +104 -11
  165. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  166. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  167. data/lib/rubocop/cop/style/float_division.rb +37 -2
  168. data/lib/rubocop/cop/style/for.rb +53 -2
  169. data/lib/rubocop/cop/style/format_string.rb +28 -6
  170. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  171. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  172. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  173. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  174. data/lib/rubocop/cop/style/hash_lookup_method.rb +42 -9
  175. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  176. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  177. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  178. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  179. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  180. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  181. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  182. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  183. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  184. data/lib/rubocop/cop/style/lambda.rb +34 -1
  185. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  186. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  187. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  188. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +26 -2
  189. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  190. data/lib/rubocop/cop/style/method_def_parentheses.rb +38 -4
  191. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  192. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  193. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  194. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  195. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  196. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  197. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  198. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  199. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  200. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  201. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  202. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  203. data/lib/rubocop/cop/style/next.rb +24 -10
  204. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  205. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  206. data/lib/rubocop/cop/style/not.rb +10 -5
  207. data/lib/rubocop/cop/style/numeric_predicate.rb +28 -5
  208. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  209. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  210. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  211. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  212. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  213. data/lib/rubocop/cop/style/reduce_to_hash.rb +47 -33
  214. data/lib/rubocop/cop/style/redundant_begin.rb +45 -5
  215. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  216. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  217. data/lib/rubocop/cop/style/redundant_exception.rb +27 -11
  218. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  219. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  220. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  221. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  222. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  223. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  224. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  225. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  226. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  227. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  228. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  229. data/lib/rubocop/cop/style/require_order.rb +25 -8
  230. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  231. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  232. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  233. data/lib/rubocop/cop/style/sample.rb +9 -8
  234. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  235. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  236. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  237. data/lib/rubocop/cop/style/single_line_methods.rb +42 -4
  238. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  239. data/lib/rubocop/cop/style/string_concatenation.rb +21 -4
  240. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  241. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  242. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  243. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  244. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  245. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  246. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  247. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  248. data/lib/rubocop/cop/team.rb +10 -5
  249. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  250. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  251. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  252. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  253. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  254. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  255. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  256. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  257. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  258. data/lib/rubocop/lsp/routes.rb +23 -23
  259. data/lib/rubocop/lsp/runtime.rb +47 -11
  260. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  261. data/lib/rubocop/lsp.rb +5 -2
  262. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  263. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  264. data/lib/rubocop/mcp/scope.rb +65 -0
  265. data/lib/rubocop/mcp/server.rb +184 -76
  266. data/lib/rubocop/mcp/source_files.rb +35 -0
  267. data/lib/rubocop/offense_limit.rb +30 -0
  268. data/lib/rubocop/options.rb +45 -10
  269. data/lib/rubocop/plugin/configuration_integrator.rb +36 -8
  270. data/lib/rubocop/remote_config.rb +4 -1
  271. data/lib/rubocop/result_cache.rb +20 -13
  272. data/lib/rubocop/runner.rb +110 -42
  273. data/lib/rubocop/server/cache.rb +6 -6
  274. data/lib/rubocop/target_ruby.rb +21 -9
  275. data/lib/rubocop/util.rb +69 -0
  276. data/lib/rubocop/version.rb +1 -1
  277. data/lib/rubocop.rb +5 -0
  278. metadata +16 -5
@@ -245,20 +245,30 @@ module RuboCop
245
245
  return if with_space.is?(operator.source)
246
246
 
247
247
  "Space around operator `#{operator.source}` detected."
248
- elsif !/^\s.*\s$/.match?(with_space.source)
248
+ elsif missing_surrounding_space?(with_space)
249
249
  "Surrounding space missing for operator `#{operator.source}`."
250
- elsif excess_leading_space?(type, operator, with_space) ||
250
+ elsif excess_leading_space?(type, operator, with_space, right_operand) ||
251
251
  excess_trailing_space?(right_operand.source_range, with_space)
252
252
  "Operator `#{operator.source}` should be surrounded " \
253
253
  'by a single space.'
254
254
  end
255
255
  end
256
256
 
257
- def excess_leading_space?(type, operator, with_space)
257
+ def missing_surrounding_space?(with_space)
258
+ return false if /\A\s.*\s\z/m.match?(with_space.source)
259
+
260
+ !with_space.source.start_with?(/\s/) || !line_continuation_after?(with_space)
261
+ end
262
+
263
+ def line_continuation_after?(range)
264
+ processed_source.buffer.source[range.end_pos, 2] == "\\\n"
265
+ end
266
+
267
+ def excess_leading_space?(type, operator, with_space, right_operand)
258
268
  return false unless allow_for_alignment?
259
269
  return false unless with_space.source.start_with?(EXCESSIVE_SPACE)
260
270
 
261
- return !aligned_with_operator?(operator) unless grouped_alignment_type?(type)
271
+ return !aligned?(type, operator, right_operand) unless grouped_alignment_type?(type)
262
272
 
263
273
  token = Token.new(operator, nil, operator.source)
264
274
  align_preceding = aligned_with_preceding_equals_operator(token)
@@ -269,6 +279,23 @@ module RuboCop
269
279
  aligned_with_subsequent_equals_operator(token) != :yes
270
280
  end
271
281
 
282
+ def aligned?(type, operator, right_operand)
283
+ return true if aligned_with_operator?(operator)
284
+
285
+ type == :pair && aligned_with_another_hash_rocket?(right_operand)
286
+ end
287
+
288
+ def aligned_with_another_hash_rocket?(pair)
289
+ hash_rocket_columns(pair.parent)[pair.loc.operator.column] > 1
290
+ end
291
+
292
+ def hash_rocket_columns(hash_node)
293
+ @hash_rocket_columns ||= {}.compare_by_identity
294
+ @hash_rocket_columns[hash_node] ||= hash_node.pairs.select(&:hash_rocket?).map do |pair|
295
+ pair.loc.operator.column
296
+ end.tally
297
+ end
298
+
272
299
  def grouped_alignment_type?(type)
273
300
  type == :assignment || (type == :special_asgn && force_equal_sign_alignment?)
274
301
  end
@@ -49,8 +49,11 @@ module RuboCop
49
49
  MISSING_MSG = 'Space missing to the left of {.'
50
50
  DETECTED_MSG = 'Space detected to the left of {.'
51
51
 
52
+ # `Style/BlockDelimiters` decides whether to put a space before the `do`
53
+ # it writes by looking at the source before this cop removed the space,
54
+ # so the two corrections cannot be applied in the same pass.
52
55
  def self.autocorrect_incompatible_with
53
- [Style::SymbolProc]
56
+ [Style::BlockDelimiters, Style::SymbolProc]
54
57
  end
55
58
 
56
59
  def on_block(node)
@@ -133,11 +136,23 @@ module RuboCop
133
136
 
134
137
  def autocorrect(corrector, range)
135
138
  case range.source
139
+ when /\n/ then remove_line_continuation(corrector, range)
136
140
  when /\s/ then corrector.remove(range)
137
141
  else corrector.insert_before(range, ' ')
138
142
  end
139
143
  end
140
144
 
145
+ # When a heredoc body sits between the `\` and the brace, the newline before
146
+ # the brace ends the heredoc, and removing it would break the terminator.
147
+ def remove_line_continuation(corrector, range)
148
+ source = processed_source.buffer.source
149
+ return unless source[range.begin_pos - 1] == '\\'
150
+
151
+ begin_pos = range.begin_pos
152
+ begin_pos -= 1 while source[begin_pos - 1].match?(/[ \t\\]/)
153
+ corrector.remove(range_between(begin_pos, range.end_pos))
154
+ end
155
+
141
156
  def style_for_empty_braces
142
157
  case cop_config['EnforcedStyleForEmptyBraces']
143
158
  when 'space' then :space
@@ -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, tokens, left, right)
120
+ compact_corrections(corrector, node, tokens, left, right)
121
121
  end
122
122
  end
123
123
 
@@ -178,6 +178,8 @@ module RuboCop
178
178
  space_offenses(node, left, nil, MSG, start_ok: start_ok, end_ok: true)
179
179
  end
180
180
 
181
+ return if closing_bracket_kept_apart?(node, tokens, left, right)
182
+
181
183
  if qualifies_for_compact?(tokens, right)
182
184
  compact_offense(node, right)
183
185
  elsif !multi_dimensional_array?(tokens, right)
@@ -212,13 +214,24 @@ module RuboCop
212
214
  end
213
215
  end
214
216
 
215
- def compact_corrections(corrector, tokens, left, right)
217
+ # A closing bracket on its own line is only collapsed when the opening
218
+ # bracket ends up on the first element's line, so that the brackets
219
+ # stay symmetrical (see `Layout/MultilineArrayBraceLayout`).
220
+ def closing_bracket_kept_apart?(node, tokens, left, right)
221
+ end_has_own_line?(right) &&
222
+ !multi_dimensional_array?(tokens, left, side: :left) &&
223
+ !same_line?(left, node.children.first)
224
+ end
225
+
226
+ def compact_corrections(corrector, node, tokens, left, right)
216
227
  if multi_dimensional_array?(tokens, left, side: :left)
217
228
  compact(corrector, left, :right)
218
229
  elsif !left.space_after?
219
230
  corrector.insert_after(left.pos, ' ')
220
231
  end
221
232
 
233
+ return if closing_bracket_kept_apart?(node, tokens, left, right)
234
+
222
235
  if multi_dimensional_array?(tokens, right)
223
236
  compact(corrector, right, :left)
224
237
  elsif !right.space_before?
@@ -83,7 +83,7 @@ module RuboCop
83
83
  extend AutoCorrector
84
84
 
85
85
  def self.autocorrect_incompatible_with
86
- [Style::BlockDelimiters]
86
+ [Layout::BlockAlignment, Style::BlockDelimiters]
87
87
  end
88
88
 
89
89
  def on_block(node)
@@ -153,7 +153,7 @@ module RuboCop
153
153
  else
154
154
  column = node.source_range.column
155
155
  return if multiline_block?(left_brace, right_brace) &&
156
- aligned_braces?(inner, right_brace, column)
156
+ aligned_braces?(inner, right_brace, column, left_brace)
157
157
 
158
158
  space_inside_right_brace(inner, right_brace, column)
159
159
  end
@@ -163,14 +163,21 @@ module RuboCop
163
163
  left_brace.first_line != right_brace.first_line
164
164
  end
165
165
 
166
- def aligned_braces?(inner, right_brace, column)
167
- column == right_brace.column || column == inner_last_space_count(inner)
166
+ def aligned_braces?(inner, right_brace, column, left_brace)
167
+ column == right_brace.column ||
168
+ column == inner_last_space_count(inner) ||
169
+ aligned_with_opening_brace_line?(left_brace, right_brace)
168
170
  end
169
171
 
170
172
  def inner_last_space_count(inner)
171
173
  inner.split("\n").last.count(' ')
172
174
  end
173
175
 
176
+ def aligned_with_opening_brace_line?(left_brace, right_brace)
177
+ begins_its_line?(right_brace) &&
178
+ right_brace.column == left_brace.source_line[/\A\s*/].length
179
+ end
180
+
174
181
  def no_space_inside_left_brace(left_brace, args_delimiter)
175
182
  if pipe?(args_delimiter)
176
183
  if left_brace.end_pos == args_delimiter.begin_pos &&
@@ -197,9 +204,7 @@ module RuboCop
197
204
  'Space between { and | detected.')
198
205
  end
199
206
  else
200
- brace_with_space = range_with_surrounding_space(left_brace, side: :right)
201
- space(brace_with_space.begin_pos + 1, brace_with_space.end_pos,
202
- 'Space inside { detected.')
207
+ space_after_left_brace(left_brace)
203
208
  end
204
209
  end
205
210
 
@@ -207,6 +212,20 @@ module RuboCop
207
212
  args_delimiter&.is?('|')
208
213
  end
209
214
 
215
+ def space_after_left_brace(left_brace)
216
+ brace_with_space = range_with_surrounding_space(left_brace, side: :right)
217
+ return if brace_with_space.source.match?(/\R/)
218
+ return if comment_at?(brace_with_space.end_pos)
219
+
220
+ space(
221
+ brace_with_space.begin_pos + 1, brace_with_space.end_pos, 'Space inside { detected.'
222
+ )
223
+ end
224
+
225
+ def comment_at?(pos)
226
+ processed_source.comments.any? { |comment| comment.source_range.begin_pos == pos }
227
+ end
228
+
210
229
  def space_inside_right_brace(inner, right_brace, column)
211
230
  brace_with_space = range_with_surrounding_space(right_brace, side: :left)
212
231
  begin_pos = brace_with_space.begin_pos
@@ -216,7 +235,7 @@ module RuboCop
216
235
  begin_pos = end_pos - (right_brace.column - column)
217
236
  end
218
237
 
219
- if inner.end_with?(']')
238
+ if closing_bracket_on_own_line?(inner)
220
239
  end_pos -= 1
221
240
  begin_pos = end_pos - (inner_last_space_count(inner) - column)
222
241
  end
@@ -224,6 +243,10 @@ module RuboCop
224
243
  space(begin_pos, end_pos, 'Space inside } detected.')
225
244
  end
226
245
 
246
+ def closing_bracket_on_own_line?(inner)
247
+ inner[/[^\n]*\z/].match?(/\A\s*\]\z/)
248
+ end
249
+
227
250
  def no_space(begin_pos, end_pos, msg)
228
251
  if style == :space
229
252
  offense(begin_pos, end_pos, msg)
@@ -241,7 +264,7 @@ module RuboCop
241
264
  end
242
265
 
243
266
  def offense(begin_pos, end_pos, msg, style_param = 'EnforcedStyle')
244
- return if begin_pos > end_pos
267
+ return if begin_pos >= end_pos
245
268
 
246
269
  range = range_between(begin_pos, end_pos)
247
270
  add_offense(range, message: msg) do |corrector|
@@ -18,6 +18,7 @@ module RuboCop
18
18
  # # good
19
19
  # 'a'..'z'
20
20
  class SpaceInsideRangeLiteral < Base
21
+ include RangeHelp
21
22
  extend AutoCorrector
22
23
 
23
24
  MSG = 'Space inside range literal.'
@@ -33,21 +34,38 @@ module RuboCop
33
34
  private
34
35
 
35
36
  def check(node)
36
- expression = node.source
37
- op = node.loc.operator.source
38
- escaped_op = op.gsub('.', '\.')
39
-
40
- # account for multiline range literals
41
- expression.sub!(/#{escaped_op}\n\s*/, op)
42
-
43
- return unless /(\s#{escaped_op})|(#{escaped_op}\s)/.match?(expression)
37
+ operator = node.loc.operator
38
+ spaces = [space_before(node, operator), space_after(node, operator)].compact
39
+ return if spaces.all? { |space| line_break_after_operator?(space, operator) }
44
40
 
45
41
  add_offense(node) do |corrector|
46
- corrector.replace(
47
- node, expression.sub(/\s+#{escaped_op}/, op).sub(/#{escaped_op}\s+/, op)
48
- )
42
+ spaces.each do |space|
43
+ corrector.remove(space)
44
+ end
49
45
  end
50
46
  end
47
+
48
+ def space_before(node, operator)
49
+ return unless node.begin
50
+
51
+ whitespace_range(node.begin.source_range.end_pos, operator.begin_pos)
52
+ end
53
+
54
+ def space_after(node, operator)
55
+ return unless node.end
56
+
57
+ whitespace_range(operator.end_pos, node.end.source_range.begin_pos)
58
+ end
59
+
60
+ def line_break_after_operator?(space, operator)
61
+ space.begin_pos == operator.end_pos && space.source.start_with?("\n")
62
+ end
63
+
64
+ def whitespace_range(begin_pos, end_pos)
65
+ range = range_between(begin_pos, end_pos)
66
+
67
+ range if range.source.match?(/\A\s+\z/)
68
+ end
51
69
  end
52
70
  end
53
71
  end
@@ -108,14 +108,9 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def left_ref_bracket(node, tokens)
111
- current_token = tokens.reverse.find(&:left_ref_bracket?)
112
- previous_token = previous_token(current_token)
111
+ bracket_pos = node.loc.selector.begin_pos
113
112
 
114
- if node.method?(:[]=) || (previous_token && !previous_token.right_bracket?)
115
- tokens.find(&:left_ref_bracket?)
116
- else
117
- current_token
118
- end
113
+ tokens.find { |token| token.left_ref_bracket? && token.begin_pos == bracket_pos }
119
114
  end
120
115
 
121
116
  def closing_bracket(tokens, opening_bracket)
@@ -129,11 +124,6 @@ module RuboCop
129
124
  end
130
125
  end
131
126
 
132
- def previous_token(current_token)
133
- index = processed_source.tokens.index(current_token)
134
- index.nil? || index.zero? ? nil : processed_source.tokens[index - 1]
135
- end
136
-
137
127
  def empty_config
138
128
  cop_config['EnforcedStyleForEmptyBrackets']
139
129
  end
@@ -37,7 +37,7 @@ module RuboCop
37
37
  'should be %<possible>s.'
38
38
 
39
39
  def self.autocorrect_incompatible_with
40
- [Naming::BlockForwarding]
40
+ [Naming::BlockForwarding, Style::ArgumentsForwarding]
41
41
  end
42
42
 
43
43
  def on_new_investigation
@@ -56,7 +56,8 @@ module RuboCop
56
56
  end
57
57
 
58
58
  def on_send(node)
59
- return if node.parenthesized?
59
+ # `foo.*` is a regular method call, not a binary operator.
60
+ return if node.parenthesized? || node.loc.dot
60
61
 
61
62
  return unless (parent = node.parent)
62
63
  return unless operator?(parent)
@@ -55,7 +55,8 @@ module RuboCop
55
55
  return node if first_argument_is_regexp?(node) || !node.parent
56
56
 
57
57
  if (node.parent.send_type? && node.receiver) ||
58
- method_chain_to_regexp_receiver?(node, regexp_receiver)
58
+ method_chain_to_regexp_receiver?(node, regexp_receiver) ||
59
+ part_of_hash_argument?(node)
59
60
  node = find_offense_node(node.parent, regexp_receiver)
60
61
  end
61
62
 
@@ -72,6 +73,10 @@ module RuboCop
72
73
 
73
74
  parent.parent && parent_receiver.receiver == regexp_receiver
74
75
  end
76
+
77
+ def part_of_hash_argument?(node)
78
+ node.pair_type? || node.parent.pair_type? || (node.hash_type? && !node.braces?)
79
+ end
75
80
  end
76
81
  end
77
82
  end
@@ -97,12 +97,14 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def can_simplify_conditional?(node)
100
- node.else_branch && node.loc.else.source == 'else'
100
+ !node.elsif? && node.else_branch && node.loc.else.source == 'else'
101
101
  end
102
102
 
103
103
  def remove_empty_branch(corrector, node)
104
104
  range = if empty_if_branch?(node) && else_branch?(node)
105
105
  branch_range(node)
106
+ elsif node.condition.last_line == node.loc.else.line
107
+ node.source_range.with(end_pos: node.loc.else.begin_pos)
106
108
  else
107
109
  deletion_range(branch_range(node))
108
110
  end
@@ -34,6 +34,10 @@ module RuboCop
34
34
 
35
35
  MSG = 'Put a method call with a HEREDOC receiver on the same line as the HEREDOC opening.'
36
36
 
37
+ def self.autocorrect_incompatible_with
38
+ [Style::MethodCallWithArgsParentheses]
39
+ end
40
+
37
41
  def on_send(node)
38
42
  heredoc = heredoc_node_descendent_receiver(node)
39
43
  return unless heredoc
@@ -41,26 +41,22 @@ module RuboCop
41
41
 
42
42
  private
43
43
 
44
- # rubocop:disable-next Metrics/CyclomaticComplexity
45
44
  def check(node)
46
45
  return if node.parent&.regexp_type?
47
46
  return unless /(?<!\\)#\{.*\}/.match?(node.source)
48
47
  return if heredoc?(node)
49
- return unless node.loc.begin && node.loc.end
48
+ return unless single_quoted?(node)
50
49
  return unless valid_syntax?(node)
51
50
 
52
51
  add_offense(node) { |corrector| autocorrect(corrector, node) }
53
52
  end
54
53
 
55
- def autocorrect(corrector, node)
56
- starting_token, ending_token = if node.source.include?('"')
57
- ['%{', '}']
58
- else
59
- ['"', '"']
60
- end
54
+ def single_quoted?(node)
55
+ node.loc.begin&.is?("'") && node.loc.end
56
+ end
61
57
 
62
- corrector.replace(node.loc.begin, starting_token)
63
- corrector.replace(node.loc.end, ending_token)
58
+ def autocorrect(corrector, node)
59
+ corrector.replace(node, interpolated_source(node))
64
60
  end
65
61
 
66
62
  def heredoc?(node)
@@ -68,15 +64,22 @@ module RuboCop
68
64
  end
69
65
 
70
66
  def valid_syntax?(node)
71
- double_quoted_string = if node.source.include?('"')
72
- node.source.sub(/\A'/, '%{').sub(/'\z/, '}')
73
- else
74
- node.source.gsub(/\A'|'\z/, '"')
75
- end
76
-
77
- processed_source = parse(double_quoted_string)
67
+ processed_source = parse(interpolated_source(node))
78
68
  processed_source.valid_syntax? && processed_source.ast.dstr_type?
79
69
  end
70
+
71
+ def interpolated_source(node)
72
+ content = node.source[1...-1]
73
+ return %("#{content}") unless content.include?('"')
74
+ return "%{#{content}}" unless continued_string_part?(node)
75
+
76
+ %("#{content.gsub(/\\.|"/) { |m| m == '"' ? '\"' : m }}")
77
+ end
78
+
79
+ # A percent literal right after another string part is parsed as the `%` operator.
80
+ def continued_string_part?(node)
81
+ node.parent&.dstr_type? && node.left_sibling
82
+ end
80
83
  end
81
84
  end
82
85
  end
@@ -98,6 +98,8 @@ module RuboCop
98
98
  end
99
99
  elsif node.condition.falsey_literal?
100
100
  add_offense(node.condition) do |corrector|
101
+ next if orphans_loop_control_keyword?(node)
102
+
101
103
  corrector.replace(node, node.body.child_nodes.map(&:source).join("\n"))
102
104
  end
103
105
  end
@@ -127,6 +129,8 @@ module RuboCop
127
129
  end
128
130
  elsif node.condition.truthy_literal?
129
131
  add_offense(node.condition) do |corrector|
132
+ next if orphans_loop_control_keyword?(node)
133
+
130
134
  corrector.replace(node, node.body.child_nodes.map(&:source).join("\n"))
131
135
  end
132
136
  end
@@ -175,6 +179,15 @@ module RuboCop
175
179
 
176
180
  private
177
181
 
182
+ # Unwrapping the body of a loop that runs once would leave a `break`,
183
+ # `next` or `redo` that belongs to it outside of any loop.
184
+ def orphans_loop_control_keyword?(node)
185
+ node.body.each_node(:next, :break, :redo).any? do |control|
186
+ inner = control.each_ancestor.take_while { |ancestor| !ancestor.equal?(node) }
187
+ inner.none? { |ancestor| ancestor.any_block_type? || ancestor.loop_keyword? }
188
+ end
189
+ end
190
+
178
191
  def void_value_expression?(node)
179
192
  node = node.children.last while node&.begin_type?
180
193
 
@@ -273,6 +286,8 @@ module RuboCop
273
286
  "#{node.else_branch.source.sub('elsif', 'if')}\nend"
274
287
  elsif node.else? || node.ternary?
275
288
  node.else_branch.source
289
+ elsif node.value_used?
290
+ 'nil'
276
291
  else
277
292
  '' # Equivalent to removing the node
278
293
  end
@@ -24,30 +24,16 @@ module RuboCop
24
24
  MSG = 'Literal interpolation detected.'
25
25
  COMPOSITE = %i[array hash pair irange erange].freeze
26
26
 
27
- # rubocop:disable-next Metrics/AbcSize
28
27
  def on_interpolation(begin_node)
29
28
  final_node = begin_node.children.last
30
29
  return unless offending?(final_node)
31
-
32
- # %W and %I split the content into words before expansion
33
- # treating each interpolation as a word component, so
34
- # interpolation should not be removed if the expanded value
35
- # contains a space character.
36
- expanded_value = autocorrected_value(final_node)
37
- expanded_value = handle_special_regexp_chars(begin_node, expanded_value)
38
-
39
- return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
30
+ return unless (expanded_value = correctable_value(begin_node, final_node))
40
31
 
41
32
  add_offense(final_node) do |corrector|
42
33
  next if final_node.dstr_type? # nested, fixed in next iteration
34
+ next if follows_literal_interpolation?(begin_node)
43
35
 
44
- replacement = if final_node.str_type? && !final_node.value.valid_encoding?
45
- final_node.source.delete_prefix('"').delete_suffix('"')
46
- else
47
- expanded_value
48
- end
49
-
50
- corrector.replace(final_node.parent, replacement)
36
+ corrector.replace(final_node.parent, replacement(final_node, expanded_value))
51
37
  end
52
38
  end
53
39
 
@@ -73,6 +59,22 @@ module RuboCop
73
59
  node.array_type? && grandparent.regexp_type?
74
60
  end
75
61
 
62
+ def correctable_value(begin_node, final_node)
63
+ value = autocorrected_value(final_node)
64
+ value = handle_special_regexp_chars(begin_node, value)
65
+ return if starts_interpolation_after_hash_sign?(begin_node, value)
66
+
67
+ value = escape_trailing_hash_sign(begin_node, value)
68
+
69
+ # %W and %I split the content into words before expansion
70
+ # treating each interpolation as a word component, so
71
+ # interpolation should not be removed if the expanded value
72
+ # contains a space character.
73
+ return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(value)
74
+
75
+ value
76
+ end
77
+
76
78
  # rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity
77
79
  def autocorrected_value(node)
78
80
  case node.type
@@ -116,6 +118,17 @@ module RuboCop
116
118
  end
117
119
  end
118
120
 
121
+ def starts_interpolation_after_hash_sign?(begin_node, value)
122
+ source = processed_source.buffer.source
123
+ range = begin_node.source_range
124
+ # Removing an empty value puts the `#` right before what follows the interpolation.
125
+ following = "#{value}#{source[range.end_pos]}"
126
+ return false unless following.start_with?('{', '@', '$')
127
+ return false unless source[range.begin_pos - 1] == '#'
128
+
129
+ unescaped_hash_sign?(source[0...range.begin_pos])
130
+ end
131
+
119
132
  def autocorrected_value_for_string(node)
120
133
  return node.source.delete_prefix('"').delete_suffix('"') unless node.value.valid_encoding?
121
134
 
@@ -180,7 +193,9 @@ module RuboCop
180
193
  end
181
194
 
182
195
  def space_literal?(node)
183
- node.str_type? && node.value.valid_encoding? && node.value.blank?
196
+ # `String#blank?` misses Unicode spaces such as U+3000 that
197
+ # `Layout/TrailingWhitespace` treats as trailing whitespace.
198
+ node.str_type? && node.value.valid_encoding? && node.value.match?(/\A[[:space:]]*\z/)
184
199
  end
185
200
 
186
201
  def ends_heredoc_line?(node)
@@ -188,7 +203,8 @@ module RuboCop
188
203
  return false unless grandparent&.dstr_type? && grandparent.heredoc?
189
204
 
190
205
  line = processed_source.lines[node.last_line - 1]
191
- line.size == node.loc.last_column + 1
206
+
207
+ line.size == node.parent.source_range.last_column
192
208
  end
193
209
 
194
210
  def in_array_percent_literal?(node)
@@ -198,6 +214,34 @@ module RuboCop
198
214
  grandparent = parent.parent
199
215
  grandparent&.array_type? && grandparent.percent_literal?
200
216
  end
217
+
218
+ def escape_trailing_hash_sign(begin_node, value)
219
+ following = processed_source.buffer.source[begin_node.source_range.end_pos]
220
+ return value unless %w[{ @ $].include?(following) && unescaped_hash_sign?(value)
221
+
222
+ "#{value.delete_suffix('#')}\\#"
223
+ end
224
+
225
+ def unescaped_hash_sign?(text)
226
+ text.end_with?('#') && text.delete_suffix('#')[/\\*\z/].length.even?
227
+ end
228
+
229
+ # Each interpolation is checked against the original source, so correcting it in the
230
+ # same pass as a preceding one that leaves a `#` (or nothing) could join that `#` and
231
+ # a `{` from this one. It's left for the next iteration instead.
232
+ def follows_literal_interpolation?(begin_node)
233
+ previous = begin_node.left_sibling
234
+ return false unless previous&.begin_type? && offending?(previous.children.last)
235
+
236
+ value = correctable_value(previous, previous.children.last)
237
+ value && (value.empty? || value.end_with?('#'))
238
+ end
239
+
240
+ def replacement(node, expanded_value)
241
+ return expanded_value unless node.str_type? && !node.value.valid_encoding?
242
+
243
+ node.source.delete_prefix('"').delete_suffix('"')
244
+ end
201
245
  end
202
246
  end
203
247
  end