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
@@ -142,12 +142,6 @@ module RuboCop
142
142
  end
143
143
  end
144
144
 
145
- def offending_prefixes_for(node, method_name)
146
- predicate_prefixes.reject do |prefix|
147
- allowed_method_name?(method_name, prefix) ||
148
- (use_sorbet_sigs? && !sorbet_sig?(node, return_type: 'T::Boolean'))
149
- end
150
- end
151
145
  alias on_defs on_def
152
146
 
153
147
  def validate_config
@@ -162,6 +156,13 @@ module RuboCop
162
156
 
163
157
  private
164
158
 
159
+ def offending_prefixes_for(node, method_name)
160
+ prefixes = predicate_prefixes.reject { |p| allowed_method_name?(method_name, p) }
161
+ return prefixes if prefixes.empty? || !use_sorbet_sigs?
162
+
163
+ sorbet_sig?(node, return_type: 'T::Boolean') ? prefixes : []
164
+ end
165
+
165
166
  # @!method sorbet_return_type(node)
166
167
  def_node_matcher :sorbet_return_type, <<~PATTERN
167
168
  (block (send nil? :sig) args (send _ :returns $_type))
@@ -210,22 +211,6 @@ module RuboCop
210
211
  def method_definition_macro?(macro_name)
211
212
  cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
212
213
  end
213
-
214
- # When `AllCops/UseProjectIndex` is enabled, methods that override a
215
- # method defined by an ancestor elsewhere in the project are not
216
- # reported: renaming an override breaks the inherited contract.
217
- def overrides_inherited_method?(node)
218
- return false unless project_index
219
- return false unless (namespace_node = node.each_ancestor(:class, :module).first)
220
-
221
- declaration = resolve_constant_in_index(namespace_node.identifier)
222
- return false unless declaration.is_a?(Rubydex::Namespace)
223
-
224
- scope = node.defs_type? ? indexed_singleton_of(declaration) : declaration
225
- !scope.nil? && inherited_index_member?(scope, "#{node.method_name}()")
226
- rescue StandardError
227
- false
228
- end
229
214
  end
230
215
  end
231
216
  end
@@ -86,6 +86,9 @@ module RuboCop
86
86
 
87
87
  NO_LOCATION = PseudoSourceRange.new(1, 0, '', 0, 0).freeze
88
88
 
89
+ EMPTY_CORRECTIONS = [].freeze
90
+ private_constant :EMPTY_CORRECTIONS
91
+
89
92
  # @api public
90
93
  #
91
94
  # @!attribute [r] justification
@@ -96,9 +99,43 @@ module RuboCop
96
99
  # or the directive carries no reason
97
100
  attr_reader :justification
98
101
 
102
+ # @api public
103
+ #
104
+ # @!attribute [r] corrections
105
+ #
106
+ # @return [Array<Correction>]
107
+ # the individual edits that autocorrecting this offense would make, in
108
+ # source order. Empty when the offense has no correction. Unlike
109
+ # {#corrector}, this survives serialization, so it is available on a
110
+ # cache hit and in parallel runs.
111
+ attr_reader :corrections
112
+
113
+ # @api public
114
+ #
115
+ # @!attribute [r] correction_safe
116
+ #
117
+ # @return [Boolean]
118
+ # whether autocorrection would apply {#corrections} in a normal `-a`
119
+ # run. A cop marked `Safe: false` or `SafeAutoCorrect: false` still
120
+ # builds its correction, but only `-A` applies it, so a consumer that
121
+ # applies edits itself has to make the same distinction.
122
+ attr_reader :correction_safe
123
+
124
+ # A single edit of an autocorrection: the source range it replaces and
125
+ # the text it replaces it with. An insertion has an empty range, a
126
+ # removal an empty replacement.
127
+ #
128
+ # @api public
129
+ Correction = Struct.new(:begin_pos, :end_pos, :replacement) do
130
+ def length
131
+ end_pos - begin_pos
132
+ end
133
+ end
134
+
99
135
  # @api private
100
136
  def initialize(severity, location, message, cop_name, # rubocop:disable Metrics/ParameterLists
101
- status = :uncorrected, corrector = nil, justification: nil)
137
+ status = :uncorrected, corrector = nil, justification: nil,
138
+ corrections: nil, correction_safe: true)
102
139
  @severity = RuboCop::Cop::Severity.new(severity)
103
140
  @location = location
104
141
 
@@ -112,15 +149,21 @@ module RuboCop
112
149
  @status = status
113
150
  @corrector = corrector
114
151
  @justification = justification.freeze
152
+ @corrections = (corrections || corrections_from(corrector)).freeze
153
+ @correction_safe = correction_safe
115
154
  freeze
116
155
  end
117
156
 
118
157
  def marshal_dump
119
- [@severity, @location, @message, @cop_name, @status, @justification]
158
+ [@severity, @location, @message, @cop_name, @status, @justification, @corrections,
159
+ @correction_safe]
120
160
  end
121
161
 
122
162
  def marshal_load(array)
123
- @severity, @location, @message, @cop_name, @status, @justification = array
163
+ @severity, @location, @message, @cop_name, @status, @justification, @corrections,
164
+ @correction_safe = array
165
+ @corrections ||= EMPTY_CORRECTIONS
166
+ @correction_safe = true if @correction_safe.nil?
124
167
  @line = @location.line
125
168
  @column = @location.column
126
169
  end
@@ -276,6 +319,19 @@ module RuboCop
276
319
  end
277
320
  0
278
321
  end
322
+
323
+ private
324
+
325
+ # Extracted eagerly rather than on demand because the offense is frozen,
326
+ # and because a corrector cannot be serialized - the cache and parallel
327
+ # workers would otherwise hand back offenses that have lost their edits.
328
+ def corrections_from(corrector)
329
+ return EMPTY_CORRECTIONS unless corrector
330
+
331
+ corrector.as_replacements.map do |range, replacement|
332
+ Correction.new(range.begin_pos, range.end_pos, replacement.freeze).freeze
333
+ end
334
+ end
279
335
  end
280
336
  end
281
337
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
 
44
44
  def self.qualified_cop?(name)
45
45
  badge = Badge.parse(name)
46
- global.qualify_badge(badge).first == badge
46
+ global.qualify_badge(badge).include?(badge)
47
47
  end
48
48
 
49
49
  attr_reader :options, :warnings
@@ -22,6 +22,13 @@ module RuboCop
22
22
  # passing the `create_additions` keyword argument emits a deprecation warning, with the
23
23
  # goal of being secure by default in the next major version 3.0.0.
24
24
  #
25
+ # NOTE: Before `json` gem version 2.17.0, `JSON.load` only takes options as its third
26
+ # argument. A hash passed as the second argument is taken as the `proc` argument, so
27
+ # its `create_additions` option is ignored. Which `json` version runs can't be told
28
+ # statically (the lockfile may resolve a newer one than the default gem loaded in
29
+ # production), so options passed as the second argument are always reported. Pass
30
+ # `nil` as the second argument instead.
31
+ #
25
32
  # @safety
26
33
  # This cop's autocorrection is unsafe because it's potentially dangerous.
27
34
  # If using a stream, like `JSON.load(open('file'))`, you will need to call
@@ -33,13 +40,16 @@ module RuboCop
33
40
  # JSON.load('{}')
34
41
  # JSON.restore('{}')
35
42
  #
43
+ # # bad - ignored by `json` older than 2.17.0
44
+ # JSON.load('{}', create_additions: false)
45
+ #
36
46
  # # good
37
47
  # JSON.parse('{}')
38
48
  # JSON.unsafe_load('{}')
39
49
  #
40
50
  # # good - explicit use of `create_additions` option
41
- # JSON.load('{}', create_additions: true)
42
- # JSON.load('{}', create_additions: false)
51
+ # JSON.load('{}', nil, create_additions: true)
52
+ # JSON.load('{}', nil, create_additions: false)
43
53
  #
44
54
  class JSONLoad < Base
45
55
  extend AutoCorrector
@@ -51,8 +61,10 @@ module RuboCop
51
61
  def_node_matcher :insecure_json_load, <<~PATTERN
52
62
  (
53
63
  send (const {nil? cbase} :JSON) ${:load :restore}
54
- ...
55
- !`(pair (sym :create_additions) _)
64
+ {
65
+ _ hash
66
+ | ... !`(pair (sym :create_additions) _)
67
+ }
56
68
  )
57
69
  PATTERN
58
70
 
@@ -187,11 +187,18 @@ module RuboCop
187
187
  def allowed?(node)
188
188
  !node.access_modifier? ||
189
189
  node.parent&.type?(:pair, :any_block) ||
190
+ dynamic_method_list?(node) ||
190
191
  allow_modifiers_on_symbols?(node) ||
191
192
  allow_modifiers_on_attrs?(node) ||
192
193
  allow_modifiers_on_alias_method?(node)
193
194
  end
194
195
 
196
+ # `private(*names)` with a list computed at runtime names no definition
197
+ # the modifier could be grouped with.
198
+ def dynamic_method_list?(node)
199
+ node.arguments.any?(&:splat_type?) && !access_modifier_with_symbol?(node)
200
+ end
201
+
195
202
  def autocorrect(corrector, node)
196
203
  case style
197
204
  when :group
@@ -60,7 +60,7 @@ module RuboCop
60
60
  add_offense(node.loc.keyword, message: MSG_ALIAS) do |corrector|
61
61
  autocorrect(corrector, node)
62
62
  end
63
- elsif node.children.none? { |arg| bareword?(arg) }
63
+ elsif node.children.all? { |arg| !bareword?(arg) && bareword_symbol?(arg) }
64
64
  add_offense_for_args(node) { |corrector| autocorrect(corrector, node) }
65
65
  end
66
66
  end
@@ -97,7 +97,7 @@ module RuboCop
97
97
 
98
98
  def add_offense_for_args(node, &block)
99
99
  existing_args = node.children.map(&:source).join(' ')
100
- preferred_args = node.children.map { |a| a.source[1..] }.join(' ')
100
+ preferred_args = node.children.map { |node| bareword_for(node) }.join(' ')
101
101
  arg_ranges = node.children.map(&:source_range)
102
102
  msg = format(MSG_SYMBOL_ARGS, prefer: preferred_args, current: existing_args)
103
103
  add_offense(arg_ranges.reduce(&:join), message: msg, &block)
@@ -138,6 +138,15 @@ module RuboCop
138
138
  !sym_node.source.start_with?(':') || sym_node.dsym_type?
139
139
  end
140
140
 
141
+ def bareword_symbol?(sym_node)
142
+ return false unless sym_node.sym_type?
143
+
144
+ value = sym_node.value.to_s
145
+ return true if value.match?(/\A[A-Za-z_]\w*[?!=]?\z/)
146
+
147
+ !sym_node.source.start_with?(':"', ":'") && !value.start_with?('@', '$')
148
+ end
149
+
141
150
  def correct_alias_method_to_alias(corrector, send_node)
142
151
  new, old = *send_node.arguments
143
152
  replacement = "alias #{identifier(new)} #{identifier(old)}"
@@ -153,17 +162,21 @@ module RuboCop
153
162
  end
154
163
 
155
164
  def correct_alias_with_symbol_args(corrector, node)
156
- corrector.replace(node.new_identifier, node.new_identifier.source[1..])
157
- corrector.replace(node.old_identifier, node.old_identifier.source[1..])
165
+ corrector.replace(node.new_identifier, bareword_for(node.new_identifier))
166
+ corrector.replace(node.old_identifier, bareword_for(node.old_identifier))
158
167
  end
159
168
 
160
169
  def identifier(node)
161
- if node.sym_type?
170
+ if bareword_symbol?(node)
162
171
  ":#{node.children.first}"
163
172
  else
164
173
  node.source
165
174
  end
166
175
  end
176
+
177
+ def bareword_for(sym_node)
178
+ sym_node.value.to_s
179
+ end
167
180
  end
168
181
  end
169
182
  end
@@ -39,9 +39,9 @@ module RuboCop
39
39
  # @!method ambiguous_endless_method_body(node)
40
40
  def_node_matcher :ambiguous_endless_method_body, <<~PATTERN
41
41
  ^${
42
- (if _ <def _>)
43
- ({and or} def _)
44
- ({while until} _ def)
42
+ (if _ <any_def _>)
43
+ ({and or} any_def _)
44
+ ({while until} _ any_def)
45
45
  }
46
46
  PATTERN
47
47
 
@@ -57,6 +57,7 @@ module RuboCop
57
57
  correct_to_multiline(corrector, node)
58
58
  end
59
59
  end
60
+ alias on_defs on_def
60
61
 
61
62
  private
62
63
 
@@ -48,6 +48,10 @@ module RuboCop
48
48
 
49
49
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
50
50
 
51
+ def self.autocorrect_incompatible_with
52
+ [Style::MethodCallWithArgsParentheses, Style::ReturnNilInPredicateMethodDefinition]
53
+ end
54
+
51
55
  def on_and(node)
52
56
  process_logical_operator(node) if style == :always
53
57
  end
@@ -153,7 +153,7 @@ module RuboCop
153
153
  BLOCK_MSG = 'Use anonymous block arguments forwarding (`&`).'
154
154
 
155
155
  def self.autocorrect_incompatible_with
156
- [Naming::BlockForwarding, Style::MethodDefParentheses]
156
+ [Naming::BlockForwarding, Style::EndlessMethod, Style::MethodDefParentheses]
157
157
  end
158
158
 
159
159
  def on_def(node)
@@ -377,6 +377,7 @@ module RuboCop
377
377
  def add_parens_if_missing(node, corrector)
378
378
  return if parentheses?(node)
379
379
  return if node.send_type? && node.method?(:[])
380
+ return unless (@parenthesized_nodes ||= Set.new.compare_by_identity).add?(node)
380
381
 
381
382
  add_parentheses(node, corrector)
382
383
  end
@@ -499,6 +500,8 @@ module RuboCop
499
500
  end
500
501
 
501
502
  def offensive_block_forwarding?
503
+ return false if @send_node.block_literal?
504
+
502
505
  @block_arg ? forwarded_block_arg : allow_offense_for_no_block?
503
506
  end
504
507
 
@@ -50,10 +50,9 @@ module RuboCop
50
50
  def autocorrect(corrector, node)
51
51
  attr_name, setter = *node.arguments
52
52
 
53
- node_expr = node.source_range
54
- attr_expr = attr_name.source_range
55
-
56
- remove = range_between(attr_expr.end_pos, node_expr.end_pos) if setter&.boolean_type?
53
+ if setter&.boolean_type?
54
+ remove = range_between(attr_name.source_range.end_pos, setter.source_range.end_pos)
55
+ end
57
56
 
58
57
  corrector.replace(node.loc.selector, replacement_method(node))
59
58
  corrector.remove(remove) if remove
@@ -66,16 +66,18 @@ module RuboCop
66
66
 
67
67
  # @!method bit_operation?(node)
68
68
  def_node_matcher :bit_operation?, <<~PATTERN
69
- (begin
70
- (send _ :& _))
69
+ {
70
+ (begin (send _ :& _))
71
+ (send _ :& _)
72
+ }
71
73
  PATTERN
72
74
 
73
75
  # rubocop:disable-next Metrics/AbcSize
74
76
  def on_send(node)
75
- return unless node.receiver&.begin_type?
77
+ return unless bit_operation?(node.receiver)
76
78
  return unless (preferred_method = preferred_method(node))
77
79
 
78
- bit_operation = node.receiver.children.first
80
+ bit_operation = node.receiver.begin_type? ? node.receiver.children.first : node.receiver
79
81
  lhs, _operator, rhs = *bit_operation
80
82
 
81
83
  preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source
@@ -177,7 +177,7 @@ module RuboCop
177
177
  BRACES_REQUIRED_MESSAGE = "Brace delimiters `{...}` required for '%<method_name>s' method."
178
178
 
179
179
  def self.autocorrect_incompatible_with
180
- [Style::RedundantBegin]
180
+ [Layout::SpaceBeforeBlockBraces, Style::MethodCallWithArgsParentheses, Style::RedundantBegin]
181
181
  end
182
182
 
183
183
  def on_send(node)
@@ -196,6 +196,8 @@ module RuboCop
196
196
  end
197
197
  end
198
198
  alias on_csend on_send
199
+ alias on_super on_send
200
+ alias on_yield on_send
199
201
 
200
202
  def on_block(node)
201
203
  return if part_of_ignored_node?(node)
@@ -292,7 +294,7 @@ module RuboCop
292
294
  corrector.replace(b, '{')
293
295
  corrector.replace(e, '}')
294
296
 
295
- corrector.wrap(node.body, "begin\n", "\nend") if begin_required?(node)
297
+ wrap_body_with_begin(corrector, node.body) if begin_required?(node)
296
298
  end
297
299
 
298
300
  def whitespace_before?(range)
@@ -372,6 +374,7 @@ module RuboCop
372
374
  # rubocop:disable-next Metrics/CyclomaticComplexity -- inlined special_method checks to avoid double evaluation
373
375
  def proper_block_style?(node)
374
376
  return true if require_do_end?(node)
377
+ return true if collection_of_for_loop?(node)
375
378
 
376
379
  method_name = node.method_name
377
380
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
@@ -398,6 +401,10 @@ module RuboCop
398
401
  !modifier_rescue?(body)
399
402
  end
400
403
 
404
+ def collection_of_for_loop?(node)
405
+ node.parent&.for_type? && node.parent.collection == node
406
+ end
407
+
401
408
  def modifier_rescue?(rescue_node)
402
409
  return false if rescue_node.body.nil? || rescue_node.else_branch
403
410
  return false unless rescue_node.resbody_branches.one?
@@ -438,11 +445,20 @@ module RuboCop
438
445
  end
439
446
 
440
447
  def correction_would_break_code?(node)
441
- return false unless node.keywords?
448
+ return loop_condition?(node) if node.braces?
442
449
 
443
450
  node.send_node.arguments? && !node.send_node.parenthesized?
444
451
  end
445
452
 
453
+ # A `do` in the condition of `while` or `until` is taken as the start of
454
+ # the loop body. Inside the body of another block it is unambiguous again.
455
+ def loop_condition?(node)
456
+ ancestors = node.each_ancestor.take_while { |ancestor| !ancestor.any_block_type? }
457
+ [node, *ancestors].each_cons(2).any? do |child, parent|
458
+ parent.type?(:while, :until) && parent.condition.equal?(child)
459
+ end
460
+ end
461
+
446
462
  def functional_method?(method_name)
447
463
  functional_methods.include?(method_name)
448
464
  end
@@ -490,6 +506,16 @@ module RuboCop
490
506
  node.type?(:array, :range)
491
507
  end
492
508
 
509
+ def wrap_body_with_begin(corrector, body)
510
+ heredoc_ends = body.each_node(:any_str).select(&:heredoc?).map do |str|
511
+ str.loc.heredoc_end
512
+ end
513
+ body_end = [body.source_range, *heredoc_ends].max_by(&:end_pos)
514
+
515
+ corrector.insert_before(body, "begin\n")
516
+ corrector.insert_after(body_end, "\nend")
517
+ end
518
+
493
519
  def begin_required?(block_node)
494
520
  # If the block contains `rescue` or `ensure`, it needs to be wrapped in
495
521
  # `begin`...`end` when changing `do-end` to `{}`.
@@ -54,6 +54,10 @@ module RuboCop
54
54
 
55
55
  MSG = 'Convert `if-elsif` to `case-when`.'
56
56
 
57
+ def self.autocorrect_incompatible_with
58
+ [Style::IfInsideElse]
59
+ end
60
+
57
61
  def on_if(node)
58
62
  return unless should_check?(node)
59
63
 
@@ -224,10 +228,9 @@ module RuboCop
224
228
  end
225
229
 
226
230
  def branch_conditions(node)
227
- conditions = []
228
- while node&.if_type? && !node.ternary?
231
+ conditions = [node.condition]
232
+ while (node = node.else_branch)&.if_type? && node.elsif?
229
233
  conditions << node.condition
230
- node = node.else_branch
231
234
  end
232
235
  conditions
233
236
  end
@@ -65,6 +65,12 @@ module RuboCop
65
65
  NESTED_MSG = 'Use nested module/class definitions instead of compact style.'
66
66
  COMPACT_MSG = 'Use compact module/class definition instead of nested style.'
67
67
 
68
+ # `Style/StructInheritance` turns a class definition into a constant assignment in
69
+ # the same pass, which the compacted `class` keyword would then precede.
70
+ def self.autocorrect_incompatible_with
71
+ [Style::StructInheritance]
72
+ end
73
+
68
74
  def on_class(node)
69
75
  return if node.parent_class && style_for_classes != :nested
70
76
 
@@ -117,7 +123,10 @@ module RuboCop
117
123
  end
118
124
 
119
125
  def heuristic_namespace_keyword(node)
120
- class_definition = node.left_sibling&.each_node(:class)&.find do |class_node|
126
+ left_sibling = node.left_sibling
127
+ return 'module' unless left_sibling.is_a?(AST::Node)
128
+
129
+ class_definition = left_sibling.each_node(:class).find do |class_node|
121
130
  class_node.identifier == node.identifier.namespace
122
131
  end
123
132
 
@@ -194,33 +203,62 @@ module RuboCop
194
203
  "#{node.body.children.first.const_name}"
195
204
  end
196
205
 
197
- # rubocop:disable-next Metrics/AbcSize
198
206
  def remove_end(corrector, body)
199
- remove_begin_pos = if same_line?(body.loc.name, body.loc.end)
200
- body.loc.name.end_pos
201
- else
202
- body.loc.end.begin_pos - leading_spaces(body).size
203
- end
204
- adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
205
- range = range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
207
+ range = if same_line?(body.loc.name, body.loc.end)
208
+ single_line_end_range(body)
209
+ else
210
+ multiline_end_range(body)
211
+ end
206
212
 
207
213
  corrector.remove(range)
208
214
  end
209
215
 
210
- def unindent(corrector, node)
211
- return unless node.body.children.last
216
+ def single_line_end_range(body)
217
+ raw = processed_source.raw_source
218
+ pos = body.loc.end.begin_pos
219
+ pos -= 1 while raw[pos - 1] == ' '
220
+ pos -= 1 if raw[pos - 1] == ';'
221
+ range_between(pos, body.loc.end.end_pos)
222
+ end
212
223
 
213
- last_child_leading_spaces = leading_spaces(node.body.children.last)
214
- return if spaces_size(leading_spaces(node)) == spaces_size(last_child_leading_spaces)
224
+ def multiline_end_range(body)
225
+ remove_begin_pos = body.loc.end.begin_pos - leading_spaces(body).size
226
+ adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
227
+ range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
228
+ end
215
229
 
216
- column_delta = configured_indentation_width - spaces_size(last_child_leading_spaces)
217
- return if column_delta.zero?
230
+ def unindent(corrector, node)
231
+ column_delta = unindent_column_delta(node)
232
+ return unless column_delta&.negative?
233
+
234
+ range = unindentable_range(node.body)
235
+ return unless range
218
236
 
219
237
  AlignmentCorrector.correct(
220
- corrector, processed_source, node, column_delta, tab_indentation: true
238
+ corrector, processed_source, range, column_delta, tab_indentation: true
221
239
  )
222
240
  end
223
241
 
242
+ def unindent_column_delta(node)
243
+ body = node.body.children.last
244
+ return unless body
245
+
246
+ node_indentation = spaces_size(leading_spaces(node))
247
+ body_indentation = spaces_size(leading_spaces(body))
248
+
249
+ node_indentation + configured_indentation_width - body_indentation
250
+ end
251
+
252
+ def unindentable_range(body)
253
+ first_line = body.loc.name.line + 1
254
+ last_line = body.loc.end.line - 1
255
+ return if first_line > last_line
256
+
257
+ buffer = processed_source.buffer
258
+ range_between(buffer.line_range(first_line).begin_pos,
259
+ buffer.line_range(last_line).end_pos)
260
+ end
261
+
224
262
  def leading_spaces(node)
225
263
  node.source_range.source_line[/\A\s*/]
226
264
  end
@@ -72,6 +72,7 @@ module RuboCop
72
72
  return unless def_self_style?
73
73
  return unless node.identifier.self_type?
74
74
  return unless all_methods_public?(node)
75
+ return unless def_nodes(node).all? { |def_node| no_code_after?(def_node.source_range) }
75
76
 
76
77
  add_offense(node, message: MSG_SCLASS) do |corrector|
77
78
  autocorrect_sclass(node, corrector)
@@ -112,6 +113,13 @@ module RuboCop
112
113
  end
113
114
  end
114
115
 
116
+ def no_code_after?(range)
117
+ trailing = processed_source.lines[range.last_line - 1][range.last_column..].to_s.strip
118
+ trailing = trailing.sub(/\A;+\s*/, '')
119
+
120
+ trailing.empty? || trailing.start_with?('#')
121
+ end
122
+
115
123
  def autocorrect_sclass(node, corrector)
116
124
  rewritten_defs = []
117
125