rubocop 1.84.2 → 1.85.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +83 -4
  3. data/config/obsoletion.yml +5 -0
  4. data/lib/rubocop/cli/command/mcp.rb +19 -0
  5. data/lib/rubocop/cli.rb +6 -3
  6. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  7. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  8. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  9. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  10. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  11. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  12. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  13. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  14. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  15. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  16. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  17. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  18. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  19. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  20. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  21. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  22. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  23. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  24. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  25. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  26. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  27. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  28. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  29. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  30. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  31. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  32. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -6
  33. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  34. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  35. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  36. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  37. data/lib/rubocop/cop/lint/void.rb +32 -12
  38. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  39. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  40. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  41. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  42. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  43. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  44. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  45. data/lib/rubocop/cop/security/eval.rb +15 -2
  46. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  47. data/lib/rubocop/cop/style/alias.rb +4 -1
  48. data/lib/rubocop/cop/style/array_join.rb +4 -2
  49. data/lib/rubocop/cop/style/ascii_comments.rb +5 -2
  50. data/lib/rubocop/cop/style/attr.rb +5 -2
  51. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  52. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  53. data/lib/rubocop/cop/style/block_delimiters.rb +2 -2
  54. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  55. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  56. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  57. data/lib/rubocop/cop/style/copyright.rb +1 -1
  58. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  59. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  60. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  61. data/lib/rubocop/cop/style/empty_class_definition.rb +21 -20
  62. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  63. data/lib/rubocop/cop/style/encoding.rb +7 -1
  64. data/lib/rubocop/cop/style/end_block.rb +3 -1
  65. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  66. data/lib/rubocop/cop/style/file_open.rb +63 -0
  67. data/lib/rubocop/cop/style/for.rb +3 -0
  68. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  69. data/lib/rubocop/cop/style/global_vars.rb +4 -1
  70. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  71. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  72. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  73. data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -3
  74. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  75. data/lib/rubocop/cop/style/map_join.rb +123 -0
  76. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  77. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  78. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  79. data/lib/rubocop/cop/style/not.rb +2 -0
  80. data/lib/rubocop/cop/style/numeric_literals.rb +2 -1
  81. data/lib/rubocop/cop/style/one_class_per_file.rb +95 -0
  82. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  83. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  84. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  85. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  86. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  87. data/lib/rubocop/cop/style/proc.rb +3 -2
  88. data/lib/rubocop/cop/style/reduce_to_hash.rb +169 -0
  89. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  90. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  91. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  92. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +6 -3
  94. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  95. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +104 -0
  96. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  97. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  98. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  99. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  100. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  101. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  102. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  103. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  104. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  105. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  106. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  107. data/lib/rubocop/directive_comment.rb +2 -1
  108. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  109. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  110. data/lib/rubocop/mcp/server.rb +174 -0
  111. data/lib/rubocop/options.rb +10 -1
  112. data/lib/rubocop/server/cache.rb +5 -7
  113. data/lib/rubocop/target_ruby.rb +18 -12
  114. data/lib/rubocop/version.rb +1 -1
  115. data/lib/rubocop.rb +14 -0
  116. metadata +34 -3
@@ -3,15 +3,28 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Security
6
- # Checks for the use of `Kernel#eval` and `Binding#eval`.
6
+ # Checks for the use of `Kernel#eval` and `Binding#eval` with
7
+ # dynamic strings as arguments. Evaluating non-literal strings
8
+ # can enable code injection attacks and makes it difficult to
9
+ # reason about what code will actually be executed.
10
+ #
11
+ # Calls to `eval` with literal strings are not flagged by this cop,
12
+ # as they do not pose the same injection risk.
7
13
  #
8
14
  # @example
9
15
  #
10
16
  # # bad
11
- #
12
17
  # eval(something)
13
18
  # binding.eval(something)
14
19
  # Kernel.eval(something)
20
+ #
21
+ # # good - use safer alternatives
22
+ # obj.public_send(method_name)
23
+ # obj.send(method_name, *args)
24
+ #
25
+ # # good - literal strings are allowed
26
+ # eval("1 + 1")
27
+ # binding.eval("foo")
15
28
  class Eval < Base
16
29
  MSG = 'The use of `eval` is a serious security risk.'
17
30
  RESTRICT_ON_SEND = %i[eval].freeze
@@ -4,8 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for grouping of accessors in `class` and `module` bodies.
7
- # By default it enforces accessors to be placed in grouped declarations,
8
- # but it can be configured to enforce separating them in multiple declarations.
7
+ # By default it enforces accessors to be placed in grouped
8
+ # declarations, reducing boilerplate. It can also be configured
9
+ # to enforce separating them into individual declarations for
10
+ # easier diffing and per-attribute documentation.
9
11
  #
10
12
  # NOTE: If there is a method call before the accessor method it is always allowed
11
13
  # as it might be intended like Sorbet.
@@ -4,7 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Enforces the use of either `#alias` or `#alias_method`
7
- # depending on configuration.
7
+ # depending on configuration. Consistent use of one or the
8
+ # other prevents confusion about their different semantics
9
+ # (e.g., `alias` is resolved at parse time, while `alias_method`
10
+ # is resolved at runtime).
8
11
  # It also flags uses of `alias :symbol` rather than `alias bareword`.
9
12
  #
10
13
  # However, it will always enforce `method_alias` when used `alias`
@@ -3,9 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of "*" as a substitute for _join_.
6
+ # Checks for uses of `*` as a substitute for `Array#join`.
7
+ # Using `join` is clearer about intent and more readable than
8
+ # overloading the `*` operator for string conversion.
7
9
  #
8
- # Not all cases can reliably checked, due to Ruby's dynamic
10
+ # Not all cases can be reliably checked, due to Ruby's dynamic
9
11
  # types, so we consider only cases when the first argument is an
10
12
  # array literal or the second is a string literal.
11
13
  #
@@ -4,8 +4,11 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for non-ascii (non-English) characters
7
- # in comments. You could set an array of allowed non-ascii chars in
8
- # `AllowedChars` attribute (copyright notice "©" by default).
7
+ # in comments. Non-ascii characters can cause issues with
8
+ # portability and encoding across different environments
9
+ # and editors. You could set an array of allowed non-ascii
10
+ # chars in `AllowedChars` attribute (copyright notice "©"
11
+ # by default).
9
12
  #
10
13
  # @example
11
14
  # # bad
@@ -3,10 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of Module#attr.
6
+ # Checks for uses of `Module#attr`. The `attr` method has confusing
7
+ # behavior: with a single argument it creates a reader (like `attr_reader`),
8
+ # but with a second boolean argument it creates an accessor (deprecated in
9
+ # Ruby 1.9). Use `attr_reader` or `attr_accessor` to make intent explicit.
7
10
  #
8
11
  # @example
9
- # # bad - creates a single attribute accessor (deprecated in Ruby 1.9)
12
+ # # bad
10
13
  # attr :something, true
11
14
  # attr :one, :two, :three # behaves as attr_reader
12
15
  #
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks if usage of %() or %Q() matches configuration.
6
+ # Checks if usage of `%()` or `%Q()` matches configuration.
7
+ # Consistent use of one style makes the codebase easier
8
+ # to read.
7
9
  #
8
10
  # @example EnforcedStyle: bare_percent (default)
9
11
  # # bad
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for BEGIN blocks.
6
+ # Checks for `BEGIN` blocks. They are Perl-style constructs that execute
7
+ # code before the rest of the file is parsed, making the control flow
8
+ # harder to follow and reason about.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -421,7 +421,7 @@ module RuboCop
421
421
 
422
422
  if node.braces?
423
423
  functional_method?(method_name) || functional_block?(node) ||
424
- (procedural_oneliners_may_have_braces? && !node.multiline?)
424
+ (procedural_oneliners_may_have_braces? && node.single_line?)
425
425
  else
426
426
  procedural_method?(method_name) || !return_value_used?(node)
427
427
  end
@@ -489,7 +489,7 @@ module RuboCop
489
489
  def begin_required?(block_node)
490
490
  # If the block contains `rescue` or `ensure`, it needs to be wrapped in
491
491
  # `begin`...`end` when changing `do-end` to `{}`.
492
- block_node.each_child_node(:rescue, :ensure).any? && !block_node.single_line?
492
+ block_node.each_child_node(:rescue, :ensure).any? && block_node.multiline?
493
493
  end
494
494
 
495
495
  def single_argument_operator_method?(node)
@@ -4,6 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for uses of the case equality operator (`===`).
7
+ # The `===` operator has different behavior depending on the
8
+ # receiver and its use outside of `case`/`when` is confusing.
9
+ # Prefer more explicit alternatives like `is_a?`, `include?`,
10
+ # or `match?`.
7
11
  #
8
12
  # If `AllowOnConstant` option is enabled, the cop will ignore violations when the receiver of
9
13
  # the case equality operator is a constant.
@@ -28,16 +28,24 @@ module RuboCop
28
28
  # manual oversight.
29
29
  #
30
30
  # @example EnforcedStyle: nested (default)
31
+ # # bad
32
+ # class Foo::Bar
33
+ # end
34
+ #
31
35
  # # good
32
- # # have each child on its own line
33
36
  # class Foo
34
37
  # class Bar
35
38
  # end
36
39
  # end
37
40
  #
38
41
  # @example EnforcedStyle: compact
42
+ # # bad
43
+ # class Foo
44
+ # class Bar
45
+ # end
46
+ # end
47
+ #
39
48
  # # good
40
- # # combine definitions as much as possible
41
49
  # class Foo::Bar
42
50
  # end
43
51
  #
@@ -4,7 +4,9 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for methods invoked via the `::` operator instead
7
- # of the `.` operator (like `FileUtils::rmdir` instead of `FileUtils.rmdir`).
7
+ # of the `.` operator (like `FileUtils::rmdir` instead of
8
+ # `FileUtils.rmdir`). The `::` operator is conventionally used to
9
+ # reference constants, so using it for method calls can be misleading.
8
10
  #
9
11
  # @example
10
12
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check that a copyright notice was given in each source file.
6
+ # Checks that a copyright notice was given in each source file.
7
7
  #
8
8
  # The default regexp for an acceptable copyright notice can be found in
9
9
  # config/default.yml. The default can be changed as follows:
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG = 'Use `Integer#times` for a simple loop which iterates a fixed number of times.'
28
28
 
29
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
29
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
30
30
  return unless offending?(node)
31
31
 
32
32
  send_node = node.send_node
@@ -40,6 +40,8 @@ module RuboCop
40
40
  end
41
41
  end
42
42
 
43
+ alias on_itblock on_block
44
+
43
45
  def on_numblock(node)
44
46
  each_with_object_numblock_candidate?(node) do |method, body|
45
47
  _, method_name, method_arg = *method
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  MSG = 'Omit pipes for the empty block parameters.'
30
30
 
31
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
31
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
32
32
  send_node = node.send_node
33
33
  check(node) unless send_node.send_type? && send_node.lambda_literal?
34
34
  end
@@ -5,15 +5,22 @@ 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_definition (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_definition` 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
+ # @example EnforcedStyle: class_keyword (default)
17
24
  # # bad
18
25
  # FooError = Class.new(StandardError)
19
26
  #
@@ -37,30 +44,30 @@ module RuboCop
37
44
  #
38
45
  class EmptyClassDefinition < Base
39
46
  include ConfigurableEnforcedStyle
40
- include RangeHelp
41
47
  extend AutoCorrector
42
48
 
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.'
49
+ MSG_CLASS_KEYWORD =
50
+ 'Use the `class` keyword instead of `Class.new` to define an empty class.'
51
+ MSG_CLASS_NEW = 'Use `Class.new` instead of the `class` keyword to define an empty class.'
46
52
 
47
53
  # @!method class_new_assignment(node)
48
54
  def_node_matcher :class_new_assignment, <<~PATTERN
49
- (casgn _ _ $(send (const _ :Class) :new ...))
55
+ (casgn _ _ $(send (const _ :Class) :new _))
50
56
  PATTERN
51
57
 
52
58
  def on_casgn(node)
53
- return unless style == :class_definition
59
+ return unless %i[class_keyword class_definition].include?(style)
54
60
  return unless (class_new_node = class_new_assignment(node))
55
61
  return if (arg = class_new_node.first_argument) && !arg.const_type?
56
62
 
57
- add_offense(node, message: MSG_CLASS_DEFINITION) do |corrector|
63
+ add_offense(node, message: MSG_CLASS_KEYWORD) do |corrector|
58
64
  autocorrect_class_new(corrector, node, class_new_node)
59
65
  end
60
66
  end
61
67
 
62
68
  def on_class(node)
63
69
  return unless style == :class_new
70
+ return unless node.parent_class
64
71
  return if (body = node.body) && !body.children.empty?
65
72
 
66
73
  add_offense(node, message: MSG_CLASS_NEW) do |corrector|
@@ -73,22 +80,16 @@ module RuboCop
73
80
  def autocorrect_class_new(corrector, node, class_new_node)
74
81
  indent = ' ' * node.loc.column
75
82
  class_name = node.name
76
- if (parent_class = class_new_node.first_argument)
77
- parent_class_name = " < #{parent_class.source}"
78
- end
83
+ parent_class_name = class_new_node.first_argument.source
79
84
 
80
- corrector.replace(node, "class #{class_name}#{parent_class_name}\n#{indent}end")
85
+ corrector.replace(node, "class #{class_name} < #{parent_class_name}\n#{indent}end")
81
86
  end
82
87
 
83
88
  def autocorrect_class_definition(corrector, node)
84
- indent = ' ' * node.loc.column
85
89
  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)
90
+ parent_class_name = node.parent_class.source
90
91
 
91
- corrector.replace(range, "#{indent}#{class_name} = Class.new#{parent_class_name}\n")
92
+ corrector.replace(node, "#{class_name} = Class.new(#{parent_class_name})")
92
93
  end
93
94
  end
94
95
  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,63 @@
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
+ # @safety
15
+ # This cop is unsafe because it detects all `File.open` calls without
16
+ # a block, including intentional uses such as one-shot reads
17
+ # (`File.open("f").read`) where the file descriptor is closed by the
18
+ # garbage collector.
19
+ #
20
+ # @example
21
+ # # bad
22
+ # f = File.open('file')
23
+ #
24
+ # # bad
25
+ # File.open('file').read
26
+ #
27
+ # # good
28
+ # File.open('file') do |f|
29
+ # f.read
30
+ # end
31
+ #
32
+ # # good
33
+ # File.open('file', &:read)
34
+ #
35
+ # # good - use File.read for one-shot reads
36
+ # File.read('file')
37
+ #
38
+ class FileOpen < Base
39
+ MSG = '`File.open` without a block may leak a file descriptor; use the block form.'
40
+ RESTRICT_ON_SEND = %i[open].freeze
41
+
42
+ # @!method file_open?(node)
43
+ def_node_matcher :file_open?, <<~PATTERN
44
+ (send (const {nil? cbase} :File) :open ...)
45
+ PATTERN
46
+
47
+ def on_send(node)
48
+ return unless file_open?(node)
49
+ return if block_form?(node)
50
+
51
+ add_offense(node)
52
+ end
53
+ alias on_csend on_send
54
+
55
+ private
56
+
57
+ def block_form?(node)
58
+ node.block_argument? || node.parent&.block_type?
59
+ end
60
+ end
61
+ end
62
+ end
63
+ 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.
@@ -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