rubocop 1.84.2 → 1.86.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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +86 -14
  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 +1 -1
  11. data/lib/rubocop/config_finder.rb +1 -1
  12. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  13. data/lib/rubocop/config_store.rb +1 -1
  14. data/lib/rubocop/config_validator.rb +1 -1
  15. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  16. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  17. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  19. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  20. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  21. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  22. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  23. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  24. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  26. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  27. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  28. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -2
  30. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  32. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  33. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  35. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  36. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  37. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  39. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  40. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  41. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  42. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  43. data/lib/rubocop/cop/lint/duplicate_methods.rb +50 -8
  44. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  45. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  46. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  47. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  48. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  49. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  50. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  51. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  52. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  53. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  54. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  55. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  56. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  57. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  58. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  59. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  60. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  61. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  62. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +6 -5
  63. data/lib/rubocop/cop/lint/void.rb +32 -12
  64. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  65. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  66. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  67. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  68. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  69. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  70. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  71. data/lib/rubocop/cop/registry.rb +20 -13
  72. data/lib/rubocop/cop/security/eval.rb +15 -2
  73. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  74. data/lib/rubocop/cop/style/alias.rb +4 -1
  75. data/lib/rubocop/cop/style/and_or.rb +1 -0
  76. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  77. data/lib/rubocop/cop/style/array_join.rb +4 -2
  78. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  79. data/lib/rubocop/cop/style/attr.rb +5 -2
  80. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  81. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  82. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  83. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  84. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  85. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  86. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  87. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  88. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  89. data/lib/rubocop/cop/style/copyright.rb +1 -1
  90. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  91. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  92. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  93. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  94. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  95. data/lib/rubocop/cop/style/encoding.rb +7 -1
  96. data/lib/rubocop/cop/style/end_block.rb +3 -1
  97. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  98. data/lib/rubocop/cop/style/file_open.rb +84 -0
  99. data/lib/rubocop/cop/style/for.rb +3 -0
  100. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  101. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  102. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  103. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  104. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  105. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  106. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  107. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  108. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  109. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  110. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  111. data/lib/rubocop/cop/style/map_join.rb +123 -0
  112. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  113. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  114. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  115. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  116. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  117. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  118. data/lib/rubocop/cop/style/not.rb +2 -0
  119. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  120. data/lib/rubocop/cop/style/one_class_per_file.rb +95 -0
  121. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  122. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  123. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  124. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  125. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  126. data/lib/rubocop/cop/style/proc.rb +3 -2
  127. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  128. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  129. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  130. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  131. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  132. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  133. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  134. data/lib/rubocop/cop/style/redundant_parentheses.rb +22 -22
  135. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  136. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  137. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  138. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +104 -0
  139. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  140. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  141. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  142. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  143. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  144. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  145. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  146. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  147. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  148. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  149. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  150. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  151. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  152. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  153. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  154. data/lib/rubocop/directive_comment.rb +2 -1
  155. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  156. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  157. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  158. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  159. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  160. data/lib/rubocop/formatter.rb +22 -21
  161. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  162. data/lib/rubocop/lsp/routes.rb +10 -3
  163. data/lib/rubocop/mcp/server.rb +198 -0
  164. data/lib/rubocop/options.rb +10 -1
  165. data/lib/rubocop/path_util.rb +14 -2
  166. data/lib/rubocop/plugin/loader.rb +1 -1
  167. data/lib/rubocop/result_cache.rb +22 -10
  168. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  169. data/lib/rubocop/runner.rb +8 -3
  170. data/lib/rubocop/server/cache.rb +5 -7
  171. data/lib/rubocop/server/core.rb +2 -0
  172. data/lib/rubocop/target_finder.rb +1 -1
  173. data/lib/rubocop/target_ruby.rb +18 -12
  174. data/lib/rubocop/version.rb +2 -2
  175. data/lib/rubocop.rb +14 -0
  176. metadata +20 -3
@@ -5,15 +5,26 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces consistent style for empty class definitions.
7
7
  #
8
- # This cop can enforce either a two-line class definition or `Class.new`
8
+ # This cop can enforce either a standard class definition or `Class.new`
9
9
  # for classes with no body.
10
10
  #
11
11
  # The supported styles are:
12
12
  #
13
- # * class_definition (default) - prefer two-line class definition over `Class.new`
13
+ # * class_keyword (default) - prefer standard class definition over `Class.new`
14
14
  # * class_new - prefer `Class.new` over class definition
15
15
  #
16
- # @example EnforcedStyle: class_definition (default)
16
+ # One difference between the two styles is that the `Class.new` form does not make
17
+ # the subclass name available to the base class's `inherited` callback.
18
+ # For this reason, `EnforcedStyle: class_keyword` is set as the default style.
19
+ # Class definitions without a superclass, which are not involved in inheritance,
20
+ # are not detected. This ensures safe detection regardless of the applied style.
21
+ # This avoids overlapping responsibilities with the `Lint/EmptyClass` cop.
22
+ #
23
+ # Use `AllowedParentClasses` to permit both styles for specific parent classes.
24
+ # For example, adding `StandardError` allows both `Error = Class.new(StandardError)`
25
+ # and `class Error < StandardError; end` regardless of the enforced style.
26
+ #
27
+ # @example EnforcedStyle: class_keyword (default)
17
28
  # # bad
18
29
  # FooError = Class.new(StandardError)
19
30
  #
@@ -35,33 +46,43 @@ module RuboCop
35
46
  # # good
36
47
  # FooError = Class.new(StandardError)
37
48
  #
49
+ # @example AllowedParentClasses: ['StandardError']
50
+ # # good - allowed regardless of EnforcedStyle
51
+ # FooError = Class.new(StandardError)
52
+ #
53
+ # # good - allowed regardless of EnforcedStyle
54
+ # class FooError < StandardError
55
+ # end
56
+ #
38
57
  class EmptyClassDefinition < Base
39
58
  include ConfigurableEnforcedStyle
40
- include RangeHelp
41
59
  extend AutoCorrector
42
60
 
43
- MSG_CLASS_DEFINITION =
44
- 'Prefer a two-line class definition over `Class.new` for classes with no body.'
45
- MSG_CLASS_NEW = 'Prefer `Class.new` over class definition for classes with no body.'
61
+ MSG_CLASS_KEYWORD =
62
+ 'Use the `class` keyword instead of `Class.new` to define an empty class.'
63
+ MSG_CLASS_NEW = 'Use `Class.new` instead of the `class` keyword to define an empty class.'
46
64
 
47
65
  # @!method class_new_assignment(node)
48
66
  def_node_matcher :class_new_assignment, <<~PATTERN
49
- (casgn _ _ $(send (const _ :Class) :new ...))
67
+ (casgn _ _ $(send (const _ :Class) :new _))
50
68
  PATTERN
51
69
 
52
70
  def on_casgn(node)
53
- return unless style == :class_definition
71
+ return unless %i[class_keyword class_definition].include?(style)
54
72
  return unless (class_new_node = class_new_assignment(node))
55
73
  return if (arg = class_new_node.first_argument) && !arg.const_type?
74
+ return if allowed_parent_class?(class_new_node.first_argument.source)
56
75
 
57
- add_offense(node, message: MSG_CLASS_DEFINITION) do |corrector|
76
+ add_offense(node, message: MSG_CLASS_KEYWORD) do |corrector|
58
77
  autocorrect_class_new(corrector, node, class_new_node)
59
78
  end
60
79
  end
61
80
 
62
81
  def on_class(node)
63
82
  return unless style == :class_new
83
+ return unless node.parent_class
64
84
  return if (body = node.body) && !body.children.empty?
85
+ return if allowed_parent_class?(node.parent_class.source)
65
86
 
66
87
  add_offense(node, message: MSG_CLASS_NEW) do |corrector|
67
88
  autocorrect_class_definition(corrector, node)
@@ -70,25 +91,27 @@ module RuboCop
70
91
 
71
92
  private
72
93
 
94
+ def allowed_parent_class?(parent_class_name)
95
+ allowed_parent_classes.include?(parent_class_name)
96
+ end
97
+
98
+ def allowed_parent_classes
99
+ cop_config.fetch('AllowedParentClasses', [])
100
+ end
101
+
73
102
  def autocorrect_class_new(corrector, node, class_new_node)
74
103
  indent = ' ' * node.loc.column
75
104
  class_name = node.name
76
- if (parent_class = class_new_node.first_argument)
77
- parent_class_name = " < #{parent_class.source}"
78
- end
105
+ parent_class_name = class_new_node.first_argument.source
79
106
 
80
- corrector.replace(node, "class #{class_name}#{parent_class_name}\n#{indent}end")
107
+ corrector.replace(node, "class #{class_name} < #{parent_class_name}\n#{indent}end")
81
108
  end
82
109
 
83
110
  def autocorrect_class_definition(corrector, node)
84
- indent = ' ' * node.loc.column
85
111
  class_name = node.identifier.source
86
- if (parent_class = node.parent_class)
87
- parent_class_name = "(#{parent_class.source})"
88
- end
89
- range = range_by_whole_lines(node.source_range, include_final_newline: true)
112
+ parent_class_name = node.parent_class.source
90
113
 
91
- corrector.replace(range, "#{indent}#{class_name} = Class.new#{parent_class_name}\n")
114
+ corrector.replace(node, "#{class_name} = Class.new(#{parent_class_name})")
92
115
  end
93
116
  end
94
117
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
 
24
24
  MSG = 'Omit parentheses for the empty lambda parameters.'
25
25
 
26
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
26
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
27
27
  send_node = node.send_node
28
28
  return unless send_node.send_type?
29
29
 
@@ -3,12 +3,18 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks ensures source files have no utf-8 encoding comments.
6
+ # Checks that source files have no utf-8 encoding comments.
7
+ # Since Ruby 2.0, UTF-8 is the default source encoding, so
8
+ # these comments are no longer necessary and just add noise.
9
+ #
7
10
  # @example
8
11
  # # bad
9
12
  # # encoding: UTF-8
10
13
  # # coding: UTF-8
11
14
  # # -*- coding: UTF-8 -*-
15
+ #
16
+ # # good
17
+ # # No encoding comment needed
12
18
  class Encoding < Base
13
19
  include RangeHelp
14
20
  extend AutoCorrector
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for END blocks.
6
+ # Checks for `END` blocks. `END` blocks are Perl-style constructs
7
+ # and `Kernel#at_exit` is the idiomatic Ruby alternative, as it's
8
+ # explicit and can be used anywhere.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -157,6 +157,7 @@ module RuboCop
157
157
  handle_require_always_style(node)
158
158
  end
159
159
  end
160
+ alias on_defs on_def
160
161
 
161
162
  private
162
163
 
@@ -170,7 +171,7 @@ module RuboCop
170
171
  end
171
172
 
172
173
  def handle_require_single_line_style(node)
173
- if node.endless? && !node.single_line?
174
+ if node.endless? && node.multiline?
174
175
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
175
176
  correct_to_multiline(corrector, node)
176
177
  end
@@ -207,7 +208,7 @@ module RuboCop
207
208
 
208
209
  def correct_to_multiline(corrector, node)
209
210
  replacement = <<~RUBY.strip
210
- def #{node.method_name}#{arguments(node)}
211
+ def #{receiver(node)}#{node.method_name}#{arguments(node)}
211
212
  #{node.body.source}
212
213
  end
213
214
  RUBY
@@ -217,10 +218,14 @@ module RuboCop
217
218
 
218
219
  def endless_replacement(node)
219
220
  <<~RUBY.strip
220
- def #{node.method_name}#{arguments(node)} = #{node.body.source}
221
+ def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
221
222
  RUBY
222
223
  end
223
224
 
225
+ def receiver(node)
226
+ node.receiver ? "#{node.receiver.source}#{node.loc.operator.source}" : ''
227
+ end
228
+
224
229
  def arguments(node, missing = '')
225
230
  node.arguments.any? ? node.arguments.source : missing
226
231
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for `File.open` without a block, which can leak file descriptors.
7
+ #
8
+ # When `File.open` is called without a block, the caller is responsible
9
+ # for closing the file descriptor. If it is not explicitly closed, it
10
+ # will only be closed when the garbage collector runs, which may lead
11
+ # to resource exhaustion. Using the block form ensures the file is
12
+ # automatically closed when the block exits.
13
+ #
14
+ # This cop only registers an offense when the result of `File.open` is
15
+ # assigned to a variable or has a method chained on it, as those are the
16
+ # clearest indicators that the block form should be used instead. When
17
+ # `File.open` is used as a return value or passed as an argument, the
18
+ # caller is likely managing the file descriptor intentionally.
19
+ #
20
+ # @safety
21
+ # This cop is unsafe because it relies on syntax heuristics and cannot
22
+ # verify whether the file descriptor is safely managed. For example, it
23
+ # still flags intentional one-shot reads (`File.open("f").read`) where
24
+ # the file descriptor is closed by the garbage collector.
25
+ #
26
+ # @example
27
+ # # bad
28
+ # f = File.open('file')
29
+ #
30
+ # # bad
31
+ # File.open('file').read
32
+ #
33
+ # # good
34
+ # File.open('file') do |f|
35
+ # f.read
36
+ # end
37
+ #
38
+ # # good
39
+ # File.open('file', &:read)
40
+ #
41
+ # # good - pass an open file object to an API that manages its lifecycle
42
+ # process(io: File.open('file'))
43
+ #
44
+ # # good - return an open file object for the caller to manage
45
+ # def json_key_io
46
+ # File.open('file')
47
+ # end
48
+ #
49
+ # # good - use File.read for one-shot reads
50
+ # File.read('file')
51
+ #
52
+ class FileOpen < Base
53
+ MSG = '`File.open` without a block may leak a file descriptor; use the block form.'
54
+ RESTRICT_ON_SEND = %i[open].freeze
55
+
56
+ # @!method file_open?(node)
57
+ def_node_matcher :file_open?, <<~PATTERN
58
+ (send (const {nil? cbase} :File) :open ...)
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ return unless file_open?(node)
63
+ return if node.block_argument?
64
+ return unless offensive_usage?(node)
65
+
66
+ add_offense(node)
67
+ end
68
+ alias on_csend on_send
69
+
70
+ private
71
+
72
+ def offensive_usage?(node)
73
+ return true unless node.value_used?
74
+
75
+ node.parent.lvasgn_type? || receiver_of_chained_call?(node)
76
+ end
77
+
78
+ def receiver_of_chained_call?(node)
79
+ node.parent.receiver == node
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # parameter. An `each` call with a block on a single line is always
9
9
  # allowed.
10
10
  #
11
+ # NOTE: `each` is preferred in idiomatic Ruby because `for` leaks
12
+ # its loop variable into the surrounding scope.
13
+ #
11
14
  # @example EnforcedStyle: each (default)
12
15
  # # bad
13
16
  # def foo
@@ -18,6 +18,11 @@ module RuboCop
18
18
  # of `EnforcedStyle`) are only considered if used in the format string argument to the
19
19
  # methods `printf`, `sprintf`, `format` and `%`.
20
20
  #
21
+ # NOTE: In `aggressive` mode, offenses are registered for all strings containing tokens,
22
+ # but autocorrection is only applied when the string appears in a known formatting context
23
+ # (`format`, `sprintf`, `printf`, or `%`). This is done in order to prevent false
24
+ # autocorrections for strings that are not actually format strings.
25
+ #
21
26
  # NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if
22
27
  # configured with `Conservative: true`. This is done in order to prevent false positives,
23
28
  # because this format is very similar to encoded URLs or Date/Time formatting strings.
@@ -90,9 +95,25 @@ module RuboCop
90
95
  # # good
91
96
  # redirect('foo/%{bar_id}')
92
97
  #
98
+ # @example Mode: aggressive (default), EnforcedStyle: annotated
99
+ #
100
+ # # bad
101
+ # "%{greeting}"
102
+ # foo("%{greeting}")
103
+ #
104
+ # # bad
105
+ # format("%{greeting}", greeting: 'Hello')
106
+ # printf("%{greeting}", greeting: 'Hello')
107
+ # sprintf("%{greeting}", greeting: 'Hello')
108
+ # "%{greeting}" % { greeting: 'Hello' }
109
+ #
110
+ # # good
111
+ # format("%<greeting>s", greeting: 'Hello')
112
+ # printf("%<greeting>s", greeting: 'Hello')
113
+ # sprintf("%<greeting>s", greeting: 'Hello')
114
+ # "%<greeting>s" % { greeting: 'Hello' }
115
+ #
93
116
  # @example Mode: conservative, EnforcedStyle: annotated
94
- # # In `conservative` mode, offenses are only registered for strings
95
- # # given to a known formatting method.
96
117
  #
97
118
  # # good
98
119
  # "%{greeting}"
@@ -104,6 +125,12 @@ module RuboCop
104
125
  # sprintf("%{greeting}", greeting: 'Hello')
105
126
  # "%{greeting}" % { greeting: 'Hello' }
106
127
  #
128
+ # # good
129
+ # format("%<greeting>s", greeting: 'Hello')
130
+ # printf("%<greeting>s", greeting: 'Hello')
131
+ # sprintf("%<greeting>s", greeting: 'Hello')
132
+ # "%<greeting>s" % { greeting: 'Hello' }
133
+ #
107
134
  class FormatStringToken < Base
108
135
  include ConfigurableEnforcedStyle
109
136
  include AllowedMethods
@@ -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)
@@ -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 $_))
@@ -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