rubocop 1.84.2 → 1.87.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +106 -16
  3. data/config/obsoletion.yml +5 -0
  4. data/lib/rubocop/cache_config.rb +1 -1
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +28 -2
  6. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  7. data/lib/rubocop/cli/command/mcp.rb +19 -0
  8. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  9. data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
  10. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  11. data/lib/rubocop/cli.rb +9 -7
  12. data/lib/rubocop/comment_config.rb +12 -15
  13. data/lib/rubocop/config.rb +14 -10
  14. data/lib/rubocop/config_finder.rb +1 -1
  15. data/lib/rubocop/config_loader.rb +17 -2
  16. data/lib/rubocop/config_loader_resolver.rb +13 -4
  17. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  18. data/lib/rubocop/config_store.rb +2 -2
  19. data/lib/rubocop/config_validator.rb +1 -1
  20. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  21. data/lib/rubocop/cop/base.rb +8 -2
  22. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  23. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  24. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  25. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  26. data/lib/rubocop/cop/correctors.rb +28 -0
  27. data/lib/rubocop/cop/documentation.rb +2 -3
  28. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  29. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  30. data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
  31. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -3
  32. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  33. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  34. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  35. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  36. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  37. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  38. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  39. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  40. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +23 -7
  41. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  42. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  43. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  44. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  46. data/lib/rubocop/cop/layout/end_alignment.rb +8 -5
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  48. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  49. data/lib/rubocop/cop/layout/indentation_width.rb +12 -0
  50. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  51. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  52. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  53. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +53 -3
  54. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  55. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  56. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  57. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  58. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  59. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  60. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  61. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  62. data/lib/rubocop/cop/lint/constant_reassignment.rb +93 -11
  63. data/lib/rubocop/cop/lint/constant_resolution.rb +6 -6
  64. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  65. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
  66. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  67. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  68. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  69. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  70. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  71. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  72. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  73. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -1
  74. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  75. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  76. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +4 -2
  77. data/lib/rubocop/cop/lint/number_conversion.rb +6 -6
  78. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  79. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  80. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -11
  81. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  82. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
  83. data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
  84. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  85. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  86. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  87. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  88. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  89. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
  90. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  91. data/lib/rubocop/cop/lint/unreachable_code.rb +2 -2
  92. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  93. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  94. data/lib/rubocop/cop/lint/useless_assignment.rb +4 -9
  95. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  96. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  97. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
  98. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +35 -9
  99. data/lib/rubocop/cop/lint/void.rb +32 -12
  100. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  101. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  102. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  103. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  104. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  105. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  106. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  107. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  108. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  109. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  110. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  111. data/lib/rubocop/cop/mixin.rb +86 -0
  112. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  113. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  114. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/predicate_method.rb +2 -2
  116. data/lib/rubocop/cop/naming/predicate_prefix.rb +1 -1
  117. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  118. data/lib/rubocop/cop/offense.rb +8 -0
  119. data/lib/rubocop/cop/registry.rb +62 -38
  120. data/lib/rubocop/cop/security/eval.rb +15 -2
  121. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  122. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  123. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  124. data/lib/rubocop/cop/style/alias.rb +14 -2
  125. data/lib/rubocop/cop/style/and_or.rb +1 -0
  126. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  127. data/lib/rubocop/cop/style/array_join.rb +4 -2
  128. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  129. data/lib/rubocop/cop/style/attr.rb +5 -2
  130. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  131. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  132. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  133. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  134. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  135. data/lib/rubocop/cop/style/class_and_module_children.rb +18 -2
  136. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  137. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  138. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  139. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  140. data/lib/rubocop/cop/style/copyright.rb +22 -11
  141. data/lib/rubocop/cop/style/date_time.rb +2 -2
  142. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  143. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  144. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  145. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  146. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  147. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  148. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  149. data/lib/rubocop/cop/style/encoding.rb +7 -1
  150. data/lib/rubocop/cop/style/end_block.rb +3 -1
  151. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  152. data/lib/rubocop/cop/style/file_open.rb +84 -0
  153. data/lib/rubocop/cop/style/file_write.rb +18 -16
  154. data/lib/rubocop/cop/style/for.rb +3 -0
  155. data/lib/rubocop/cop/style/format_string.rb +4 -3
  156. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  157. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  158. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  160. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +19 -7
  162. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  163. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  164. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  166. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  167. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  168. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  169. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -3
  170. data/lib/rubocop/cop/style/map_join.rb +123 -0
  171. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  172. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  173. data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
  174. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  175. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  176. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  177. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  178. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  179. data/lib/rubocop/cop/style/not.rb +2 -0
  180. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  181. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  182. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  183. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  184. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  185. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  186. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  187. data/lib/rubocop/cop/style/proc.rb +3 -2
  188. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  189. data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
  190. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  191. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  192. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  193. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  194. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  195. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  196. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  197. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  198. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  199. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  200. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  201. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  202. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  203. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  204. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  205. data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
  206. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  207. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  208. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  209. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  210. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  211. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  212. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  213. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  214. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  215. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  216. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  217. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  218. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  219. data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
  220. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  221. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  222. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  223. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  224. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  225. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  226. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  227. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  228. data/lib/rubocop/cop/team.rb +86 -35
  229. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  230. data/lib/rubocop/directive_comment.rb +2 -1
  231. data/lib/rubocop/file_patterns.rb +9 -1
  232. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -2
  233. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  234. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  235. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  236. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  237. data/lib/rubocop/formatter.rb +22 -21
  238. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  239. data/lib/rubocop/lsp/routes.rb +10 -3
  240. data/lib/rubocop/lsp/runtime.rb +1 -2
  241. data/lib/rubocop/mcp/server.rb +200 -0
  242. data/lib/rubocop/options.rb +35 -4
  243. data/lib/rubocop/path_util.rb +14 -2
  244. data/lib/rubocop/plugin/loader.rb +1 -1
  245. data/lib/rubocop/project_index_loader.rb +66 -0
  246. data/lib/rubocop/result_cache.rb +22 -10
  247. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  248. data/lib/rubocop/rspec/shared_contexts.rb +32 -2
  249. data/lib/rubocop/runner.rb +124 -53
  250. data/lib/rubocop/server/cache.rb +5 -7
  251. data/lib/rubocop/server/core.rb +2 -0
  252. data/lib/rubocop/target_finder.rb +14 -7
  253. data/lib/rubocop/target_ruby.rb +18 -12
  254. data/lib/rubocop/version.rb +21 -3
  255. data/lib/rubocop.rb +22 -96
  256. metadata +27 -5
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for redundant `keyword_init` option for `Struct.new`.
7
+ #
8
+ # Since Ruby 3.2, `keyword_init` in `Struct.new` defaults to `nil` behavior.
9
+ # Therefore, this cop detects and autocorrects redundant `keyword_init: nil`
10
+ # and `keyword_init: true` in `Struct.new`.
11
+ #
12
+ # This cop is disabled by default because `keyword_init: true` is not purely
13
+ # redundant. It changes behavior in the following ways:
14
+ #
15
+ # - `Struct#keyword_init?` returns `true` instead of `nil`.
16
+ # - A `Struct` with `keyword_init: true` accepts a `Hash` argument and
17
+ # expands it as keyword arguments, whereas without it the `Hash` is
18
+ # treated as a positional argument.
19
+ # - `keyword_init: true` raises an `ArgumentError` for positional arguments,
20
+ # enforcing keyword-only initialization.
21
+ #
22
+ # @safety
23
+ # This autocorrect is unsafe because when the value of `keyword_init` changes
24
+ # from `true` to `nil`, the return value of `Struct#keyword_init?` changes.
25
+ #
26
+ # @example
27
+ #
28
+ # # bad
29
+ # Struct.new(:foo, keyword_init: nil)
30
+ # Struct.new(:foo, keyword_init: true)
31
+ #
32
+ # # good
33
+ # Struct.new(:foo)
34
+ #
35
+ class RedundantStructKeywordInit < Base
36
+ extend AutoCorrector
37
+ extend TargetRubyVersion
38
+
39
+ MSG = 'Remove the redundant `keyword_init: %<value>s`.'
40
+ RESTRICT_ON_SEND = %i[new].freeze
41
+
42
+ minimum_target_ruby_version 3.2
43
+
44
+ # @!method struct_new?(node)
45
+ def_node_matcher :struct_new?, <<~PATTERN
46
+ (call (const {nil? cbase} :Struct) :new ...)
47
+ PATTERN
48
+
49
+ # @!method keyword_init?(node)
50
+ def_node_matcher :keyword_init?, <<~PATTERN
51
+ {#redundant_keyword_init? #keyword_init_false?}
52
+ PATTERN
53
+
54
+ # @!method redundant_keyword_init?(node)
55
+ def_node_matcher :redundant_keyword_init?, <<~PATTERN
56
+ (pair (sym :keyword_init) {(true) (nil)})
57
+ PATTERN
58
+
59
+ # @!method keyword_init_false?(node)
60
+ def_node_matcher :keyword_init_false?, <<~PATTERN
61
+ (pair (sym :keyword_init) (false))
62
+ PATTERN
63
+
64
+ def on_send(node)
65
+ return if !struct_new?(node) || node.arguments.none? || !node.last_argument.hash_type?
66
+
67
+ keyword_init_nodes = select_keyword_init_nodes(node)
68
+ return if keyword_init_nodes.any? { |node| keyword_init_false?(node) }
69
+
70
+ redundant_keyword_init_nodes = select_redundant_keyword_init_nodes(keyword_init_nodes)
71
+
72
+ redundant_keyword_init_nodes.each do |redundant_keyword_init|
73
+ register_offense(redundant_keyword_init)
74
+ end
75
+ end
76
+ alias on_csend on_send
77
+
78
+ private
79
+
80
+ def select_keyword_init_nodes(node)
81
+ node.last_argument.pairs.select do |pair|
82
+ keyword_init?(pair)
83
+ end
84
+ end
85
+
86
+ def select_redundant_keyword_init_nodes(keyword_init_nodes)
87
+ keyword_init_nodes.select do |keyword_init_node|
88
+ redundant_keyword_init?(keyword_init_node)
89
+ end
90
+ end
91
+
92
+ def register_offense(keyword_init)
93
+ message = format(MSG, value: keyword_init.value.source)
94
+
95
+ add_offense(keyword_init, message: message) do |corrector|
96
+ range = range(keyword_init)
97
+
98
+ corrector.remove(range)
99
+ end
100
+ end
101
+
102
+ def range(redundant_keyword_init)
103
+ if redundant_keyword_init.parent.left_siblings.last.is_a?(AST::Node)
104
+ beginning_of_range = redundant_keyword_init.parent.left_siblings.last.source_range.end
105
+
106
+ beginning_of_range.join(redundant_keyword_init.source_range.end)
107
+ else
108
+ redundant_keyword_init
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces using `//` or `%r` around regular expressions.
7
7
  #
8
- # NOTE: The following `%r` cases using a regexp starts with a blank or `=`
9
- # as a method argument allowed to prevent syntax errors.
8
+ # NOTE: The following `%r` cases using a regexp that starts with a blank or `=`
9
+ # as a method argument are allowed to prevent syntax errors.
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -98,7 +98,16 @@ module RuboCop
98
98
  MSG_USE_SLASHES = 'Use `//` around regular expression.'
99
99
  MSG_USE_PERCENT_R = 'Use `%r` around regular expression.'
100
100
 
101
+ PAIR_DELIMITER_PATTERNS = {
102
+ ['(', ')'] => /\\.|[()]/,
103
+ ['[', ']'] => /\\.|[\[\]]/,
104
+ ['{', '}'] => /\\.|[{}]/,
105
+ ['<', '>'] => /\\.|[<>]/
106
+ }.freeze
107
+
101
108
  def on_regexp(node)
109
+ return if slash_literal?(node) && percent_r_delimiters_conflict?(node)
110
+
102
111
  message = if slash_literal?(node)
103
112
  MSG_USE_PERCENT_R unless allowed_slash_literal?(node)
104
113
  else
@@ -115,6 +124,26 @@ module RuboCop
115
124
 
116
125
  private
117
126
 
127
+ def percent_r_delimiters_conflict?(node)
128
+ opening, closing = preferred_delimiters
129
+ return false unless (pattern = PAIR_DELIMITER_PATTERNS[[opening, closing]])
130
+
131
+ !balanced_delimiters?(node_body(node), opening, closing, pattern)
132
+ end
133
+
134
+ def balanced_delimiters?(text, opening, closing, pattern)
135
+ depth = 0
136
+ text.scan(pattern) do |match|
137
+ if match == opening
138
+ depth += 1
139
+ elsif match == closing
140
+ depth -= 1
141
+ return false if depth.negative?
142
+ end
143
+ end
144
+ depth.zero?
145
+ end
146
+
118
147
  def allowed_slash_literal?(node)
119
148
  (style == :slashes && !contains_disallowed_slash?(node)) || allowed_mixed_slash?(node)
120
149
  end
@@ -3,17 +3,17 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of `rescue` in its modifier form is added for following
6
+ # Checks for uses of `rescue` in its modifier form. It is added for the following
7
7
  # reasons:
8
8
  #
9
9
  # * The syntax of modifier form `rescue` can be misleading because it
10
10
  # might lead us to believe that `rescue` handles the given exception
11
- # but it actually rescue all exceptions to return the given rescue
11
+ # but it actually rescues all exceptions to return the given rescue
12
12
  # block. In this case, value returned by handle_error or
13
13
  # SomeException.
14
14
  #
15
15
  # * Modifier form `rescue` would rescue all the exceptions. It would
16
- # silently skip all exception or errors and handle the error.
16
+ # silently skip all exceptions or errors and handle the error.
17
17
  # Example: If `NoMethodError` is raised, modifier form rescue would
18
18
  # handle the exception.
19
19
  #
@@ -367,13 +367,13 @@ module RuboCop
367
367
  def unsafe_method_used?(node, method_chain, method)
368
368
  return true if unsafe_method?(node, method)
369
369
 
370
- method.each_ancestor(:send).any? do |ancestor|
371
- break true unless config.cop_enabled?('Lint/SafeNavigationChain')
372
-
373
- break true if unsafe_method?(node, ancestor)
374
- break true if nil_methods.include?(ancestor.method_name)
375
- break false if ancestor == method_chain
370
+ method.each_ancestor(:send) do |ancestor|
371
+ return true unless config.cop_enabled?('Lint/SafeNavigationChain')
372
+ return true if unsafe_method?(node, ancestor)
373
+ return true if nil_methods.include?(ancestor.method_name)
374
+ return false if ancestor == method_chain
376
375
  end
376
+ false
377
377
  end
378
378
 
379
379
  def unsafe_method?(node, send_node)
@@ -409,7 +409,7 @@ module RuboCop
409
409
  start_method,
410
410
  method_chain)
411
411
  start_method.each_ancestor do |ancestor|
412
- break unless %i[send block].include?(ancestor.type)
412
+ break unless ancestor.type?(:call, :any_block)
413
413
  next if !ancestor.send_type? || ancestor.operator_method?
414
414
 
415
415
  corrector.insert_before(ancestor.loc.dot, '&')
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for places where a subset of an Enumerable (array,
7
+ # range, set, etc.; see note below) is calculated based on a class type
8
+ # check, and suggests `grep` or `grep_v` instead.
9
+ #
10
+ # NOTE: Hashes do not behave as you may expect with `grep`, which
11
+ # means that `hash.grep` is not equivalent to `hash.select`. Although
12
+ # RuboCop is limited by static analysis, this cop attempts to avoid
13
+ # registering an offense when the receiver is a hash (hash literal,
14
+ # `Hash.new`, `Hash#[]`, or `to_h`/`to_hash`).
15
+ #
16
+ # @safety
17
+ # Autocorrection is marked as unsafe because the cop cannot guarantee
18
+ # that the receiver is actually an array by static analysis, so the
19
+ # correction may not be actually equivalent.
20
+ #
21
+ # @example
22
+ # # bad (select or find_all)
23
+ # array.select { |x| x.is_a?(Foo) }
24
+ # array.select { |x| x.kind_of?(Foo) }
25
+ #
26
+ # # bad (reject)
27
+ # array.reject { |x| x.is_a?(Foo) }
28
+ #
29
+ # # bad (negative form)
30
+ # array.reject { |x| !x.is_a?(Foo) }
31
+ #
32
+ # # good
33
+ # array.grep(Foo)
34
+ # array.grep_v(Foo)
35
+ class SelectByKind < Base
36
+ extend AutoCorrector
37
+ include RangeHelp
38
+
39
+ MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a kind check.'
40
+ RESTRICT_ON_SEND = %i[select filter find_all reject].freeze
41
+ SELECT_METHODS = %i[select filter find_all].freeze
42
+ CLASS_CHECK_METHODS = %i[is_a? kind_of?].to_set.freeze
43
+
44
+ # @!method class_check?(node)
45
+ def_node_matcher :class_check?, <<~PATTERN
46
+ {
47
+ (block call (args (arg $_)) ${(send (lvar _) %CLASS_CHECK_METHODS _)})
48
+ (block call (args (arg $_)) ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
49
+ (numblock call $1 ${(send (lvar _) %CLASS_CHECK_METHODS _)})
50
+ (numblock call $1 ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
51
+ (itblock call $_ ${(send (lvar _) %CLASS_CHECK_METHODS _)})
52
+ (itblock call $_ ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
53
+ }
54
+ PATTERN
55
+
56
+ # Returns true if a node appears to return a hash
57
+ # @!method creates_hash?(node)
58
+ def_node_matcher :creates_hash?, <<~PATTERN
59
+ {
60
+ (call (const _ :Hash) {:new :[]} ...)
61
+ (block (call (const _ :Hash) :new ...) ...)
62
+ (call _ { :to_h :to_hash } ...)
63
+ }
64
+ PATTERN
65
+
66
+ # @!method env_const?(node)
67
+ def_node_matcher :env_const?, <<~PATTERN
68
+ (const {nil? cbase} :ENV)
69
+ PATTERN
70
+
71
+ # @!method calls_lvar?(node, name)
72
+ def_node_matcher :calls_lvar?, <<~PATTERN
73
+ (send (lvar %1) %CLASS_CHECK_METHODS _)
74
+ PATTERN
75
+
76
+ # @!method negated_calls_lvar?(node, name)
77
+ def_node_matcher :negated_calls_lvar?, <<~PATTERN
78
+ (send (send (lvar %1) %CLASS_CHECK_METHODS _) :!)
79
+ PATTERN
80
+
81
+ def on_send(node)
82
+ return unless (block_node = node.block_node)
83
+ return if block_node.body&.begin_type?
84
+ return if receiver_allowed?(block_node.receiver)
85
+ return unless (class_check_send_node = extract_send_node(block_node))
86
+
87
+ replacement = replacement(class_check_send_node, node)
88
+ class_constant = find_class_constant(class_check_send_node)
89
+
90
+ register_offense(node, block_node, class_constant, replacement)
91
+ end
92
+ alias on_csend on_send
93
+
94
+ private
95
+
96
+ def receiver_allowed?(node)
97
+ return false unless node
98
+
99
+ node.hash_type? || creates_hash?(node) || env_const?(node)
100
+ end
101
+
102
+ def replacement(class_check_send_node, node)
103
+ negated = negated?(class_check_send_node)
104
+
105
+ method_name = node.method_name
106
+
107
+ if SELECT_METHODS.include?(method_name)
108
+ negated ? 'grep_v' : 'grep'
109
+ else # reject
110
+ negated ? 'grep' : 'grep_v'
111
+ end
112
+ end
113
+
114
+ def register_offense(node, block_node, class_constant, replacement)
115
+ message = format(MSG, replacement: replacement, original_method: node.method_name)
116
+
117
+ add_offense(block_node, message: message) do |corrector|
118
+ if class_constant
119
+ range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
120
+ corrector.replace(range, "#{replacement}(#{class_constant.source})")
121
+ end
122
+ end
123
+ end
124
+
125
+ def extract_send_node(block_node)
126
+ return unless (block_arg_name, class_check_send_node = class_check?(block_node))
127
+
128
+ block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
129
+ block_arg_name = :it if block_node.type?(:itblock)
130
+
131
+ inner_node = unwrap_negation(class_check_send_node)
132
+
133
+ if calls_lvar?(inner_node, block_arg_name) ||
134
+ negated_calls_lvar?(class_check_send_node, block_arg_name)
135
+ class_check_send_node
136
+ end
137
+ end
138
+
139
+ def negated?(class_check_send_node)
140
+ class_check_send_node.send_type? && class_check_send_node.method?(:!)
141
+ end
142
+
143
+ def unwrap_negation(node)
144
+ if node.send_type? && node.method?(:!)
145
+ node.receiver
146
+ else
147
+ node
148
+ end
149
+ end
150
+
151
+ def find_class_constant(node)
152
+ inner_node = unwrap_negation(node)
153
+ inner_node.first_argument if inner_node.send_type?
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for places where a subset of an Enumerable (array,
7
+ # range, set, etc.; see note below) is calculated based on a range
8
+ # check, and suggests `grep` or `grep_v` instead.
9
+ #
10
+ # NOTE: Hashes do not behave as you may expect with `grep`, which
11
+ # means that `hash.grep` is not equivalent to `hash.select`. Although
12
+ # RuboCop is limited by static analysis, this cop attempts to avoid
13
+ # registering an offense when the receiver is a hash (hash literal,
14
+ # `Hash.new`, `Hash#[]`, or `to_h`/`to_hash`).
15
+ #
16
+ # @safety
17
+ # Autocorrection is marked as unsafe because the cop cannot guarantee
18
+ # that the receiver is actually an array by static analysis, so the
19
+ # correction may not be actually equivalent.
20
+ #
21
+ # @example
22
+ # # bad (select or find_all)
23
+ # array.select { |x| x.between?(1, 10) }
24
+ # array.select { |x| (1..10).cover?(x) }
25
+ # array.select { |x| (1..10).include?(x) }
26
+ #
27
+ # # bad (reject)
28
+ # array.reject { |x| x.between?(1, 10) }
29
+ #
30
+ # # bad (find or detect)
31
+ # array.find { |x| x.between?(1, 10) }
32
+ # array.detect { |x| (1..10).cover?(x) }
33
+ #
34
+ # # bad (negative form)
35
+ # array.reject { |x| !x.between?(1, 10) }
36
+ # array.find { |x| !(1..10).cover?(x) }
37
+ #
38
+ # # good
39
+ # array.grep(1..10)
40
+ # array.grep_v(1..10)
41
+ # array.grep(1..10).first
42
+ # array.grep_v(1..10).first
43
+ class SelectByRange < Base
44
+ extend AutoCorrector
45
+ include RangeHelp
46
+
47
+ MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a range check.'
48
+ RESTRICT_ON_SEND = %i[select filter find_all reject find detect].freeze
49
+ SELECT_METHODS = %i[select filter find_all].freeze
50
+ FIND_METHODS = %i[find detect].freeze
51
+
52
+ # @!method range_check?(node)
53
+ # Matches: x.between?(min, max) or (min..max).cover?(x) or (min..max).include?(x)
54
+ def_node_matcher :range_check?, <<~PATTERN
55
+ {
56
+ (block call (args (arg $_)) ${(send (lvar _) :between? _ _)})
57
+ (block call (args (arg $_)) ${(send {range (begin range)} {:cover? :include?} (lvar _))})
58
+ (block call (args (arg $_)) ${(send (send (lvar _) :between? _ _) :!)})
59
+ (block call (args (arg $_)) ${(send (send {range (begin range)} {:cover? :include?} (lvar _)) :!)})
60
+ (block call (args (arg $_)) ${(send (begin (send (lvar _) :between? _ _)) :!)})
61
+ (block call (args (arg $_)) ${(send (begin (send {range (begin range)} {:cover? :include?} (lvar _))) :!)})
62
+ (numblock call $1 ${(send (lvar _) :between? _ _)})
63
+ (numblock call $1 ${(send {range (begin range)} {:cover? :include?} (lvar _))})
64
+ (numblock call $1 ${(send (send (lvar _) :between? _ _) :!)})
65
+ (numblock call $1 ${(send (send {range (begin range)} {:cover? :include?} (lvar _)) :!)})
66
+ (numblock call $1 ${(send (begin (send (lvar _) :between? _ _)) :!)})
67
+ (numblock call $1 ${(send (begin (send {range (begin range)} {:cover? :include?} (lvar _))) :!)})
68
+ (itblock call $_ ${(send (lvar _) :between? _ _)})
69
+ (itblock call $_ ${(send {range (begin range)} {:cover? :include?} (lvar _))})
70
+ (itblock call $_ ${(send (send (lvar _) :between? _ _) :!)})
71
+ (itblock call $_ ${(send (send {range (begin range)} {:cover? :include?} (lvar _)) :!)})
72
+ (itblock call $_ ${(send (begin (send (lvar _) :between? _ _)) :!)})
73
+ (itblock call $_ ${(send (begin (send {range (begin range)} {:cover? :include?} (lvar _))) :!)})
74
+ }
75
+ PATTERN
76
+
77
+ # Returns true if a node appears to return a hash
78
+ # @!method creates_hash?(node)
79
+ def_node_matcher :creates_hash?, <<~PATTERN
80
+ {
81
+ (call (const _ :Hash) {:new :[]} ...)
82
+ (block (call (const _ :Hash) :new ...) ...)
83
+ (call _ { :to_h :to_hash } ...)
84
+ }
85
+ PATTERN
86
+
87
+ # @!method env_const?(node)
88
+ def_node_matcher :env_const?, <<~PATTERN
89
+ (const {nil? cbase} :ENV)
90
+ PATTERN
91
+
92
+ # @!method between_call?(node, name)
93
+ def_node_matcher :between_call?, <<~PATTERN
94
+ (send (lvar %1) :between? _ _)
95
+ PATTERN
96
+
97
+ # @!method range_cover_call?(node, name)
98
+ def_node_matcher :range_cover_call?, <<~PATTERN
99
+ (send {range (begin range)} {:cover? :include?} (lvar %1))
100
+ PATTERN
101
+
102
+ def on_send(node)
103
+ return unless (block_node = node.block_node)
104
+ return if block_node.body&.begin_type?
105
+ return if receiver_allowed?(block_node.receiver)
106
+ return unless (range_check_send_node = extract_send_node(block_node))
107
+
108
+ replacement = replacement(range_check_send_node, node)
109
+ range_literal = find_range(range_check_send_node)
110
+
111
+ register_offense(node, block_node, range_literal, replacement)
112
+ end
113
+ alias on_csend on_send
114
+
115
+ private
116
+
117
+ def receiver_allowed?(node)
118
+ return false unless node
119
+
120
+ node.hash_type? || creates_hash?(node) || env_const?(node)
121
+ end
122
+
123
+ def replacement(range_check_send_node, node)
124
+ negated = negated?(range_check_send_node)
125
+ method_name = node.method_name
126
+
127
+ if SELECT_METHODS.include?(method_name)
128
+ negated ? 'grep_v' : 'grep'
129
+ elsif FIND_METHODS.include?(method_name)
130
+ negated ? 'grep_v(...).first' : 'grep(...).first'
131
+ else # reject
132
+ negated ? 'grep' : 'grep_v'
133
+ end
134
+ end
135
+
136
+ def register_offense(node, block_node, range_literal, replacement)
137
+ message = format(MSG, replacement: replacement, original_method: node.method_name)
138
+
139
+ add_offense(block_node, message: message) do |corrector|
140
+ if range_literal
141
+ range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
142
+ grep_method = replacement.include?('grep_v') ? 'grep_v' : 'grep'
143
+ suffix = replacement.include?('.first') ? '.first' : ''
144
+ corrector.replace(range, "#{grep_method}(#{range_literal})#{suffix}")
145
+ end
146
+ end
147
+ end
148
+
149
+ def extract_send_node(block_node)
150
+ return unless (block_arg_name, range_check_send_node = range_check?(block_node))
151
+
152
+ block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
153
+ block_arg_name = :it if block_node.type?(:itblock)
154
+
155
+ inner_node = unwrap_negation(range_check_send_node)
156
+
157
+ range_check_send_node if calls_lvar_in_range_check?(inner_node, block_arg_name)
158
+ end
159
+
160
+ def calls_lvar_in_range_check?(node, block_arg_name)
161
+ between_call?(node, block_arg_name) || range_cover_call?(node, block_arg_name)
162
+ end
163
+
164
+ def negated?(range_check_send_node)
165
+ range_check_send_node.send_type? && range_check_send_node.method?(:!)
166
+ end
167
+
168
+ def unwrap_negation(node)
169
+ if node.send_type? && node.method?(:!)
170
+ receiver = node.receiver
171
+ receiver = receiver.children.first if receiver.begin_type?
172
+ receiver
173
+ else
174
+ node
175
+ end
176
+ end
177
+
178
+ def find_range(node)
179
+ inner = unwrap_negation(node)
180
+
181
+ if inner.method?(:between?)
182
+ # x.between?(min, max) -> min..max
183
+ min = inner.first_argument.source
184
+ max = inner.arguments[1].source
185
+ "#{min}..#{max}"
186
+ else
187
+ # (min..max).cover?(x) or (min..max).include?(x)
188
+ receiver = inner.receiver
189
+ # Unwrap begin node from parentheses
190
+ receiver = receiver.children.first if receiver.begin_type?
191
+ receiver.source
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end