rubocop 1.91.0 → 1.92.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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +3 -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/base.rb +42 -21
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  20. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  21. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  22. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  23. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  24. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  25. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  27. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  28. data/lib/rubocop/cop/documentation.rb +1 -1
  29. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  30. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  31. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  32. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  33. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  34. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  35. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  36. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  37. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  38. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  40. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  41. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  43. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  44. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  45. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  48. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  49. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  50. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  51. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  52. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  53. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  54. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  55. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  56. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  57. data/lib/rubocop/cop/layout/line_length.rb +52 -18
  58. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  59. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  60. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  61. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  62. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  63. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  64. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  65. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  66. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  67. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  68. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  69. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  70. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  71. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  72. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  73. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  74. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  75. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  76. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  78. data/lib/rubocop/cop/lint/loop.rb +24 -2
  79. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  80. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  81. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  82. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  83. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  84. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  86. data/lib/rubocop/cop/lint/void.rb +8 -1
  87. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  88. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  89. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  90. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  91. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  92. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  93. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  94. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  95. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  96. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  97. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
  98. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  99. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  100. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  101. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  102. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  103. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  104. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  105. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  106. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  107. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  108. data/lib/rubocop/cop/mixin.rb +2 -0
  109. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  110. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  111. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  113. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  114. data/lib/rubocop/cop/offense.rb +59 -3
  115. data/lib/rubocop/cop/registry.rb +1 -1
  116. data/lib/rubocop/cop/security/json_load.rb +16 -4
  117. data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
  118. data/lib/rubocop/cop/style/alias.rb +18 -5
  119. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  120. data/lib/rubocop/cop/style/and_or.rb +4 -0
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
  122. data/lib/rubocop/cop/style/attr.rb +3 -4
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  124. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  125. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  127. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  128. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  129. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  130. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  131. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  132. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  133. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  134. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  135. data/lib/rubocop/cop/style/endless_method.rb +67 -6
  136. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  137. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  138. data/lib/rubocop/cop/style/float_division.rb +22 -2
  139. data/lib/rubocop/cop/style/for.rb +40 -2
  140. data/lib/rubocop/cop/style/format_string.rb +28 -6
  141. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  142. data/lib/rubocop/cop/style/guard_clause.rb +9 -1
  143. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  144. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  145. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  146. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  147. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  148. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  149. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  150. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  151. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  152. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  153. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  154. data/lib/rubocop/cop/style/lambda.rb +18 -0
  155. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  156. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  157. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  159. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  160. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  161. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  162. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  163. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  166. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  167. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  168. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  169. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  170. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  172. data/lib/rubocop/cop/style/next.rb +24 -10
  173. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  174. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  175. data/lib/rubocop/cop/style/not.rb +10 -5
  176. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  177. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  178. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  179. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  180. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  181. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  182. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  183. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  184. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  185. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  186. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  187. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  188. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  189. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  190. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  191. data/lib/rubocop/cop/style/require_order.rb +9 -0
  192. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  193. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  194. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  195. data/lib/rubocop/cop/style/sample.rb +9 -8
  196. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  197. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  198. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  199. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  200. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  201. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  202. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  203. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  204. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  205. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  206. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  207. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  208. data/lib/rubocop/cop/team.rb +10 -5
  209. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  210. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  211. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  212. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  213. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  214. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  215. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  216. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  217. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  218. data/lib/rubocop/lsp/runtime.rb +47 -11
  219. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  220. data/lib/rubocop/lsp.rb +5 -2
  221. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  222. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  223. data/lib/rubocop/mcp/scope.rb +65 -0
  224. data/lib/rubocop/mcp/server.rb +184 -76
  225. data/lib/rubocop/mcp/source_files.rb +35 -0
  226. data/lib/rubocop/offense_limit.rb +30 -0
  227. data/lib/rubocop/options.rb +45 -10
  228. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  229. data/lib/rubocop/remote_config.rb +4 -1
  230. data/lib/rubocop/result_cache.rb +20 -13
  231. data/lib/rubocop/runner.rb +109 -41
  232. data/lib/rubocop/server/cache.rb +2 -2
  233. data/lib/rubocop/util.rb +69 -0
  234. data/lib/rubocop/version.rb +1 -1
  235. data/lib/rubocop.rb +5 -0
  236. metadata +16 -5
@@ -121,7 +121,6 @@ module RuboCop
121
121
  tokens[1]&.type == :tSTRING_DBEG && tokens[2]&.semicolon?
122
122
  end
123
123
 
124
- # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
125
124
  def register_semicolon(line, column, after_expression, token_before_semicolon = nil)
126
125
  range = source_range(processed_source.buffer, line, column)
127
126
 
@@ -134,13 +133,11 @@ module RuboCop
134
133
  # See: https://github.com/rubocop/rubocop/issues/10791
135
134
  if token_before_semicolon&.regexp_dots?
136
135
  node = find_node(range_nodes, token_before_semicolon)
136
+ corrector.wrap(node, '(', ')') if node
137
137
  elsif token_before_semicolon&.type == :tLABEL
138
- node = find_node(value_omission_pair_nodes, token_before_semicolon).parent
139
- space = node.parent.loc.selector.end.join(node.source_range.begin)
140
- corrector.remove(space)
138
+ parenthesize_arguments(corrector, value_omission_hash(token_before_semicolon))
141
139
  end
142
140
 
143
- corrector.wrap(node, '(', ')') if node
144
141
  corrector.remove(range)
145
142
  end
146
143
  end
@@ -161,6 +158,20 @@ module RuboCop
161
158
  end
162
159
  end
163
160
 
161
+ def value_omission_hash(token_before_semicolon)
162
+ find_node(value_omission_pair_nodes, token_before_semicolon)&.parent
163
+ end
164
+
165
+ def parenthesize_arguments(corrector, hash_node)
166
+ return unless hash_node
167
+
168
+ call = hash_node.parent
169
+ before_arguments = args_begin(call).begin.join(call.first_argument.source_range.begin)
170
+
171
+ corrector.replace(before_arguments, '(')
172
+ corrector.insert_after(call.last_argument, ')')
173
+ end
174
+
164
175
  def expressions_per_line(exprs)
165
176
  # create a map matching lines to the number of expressions on them
166
177
  exprs_lines = exprs.map(&:last_line)
@@ -35,6 +35,7 @@ module RuboCop
35
35
  #
36
36
  class SingleLineMethods < Base
37
37
  include Alignment
38
+ include EndlessMethodBodyHelp
38
39
  extend AutoCorrector
39
40
 
40
41
  MSG = 'Avoid single-line method definitions.'
@@ -67,10 +68,14 @@ module RuboCop
67
68
  return false if target_ruby_version < 3.0
68
69
  return false if disallow_endless_method_style?
69
70
  return false unless body_node
70
- return false if body_node.basic_conditional? || body_node.parent.assignment_method? ||
71
- NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
71
+ return false if body_node.parent.assignment_method?
72
72
 
73
- !body_node.type?(:begin, :kwbegin)
73
+ !unsupported_endless_method_body?(body_node) && endless_method_body?(body_node)
74
+ end
75
+
76
+ def unsupported_endless_method_body?(body_node)
77
+ body_node.basic_conditional? ||
78
+ NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
74
79
  end
75
80
 
76
81
  def correct_to_multiline(corrector, node)
@@ -89,8 +94,8 @@ module RuboCop
89
94
 
90
95
  def correct_to_endless(corrector, node)
91
96
  receiver = "#{node.receiver.source}." if node.receiver
92
- arguments = node.arguments.any? ? node.arguments.source : '()'
93
97
  body_source = method_body_source(node.body)
98
+ arguments = endless_arguments(node)
94
99
  replacement = "def #{receiver}#{node.method_name}#{arguments} = #{body_source}"
95
100
 
96
101
  corrector.replace(node, replacement)
@@ -138,6 +143,13 @@ module RuboCop
138
143
  !method_body.arguments.empty? && !method_body.comparison_method?
139
144
  end
140
145
 
146
+ def endless_arguments(node)
147
+ arguments = node.arguments
148
+ return '()' if arguments.none?
149
+
150
+ arguments.loc.begin ? arguments.source : "(#{arguments.source})"
151
+ end
152
+
141
153
  def disallow_endless_method_style?
142
154
  return true unless config.cop_enabled?('Style/EndlessMethod')
143
155
 
@@ -50,7 +50,33 @@ module RuboCop
50
50
  end
51
51
 
52
52
  def redundant_parentheses?(node)
53
- style == :require_no_parentheses && parentheses?(node)
53
+ return false unless style == :require_no_parentheses && parentheses?(node)
54
+
55
+ arguments = node.block_node.arguments
56
+ !arguments_require_parentheses?(arguments) && !comment_inside_parentheses?(arguments)
57
+ end
58
+
59
+ def arguments_require_parentheses?(arguments)
60
+ arguments.each_descendant(:any_block, :hash).any? do |node|
61
+ !node.hash_type? || node.braces?
62
+ end
63
+ end
64
+
65
+ def comment_inside_parentheses?(arguments)
66
+ !leading_padding(arguments).source.strip.empty? ||
67
+ !trailing_padding(arguments).source.strip.empty?
68
+ end
69
+
70
+ def leading_padding(arguments)
71
+ first_argument = arguments.children.first
72
+
73
+ arguments.loc.begin.end.join(first_argument.source_range.begin)
74
+ end
75
+
76
+ def trailing_padding(arguments)
77
+ last_argument = arguments.children.last
78
+
79
+ last_argument.source_range.end.join(arguments.loc.end.begin)
54
80
  end
55
81
 
56
82
  def message(_node)
@@ -62,10 +88,8 @@ module RuboCop
62
88
  end
63
89
 
64
90
  def unwanted_parentheses_corrector(corrector, node)
65
- args_loc = node.loc
66
-
67
- corrector.replace(args_loc.begin, '')
68
- corrector.remove(args_loc.end)
91
+ corrector.remove(node.loc.begin.join(leading_padding(node)))
92
+ corrector.remove(trailing_padding(node).join(node.loc.end))
69
93
  end
70
94
 
71
95
  def stabby_lambda_with_args?(node)
@@ -160,7 +160,9 @@ module RuboCop
160
160
  end
161
161
 
162
162
  def adjust_str_literal(part)
163
- if single_quoted?(part)
163
+ if file_keyword?(part)
164
+ "\#{#{part.source}}"
165
+ elsif single_quoted?(part)
164
166
  part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
165
167
  elsif double_quoted?(part)
166
168
  # Reuse the source as written - rebuilding it from the value via
@@ -178,12 +180,16 @@ module RuboCop
178
180
  end
179
181
  end
180
182
 
183
+ def file_keyword?(str_node)
184
+ str_node.source == '__FILE__' && !str_node.parent.dstr_type?
185
+ end
186
+
181
187
  def single_quoted?(str_node)
182
- str_node.source.start_with?("'")
188
+ str_node.loc.begin && str_node.source.start_with?("'")
183
189
  end
184
190
 
185
191
  def double_quoted?(str_node)
186
- str_node.source.start_with?('"')
192
+ str_node.loc.begin && str_node.source.start_with?('"')
187
193
  end
188
194
 
189
195
  def mode
@@ -76,6 +76,9 @@ module RuboCop
76
76
 
77
77
  def on_super(super_node)
78
78
  return unless (def_node = find_def_node(super_node))
79
+ # Zsuper passes a destructured argument as the original value, while
80
+ # `argument_list` flattens it into its parts.
81
+ return if def_node.arguments.any?(&:mlhs_type?)
79
82
 
80
83
  def_node_args = def_node.arguments.argument_list
81
84
  super_args = preprocess_super_args(super_node.arguments)
@@ -142,9 +142,11 @@ module RuboCop
142
142
  include RangeHelp
143
143
  include AllowedMethods
144
144
  include AllowedPattern
145
+ include PatternMatchingHelp
145
146
  extend AutoCorrector
146
147
 
147
148
  MSG = 'Pass `&:%<method>s` as an argument to `%<block_method>s` instead of a block.'
149
+ MSG_LAMBDA_LITERAL = 'Use `lambda(&:%<method>s)` instead of %<original>s.'
148
150
  SUPER_TYPES = %i[super zsuper].freeze
149
151
  LAMBDA_OR_PROC = %i[lambda proc].freeze
150
152
 
@@ -174,6 +176,8 @@ module RuboCop
174
176
  return if proc_node?(dispatch_node)
175
177
  return if LAMBDA_OR_PROC.include?(dispatch_node.method_name)
176
178
  end
179
+ # A pattern accepts a lambda literal, but not `lambda(&:foo)`.
180
+ return if in_pattern?(node)
177
181
  return if unsafe_hash_usage?(dispatch_node)
178
182
  return if unsafe_array_usage?(dispatch_node)
179
183
  return if allowed_method_name?(dispatch_node.method_name)
@@ -207,12 +211,26 @@ module RuboCop
207
211
  end
208
212
 
209
213
  def register_offense(node, method_name, block_method_name)
210
- block_start = node.loc.begin.begin_pos
211
- block_end = node.loc.end.end_pos
212
- range = range_between(block_start, block_end)
213
- message = format(MSG, method: method_name, block_method: block_method_name)
214
+ range = if node.send_node.lambda_literal?
215
+ node.source_range
216
+ else
217
+ node.loc.begin.begin.join(node.loc.end.end)
218
+ end
219
+ message = message(node, method_name, block_method_name)
214
220
 
215
- add_offense(range, message: message) { |corrector| autocorrect(corrector, node) }
221
+ add_offense(range, message: message) do |corrector|
222
+ autocorrect(corrector, node)
223
+ end
224
+ end
225
+
226
+ def message(node, method_name, block_method_name)
227
+ if node.send_node.lambda_literal?
228
+ original = node.single_line? ? "`#{node.source}`" : 'a lambda literal with a block'
229
+
230
+ format(MSG_LAMBDA_LITERAL, method: method_name, original: original)
231
+ else
232
+ format(MSG, method: method_name, block_method: block_method_name)
233
+ end
216
234
  end
217
235
 
218
236
  def autocorrect(corrector, node)
@@ -58,6 +58,7 @@ module RuboCop
58
58
  include SafeAssignment
59
59
  include ConfigurableEnforcedStyle
60
60
  include SurroundingSpace
61
+ include ReparsedEquivalence
61
62
  extend AutoCorrector
62
63
 
63
64
  VARIABLE_TYPES = AST::Node::VARIABLES
@@ -101,7 +102,8 @@ module RuboCop
101
102
  condition = node.condition
102
103
 
103
104
  return nil if parenthesized?(condition) &&
104
- (safe_assignment?(condition) || unsafe_autocorrect?(condition))
105
+ (safe_assignment?(condition) || unsafe_autocorrect?(condition) ||
106
+ verified_by_reparse([condition]).empty?)
105
107
 
106
108
  if parenthesized?(condition)
107
109
  correct_parenthesized(corrector, condition)
@@ -218,6 +220,26 @@ module RuboCop
218
220
  end
219
221
  end
220
222
 
223
+ # Whatever binds looser than the ternary operator (`n += 1`, `a rescue b`,
224
+ # a nested ternary, `yield a`, several statements, ...) changes
225
+ # meaning without the parentheses, so their removal is verified by
226
+ # reparsing.
227
+ def apply_reparse_correction(corrector, condition)
228
+ correct_parenthesized(corrector, condition)
229
+ end
230
+
231
+ # The removed parentheses are the only expected difference.
232
+ def normalize_reparsed_ast(node)
233
+ return node unless node.is_a?(::Parser::AST::Node)
234
+
235
+ children = node.children.map { |child| normalize_reparsed_ast(child) }
236
+ if node.begin_type? && children.one? && children.first.is_a?(::Parser::AST::Node)
237
+ children.first
238
+ else
239
+ node.updated(nil, children)
240
+ end
241
+ end
242
+
221
243
  def correct_unparenthesized(corrector, condition)
222
244
  corrector.wrap(condition, '(', ')')
223
245
  end
@@ -35,19 +35,20 @@ module RuboCop
35
35
 
36
36
  MSG = 'Do not use trailing `_`s in parallel assignment. Prefer `%<code>s`.'
37
37
  UNDERSCORE = '_'
38
+ ARRAY_OPENING = '['
38
39
  DISALLOW = %i[lvasgn splat].freeze
39
40
  private_constant :DISALLOW
40
41
 
41
42
  def on_masgn(node)
42
- ranges = unneeded_ranges(node)
43
-
44
- ranges.each do |range|
43
+ unneeded_ranges(node).each do |range, replacement|
44
+ closing = replacement == ARRAY_OPENING ? ']' : ''
45
45
  good_code = node.source
46
46
  offset = range.begin_pos - node.source_range.begin_pos
47
- good_code[offset, range.size] = ''
47
+ good_code[offset, range.size] = replacement
48
48
 
49
- add_offense(range, message: format(MSG, code: good_code)) do |corrector|
50
- corrector.remove(range)
49
+ add_offense(range, message: format(MSG, code: "#{good_code}#{closing}")) do |corrector|
50
+ corrector.replace(range, replacement)
51
+ corrector.insert_after(node, closing) unless closing.empty?
51
52
  end
52
53
  end
53
54
  end
@@ -115,9 +116,12 @@ module RuboCop
115
116
 
116
117
  return unused_range(node, mlhs_node) if unused_variables_only?(first_offense, variables)
117
118
 
118
- return range_for_parentheses(first_offense, mlhs_node) if Util.parentheses?(mlhs_node)
119
-
120
- range_between(first_offense.source_range.begin_pos, node.loc.operator.begin_pos)
119
+ range = if Util.parentheses?(mlhs_node)
120
+ range_for_parentheses(first_offense, mlhs_node)
121
+ else
122
+ range_between(first_offense.source_range.begin_pos, node.loc.operator.begin_pos)
123
+ end
124
+ [range, '']
121
125
  end
122
126
 
123
127
  def children_offenses(variables)
@@ -129,17 +133,17 @@ module RuboCop
129
133
  end
130
134
 
131
135
  def unused_range(node, mlhs_node)
132
- start_range = mlhs_node.source_range.begin_pos
133
-
134
136
  # `node` can be an `mlhs` when recursing into a nested destructuring
135
- # group; only a `masgn` has a right-hand side to anchor against.
136
- end_range = if node.masgn_type?
137
- node.rhs.source_range.begin_pos
138
- else
139
- mlhs_node.source_range.end_pos
140
- end
141
-
142
- range_between(start_range, end_range)
137
+ # group. Removing the group could leave a dangling comma or put
138
+ # something after a splat, so it is collapsed into a single `_`,
139
+ # which the parent group handles in turn.
140
+ return [mlhs_node.source_range, UNDERSCORE] unless node.masgn_type?
141
+
142
+ rhs = node.rhs
143
+ range = range_between(mlhs_node.source_range.begin_pos, rhs.source_range.begin_pos)
144
+
145
+ # `_, _ = 1, 2` cannot become `1, 2`, so the values become an array literal.
146
+ [range, rhs.array_type? && !rhs.bracketed? ? ARRAY_OPENING : '']
143
147
  end
144
148
 
145
149
  def range_for_parentheses(offense, left)
@@ -59,7 +59,7 @@ module RuboCop
59
59
  private
60
60
 
61
61
  def non_eligible_body?(body)
62
- body&.conditional? || super
62
+ body&.conditional? || body&.kwbegin_type? || super
63
63
  end
64
64
  end
65
65
  end
@@ -104,7 +104,7 @@ module RuboCop
104
104
  valid_yoda?(node)
105
105
 
106
106
  add_offense(node) do |corrector|
107
- corrector.replace(actual_code_range(node), corrected_code(node))
107
+ autocorrect(corrector, node)
108
108
  end
109
109
  end
110
110
 
@@ -138,22 +138,37 @@ module RuboCop
138
138
  format(MSG, source: node.source)
139
139
  end
140
140
 
141
- def corrected_code(node)
142
- lhs = node.receiver
143
- rhs = node.first_argument
144
-
145
- "#{rhs.source} #{reverse_comparison(node.method_name)} #{lhs.source}"
141
+ def autocorrect(corrector, node)
142
+ if constant_portion?(node.first_argument)
143
+ move_argument_to_front(corrector, node)
144
+ else
145
+ move_receiver_to_back(corrector, node)
146
+ end
146
147
  end
147
148
 
148
149
  def constant_portion?(node)
149
150
  node.recursive_literal? || node.const_type?
150
151
  end
151
152
 
152
- def actual_code_range(node)
153
- range_between(node.source_range.begin_pos, node.source_range.end_pos)
153
+ def move_argument_to_front(corrector, node)
154
+ receiver = node.receiver
155
+
156
+ replacement = "#{node.first_argument.source} #{reverse_comparison(node)} "
157
+
158
+ corrector.insert_before(receiver, replacement)
159
+ corrector.remove(receiver.source_range.end.join(node.source_range.end))
154
160
  end
155
161
 
156
- def reverse_comparison(operator)
162
+ def move_receiver_to_back(corrector, node)
163
+ argument = node.first_argument
164
+
165
+ corrector.remove(node.source_range.begin.join(argument.source_range.begin))
166
+ corrector.insert_after(argument, " #{reverse_comparison(node)} #{node.receiver.source}")
167
+ end
168
+
169
+ def reverse_comparison(node)
170
+ operator = node.method_name
171
+
157
172
  REVERSE_COMPARISON.fetch(operator.to_s, operator)
158
173
  end
159
174
 
@@ -43,6 +43,9 @@ module RuboCop
43
43
  MSG = 'Non-literal operand (`%<source>s`) should be first.'
44
44
 
45
45
  RESTRICT_ON_SEND = %i[* + & | ^].freeze
46
+ ARGUMENT_ONLY_TYPES = %i[
47
+ splat block_pass forwarded_args forwarded_restarg forwarded_kwrestarg
48
+ ].freeze
46
49
 
47
50
  def on_new_investigation
48
51
  @offended_nodes = nil
@@ -68,13 +71,19 @@ module RuboCop
68
71
  private
69
72
 
70
73
  def yoda_expression_constant?(lhs, rhs)
71
- constant_portion?(lhs) && !constant_portion?(rhs)
74
+ constant_portion?(lhs) && !constant_portion?(rhs) && operand?(rhs)
72
75
  end
73
76
 
74
77
  def constant_portion?(node)
75
78
  node.type?(:numeric, :const)
76
79
  end
77
80
 
81
+ def operand?(node)
82
+ return false if node.hash_type? && !node.braces?
83
+
84
+ !node.type?(*ARGUMENT_ONLY_TYPES)
85
+ end
86
+
78
87
  def supported_operators
79
88
  @supported_operators ||= Array(cop_config['SupportedOperators']).freeze
80
89
  end
@@ -178,7 +178,7 @@ module RuboCop
178
178
  elsif defer_corrections
179
179
  @updated_source = new_source
180
180
  else
181
- File.write(processed_source.file_path, new_source)
181
+ RuboCop::Util.replace_file_contents(processed_source.file_path, new_source)
182
182
  end
183
183
  end
184
184
 
@@ -342,20 +342,25 @@ module RuboCop
342
342
  end
343
343
 
344
344
  def each_corrector(report)
345
- skips = Set.new
345
+ applied = []
346
346
  report.cop_reports.each do |cop_report|
347
- cop = cop_report.cop
347
+ cop_class = cop_report.cop.class
348
348
  corrector = cop_report.corrector
349
349
 
350
350
  next if corrector.nil? || corrector.empty?
351
- next if skips.include?(cop.class)
351
+ next if applied.any? { |other| autocorrect_incompatible?(cop_class, other) }
352
352
 
353
353
  yield corrector
354
354
 
355
- skips.merge(cop.class.autocorrect_incompatible_with)
355
+ applied << cop_class
356
356
  end
357
357
  end
358
358
 
359
+ def autocorrect_incompatible?(cop_class, other)
360
+ cop_class.autocorrect_incompatible_with.include?(other) ||
361
+ other.autocorrect_incompatible_with.include?(cop_class)
362
+ end
363
+
359
364
  def suppress_clobbering
360
365
  yield
361
366
  rescue ::Parser::ClobberingError
@@ -48,7 +48,7 @@ module RuboCop
48
48
  end
49
49
 
50
50
  def used?
51
- (!reassigned? && @variable.captured_by_block?) || @referenced
51
+ (!reassigned? && @variable.captured_by_block? && @variable.referenced?) || @referenced
52
52
  end
53
53
 
54
54
  def regexp_named_capture?
@@ -67,7 +67,8 @@ module RuboCop
67
67
 
68
68
  break if !assignment.branch || assignment.branch == reference.branch
69
69
 
70
- unless assignment.branch.may_run_incompletely?
70
+ unless assignment.branch.may_run_incompletely? ||
71
+ exception_may_reach?(assignment.branch, node)
71
72
  (consumed_branches ||= Set.new) << assignment.branch
72
73
  end
73
74
  end
@@ -83,6 +84,18 @@ module RuboCop
83
84
  covers?(conditional, reference_node) && !covers?(conditional.condition, reference_node)
84
85
  end
85
86
 
87
+ # A branch nested in the main body of `begin` can be cut short by an exception
88
+ # too. An earlier assignment in it is visible to a reference in the `rescue` or
89
+ # `ensure` clause, or after a `rescue` that handles the exception.
90
+ def exception_may_reach?(branch, reference_node)
91
+ branch.each_ancestor.any? do |ancestor|
92
+ next false unless ancestor.may_run_incompletely?
93
+ next false if covers?(ancestor.child_node, reference_node)
94
+
95
+ ancestor.control_node.rescue_type? || covers?(ancestor.control_node, reference_node)
96
+ end
97
+ end
98
+
86
99
  def modifier_conditional_of(node)
87
100
  node.each_ancestor(:if, :while, :until).find do |conditional|
88
101
  conditional.modifier_form? && covers?(conditional.condition, node)
@@ -35,6 +35,7 @@ module RuboCop
35
35
  # * `#file_started`
36
36
  # * `#file_finished`
37
37
  # * `#file_started`
38
+ # * `#file_problems` (only when a cop crashed or warned on the file)
38
39
  # * `#file_finished`
39
40
  # * `#finished`
40
41
  #
@@ -88,6 +89,27 @@ module RuboCop
88
89
  # @return [void]
89
90
  def file_started(file, options); end
90
91
 
92
+ # @api public
93
+ #
94
+ # Invoked before `#file_finished` for a file that a cop crashed on or
95
+ # raised a warning for, or whose corrections went into an infinite loop.
96
+ # Optional: a formatter implementing the API by duck typing is skipped
97
+ # when it doesn't define it.
98
+ #
99
+ # @param file [String]
100
+ # the file path
101
+ #
102
+ # @param errors [Array(String)]
103
+ # the error messages as they're printed, without colors. A cop that
104
+ # crashes on every node of a kind has a message for each, naming the
105
+ # line and column.
106
+ #
107
+ # @param warnings [Array(String)]
108
+ # the warning messages, in the same form
109
+ #
110
+ # @return [void]
111
+ def file_problems(file, errors, warnings); end
112
+
91
113
  # @api public
92
114
  #
93
115
  # Invoked at the end of inspecting each files.
@@ -49,6 +49,10 @@ module RuboCop
49
49
  each { |f| f.file_started(file, options) }
50
50
  end
51
51
 
52
+ def file_problems(file, errors, warnings)
53
+ each { |f| f.file_problems(file, errors, warnings) if f.respond_to?(:file_problems) }
54
+ end
55
+
52
56
  def file_finished(file, offenses)
53
57
  each { |f| f.file_finished(file, offenses) }
54
58
  offenses