rubocop 1.84.2 → 1.86.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +91 -15
  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 +1 -1
  6. data/lib/rubocop/cli/command/mcp.rb +19 -0
  7. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  8. data/lib/rubocop/cli/command/show_docs_url.rb +1 -1
  9. data/lib/rubocop/cli.rb +6 -3
  10. data/lib/rubocop/config.rb +14 -10
  11. data/lib/rubocop/config_finder.rb +1 -1
  12. data/lib/rubocop/config_loader_resolver.rb +2 -1
  13. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  14. data/lib/rubocop/config_store.rb +1 -1
  15. data/lib/rubocop/config_validator.rb +1 -1
  16. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  17. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  18. data/lib/rubocop/cop/documentation.rb +2 -3
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  20. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  23. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  26. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  27. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  28. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  29. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  30. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  31. data/lib/rubocop/cop/layout/end_alignment.rb +6 -3
  32. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  33. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  34. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  35. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  36. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  37. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  38. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  40. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  42. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  43. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  44. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  45. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  46. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  47. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  48. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  49. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  50. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  51. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  52. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  53. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  54. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  56. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  57. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  58. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  59. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  60. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  61. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  62. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  63. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  64. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  66. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  67. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +22 -7
  68. data/lib/rubocop/cop/lint/void.rb +32 -12
  69. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  70. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  71. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  72. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  73. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  74. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  75. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  76. data/lib/rubocop/cop/registry.rb +20 -13
  77. data/lib/rubocop/cop/security/eval.rb +15 -2
  78. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  79. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  80. data/lib/rubocop/cop/style/alias.rb +4 -1
  81. data/lib/rubocop/cop/style/and_or.rb +1 -0
  82. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  83. data/lib/rubocop/cop/style/array_join.rb +4 -2
  84. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  85. data/lib/rubocop/cop/style/attr.rb +5 -2
  86. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  87. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  88. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  89. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  90. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  91. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  92. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  93. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  94. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  95. data/lib/rubocop/cop/style/copyright.rb +1 -1
  96. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  97. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  98. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  99. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  100. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  101. data/lib/rubocop/cop/style/encoding.rb +7 -1
  102. data/lib/rubocop/cop/style/end_block.rb +3 -1
  103. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  104. data/lib/rubocop/cop/style/file_open.rb +84 -0
  105. data/lib/rubocop/cop/style/for.rb +3 -0
  106. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  107. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  108. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  109. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  110. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  111. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  112. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  113. data/lib/rubocop/cop/style/if_inside_else.rb +1 -5
  114. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  115. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  116. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  117. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  118. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  119. data/lib/rubocop/cop/style/map_join.rb +123 -0
  120. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  121. data/lib/rubocop/cop/style/module_member_existence_check.rb +1 -11
  122. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  123. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  124. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  125. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  126. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  127. data/lib/rubocop/cop/style/not.rb +2 -0
  128. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  129. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  130. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  131. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  132. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  133. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  134. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  135. data/lib/rubocop/cop/style/proc.rb +3 -2
  136. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  137. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  138. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  139. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  140. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  141. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  142. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  143. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  144. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  145. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  146. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  147. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  148. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  149. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  150. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  151. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  152. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  153. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  154. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  155. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  156. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  157. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  158. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  159. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  160. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  161. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  162. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  163. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  164. data/lib/rubocop/directive_comment.rb +2 -1
  165. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  166. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  167. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  168. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  169. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  170. data/lib/rubocop/formatter.rb +22 -21
  171. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  172. data/lib/rubocop/lsp/routes.rb +10 -3
  173. data/lib/rubocop/mcp/server.rb +200 -0
  174. data/lib/rubocop/options.rb +10 -1
  175. data/lib/rubocop/path_util.rb +14 -2
  176. data/lib/rubocop/plugin/loader.rb +1 -1
  177. data/lib/rubocop/result_cache.rb +22 -10
  178. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  179. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  180. data/lib/rubocop/runner.rb +8 -3
  181. data/lib/rubocop/server/cache.rb +5 -7
  182. data/lib/rubocop/server/core.rb +2 -0
  183. data/lib/rubocop/target_finder.rb +1 -1
  184. data/lib/rubocop/target_ruby.rb +18 -12
  185. data/lib/rubocop/version.rb +2 -2
  186. data/lib/rubocop.rb +14 -0
  187. metadata +22 -5
@@ -3,7 +3,10 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Looks for uses of global variables.
6
+ # Looks for uses of global variables. Global variables introduce
7
+ # shared mutable state that makes code harder to test, debug,
8
+ # and reason about, since any part of the program can read or modify them.
9
+ #
7
10
  # It does not report offenses for built-in global variables.
8
11
  # Built-in global variables are allowed by default. Additionally
9
12
  # users can allow additional variables via the AllowedVariables option.
@@ -54,7 +57,7 @@ module RuboCop
54
57
  ].map(&:to_sym)
55
58
 
56
59
  def user_vars
57
- cop_config['AllowedVariables'].map(&:to_sym)
60
+ @user_vars ||= cop_config['AllowedVariables'].map(&:to_sym).freeze
58
61
  end
59
62
 
60
63
  def allowed_var?(global_var)
@@ -224,15 +224,18 @@ module RuboCop
224
224
  end
225
225
 
226
226
  def find_heredoc_argument(node)
227
- return unless node&.call_type?
227
+ return unless node
228
228
 
229
- last_arg = node.last_argument
229
+ node = node.children.first while node.begin_type?
230
+ return node if heredoc?(node)
231
+ return unless node.call_type?
230
232
 
231
- if heredoc?(last_arg)
232
- last_arg
233
- elsif last_arg&.call_type?
234
- find_heredoc_argument(last_arg)
233
+ node.arguments.reverse_each do |argument|
234
+ heredoc_argument = find_heredoc_argument(argument)
235
+ return heredoc_argument if heredoc_argument
235
236
  end
237
+
238
+ find_heredoc_argument(node.receiver)
236
239
  end
237
240
 
238
241
  def autocorrect_heredoc_argument(corrector, node, heredoc_node, leave_branch, guard)
@@ -6,8 +6,13 @@ module RuboCop
6
6
  # Checks for presence or absence of braces around hash literal as a last
7
7
  # array item depending on configuration.
8
8
  #
9
- # NOTE: This cop will ignore arrays where all items are hashes, regardless of
10
- # EnforcedStyle.
9
+ # NOTE: This cop will ignore arrays where multiple items are all hashes,
10
+ # regardless of `EnforcedStyle`.
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # [{ one: 1 }, { two: 2 }]
15
+ # ----
11
16
  #
12
17
  # @example EnforcedStyle: braces (default)
13
18
  # # bad
@@ -16,8 +21,11 @@ module RuboCop
16
21
  # # good
17
22
  # [1, 2, { one: 1, two: 2 }]
18
23
  #
24
+ # # bad
25
+ # [one: 1, two: 2]
26
+ #
19
27
  # # good
20
- # [{ one: 1 }, { two: 2 }]
28
+ # [{ one: 1, two: 2 }]
21
29
  #
22
30
  # @example EnforcedStyle: no_braces
23
31
  # # bad
@@ -26,8 +34,11 @@ module RuboCop
26
34
  # # good
27
35
  # [1, 2, one: 1, two: 2]
28
36
  #
37
+ # # bad
38
+ # [{ one: 1, two: 2 }]
39
+ #
29
40
  # # good
30
- # [{ one: 1 }, { two: 2 }]
41
+ # [one: 1, two: 2]
31
42
  class HashAsLastArrayItem < Base
32
43
  include RangeHelp
33
44
  include ConfigurableEnforcedStyle
@@ -69,7 +80,12 @@ module RuboCop
69
80
  return if node.braces?
70
81
 
71
82
  add_offense(node, message: 'Wrap hash in `{` and `}`.') do |corrector|
72
- corrector.wrap(node, '{', '}')
83
+ if node.single_line? || same_line?(node, node.parent)
84
+ corrector.wrap(node, '{', '}')
85
+ else
86
+ indent = indent(node)
87
+ corrector.wrap(node, "{\n#{indent}", "\n#{indent}}")
88
+ end
73
89
  end
74
90
  end
75
91
 
@@ -41,8 +41,13 @@ module RuboCop
41
41
  # # good
42
42
  # hash.fetch(key)
43
43
  #
44
+ # @example AllowedReceivers: ['Rails.cache']
45
+ # # good
46
+ # Rails.cache.fetch(name, options) { block }
47
+ #
44
48
  class HashLookupMethod < Base
45
49
  include ConfigurableEnforcedStyle
50
+ include AllowedReceivers
46
51
  extend AutoCorrector
47
52
 
48
53
  BRACKET_MSG = 'Use `Hash#[]` instead of `Hash#fetch`.'
@@ -51,6 +56,8 @@ module RuboCop
51
56
  RESTRICT_ON_SEND = %i[[] fetch].freeze
52
57
 
53
58
  def on_send(node)
59
+ return if (receiver = node.receiver) && allowed_receiver?(receiver)
60
+
54
61
  if offense_for_brackets?(node)
55
62
  add_offense(node.loc.selector, message: BRACKET_MSG) do |corrector|
56
63
  correct_fetch_to_brackets(corrector, node)
@@ -11,9 +11,11 @@ module RuboCop
11
11
  # (`transform_keys` was added in Ruby 2.5.)
12
12
  #
13
13
  # @safety
14
- # This cop is unsafe, as it can produce false positives if we are
15
- # transforming an enumerable of key-value-like pairs that isn't actually
16
- # a hash, e.g.: `[[k1, v1], [k2, v2], ...]`
14
+ # This cop identifies the receiver as a hash by checking for literal hash
15
+ # syntax and common methods that are known to return hashes (e.g. `to_h`,
16
+ # `merge`, `invert`, `group_by`, etc.). However, it is unsafe because it
17
+ # is possible for a custom class to define one of these methods and return
18
+ # something other than a hash.
17
19
  #
18
20
  # @example
19
21
  # # bad
@@ -21,10 +23,18 @@ module RuboCop
21
23
  # Hash[{a: 1, b: 2}.collect { |k, v| [foo(k), v] }]
22
24
  # {a: 1, b: 2}.map { |k, v| [k.to_s, v] }.to_h
23
25
  # {a: 1, b: 2}.to_h { |k, v| [k.to_s, v] }
26
+ # foo.to_h.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
27
+ # foo.merge(bar).map { |k, v| [k.to_s, v] }.to_h
24
28
  #
25
29
  # # good
26
30
  # {a: 1, b: 2}.transform_keys { |k| foo(k) }
27
31
  # {a: 1, b: 2}.transform_keys { |k| k.to_s }
32
+ # foo.to_h.transform_keys { |k| k.to_sym }
33
+ # foo.merge(bar).transform_keys { |k| k.to_s }
34
+ #
35
+ # # Won't register an offense - receiver is not known to be a hash
36
+ # foo.bar.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
37
+ # baz.map { |k, v| [k.to_s, v] }.to_h
28
38
  class HashTransformKeys < Base
29
39
  include HashTransformMethod
30
40
  extend AutoCorrector
@@ -35,7 +45,7 @@ module RuboCop
35
45
  # @!method on_bad_each_with_object(node)
36
46
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
37
47
  (block
38
- (call !#array_receiver? :each_with_object (hash))
48
+ (call #hash_receiver? :each_with_object (hash))
39
49
  (args
40
50
  (mlhs
41
51
  (arg $_)
@@ -50,7 +60,7 @@ module RuboCop
50
60
  (const _ :Hash)
51
61
  :[]
52
62
  (block
53
- (call !#array_receiver? {:map :collect})
63
+ (call #hash_receiver? {:map :collect})
54
64
  (args
55
65
  (arg $_)
56
66
  (arg _val))
@@ -61,7 +71,7 @@ module RuboCop
61
71
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
62
72
  (call
63
73
  (block
64
- (call !#array_receiver? {:map :collect})
74
+ (call #hash_receiver? {:map :collect})
65
75
  (args
66
76
  (arg $_)
67
77
  (arg _val))
@@ -72,7 +82,7 @@ module RuboCop
72
82
  # @!method on_bad_to_h(node)
73
83
  def_node_matcher :on_bad_to_h, <<~PATTERN
74
84
  (block
75
- (call !#array_receiver? :to_h)
85
+ (call #hash_receiver? :to_h)
76
86
  (args
77
87
  (arg $_)
78
88
  (arg _val))
@@ -9,9 +9,11 @@ module RuboCop
9
9
  # call to `transform_values` instead.
10
10
  #
11
11
  # @safety
12
- # This cop is unsafe, as it can produce false positives if we are
13
- # transforming an enumerable of key-value-like pairs that isn't actually
14
- # a hash, e.g.: `[[k1, v1], [k2, v2], ...]`
12
+ # This cop identifies the receiver as a hash by checking for literal hash
13
+ # syntax and common methods that are known to return hashes (e.g. `to_h`,
14
+ # `merge`, `invert`, `group_by`, etc.). However, it is unsafe because it
15
+ # is possible for a custom class to define one of these methods and return
16
+ # something other than a hash.
15
17
  #
16
18
  # @example
17
19
  # # bad
@@ -19,10 +21,18 @@ module RuboCop
19
21
  # Hash[{a: 1, b: 2}.collect { |k, v| [k, foo(v)] }]
20
22
  # {a: 1, b: 2}.map { |k, v| [k, v * v] }.to_h
21
23
  # {a: 1, b: 2}.to_h { |k, v| [k, v * v] }
24
+ # foo.to_h.each_with_object({}) { |(k, v), h| h[k] = foo(v) }
25
+ # foo.merge(bar).map { |k, v| [k, v.to_s] }.to_h
22
26
  #
23
27
  # # good
24
28
  # {a: 1, b: 2}.transform_values { |v| foo(v) }
25
29
  # {a: 1, b: 2}.transform_values { |v| v * v }
30
+ # foo.to_h.transform_values { |v| foo(v) }
31
+ # foo.merge(bar).transform_values { |v| v.to_s }
32
+ #
33
+ # # Won't register an offense - receiver is not known to be a hash
34
+ # foo.bar.each_with_object({}) { |(k, v), h| h[k] = v.to_s }
35
+ # baz.map { |k, v| [k, v.to_s] }.to_h
26
36
  class HashTransformValues < Base
27
37
  include HashTransformMethod
28
38
  extend AutoCorrector
@@ -33,7 +43,7 @@ module RuboCop
33
43
  # @!method on_bad_each_with_object(node)
34
44
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
35
45
  (block
36
- (call !#array_receiver? :each_with_object (hash))
46
+ (call #hash_receiver? :each_with_object (hash))
37
47
  (args
38
48
  (mlhs
39
49
  (arg _key)
@@ -48,7 +58,7 @@ module RuboCop
48
58
  (const _ :Hash)
49
59
  :[]
50
60
  (block
51
- (call !#array_receiver? {:map :collect})
61
+ (call #hash_receiver? {:map :collect})
52
62
  (args
53
63
  (arg _key)
54
64
  (arg $_))
@@ -59,7 +69,7 @@ module RuboCop
59
69
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
60
70
  (call
61
71
  (block
62
- (call !#array_receiver? {:map :collect})
72
+ (call #hash_receiver? {:map :collect})
63
73
  (args
64
74
  (arg _key)
65
75
  (arg $_))
@@ -70,7 +80,7 @@ module RuboCop
70
80
  # @!method on_bad_to_h(node)
71
81
  def_node_matcher :on_bad_to_h, <<~PATTERN
72
82
  (block
73
- (call !#array_receiver? :to_h)
83
+ (call #hash_receiver? :to_h)
74
84
  (args
75
85
  (arg _key)
76
86
  (arg $_))
@@ -85,7 +85,7 @@ module RuboCop
85
85
  private
86
86
 
87
87
  def autocorrect(corrector, node)
88
- if then?(node)
88
+ if node.then?
89
89
  # If the nested `if` is a then node, correct it first,
90
90
  # then the next pass will use `correct_to_elsif_from_if_inside_else_form`
91
91
  IfThenCorrector.new(node, indentation: 0).call(corrector)
@@ -124,10 +124,6 @@ module RuboCop
124
124
  corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
125
125
  end
126
126
 
127
- def then?(node)
128
- node.loc.begin&.source == 'then'
129
- end
130
-
131
127
  def find_end_range(node)
132
128
  end_range = node.loc.end
133
129
  return end_range if end_range
@@ -89,9 +89,9 @@ module RuboCop
89
89
  [Style::SoleNestedConditional]
90
90
  end
91
91
 
92
- # rubocop:disable Metrics/AbcSize
92
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
93
93
  def on_if(node)
94
- return if endless_method?(node.body)
94
+ return if endless_method?(node.body) || node.ancestors.any?(&:dstr_type?)
95
95
 
96
96
  condition = node.condition
97
97
  return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
@@ -101,12 +101,13 @@ module RuboCop
101
101
 
102
102
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
103
103
  next if part_of_ignored_node?(node)
104
+ next if another_modifier_if_on_same_line?(node)
104
105
 
105
106
  autocorrect(corrector, node)
106
107
  ignore_node(node)
107
108
  end
108
109
  end
109
- # rubocop:enable Metrics/AbcSize
110
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
111
 
111
112
  private
112
113
 
@@ -280,6 +281,16 @@ module RuboCop
280
281
  node.parent if node&.type?(:pair)
281
282
  end
282
283
 
284
+ def another_modifier_if_on_same_line?(node)
285
+ collection = find_containing_collection(node)
286
+ return false unless collection
287
+
288
+ line = node.source_range.line
289
+ collection.each_descendant(:if).any? do |sibling|
290
+ sibling != node && sibling.modifier_form? && sibling.source_range.line == line
291
+ end
292
+ end
293
+
283
294
  def non_simple_if_unless?(node)
284
295
  node.ternary? || node.elsif? || node.else?
285
296
  end
@@ -17,9 +17,9 @@ module RuboCop
17
17
  include OnNormalIfUnless
18
18
  extend AutoCorrector
19
19
 
20
- MSG_IF_ELSE = 'Do not use `if %<expr>s;` - use `if/else` instead.'
21
- MSG_NEWLINE = 'Do not use `if %<expr>s;` - use a newline instead.'
22
- MSG_TERNARY = 'Do not use `if %<expr>s;` - use a ternary operator instead.'
20
+ MSG_IF_ELSE = 'Do not use `%<keyword>s %<expr>s;` - use `if/else` instead.'
21
+ MSG_NEWLINE = 'Do not use `%<keyword>s %<expr>s;` - use a newline instead.'
22
+ MSG_TERNARY = 'Do not use `%<keyword>s %<expr>s;` - use a ternary operator instead.'
23
23
 
24
24
  def on_normal_if_unless(node)
25
25
  return if node.parent&.if_type?
@@ -49,7 +49,7 @@ module RuboCop
49
49
  MSG_TERNARY
50
50
  end
51
51
 
52
- format(template, expr: node.condition.source)
52
+ format(template, keyword: node.keyword, expr: node.condition.source)
53
53
  end
54
54
 
55
55
  def autocorrect(corrector, node)
@@ -71,7 +71,7 @@ module RuboCop
71
71
  end
72
72
 
73
73
  def use_return_with_argument?(node)
74
- node.if_branch&.return_type? && node.if_branch&.arguments&.any?
74
+ node.branches.compact.any? { |branch| branch.return_type? && branch.arguments.any? }
75
75
  end
76
76
 
77
77
  def replacement(node)
@@ -80,6 +80,8 @@ module RuboCop
80
80
  then_code = node.if_branch ? build_expression(node.if_branch) : 'nil'
81
81
  else_code = node.else_branch ? build_expression(node.else_branch) : 'nil'
82
82
 
83
+ then_code, else_code = else_code, then_code if node.unless?
84
+
83
85
  "#{node.condition.source} ? #{then_code} : #{else_code}"
84
86
  end
85
87
 
@@ -3,7 +3,10 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for trailing inline comments.
6
+ # Checks for trailing inline comments. Inline comments can
7
+ # make lines harder to read, especially when they are long.
8
+ # Placing comments on their own line above the code they
9
+ # describe is often clearer.
7
10
  #
8
11
  # @example
9
12
  #
@@ -48,8 +48,7 @@ module RuboCop
48
48
  private
49
49
 
50
50
  def allowed_addresses
51
- allowed_addresses = cop_config['AllowedAddresses']
52
- Array(allowed_addresses).map(&:downcase)
51
+ @allowed_addresses ||= Array(cop_config['AllowedAddresses']).map(&:downcase).freeze
53
52
  end
54
53
 
55
54
  def potential_ip?(str)
@@ -176,7 +176,7 @@ module RuboCop
176
176
  if first_non_comment_token
177
177
  0...first_non_comment_token.line
178
178
  else
179
- (0..)
179
+ 0..
180
180
  end
181
181
  end
182
182
 
@@ -299,7 +299,7 @@ module RuboCop
299
299
  end
300
300
 
301
301
  def supported_capitalizations
302
- cop_config['SupportedCapitalizations'].map(&:to_sym)
302
+ @supported_capitalizations ||= cop_config['SupportedCapitalizations'].map(&:to_sym).freeze
303
303
  end
304
304
  end
305
305
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for `map { |x| x.to_s }.join` and similar calls where the
7
+ # `map` is redundant because `Array#join` implicitly calls `#to_s` on
8
+ # each element.
9
+ #
10
+ # @safety
11
+ # This cop is unsafe because it cannot guarantee that the receiver
12
+ # is an `Array` by static analysis. If the receiver does not have
13
+ # an `Array#join`-compatible implementation (i.e. one that calls
14
+ # `#to_s` on elements), the correction may change behavior.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # array.map(&:to_s).join(', ')
19
+ #
20
+ # # bad
21
+ # array.map { |x| x.to_s }.join(', ')
22
+ #
23
+ # # bad
24
+ # array.collect(&:to_s).join
25
+ #
26
+ # # good
27
+ # array.join(', ')
28
+ #
29
+ # # good
30
+ # array.join
31
+ #
32
+ class MapJoin < Base
33
+ extend AutoCorrector
34
+ include RangeHelp
35
+
36
+ MSG = 'Remove redundant `%<method>s(&:to_s)` before `join`.'
37
+ RESTRICT_ON_SEND = %i[join].freeze
38
+
39
+ # map(&:to_s).join(...)
40
+ # @!method map_to_s_join?(node)
41
+ def_node_matcher :map_to_s_join?, <<~PATTERN
42
+ (call
43
+ $(call _ ${:map :collect} (block_pass (sym :to_s)))
44
+ :join ...)
45
+ PATTERN
46
+
47
+ # map { |x| x.to_s }.join(...)
48
+ # @!method map_to_s_block_join?(node)
49
+ def_node_matcher :map_to_s_block_join?, <<~PATTERN
50
+ (call
51
+ $(block
52
+ (call _ ${:map :collect})
53
+ (args (arg _x))
54
+ (send (lvar _x) :to_s))
55
+ :join ...)
56
+ PATTERN
57
+
58
+ # map { _1.to_s }.join(...)
59
+ # @!method map_to_s_numblock_join?(node)
60
+ def_node_matcher :map_to_s_numblock_join?, <<~PATTERN
61
+ (call
62
+ $(numblock
63
+ (call _ ${:map :collect})
64
+ 1
65
+ (send (lvar :_1) :to_s))
66
+ :join ...)
67
+ PATTERN
68
+
69
+ # map { it.to_s }.join(...)
70
+ # @!method map_to_s_itblock_join?(node)
71
+ def_node_matcher :map_to_s_itblock_join?, <<~PATTERN
72
+ (call
73
+ $(itblock
74
+ (call _ ${:map :collect})
75
+ :it
76
+ (send (lvar :it) :to_s))
77
+ :join ...)
78
+ PATTERN
79
+
80
+ def on_send(node)
81
+ map_to_s_join?(node) { |m, n| register_offense(node, m, n) } ||
82
+ map_to_s_block_join?(node) { |m, n| register_offense(node, m, n) } ||
83
+ map_to_s_numblock_join?(node) { |m, n| register_offense(node, m, n) } ||
84
+ map_to_s_itblock_join?(node) { |m, n| register_offense(node, m, n) }
85
+ end
86
+ alias on_csend on_send
87
+
88
+ private
89
+
90
+ def register_offense(join_node, map_node, method_name)
91
+ map_send = map_node.any_block_type? ? map_node.send_node : map_node
92
+ message = format(MSG, method: method_name)
93
+
94
+ add_offense(map_send.loc.selector, message: message) do |corrector|
95
+ remove_map_call(corrector, join_node, map_node, map_send)
96
+ end
97
+ end
98
+
99
+ def remove_map_call(corrector, join_node, map_node, map_send)
100
+ receiver = map_send.receiver
101
+ if receiver
102
+ corrector.replace(removal_range(receiver, map_node, map_send), '')
103
+ else
104
+ corrector.replace(no_receiver_range(map_node, join_node), '')
105
+ end
106
+ end
107
+
108
+ def removal_range(receiver, map_node, map_send)
109
+ start_pos = if receiver.last_line < map_send.loc.dot.line
110
+ receiver.source_range.end_pos
111
+ else
112
+ map_send.loc.dot.begin_pos
113
+ end
114
+ range_between(start_pos, map_node.source_range.end_pos)
115
+ end
116
+
117
+ def no_receiver_range(map_node, join_node)
118
+ range_between(map_node.source_range.begin_pos, join_node.loc.dot.end_pos)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -33,16 +33,18 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def included_macros_list
36
- cop_config.fetch('IncludedMacros', []).map(&:to_sym)
36
+ @included_macros_list ||= cop_config.fetch('IncludedMacros', []).map(&:to_sym).freeze
37
37
  end
38
38
 
39
39
  def included_macro_patterns
40
- cop_config.fetch('IncludedMacroPatterns', [])
40
+ @included_macro_patterns ||=
41
+ cop_config.fetch('IncludedMacroPatterns', [])
42
+ .map { |pattern| Regexp.new(pattern) }.freeze
41
43
  end
42
44
 
43
45
  def matches_included_macro_pattern?(method_name)
44
46
  included_macro_patterns.any? do |pattern|
45
- Regexp.new(pattern).match?(method_name.to_s)
47
+ pattern.match?(method_name.to_s)
46
48
  end
47
49
  end
48
50
 
@@ -32,7 +32,6 @@ module RuboCop
32
32
  # Array.private_instance_methods.include?(:foo)
33
33
  # Array.protected_instance_methods.include?(:foo)
34
34
  # Array.public_instance_methods.include?(:foo)
35
- # Array.included_modules.include?(:foo)
36
35
  #
37
36
  # # good
38
37
  # Array.class_variable_defined?(:foo)
@@ -40,15 +39,8 @@ module RuboCop
40
39
  # Array.private_method_defined?(:foo)
41
40
  # Array.protected_method_defined?(:foo)
42
41
  # Array.public_method_defined?(:foo)
43
- # Array.include?(:foo)
44
- #
45
- # @example AllowedMethods: [included_modules]
46
- #
47
- # # good
48
- # Array.included_modules.include?(:foo)
49
42
  #
50
43
  class ModuleMemberExistenceCheck < Base
51
- include AllowedMethods
52
44
  extend AutoCorrector
53
45
 
54
46
  MSG = 'Use `%<replacement>s` instead.'
@@ -64,14 +56,13 @@ module RuboCop
64
56
  METHOD_REPLACEMENTS = {
65
57
  class_variables: :class_variable_defined?,
66
58
  constants: :const_defined?,
67
- included_modules: :include?,
68
59
  instance_methods: :method_defined?,
69
60
  private_instance_methods: :private_method_defined?,
70
61
  protected_instance_methods: :protected_method_defined?,
71
62
  public_instance_methods: :public_method_defined?
72
63
  }.freeze
73
64
 
74
- METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables, :included_modules].freeze
65
+ METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables].freeze
75
66
  METHODS_WITH_INHERIT_PARAM =
76
67
  (METHOD_REPLACEMENTS.keys.to_set - METHODS_WITHOUT_INHERIT_PARAM).freeze
77
68
 
@@ -81,7 +72,6 @@ module RuboCop
81
72
  return unless (parent = node.parent)
82
73
  return unless module_member_inclusion?(parent)
83
74
  return unless simple_method_argument?(node) && simple_method_argument?(parent)
84
- return if allowed_method?(node.method_name)
85
75
 
86
76
  offense_range = node.location.selector.join(parent.source_range.end)
87
77
  replacement = replacement_for(node, parent)
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of the `then` keyword in multi-line if statements.
6
+ # Checks for uses of the `then` keyword in multi-line `if` statements.
7
+ # In multi-line `if` statements, `then` is redundant because the newline
8
+ # already separates the condition from the body.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -125,7 +125,7 @@ module RuboCop
125
125
  MSG = 'Freeze mutable objects assigned to constants.'
126
126
 
127
127
  def on_casgn(node)
128
- if node.expression.nil? # This is only the case for `CONST += ...` or similarg66
128
+ if node.expression.nil? # This is only the case for `CONST += ...` or similar
129
129
  parent = node.parent
130
130
  return unless parent.or_asgn_type? # We only care about `CONST ||= ...`
131
131
 
@@ -4,9 +4,8 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for comparison of something with nil using `==` and
7
- # `nil?`.
8
- #
9
- # Supported styles are: predicate, comparison.
7
+ # `nil?`. Enforcing a consistent style (either the `nil?`
8
+ # predicate or `==` comparison) improves readability.
10
9
  #
11
10
  # @example EnforcedStyle: predicate (default)
12
11
  #
@@ -43,7 +43,7 @@ module RuboCop
43
43
  { ({return next break} nil) (nil) }
44
44
  PATTERN
45
45
 
46
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
46
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
47
47
  return unless node.lambda_or_proc?
48
48
  return unless nil_return?(node.body)
49
49
 
@@ -121,17 +121,11 @@ module RuboCop
121
121
  end
122
122
 
123
123
  def autocorrect_comparison(corrector, node)
124
- expr = node.source
125
-
126
- new_code = if include_semantic_changes?
127
- expr.sub(/\s*!=\s*nil/, '')
128
- else
129
- expr.sub(/^(\S*)\s*!=\s*nil/, '!\1.nil?')
130
- end
131
-
132
- return if expr == new_code
133
-
134
- corrector.replace(node, new_code)
124
+ if include_semantic_changes?
125
+ corrector.replace(node, node.receiver.source)
126
+ else
127
+ corrector.replace(node, "!#{node.receiver.source}.nil?")
128
+ end
135
129
  end
136
130
 
137
131
  def autocorrect_non_nil(corrector, node, inner_node)