rubocop 1.47.0 → 1.48.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3790198ce32105233a07bc15c82a62ec188e7430910b16a7ff10eefbfa89c0a2
4
- data.tar.gz: aa440d2b6a863b774d63731cbf779cd204491f675b5ad0d4b708a1b8708dcd3b
3
+ metadata.gz: 39e28428d694fdd8cf940d5e3494f62a0266b3ffd41e823cf50131d16b45cf2a
4
+ data.tar.gz: 75eadde7a899742fcb5f08c4b3cd845658dd64087dd80c0dad1bda5fef30804a
5
5
  SHA512:
6
- metadata.gz: 2adcafe5b28f4c2a440d3cee024b3e2462a4b60d6ddf5136f63972255e484059780281f2be264c82bd21a8c5ebe83433c010b53b8d893f01de9d9e58de586b7f
7
- data.tar.gz: 66eab3f5f70f3c93ee3263a982b6e7795fec658dd91462d6c5d5bc9f87f1ae99de17fb0fcd04afb060985ad1085505ff7c98d5df6d11a33b40432260ef5e91fc
6
+ metadata.gz: 21bba5ce4024ff5c66695c56e4261fbefdedc8b293396b8d0687f836d4a56d0f53ae307435cb714a3e4b059def785a1d9862504910f94fe1815223b6036b727b
7
+ data.tar.gz: aec136f1e174b57ba2ae6307a3fdeda0257614bcaaf77a6d5c5eca9733cfd185970dd998ecc95a6691d9aa8310419cd69679ba9b021f5b8284b9d12ef675dcc0
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.47', require: false
56
+ gem 'rubocop', '~> 1.48', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -1659,6 +1659,7 @@ Lint/Debugger:
1659
1659
  - Kernel.binding.remote_pry
1660
1660
  - Kernel.binding.pry_remote
1661
1661
  - Pry.rescue
1662
+ - pry
1662
1663
  Rails:
1663
1664
  - debugger
1664
1665
  - Kernel.debugger
@@ -3261,6 +3262,9 @@ Style/CaseLikeIf:
3261
3262
  Enabled: true
3262
3263
  Safe: false
3263
3264
  VersionAdded: '0.88'
3265
+ VersionChanged: '1.48'
3266
+ # `MinBranchesCount` defines the number of branches `if` needs to have to trigger this cop.
3267
+ MinBranchesCount: 3
3264
3268
 
3265
3269
  Style/CharacterLiteral:
3266
3270
  Description: 'Checks for uses of character literals.'
@@ -3527,6 +3531,12 @@ Style/Dir:
3527
3531
  Enabled: true
3528
3532
  VersionAdded: '0.50'
3529
3533
 
3534
+ Style/DirEmpty:
3535
+ Description: >-
3536
+ Prefer to use `Dir.empty?('path/to/dir')` when checking if a directory is empty.
3537
+ Enabled: pending
3538
+ VersionAdded: '1.48'
3539
+
3530
3540
  Style/DisableCopsWithinSourceCodeDirective:
3531
3541
  Description: >-
3532
3542
  Forbids disabling/enabling cops within source code.
@@ -3722,6 +3732,14 @@ Style/FetchEnvVar:
3722
3732
  # Environment variables to be excluded from the inspection.
3723
3733
  AllowedVars: []
3724
3734
 
3735
+ Style/FileEmpty:
3736
+ Description: >-
3737
+ Prefer to use `File.empty?('path/to/file')` when checking if a file is empty.
3738
+ Enabled: pending
3739
+ Safe: false
3740
+ SafeAutoCorrect: false
3741
+ VersionAdded: '1.48'
3742
+
3725
3743
  Style/FileRead:
3726
3744
  Description: 'Favor `File.(bin)read` convenience methods.'
3727
3745
  StyleGuide: '#file-read'
@@ -304,8 +304,8 @@ module RuboCop
304
304
  end
305
305
 
306
306
  def enable_cop?(qualified_cop_name, cop_options)
307
- # If the cop is explicitly enabled, the other checks can be skipped.
308
- return true if cop_options['Enabled'] == true
307
+ # If the cop is explicitly enabled or `Lint/Syntax`, the other checks can be skipped.
308
+ return true if cop_options['Enabled'] == true || qualified_cop_name == 'Lint/Syntax'
309
309
 
310
310
  department = department_of(qualified_cop_name)
311
311
  cop_enabled = cop_options.fetch('Enabled') { !for_all_cops['DisabledByDefault'] }
@@ -94,7 +94,7 @@ module RuboCop
94
94
  private
95
95
 
96
96
  def allowed_gem?(node)
97
- allowed_gems.include?(node.first_argument.value)
97
+ allowed_gems.include?(node.first_argument.str_content)
98
98
  end
99
99
 
100
100
  def allowed_gems
@@ -36,17 +36,19 @@ module RuboCop
36
36
  # If the end is on its own line, there is no offense
37
37
  return if begins_its_line?(node.loc.end)
38
38
 
39
- register_offense(node)
39
+ offense_range = offense_range(node)
40
+ return if offense_range.source.lstrip.start_with?(';')
41
+
42
+ register_offense(node, offense_range)
40
43
  end
41
44
 
42
45
  alias on_numblock on_block
43
46
 
44
47
  private
45
48
 
46
- def register_offense(node)
49
+ def register_offense(node, offense_range)
47
50
  add_offense(node.loc.end, message: message(node)) do |corrector|
48
- offense_range = offense_range(node)
49
- replacement = replacement(node)
51
+ replacement = "\n#{offense_range.source.lstrip}"
50
52
 
51
53
  if (heredoc = last_heredoc_argument(node.body))
52
54
  corrector.remove(offense_range)
@@ -72,23 +74,7 @@ module RuboCop
72
74
  end
73
75
 
74
76
  def offense_range(node)
75
- Parser::Source::Range.new(
76
- node.source_range.source_buffer,
77
- node.children.compact.last.source_range.end_pos,
78
- end_of_method_chain(node).source_range.end_pos
79
- )
80
- end
81
-
82
- def replacement(node)
83
- end_with_method_chain = node.loc.end.join(end_of_method_chain(node).source_range.end)
84
-
85
- "\n#{end_with_method_chain.source.strip}"
86
- end
87
-
88
- def end_of_method_chain(node)
89
- return node unless node.parent&.call_type?
90
-
91
- end_of_method_chain(node.parent)
77
+ node.children.compact.last.source_range.end.join(node.loc.end)
92
78
  end
93
79
  end
94
80
  end
@@ -110,12 +110,12 @@ module RuboCop
110
110
  end
111
111
 
112
112
  def inside_class_with_stateful_parent?(node)
113
- if (class_node = node.parent) && class_node.class_type?
114
- class_node.parent_class && !stateless_class?(class_node.parent_class)
115
- elsif (block_node = node.each_ancestor(:block, :numblock).first)
113
+ if (block_node = node.each_ancestor(:block, :numblock).first)
116
114
  return false unless (super_class = class_new_block(block_node))
117
115
 
118
116
  !stateless_class?(super_class)
117
+ elsif (class_node = node.each_ancestor(:class).first)
118
+ class_node.parent_class && !stateless_class?(class_node.parent_class)
119
119
  else
120
120
  false
121
121
  end
@@ -31,6 +31,8 @@ module RuboCop
31
31
  return unless lhs&.casgn_type?
32
32
 
33
33
  add_offense(node.loc.operator) do |corrector|
34
+ next if node.each_ancestor(:def, :defs).any?
35
+
34
36
  corrector.replace(node.loc.operator, '=')
35
37
  end
36
38
  end
@@ -33,6 +33,10 @@ module RuboCop
33
33
  message << '.' unless message.end_with?('.')
34
34
  message
35
35
  end
36
+
37
+ def find_severity(_range, _severity)
38
+ :fatal
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -137,7 +137,7 @@ module RuboCop
137
137
  alias on_sclass on_class
138
138
 
139
139
  def on_block(node)
140
- return unless eval_call?(node)
140
+ return unless eval_call?(node) || included_block?(node)
141
141
 
142
142
  check_node(node.body)
143
143
  end
@@ -192,10 +192,13 @@ module RuboCop
192
192
  end
193
193
  end
194
194
 
195
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
195
196
  def check_child_nodes(node, unused, cur_vis)
196
197
  node.child_nodes.each do |child|
197
198
  if child.send_type? && access_modifier?(child)
198
199
  cur_vis, unused = check_send_node(child, cur_vis, unused)
200
+ elsif child.block_type? && included_block?(child)
201
+ next
199
202
  elsif method_definition?(child)
200
203
  unused = nil
201
204
  elsif start_of_new_scope?(child)
@@ -207,6 +210,7 @@ module RuboCop
207
210
 
208
211
  [cur_vis, unused]
209
212
  end
213
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
210
214
 
211
215
  def check_send_node(node, cur_vis, unused)
212
216
  if node.bare_access_modifier?
@@ -240,6 +244,10 @@ module RuboCop
240
244
  [new_vis, unused]
241
245
  end
242
246
 
247
+ def included_block?(block_node)
248
+ active_support_extensions_enabled? && block_node.method?(:included)
249
+ end
250
+
243
251
  def method_definition?(child)
244
252
  static_method_definition?(child) ||
245
253
  dynamic_method_definition?(child) ||
@@ -56,11 +56,13 @@ module RuboCop
56
56
 
57
57
  private
58
58
 
59
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
59
60
  def only_reraising?(resbody_node)
60
61
  return false if use_exception_variable_in_ensure?(resbody_node)
61
62
 
62
63
  body = resbody_node.body
63
- return false if body.nil? || !body.send_type? || !body.method?(:raise)
64
+
65
+ return false if body.nil? || !body.send_type? || !body.method?(:raise) || body.receiver
64
66
  return true unless body.arguments?
65
67
  return false if body.arguments.size > 1
66
68
 
@@ -68,6 +70,7 @@ module RuboCop
68
70
 
69
71
  exception_objects(resbody_node).include?(exception_name)
70
72
  end
73
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
71
74
 
72
75
  def use_exception_variable_in_ensure?(resbody_node)
73
76
  return false unless (exception_variable = resbody_node.exception_variable)
@@ -95,19 +95,21 @@ module RuboCop
95
95
  use_modifier_form_without_parenthesized_method_call?(method_dispatch_node)
96
96
  end
97
97
 
98
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
98
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
99
  def def_node_that_require_parentheses(node)
100
100
  last_pair = node.parent.pairs.last
101
101
  return unless last_pair.key.source == last_pair.value.source
102
102
  return unless (dispatch_node = find_ancestor_method_dispatch_node(node))
103
+ return if dispatch_node.assignment_method?
103
104
  return if dispatch_node.parenthesized?
105
+ return if dispatch_node.parent && parentheses?(dispatch_node.parent)
104
106
  return if last_expression?(dispatch_node) && !method_dispatch_as_argument?(dispatch_node)
105
107
 
106
108
  def_node = node.each_ancestor(:send, :csend, :super, :yield).first
107
109
 
108
110
  DefNode.new(def_node) unless def_node && def_node.arguments.empty?
109
111
  end
110
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
112
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
111
113
 
112
114
  def find_ancestor_method_dispatch_node(node)
113
115
  return unless (ancestor = node.parent.parent)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking minimum branches count.
6
+ module MinBranchesCount
7
+ private
8
+
9
+ def min_branches_count?(node)
10
+ branches =
11
+ if node.case_type?
12
+ node.when_branches
13
+ elsif node.if_type?
14
+ if_conditional_branches(node)
15
+ else
16
+ raise ArgumentError, "Unsupported #{node.type.inspect} node type"
17
+ end
18
+
19
+ branches.size >= min_branches_count
20
+ end
21
+
22
+ def min_branches_count
23
+ length = cop_config['MinBranchesCount'] || 3
24
+ return length if length.is_a?(Integer) && length.positive?
25
+
26
+ raise 'MinBranchesCount needs to be a positive integer!'
27
+ end
28
+
29
+ def if_conditional_branches(node, branches = [])
30
+ return [] if node.nil? || !node.if_type?
31
+
32
+ branches << node.if_branch
33
+
34
+ else_branch = node.else_branch
35
+ if_conditional_branches(else_branch, branches) if else_branch&.if_type?
36
+ branches
37
+ end
38
+ end
39
+ end
40
+ end
@@ -10,8 +10,8 @@ module RuboCop
10
10
  #
11
11
  # Naming/MethodName:
12
12
  # AllowedPatterns:
13
- # - '\A\s*onSelectionBulkChange\s*'
14
- # - '\A\s*onSelectionCleared\s*'
13
+ # - '\AonSelectionBulkChange\z'
14
+ # - '\AonSelectionCleared\z'
15
15
  #
16
16
  # Method names matching patterns are always allowed.
17
17
  #
@@ -197,7 +197,9 @@ module RuboCop
197
197
  def enabled?(cop, config)
198
198
  return true if options[:only]&.include?(cop.cop_name)
199
199
 
200
- cfg = config.for_cop(cop)
200
+ # We need to use `cop_name` in this case, because `for_cop` uses caching
201
+ # which expects cop names or cop classes as keys.
202
+ cfg = config.for_cop(cop.cop_name)
201
203
 
202
204
  cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config)
203
205
 
@@ -14,12 +14,13 @@ module RuboCop
14
14
  # # bad
15
15
  # class Foo
16
16
  # attr_reader :bar
17
+ # attr_reader :bax
17
18
  # attr_reader :baz
18
19
  # end
19
20
  #
20
21
  # # good
21
22
  # class Foo
22
- # attr_reader :bar, :baz
23
+ # attr_reader :bar, :bax, :baz
23
24
  # end
24
25
  #
25
26
  # # good
@@ -27,6 +28,9 @@ module RuboCop
27
28
  # # may be intended comment for bar.
28
29
  # attr_reader :bar
29
30
  #
31
+ # sig { returns(String) }
32
+ # attr_reader :bax
33
+ #
30
34
  # may_be_intended_annotation :baz
31
35
  # attr_reader :baz
32
36
  # end
@@ -66,7 +70,7 @@ module RuboCop
66
70
 
67
71
  def check(send_node)
68
72
  return if previous_line_comment?(send_node) || !groupable_accessor?(send_node)
69
- return unless (grouped_style? && sibling_accessors(send_node).size > 1) ||
73
+ return unless (grouped_style? && groupable_sibling_accessors(send_node).size > 1) ||
70
74
  (separated_style? && send_node.arguments.size > 1)
71
75
 
72
76
  message = message(send_node)
@@ -90,6 +94,14 @@ module RuboCop
90
94
 
91
95
  def groupable_accessor?(node)
92
96
  return true unless (previous_expression = node.left_siblings.last)
97
+
98
+ # Accessors with Sorbet `sig { ... }` blocks shouldn't be groupable.
99
+ if previous_expression.block_type?
100
+ previous_expression.child_nodes.each do |child_node|
101
+ break previous_expression = child_node if child_node.send_type?
102
+ end
103
+ end
104
+
93
105
  return true unless previous_expression.send_type?
94
106
 
95
107
  previous_expression.attribute_accessor? || previous_expression.access_modifier?
@@ -115,12 +127,12 @@ module RuboCop
115
127
  style == :separated
116
128
  end
117
129
 
118
- def sibling_accessors(send_node)
130
+ def groupable_sibling_accessors(send_node)
119
131
  send_node.parent.each_child_node(:send).select do |sibling|
120
132
  sibling.attribute_accessor? &&
121
133
  sibling.method?(send_node.method_name) &&
122
134
  node_visibility(sibling) == node_visibility(send_node) &&
123
- !previous_line_comment?(sibling)
135
+ groupable_accessor?(sibling) && !previous_line_comment?(sibling)
124
136
  end
125
137
  end
126
138
 
@@ -131,7 +143,7 @@ module RuboCop
131
143
 
132
144
  def preferred_accessors(node)
133
145
  if grouped_style?
134
- accessors = sibling_accessors(node)
146
+ accessors = groupable_sibling_accessors(node)
135
147
  group_accessors(node, accessors) if node.loc == accessors.first.loc
136
148
  else
137
149
  separate_accessors(node)
@@ -342,6 +342,7 @@ module RuboCop
342
342
  end
343
343
 
344
344
  def proper_block_style?(node)
345
+ return true if require_braces?(node)
345
346
  return special_method_proper_block_style?(node) if special_method?(node.method_name)
346
347
 
347
348
  case style
@@ -352,6 +353,14 @@ module RuboCop
352
353
  end
353
354
  end
354
355
 
356
+ def require_braces?(node)
357
+ return false unless node.braces?
358
+
359
+ node.each_ancestor(:send).any? do |send|
360
+ send.arithmetic_operation? && node.source_range.end_pos < send.loc.selector.begin_pos
361
+ end
362
+ end
363
+
355
364
  def special_method?(method_name)
356
365
  allowed_method?(method_name) ||
357
366
  matches_allowed_pattern?(method_name) ||
@@ -11,12 +11,14 @@ module RuboCop
11
11
  # so if the original conditional used a different equality operator, the
12
12
  # behavior may be different.
13
13
  #
14
- # @example
14
+ # @example MinBranchesCount: 3 (default)
15
15
  # # bad
16
16
  # if status == :active
17
17
  # perform_action
18
18
  # elsif status == :inactive || status == :hibernating
19
19
  # check_timeout
20
+ # elsif status == :invalid
21
+ # report_invalid
20
22
  # else
21
23
  # final_action
22
24
  # end
@@ -27,12 +29,27 @@ module RuboCop
27
29
  # perform_action
28
30
  # when :inactive, :hibernating
29
31
  # check_timeout
32
+ # when :invalid
33
+ # report_invalid
34
+ # else
35
+ # final_action
36
+ # end
37
+ #
38
+ # @example MinBranchesCount: 4
39
+ # # good
40
+ # if status == :active
41
+ # perform_action
42
+ # elsif status == :inactive || status == :hibernating
43
+ # check_timeout
44
+ # elsif status == :invalid
45
+ # report_invalid
30
46
  # else
31
47
  # final_action
32
48
  # end
33
49
  #
34
50
  class CaseLikeIf < Base
35
51
  include RangeHelp
52
+ include MinBranchesCount
36
53
  extend AutoCorrector
37
54
 
38
55
  MSG = 'Convert `if-elsif` to `case-when`.'
@@ -78,7 +95,7 @@ module RuboCop
78
95
 
79
96
  def should_check?(node)
80
97
  !node.unless? && !node.elsif? && !node.modifier_form? && !node.ternary? &&
81
- node.elsif_conditional?
98
+ node.elsif_conditional? && min_branches_count?(node)
82
99
  end
83
100
 
84
101
  # rubocop:disable Metrics/MethodLength
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer to use `Dir.empty?('path/to/dir')` when checking if a directory is empty.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Dir.entries('path/to/dir').size == 2
11
+ # Dir.children('path/to/dir').empty?
12
+ # Dir.children('path/to/dir').size == 0
13
+ # Dir.each_child('path/to/dir').none?
14
+ #
15
+ # # good
16
+ # Dir.empty?('path/to/dir')
17
+ #
18
+ class DirEmpty < Base
19
+ extend AutoCorrector
20
+ extend TargetRubyVersion
21
+
22
+ MSG = 'Use `Dir.empty?(%<arg>s)` instead.'
23
+ RESTRICT_ON_SEND = %i[== > empty? none?].freeze
24
+
25
+ minimum_target_ruby_version 2.4
26
+
27
+ # @!method offensive?(node)
28
+ def_node_matcher :offensive?, <<~PATTERN
29
+ {
30
+ (send (send (send $(const {nil? cbase} :Dir) :entries $_) :size) {:== :>} (int 2))
31
+ (send (send (send $(const {nil? cbase} :Dir) :children $_) :size) {:== :>} (int 0))
32
+ (send (send (send (send $(const {nil? cbase} :Dir) :entries $_) :size) :!) {:== :>} (int 2))
33
+ (send (send (send (send $(const {nil? cbase} :Dir) :children $_) :size) :!) {:== :>} (int 0))
34
+ (send (send $(const {nil? cbase} :Dir) :children $_) :empty?)
35
+ (send (send $(const {nil? cbase} :Dir) :each_child $_) :none?)
36
+ }
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ offensive?(node) do |const_node, arg_node|
41
+ add_offense(node, message: format(MSG, arg: arg_node.source)) do |corrector|
42
+ bang(node)
43
+ corrector.replace(node,
44
+ "#{bang(node)}#{const_node.source}.empty?(#{arg_node.source})")
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def bang(node)
52
+ if (node.method?(:==) && node.child_nodes.first.method?(:!)) ||
53
+ (node.method?(:>) && !node.child_nodes.first.method?(:!))
54
+ '!'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -178,13 +178,19 @@ module RuboCop
178
178
  def identifier(node)
179
179
  # Get the fully qualified identifier for a class/module
180
180
  nodes = [node, *node.each_ancestor(:class, :module)]
181
- nodes.reverse_each.flat_map { |n| qualify_const(n.identifier) }.join('::')
181
+ identifier = nodes.reverse_each.flat_map { |n| qualify_const(n.identifier) }.join('::')
182
+
183
+ identifier.sub('::::', '::')
182
184
  end
183
185
 
184
186
  def qualify_const(node)
185
- return if node.nil? || node.cbase_type? || node.self_type? || node.send_type?
187
+ return if node.nil?
186
188
 
187
- [qualify_const(node.namespace), node.short_name].compact
189
+ if node.cbase_type? || node.self_type? || node.call_type? || node.variable?
190
+ node.source
191
+ else
192
+ [qualify_const(node.namespace), node.short_name].compact
193
+ end
188
194
  end
189
195
  end
190
196
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer to use `File.empty?('path/to/file')` when checking if a file is empty.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe it because `File.size`, `File.read`,
10
+ # and `File.binread` raise `ENOENT` exception when there is no file
11
+ # corresponding to the path, while `File.empty?` does not raise an exception.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # File.zero?('path/to/file')
16
+ # File.size('path/to/file') == 0
17
+ # File.size('path/to/file') >= 0
18
+ # File.size('path/to/file').zero?
19
+ # File.read('path/to/file').empty?
20
+ # File.binread('path/to/file') == ''
21
+ # FileTest.zero?('path/to/file')
22
+ #
23
+ # # good
24
+ # File.empty?('path/to/file')
25
+ # FileTest.empty?('path/to/file')
26
+ #
27
+ class FileEmpty < Base
28
+ extend AutoCorrector
29
+ extend TargetRubyVersion
30
+
31
+ MSG = 'Use `%<file_class>s.empty?(%<arg>s)` instead.'
32
+ RESTRICT_ON_SEND = %i[>= != == zero? empty?].freeze
33
+
34
+ minimum_target_ruby_version 2.4
35
+
36
+ # @!method offensive?(node)
37
+ def_node_matcher :offensive?, <<~PATTERN
38
+ {
39
+ (send $(const {nil? cbase} {:File :FileTest}) :zero? $_)
40
+ (send (send $(const {nil? cbase} {:File :FileTest}) :size $_) {:== :>=} (int 0))
41
+ (send (send (send $(const {nil? cbase} {:File :FileTest}) :size $_) :!) {:== :>=} (int 0))
42
+ (send (send $(const {nil? cbase} {:File :FileTest}) :size $_) :zero?)
43
+ (send (send $(const {nil? cbase} {:File :FileTest}) {:read :binread} $_) {:!= :==} (str empty?))
44
+ (send (send (send $(const {nil? cbase} {:File :FileTest}) {:read :binread} $_) :!) {:!= :==} (str empty?))
45
+ (send (send $(const {nil? cbase} {:File :FileTest}) {:read :binread} $_) :empty?)
46
+ }
47
+ PATTERN
48
+
49
+ def on_send(node)
50
+ offensive?(node) do |const_node, arg_node|
51
+ add_offense(node,
52
+ message: format(MSG, file_class: const_node.source,
53
+ arg: arg_node.source)) do |corrector|
54
+ corrector.replace(node,
55
+ "#{bang(node)}#{const_node.source}.empty?(#{arg_node.source})")
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def bang(node)
63
+ if (node.method?(:==) && node.child_nodes.first.method?(:!)) ||
64
+ (%i[>= !=].include?(node.method_name) && !node.child_nodes.first.method?(:!))
65
+ '!'
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -37,6 +37,8 @@ module RuboCop
37
37
  # end
38
38
  #
39
39
  class HashLikeCase < Base
40
+ include MinBranchesCount
41
+
40
42
  MSG = 'Consider replacing `case-when` with a hash lookup.'
41
43
 
42
44
  # @!method hash_like_case?(node)
@@ -49,7 +51,7 @@ module RuboCop
49
51
  PATTERN
50
52
 
51
53
  def on_case(node)
52
- return if node.when_branches.size < min_branches_count
54
+ return unless min_branches_count?(node)
53
55
 
54
56
  hash_like_case?(node) do |condition_nodes, body_nodes|
55
57
  if nodes_of_same_type?(condition_nodes) && nodes_of_same_type?(body_nodes)
@@ -63,14 +65,6 @@ module RuboCop
63
65
  def nodes_of_same_type?(nodes)
64
66
  nodes.all? { |node| node.type == nodes.first.type }
65
67
  end
66
-
67
- def min_branches_count
68
- length = cop_config['MinBranchesCount'] || 3
69
- return length if length.is_a?(Integer) && length.positive?
70
-
71
- warn Rainbow('`MinBranchesCount` needs to be a positive integer!').red
72
- exit!
73
- end
74
68
  end
75
69
  end
76
70
  end
@@ -53,6 +53,7 @@ module RuboCop
53
53
  include LineLengthHelp
54
54
  include AllowedPattern
55
55
  include RangeHelp
56
+ include CommentsHelp
56
57
  extend AutoCorrector
57
58
 
58
59
  MSG_USE_MODIFIER = 'Favor modifier `%<keyword>s` usage when having a ' \
@@ -101,26 +102,43 @@ module RuboCop
101
102
 
102
103
  def autocorrect(corrector, node)
103
104
  replacement = if node.modifier_form?
104
- last_argument = node.if_branch.last_argument if node.if_branch.send_type?
105
-
106
- if last_argument.respond_to?(:heredoc?) && last_argument.heredoc?
107
- heredoc = extract_heredoc_from(last_argument)
108
- remove_heredoc(corrector, heredoc)
109
- to_normal_form_with_heredoc(node, indent(node), heredoc)
110
- else
111
- to_normal_form(node, indent(node))
112
- end
105
+ replacement_for_modifier_form(corrector, node)
113
106
  else
114
107
  to_modifier_form(node)
115
108
  end
116
109
  corrector.replace(node, replacement)
117
110
  end
118
111
 
112
+ def replacement_for_modifier_form(corrector, node) # rubocop:disable Metrics/AbcSize
113
+ comment = comment_on_node_line(node)
114
+ if comment && too_long_due_to_comment_after_modifier?(node, comment)
115
+ remove_comment(corrector, node, comment)
116
+
117
+ return to_modifier_form_with_move_comment(node, indent(node), comment)
118
+ end
119
+
120
+ last_argument = node.if_branch.last_argument if node.if_branch.send_type?
121
+ if last_argument.respond_to?(:heredoc?) && last_argument.heredoc?
122
+ heredoc = extract_heredoc_from(last_argument)
123
+ remove_heredoc(corrector, heredoc)
124
+
125
+ return to_normal_form_with_heredoc(node, indent(node), heredoc)
126
+ end
127
+
128
+ to_normal_form(node, indent(node))
129
+ end
130
+
119
131
  def too_long_due_to_modifier?(node)
120
132
  node.modifier_form? && too_long_single_line?(node) &&
121
133
  !another_statement_on_same_line?(node)
122
134
  end
123
135
 
136
+ def too_long_due_to_comment_after_modifier?(node, comment)
137
+ source_length = processed_source.lines[node.first_line - 1].length
138
+ source_length >= max_line_length &&
139
+ source_length - comment.source_range.length <= max_line_length
140
+ end
141
+
124
142
  def allowed_patterns
125
143
  line_length_config = config.for_cop('Layout/LineLength')
126
144
  line_length_config['AllowedPatterns'] || line_length_config['IgnoredPatterns'] || []
@@ -215,6 +233,13 @@ module RuboCop
215
233
  RUBY
216
234
  end
217
235
 
236
+ def to_modifier_form_with_move_comment(node, indentation, comment)
237
+ <<~RUBY.chomp
238
+ #{comment.source}
239
+ #{indentation}#{node.body.source} #{node.keyword} #{node.condition.source}
240
+ RUBY
241
+ end
242
+
218
243
  def extract_heredoc_from(last_argument)
219
244
  heredoc_body = last_argument.loc.heredoc_body
220
245
  heredoc_end = last_argument.loc.heredoc_end
@@ -227,6 +252,14 @@ module RuboCop
227
252
  corrector.remove(range_by_whole_lines(range, include_final_newline: true))
228
253
  end
229
254
  end
255
+
256
+ def comment_on_node_line(node)
257
+ processed_source.comments.find { |c| same_line?(c, node) }
258
+ end
259
+
260
+ def remove_comment(corrector, _node, comment)
261
+ corrector.remove(range_with_surrounding_space(range: comment.source_range, side: :left))
262
+ end
230
263
  end
231
264
  end
232
265
  end
@@ -118,6 +118,8 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def return_boolean_value?(condition)
121
+ return false unless condition
122
+
121
123
  if condition.begin_type?
122
124
  return_boolean_value?(condition.children.first)
123
125
  elsif condition.or_type?
@@ -48,13 +48,11 @@ module RuboCop
48
48
 
49
49
  def on_if(node)
50
50
  return unless if_else?(node)
51
-
52
- condition = unwrap_begin_nodes(node.condition)
53
-
51
+ return unless (condition = unwrap_begin_nodes(node.condition))
54
52
  return if double_negation?(condition) || !negated_condition?(condition)
55
53
 
56
- type = node.ternary? ? 'ternary' : 'if-else'
57
- add_offense(node, message: format(MSG, type: type)) do |corrector|
54
+ message = message(node)
55
+ add_offense(node, message: message) do |corrector|
58
56
  unless corrected_ancestor?(node)
59
57
  correct_negated_condition(corrector, condition)
60
58
  swap_branches(corrector, node)
@@ -73,7 +71,8 @@ module RuboCop
73
71
  end
74
72
 
75
73
  def unwrap_begin_nodes(node)
76
- node = node.children.first while node.begin_type? || node.kwbegin_type?
74
+ node = node.children.first while node && (node.begin_type? || node.kwbegin_type?)
75
+
77
76
  node
78
77
  end
79
78
 
@@ -82,6 +81,12 @@ module RuboCop
82
81
  (node.negation_method? || NEGATED_EQUALITY_METHODS.include?(node.method_name))
83
82
  end
84
83
 
84
+ def message(node)
85
+ type = node.ternary? ? 'ternary' : 'if-else'
86
+
87
+ format(MSG, type: type)
88
+ end
89
+
85
90
  def corrected_ancestor?(node)
86
91
  node.each_ancestor(:if).any? { |ancestor| @corrected_nodes&.include?(ancestor) }
87
92
  end
@@ -160,8 +160,8 @@ module RuboCop
160
160
  def roundup_relevant_cops(processed_source)
161
161
  cops.select do |cop|
162
162
  next true if processed_source.comment_config.cop_opted_in?(cop)
163
- next false unless @registry.enabled?(cop, @config)
164
163
  next false if cop.excluded_file?(processed_source.file_path)
164
+ next false unless @registry.enabled?(cop, @config)
165
165
 
166
166
  support_target_ruby_version?(cop) && support_target_rails_version?(cop)
167
167
  end
@@ -62,7 +62,10 @@ module RuboCop
62
62
  end
63
63
 
64
64
  def classname_attribute_value(file)
65
- file.gsub(/\.rb\Z/, '').gsub("#{Dir.pwd}/", '').tr('/', '.')
65
+ @classname_attribute_value_cache ||= Hash.new do |hash, key|
66
+ hash[key] = key.gsub(/\.rb\Z/, '').gsub("#{Dir.pwd}/", '').tr('/', '.')
67
+ end
68
+ @classname_attribute_value_cache[file]
66
69
  end
67
70
 
68
71
  def finished(_inspected_files)
@@ -25,4 +25,5 @@ RSpec.configure do |config|
25
25
  config.include_context 'ruby 3.0', :ruby30
26
26
  config.include_context 'ruby 3.1', :ruby31
27
27
  config.include_context 'ruby 3.2', :ruby32
28
+ config.include_context 'ruby 3.3', :ruby33
28
29
  end
@@ -100,7 +100,7 @@ module RuboCop
100
100
 
101
101
  def use_json_format?
102
102
  return true if ARGV.include?('--format=json') || ARGV.include?('--format=j')
103
- return false unless (index = ARGV.index('--format'))
103
+ return false unless (index = ARGV.index('--format') || ARGV.index('-f'))
104
104
 
105
105
  format = ARGV[index + 1]
106
106
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.47.0'
6
+ STRING = '1.48.1'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -102,6 +102,7 @@ require_relative 'rubocop/cop/mixin/hash_shorthand_syntax'
102
102
  require_relative 'rubocop/cop/mixin/method_complexity'
103
103
  require_relative 'rubocop/cop/mixin/method_preference'
104
104
  require_relative 'rubocop/cop/mixin/min_body_length'
105
+ require_relative 'rubocop/cop/mixin/min_branches_count'
105
106
  require_relative 'rubocop/cop/mixin/multiline_element_indentation'
106
107
  require_relative 'rubocop/cop/mixin/multiline_element_line_breaks'
107
108
  require_relative 'rubocop/cop/mixin/multiline_expression_indentation'
@@ -482,6 +483,7 @@ require_relative 'rubocop/cop/style/copyright'
482
483
  require_relative 'rubocop/cop/style/date_time'
483
484
  require_relative 'rubocop/cop/style/def_with_parentheses'
484
485
  require_relative 'rubocop/cop/style/dir'
486
+ require_relative 'rubocop/cop/style/dir_empty'
485
487
  require_relative 'rubocop/cop/style/disable_cops_within_source_code_directive'
486
488
  require_relative 'rubocop/cop/style/documentation_method'
487
489
  require_relative 'rubocop/cop/style/documentation'
@@ -507,6 +509,7 @@ require_relative 'rubocop/cop/style/expand_path_arguments'
507
509
  require_relative 'rubocop/cop/style/explicit_block_argument'
508
510
  require_relative 'rubocop/cop/style/exponential_notation'
509
511
  require_relative 'rubocop/cop/style/fetch_env_var'
512
+ require_relative 'rubocop/cop/style/file_empty'
510
513
  require_relative 'rubocop/cop/style/file_read'
511
514
  require_relative 'rubocop/cop/style/file_write'
512
515
  require_relative 'rubocop/cop/style/float_division'
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.47.0
4
+ version: 1.48.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
8
8
  - Jonas Arvidsson
9
9
  - Yuji Nakayama
10
- autorequire:
10
+ autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-03-01 00:00:00.000000000 Z
13
+ date: 2023-03-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -584,6 +584,7 @@ files:
584
584
  - lib/rubocop/cop/mixin/method_complexity.rb
585
585
  - lib/rubocop/cop/mixin/method_preference.rb
586
586
  - lib/rubocop/cop/mixin/min_body_length.rb
587
+ - lib/rubocop/cop/mixin/min_branches_count.rb
587
588
  - lib/rubocop/cop/mixin/multiline_element_indentation.rb
588
589
  - lib/rubocop/cop/mixin/multiline_element_line_breaks.rb
589
590
  - lib/rubocop/cop/mixin/multiline_expression_indentation.rb
@@ -685,6 +686,7 @@ files:
685
686
  - lib/rubocop/cop/style/date_time.rb
686
687
  - lib/rubocop/cop/style/def_with_parentheses.rb
687
688
  - lib/rubocop/cop/style/dir.rb
689
+ - lib/rubocop/cop/style/dir_empty.rb
688
690
  - lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb
689
691
  - lib/rubocop/cop/style/document_dynamic_eval_definition.rb
690
692
  - lib/rubocop/cop/style/documentation.rb
@@ -710,6 +712,7 @@ files:
710
712
  - lib/rubocop/cop/style/explicit_block_argument.rb
711
713
  - lib/rubocop/cop/style/exponential_notation.rb
712
714
  - lib/rubocop/cop/style/fetch_env_var.rb
715
+ - lib/rubocop/cop/style/file_empty.rb
713
716
  - lib/rubocop/cop/style/file_read.rb
714
717
  - lib/rubocop/cop/style/file_write.rb
715
718
  - lib/rubocop/cop/style/float_division.rb
@@ -985,10 +988,10 @@ metadata:
985
988
  homepage_uri: https://rubocop.org/
986
989
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
987
990
  source_code_uri: https://github.com/rubocop/rubocop/
988
- documentation_uri: https://docs.rubocop.org/rubocop/1.47/
991
+ documentation_uri: https://docs.rubocop.org/rubocop/1.48/
989
992
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
990
993
  rubygems_mfa_required: 'true'
991
- post_install_message:
994
+ post_install_message:
992
995
  rdoc_options: []
993
996
  require_paths:
994
997
  - lib
@@ -1003,8 +1006,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1003
1006
  - !ruby/object:Gem::Version
1004
1007
  version: '0'
1005
1008
  requirements: []
1006
- rubygems_version: 3.3.7
1007
- signing_key:
1009
+ rubygems_version: 3.1.2
1010
+ signing_key:
1008
1011
  specification_version: 4
1009
1012
  summary: Automatic Ruby code style checking tool.
1010
1013
  test_files: []