rubocop 1.37.1 → 1.38.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +6 -0
  4. data/lib/rubocop/comment_config.rb +36 -1
  5. data/lib/rubocop/cop/commissioner.rb +3 -1
  6. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  7. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  8. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -0
  9. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +6 -2
  10. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  11. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  12. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +11 -0
  13. data/lib/rubocop/cop/lint/redundant_require_statement.rb +10 -2
  14. data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -3
  15. data/lib/rubocop/cop/registry.rb +10 -4
  16. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  17. data/lib/rubocop/cop/style/collection_compact.rb +3 -1
  18. data/lib/rubocop/cop/style/guard_clause.rb +62 -21
  19. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +25 -2
  20. data/lib/rubocop/cop/style/module_function.rb +28 -6
  21. data/lib/rubocop/cop/style/redundant_each.rb +111 -0
  22. data/lib/rubocop/cop/team.rb +3 -4
  23. data/lib/rubocop/cop/variable_force/variable_table.rb +1 -1
  24. data/lib/rubocop/cops_documentation_generator.rb +4 -2
  25. data/lib/rubocop/ext/processed_source.rb +2 -0
  26. data/lib/rubocop/formatter/offense_count_formatter.rb +8 -5
  27. data/lib/rubocop/formatter/worst_offenders_formatter.rb +6 -3
  28. data/lib/rubocop/options.rb +6 -2
  29. data/lib/rubocop/rspec/cop_helper.rb +21 -1
  30. data/lib/rubocop/rspec/shared_contexts.rb +13 -12
  31. data/lib/rubocop/runner.rb +15 -11
  32. data/lib/rubocop/server/core.rb +1 -0
  33. data/lib/rubocop/version.rb +1 -1
  34. data/lib/rubocop.rb +1 -0
  35. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: caf1d6d45b2da199a82152b2c5b20ffa6a5f99df5115dbd8fbf4c4e895fd00c5
4
- data.tar.gz: 50520bd1d5496971e8b38347dfea57a3387a372b7a815b3c9ef2eb7c1fdc448a
3
+ metadata.gz: 1ed47ef39bb205db15249a399d0502f730c2656f712293357651cc0ddd328715
4
+ data.tar.gz: 5e6411a2b459e0ac426e0ef0d8b8508b9b1693fea0c5b8cb5d82ed23f71d39b9
5
5
  SHA512:
6
- metadata.gz: 0fa612429aa29b3ce4480fbbc7d77f1efc6862ac89098bfadf7c8f79a4e255709f6752f1b1075497b9ad17aa261afa006089152d10e0deb4f4361bce95537999
7
- data.tar.gz: b55827239ce8508d6f884f03838c7fe8ca89268c93ac7ab88409af176e527c74d0d24a14680fa3c7c84353eb078a97699429015b69e6efdb051582914d37ff30
6
+ metadata.gz: 5cfd13ac3c283e77e300f87ee923be282740f2763e26c73d21d27b5ae176629d15d8d580d9d326349acf5b71c74e6335689c2192050a818577b9f2e0a6a4484f
7
+ data.tar.gz: 27f36547ccdf6cc1336e1c44c5e164410f65344cba77fda76ad2db463054adaa93c96a406939b3826412280463a1d6a2940708f4080bc3170999bcc81594ba9d
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.37', require: false
56
+ gem 'rubocop', '~> 1.38', 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
@@ -4696,6 +4696,12 @@ Style/RedundantConditional:
4696
4696
  Enabled: true
4697
4697
  VersionAdded: '0.50'
4698
4698
 
4699
+ Style/RedundantEach:
4700
+ Description: 'Checks for redundant `each`.'
4701
+ Enabled: pending
4702
+ Safe: false
4703
+ VersionAdded: '1.38'
4704
+
4699
4705
  Style/RedundantException:
4700
4706
  Description: "Checks for an obsolete RuntimeException argument in raise/fail."
4701
4707
  StyleGuide: '#no-explicit-runtimeerror'
@@ -4,10 +4,31 @@ module RuboCop
4
4
  # This class parses the special `rubocop:disable` comments in a source
5
5
  # and provides a way to check if each cop is enabled at arbitrary line.
6
6
  class CommentConfig
7
+ extend Forwardable
8
+
9
+ CONFIG_DISABLED_LINE_RANGE_MIN = -Float::INFINITY
10
+
11
+ # This class provides an API compatible with RuboCop::DirectiveComment
12
+ # to be used for cops that are disabled in the config file
13
+ class ConfigDisabledCopDirectiveComment
14
+ attr_reader :text, :loc, :line_number
15
+
16
+ Loc = Struct.new(:expression)
17
+ Expression = Struct.new(:line)
18
+
19
+ def initialize(cop_name)
20
+ @text = "# rubocop:disable #{cop_name}"
21
+ @line_number = CONFIG_DISABLED_LINE_RANGE_MIN
22
+ @loc = Loc.new(Expression.new(CONFIG_DISABLED_LINE_RANGE_MIN))
23
+ end
24
+ end
25
+
7
26
  CopAnalysis = Struct.new(:line_ranges, :start_line_number)
8
27
 
9
28
  attr_reader :processed_source
10
29
 
30
+ def_delegators :@processed_source, :config, :registry
31
+
11
32
  def initialize(processed_source)
12
33
  @processed_source = processed_source
13
34
  end
@@ -25,7 +46,11 @@ module RuboCop
25
46
  end
26
47
 
27
48
  def extra_enabled_comments
28
- extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: Hash.new(0))
49
+ disable_count = Hash.new(0)
50
+ registry.disabled(config).each do |cop|
51
+ disable_count[cop.cop_name] += 1
52
+ end
53
+ extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: disable_count)
29
54
  end
30
55
 
31
56
  def comment_only_line?(line_number)
@@ -50,6 +75,7 @@ module RuboCop
50
75
 
51
76
  def analyze # rubocop:todo Metrics/AbcSize
52
77
  analyses = Hash.new { |hash, key| hash[key] = CopAnalysis.new([], nil) }
78
+ inject_disabled_cops_directives(analyses)
53
79
 
54
80
  each_directive do |directive|
55
81
  directive.cop_names.each do |cop_name|
@@ -64,6 +90,15 @@ module RuboCop
64
90
  end
65
91
  end
66
92
 
93
+ def inject_disabled_cops_directives(analyses)
94
+ registry.disabled(config).each do |cop|
95
+ analyses[cop.cop_name] = analyze_cop(
96
+ analyses[cop.cop_name],
97
+ DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop.cop_name))
98
+ )
99
+ end
100
+ end
101
+
67
102
  def analyze_cop(analysis, directive)
68
103
  # Disabling cops after comments like `#=SomeDslDirective` does not related to single line
69
104
  if !comment_only_line?(directive.line_number) || directive.single_line?
@@ -159,9 +159,11 @@ module RuboCop
159
159
  def with_cop_error_handling(cop, node = nil)
160
160
  yield
161
161
  rescue StandardError => e
162
- raise e if @options[:raise_error]
162
+ raise e if @options[:raise_error] # For internal testing
163
163
 
164
164
  err = ErrorWithAnalyzedFileLocation.new(cause: e, node: node, cop: cop)
165
+ raise err if @options[:raise_cop_error] # From user-input option
166
+
165
167
  @errors << err
166
168
  end
167
169
  end
@@ -90,7 +90,7 @@ module RuboCop
90
90
  # which lines start inside a string literal?
91
91
  return [] if ast.nil?
92
92
 
93
- ast.each_node(:str, :dstr).each_with_object(Set.new) do |str, ranges|
93
+ ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
94
94
  loc = str.location
95
95
 
96
96
  if str.heredoc?
@@ -92,7 +92,7 @@ module RuboCop
92
92
  # which lines start inside a string literal?
93
93
  return [] if ast.nil?
94
94
 
95
- ast.each_node(:str, :dstr).each_with_object(Set.new) do |str, ranges|
95
+ ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
96
96
  loc = str.location
97
97
 
98
98
  if str.heredoc?
@@ -194,6 +194,8 @@ module RuboCop
194
194
  end
195
195
 
196
196
  def range_inside_hash(node)
197
+ return node.source_range if node.location.begin.nil?
198
+
197
199
  range_between(node.location.begin.end_pos, node.location.end.begin_pos)
198
200
  end
199
201
 
@@ -38,6 +38,8 @@ module RuboCop
38
38
  # # bad
39
39
  # foo[ ]
40
40
  # foo[ ]
41
+ # foo[
42
+ # ]
41
43
  #
42
44
  # # good
43
45
  # foo[]
@@ -49,6 +51,8 @@ module RuboCop
49
51
  # # bad
50
52
  # foo[]
51
53
  # foo[ ]
54
+ # foo[
55
+ # ]
52
56
  #
53
57
  # # good
54
58
  # foo[ ]
@@ -64,8 +68,6 @@ module RuboCop
64
68
  RESTRICT_ON_SEND = %i[[] []=].freeze
65
69
 
66
70
  def on_send(node)
67
- return if node.multiline?
68
-
69
71
  tokens = processed_source.tokens_within(node)
70
72
  left_token = left_ref_bracket(node, tokens)
71
73
  return unless left_token
@@ -76,6 +78,8 @@ module RuboCop
76
78
  return empty_offenses(node, left_token, right_token, EMPTY_MSG)
77
79
  end
78
80
 
81
+ return if node.multiline?
82
+
79
83
  if style == :no_space
80
84
  no_space_offenses(node, left_token, right_token, MSG)
81
85
  else
@@ -141,7 +141,7 @@ module RuboCop
141
141
  if empty_if_branch?(node) && else_branch?(node)
142
142
  node.source_range.with(end_pos: node.loc.else.begin_pos)
143
143
  elsif node.loc.else
144
- node.source_range.with(end_pos: node.loc.else.begin_pos - 1)
144
+ node.source_range.with(end_pos: node.condition.loc.expression.end_pos)
145
145
  elsif all_branches_body_missing?(node)
146
146
  if_node = node.ancestors.detect(&:if?)
147
147
  node.source_range.with(end_pos: if_node.loc.end.end_pos)
@@ -50,9 +50,7 @@ module RuboCop
50
50
 
51
51
  def on_new_investigation
52
52
  each_missing_enable do |cop, line_range|
53
- # This has to remain a strict inequality to handle
54
- # the case when max_range is Float::INFINITY
55
- next if line_range.max - line_range.min < max_range + 2
53
+ next if acceptable_range?(cop, line_range)
56
54
 
57
55
  range = source_range(processed_source.buffer, line_range.min, (0..0))
58
56
  comment = processed_source.comment_at_line(line_range.begin)
@@ -69,6 +67,23 @@ module RuboCop
69
67
  end
70
68
  end
71
69
 
70
+ def acceptable_range?(cop, line_range)
71
+ # This has to remain a strict inequality to handle
72
+ # the case when max_range is Float::INFINITY
73
+ return true if line_range.max - line_range.min < max_range + 2
74
+ # This cop is disabled in the config, it is not expected to be re-enabled
75
+ return true if line_range.min == CommentConfig::CONFIG_DISABLED_LINE_RANGE_MIN
76
+
77
+ cop_class = RuboCop::Cop::Registry.global.find_by_cop_name cop
78
+ if cop_class &&
79
+ !processed_source.registry.enabled?(cop_class, config) &&
80
+ line_range.max == Float::INFINITY
81
+ return true
82
+ end
83
+
84
+ false
85
+ end
86
+
72
87
  def max_range
73
88
  @max_range ||= cop_config['MaximumRangeSize']
74
89
  end
@@ -113,6 +113,7 @@ module RuboCop
113
113
  def each_line_range(cop, line_ranges)
114
114
  line_ranges.each_with_index do |line_range, line_range_index|
115
115
  next if ignore_offense?(line_range)
116
+ next if expected_final_disable?(cop, line_range)
116
117
 
117
118
  comment = processed_source.comment_at_line(line_range.begin)
118
119
  redundant = if all_disabled?(comment)
@@ -179,11 +180,21 @@ module RuboCop
179
180
  end
180
181
 
181
182
  def ignore_offense?(line_range)
183
+ return true if line_range.min == CommentConfig::CONFIG_DISABLED_LINE_RANGE_MIN
184
+
182
185
  disabled_ranges.any? do |range|
183
186
  range.cover?(line_range.min) && range.cover?(line_range.max)
184
187
  end
185
188
  end
186
189
 
190
+ def expected_final_disable?(cop, line_range)
191
+ # A cop which is disabled in the config is being re-disabled until end of file
192
+ cop_class = RuboCop::Cop::Registry.global.find_by_cop_name cop
193
+ cop_class &&
194
+ !processed_source.registry.enabled?(cop_class, config) &&
195
+ line_range.max == Float::INFINITY
196
+ end
197
+
187
198
  def department_disabled?(cop, comment)
188
199
  directive = DirectiveComment.new(comment)
189
200
  directive.in_directive_department?(cop) && !directive.overridden_by_department?(cop)
@@ -20,6 +20,7 @@ module RuboCop
20
20
  # * 2.5+ ... Add `pp` above
21
21
  # * 2.7+ ... Add `ruby2_keywords` above
22
22
  # * 3.1+ ... Add `fiber` above
23
+ # * 3.2+ ... `set`
23
24
  #
24
25
  # This cop target those features.
25
26
  #
@@ -48,7 +49,13 @@ module RuboCop
48
49
  return unless redundant_require_statement?(node)
49
50
 
50
51
  add_offense(node) do |corrector|
51
- range = range_with_surrounding_space(node.loc.expression, side: :right)
52
+ if node.parent.respond_to?(:modifier_form?) && node.parent.modifier_form?
53
+ corrector.insert_after(node.parent, "\nend")
54
+
55
+ range = range_with_surrounding_space(node.loc.expression, side: :right)
56
+ else
57
+ range = range_by_whole_lines(node.source_range, include_final_newline: true)
58
+ end
52
59
 
53
60
  corrector.remove(range)
54
61
  end
@@ -63,7 +70,8 @@ module RuboCop
63
70
  (target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
64
71
  (target_ruby_version >= 2.5 && feature_name == 'pp') ||
65
72
  (target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
66
- (target_ruby_version >= 3.1 && feature_name == 'fiber')
73
+ (target_ruby_version >= 3.1 && feature_name == 'fiber') ||
74
+ (target_ruby_version >= 3.2 && feature_name == 'set')
67
75
  end
68
76
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
69
77
  end
@@ -118,14 +118,15 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def offending_empty_no_space?(config, left_token, right_token)
121
- config == 'no_space' && !no_space_between?(left_token, right_token)
121
+ config == 'no_space' && !no_character_between?(left_token, right_token)
122
122
  end
123
123
 
124
124
  def space_between?(left_bracket_token, right_bracket_token)
125
- left_bracket_token.end_pos + 1 == right_bracket_token.begin_pos
125
+ left_bracket_token.end_pos + 1 == right_bracket_token.begin_pos &&
126
+ processed_source.buffer.source[left_bracket_token.end_pos] == ' '
126
127
  end
127
128
 
128
- def no_space_between?(left_bracket_token, right_bracket_token)
129
+ def no_character_between?(left_bracket_token, right_bracket_token)
129
130
  left_bracket_token.end_pos == right_bracket_token.begin_pos
130
131
  end
131
132
  end
@@ -149,16 +149,22 @@ module RuboCop
149
149
  @registry.size
150
150
  end
151
151
 
152
- def enabled(config, only = [], only_safe: false)
153
- select { |cop| only.include?(cop.cop_name) || enabled?(cop, config, only_safe) }
152
+ def enabled(config)
153
+ select { |cop| enabled?(cop, config) }
154
154
  end
155
155
 
156
- def enabled?(cop, config, only_safe)
156
+ def disabled(config)
157
+ reject { |cop| enabled?(cop, config) }
158
+ end
159
+
160
+ def enabled?(cop, config)
161
+ return true if options.fetch(:only, []).include?(cop.cop_name)
162
+
157
163
  cfg = config.for_cop(cop)
158
164
 
159
165
  cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config)
160
166
 
161
- if only_safe
167
+ if options.fetch(:safe, false)
162
168
  cop_enabled && cfg.fetch('Safe', true)
163
169
  else
164
170
  cop_enabled
@@ -425,7 +425,7 @@ module RuboCop
425
425
  if node.parent.begin_type?
426
426
  return_value_used?(node.parent)
427
427
  else
428
- node.parent.assignment? || node.parent.send_type?
428
+ node.parent.assignment? || node.parent.call_type?
429
429
  end
430
430
  end
431
431
 
@@ -8,7 +8,9 @@ module RuboCop
8
8
  #
9
9
  # @safety
10
10
  # It is unsafe by default because false positives may occur in the
11
- # `nil` check of block arguments to the receiver object.
11
+ # `nil` check of block arguments to the receiver object. Additionally,
12
+ # we can't know the type of the receiver object for sure, which may
13
+ # result in false positives as well.
12
14
  #
13
15
  # For example, `[[1, 2], [3, nil]].reject { |first, second| second.nil? }`
14
16
  # and `[[1, 2], [3, nil]].compact` are not compatible. This will work fine
@@ -10,6 +10,9 @@ module RuboCop
10
10
  # one of `return`, `break`, `next`, `raise`, or `fail` is used
11
11
  # in the body of the conditional expression.
12
12
  #
13
+ # NOTE: Autocorrect works in most cases except with if-else statements
14
+ # that contain logical operators such as `foo || raise('exception')`
15
+ #
13
16
  # @example
14
17
  # # bad
15
18
  # def test
@@ -90,6 +93,7 @@ module RuboCop
90
93
  # end
91
94
  #
92
95
  class GuardClause < Base
96
+ extend AutoCorrector
93
97
  include MinBodyLength
94
98
  include StatementModifier
95
99
 
@@ -101,40 +105,49 @@ module RuboCop
101
105
 
102
106
  return unless body
103
107
 
104
- if body.if_type?
105
- check_ending_if(body)
106
- elsif body.begin_type?
107
- final_expression = body.children.last
108
- check_ending_if(final_expression) if final_expression&.if_type?
109
- end
108
+ check_ending_body(body)
110
109
  end
111
110
  alias on_defs on_def
112
111
 
113
112
  def on_if(node)
114
113
  return if accepted_form?(node)
115
114
 
116
- guard_clause_in_if = node.if_branch&.guard_clause?
117
- guard_clause_in_else = node.else_branch&.guard_clause?
118
- guard_clause = guard_clause_in_if || guard_clause_in_else
119
- return unless guard_clause
115
+ if (guard_clause = node.if_branch&.guard_clause?)
116
+ kw = node.loc.keyword.source
117
+ guard = :if
118
+ elsif (guard_clause = node.else_branch&.guard_clause?)
119
+ kw = node.inverse_keyword
120
+ guard = :else
121
+ else
122
+ return
123
+ end
120
124
 
121
- kw = if guard_clause_in_if
122
- node.loc.keyword.source
123
- else
124
- node.inverse_keyword
125
- end
125
+ guard = nil if and_or_guard_clause?(guard_clause)
126
126
 
127
- register_offense(node, guard_clause_source(guard_clause), kw)
127
+ register_offense(node, guard_clause_source(guard_clause), kw, guard)
128
128
  end
129
129
 
130
130
  private
131
131
 
132
+ def check_ending_body(body)
133
+ return if body.nil?
134
+
135
+ if body.if_type?
136
+ check_ending_if(body)
137
+ elsif body.begin_type?
138
+ final_expression = body.children.last
139
+ check_ending_if(final_expression) if final_expression&.if_type?
140
+ end
141
+ end
142
+
132
143
  def check_ending_if(node)
133
144
  return if accepted_form?(node, ending: true) || !min_body_length?(node)
134
145
  return if allowed_consecutive_conditionals? &&
135
146
  consecutive_conditionals?(node.parent, node)
136
147
 
137
148
  register_offense(node, 'return', node.inverse_keyword)
149
+
150
+ check_ending_body(node.if_branch)
138
151
  end
139
152
 
140
153
  def consecutive_conditionals?(parent, node)
@@ -145,28 +158,56 @@ module RuboCop
145
158
  end
146
159
  end
147
160
 
148
- def register_offense(node, scope_exiting_keyword, conditional_keyword)
161
+ def register_offense(node, scope_exiting_keyword, conditional_keyword, guard = nil)
149
162
  condition, = node.node_parts
150
163
  example = [scope_exiting_keyword, conditional_keyword, condition.source].join(' ')
151
164
  if too_long_for_single_line?(node, example)
152
165
  return if trivial?(node)
153
166
 
154
167
  example = "#{conditional_keyword} #{condition.source}; #{scope_exiting_keyword}; end"
168
+ replacement = <<~RUBY.chomp
169
+ #{conditional_keyword} #{condition.source}
170
+ #{scope_exiting_keyword}
171
+ end
172
+ RUBY
155
173
  end
156
174
 
157
- add_offense(node.loc.keyword, message: format(MSG, example: example))
175
+ add_offense(node.loc.keyword, message: format(MSG, example: example)) do |corrector|
176
+ next if node.else? && guard.nil?
177
+
178
+ autocorrect(corrector, node, condition, replacement || example, guard)
179
+ end
158
180
  end
159
181
 
160
- def guard_clause_source(guard_clause)
161
- parent = guard_clause.parent
182
+ def autocorrect(corrector, node, condition, replacement, guard)
183
+ corrector.replace(node.loc.keyword.join(condition.loc.expression), replacement)
184
+ corrector.remove(node.loc.end)
185
+ return unless node.else?
186
+
187
+ corrector.remove(node.loc.else)
188
+ corrector.remove(branch_to_remove(node, guard))
189
+ end
190
+
191
+ def branch_to_remove(node, guard)
192
+ case guard
193
+ when :if then node.if_branch
194
+ when :else then node.else_branch
195
+ end
196
+ end
162
197
 
163
- if parent.and_type? || parent.or_type?
198
+ def guard_clause_source(guard_clause)
199
+ if and_or_guard_clause?(guard_clause)
164
200
  guard_clause.parent.source
165
201
  else
166
202
  guard_clause.source
167
203
  end
168
204
  end
169
205
 
206
+ def and_or_guard_clause?(guard_clause)
207
+ parent = guard_clause.parent
208
+ parent.and_type? || parent.or_type?
209
+ end
210
+
170
211
  def too_long_for_single_line?(node, example)
171
212
  max = max_line_length
172
213
  max && node.source_range.column + example.length > max
@@ -5,10 +5,27 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for redundant `if` with boolean literal branches.
7
7
  # It checks only conditions to return boolean value (`true` or `false`) for safe detection.
8
- # The conditions to be checked are comparison methods, predicate methods, and double negative.
8
+ # The conditions to be checked are comparison methods, predicate methods, and
9
+ # double negation (!!).
9
10
  # `nonzero?` method is allowed by default.
10
11
  # These are customizable with `AllowedMethods` option.
11
12
  #
13
+ # This cop targets only `if`s with a single `elsif` or `else` branch. The following
14
+ # code will be allowed, because it has two `elsif` branches:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # if foo
19
+ # true
20
+ # elsif bar > baz
21
+ # true
22
+ # elsif qux > quux # Single `elsif` is warned, but two or more `elsif`s are not.
23
+ # true
24
+ # else
25
+ # false
26
+ # end
27
+ # ----
28
+ #
12
29
  # @safety
13
30
  # Autocorrection is unsafe because there is no guarantee that all predicate methods
14
31
  # will return a boolean value. Those methods can be allowed with `AllowedMethods` config.
@@ -57,7 +74,7 @@ module RuboCop
57
74
  def_node_matcher :double_negative?, '(send (send _ :!) :!)'
58
75
 
59
76
  def on_if(node)
60
- return unless if_with_boolean_literal_branches?(node)
77
+ return if !if_with_boolean_literal_branches?(node) || multiple_elsif?(node)
61
78
 
62
79
  condition = node.condition
63
80
  range, keyword = offense_range_with_keyword(node, condition)
@@ -76,6 +93,12 @@ module RuboCop
76
93
 
77
94
  private
78
95
 
96
+ def multiple_elsif?(node)
97
+ return false unless (parent = node.parent)
98
+
99
+ parent.if_type? && parent.elsif?
100
+ end
101
+
79
102
  def offense_range_with_keyword(node, condition)
80
103
  if node.ternary?
81
104
  range = condition.source_range.end.join(node.source_range.end)
@@ -3,13 +3,15 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for use of `extend self` or `module_function` in a
7
- # module.
6
+ # Checks for use of `extend self` or `module_function` in a module.
8
7
  #
9
- # Supported styles are: module_function, extend_self, forbidden. `forbidden`
10
- # style prohibits the usage of both styles.
8
+ # Supported styles are: `module_function` (default), `extend_self` and `forbidden`.
11
9
  #
12
- # NOTE: the cop won't be activated when the module contains any private methods.
10
+ # A couple of things to keep in mind:
11
+ #
12
+ # - `forbidden` style prohibits the usage of both styles
13
+ # - in default mode (`module_function`), the cop won't be activated when the module
14
+ # contains any private methods
13
15
  #
14
16
  # @safety
15
17
  # Autocorrection is unsafe (and is disabled by default) because `extend self`
@@ -28,7 +30,6 @@ module RuboCop
28
30
  # # ...
29
31
  # end
30
32
  #
31
- # @example EnforcedStyle: module_function (default)
32
33
  # # good
33
34
  # module Test
34
35
  # extend self
@@ -37,6 +38,13 @@ module RuboCop
37
38
  # # ...
38
39
  # end
39
40
  #
41
+ # # good
42
+ # module Test
43
+ # class << self
44
+ # # ...
45
+ # end
46
+ # end
47
+ #
40
48
  # @example EnforcedStyle: extend_self
41
49
  # # bad
42
50
  # module Test
@@ -50,6 +58,13 @@ module RuboCop
50
58
  # # ...
51
59
  # end
52
60
  #
61
+ # # good
62
+ # module Test
63
+ # class << self
64
+ # # ...
65
+ # end
66
+ # end
67
+ #
53
68
  # @example EnforcedStyle: forbidden
54
69
  # # bad
55
70
  # module Test
@@ -70,6 +85,13 @@ module RuboCop
70
85
  # private
71
86
  # # ...
72
87
  # end
88
+ #
89
+ # # good
90
+ # module Test
91
+ # class << self
92
+ # # ...
93
+ # end
94
+ # end
73
95
  class ModuleFunction < Base
74
96
  include ConfigurableEnforcedStyle
75
97
  extend AutoCorrector
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for redundant `each`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe, as it can produce false positives if the receiver
10
+ # is not an `Enumerable`.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # array.each.each { |v| do_something(v) }
16
+ #
17
+ # # good
18
+ # array.each { |v| do_something(v) }
19
+ #
20
+ # # bad
21
+ # array.each.each_with_index { |v, i| do_something(v, i) }
22
+ #
23
+ # # good
24
+ # array.each.with_index { |v, i| do_something(v, i) }
25
+ # array.each_with_index { |v, i| do_something(v, i) }
26
+ #
27
+ # # bad
28
+ # array.each.each_with_object { |v, o| do_something(v, o) }
29
+ #
30
+ # # good
31
+ # array.each.with_object { |v, o| do_something(v, o) }
32
+ # array.each_with_object { |v, o| do_something(v, o) }
33
+ #
34
+ class RedundantEach < Base
35
+ extend AutoCorrector
36
+
37
+ MSG = 'Remove redundant `each`.'
38
+ MSG_WITH_INDEX = 'Use `with_index` to remove redundant `each`.'
39
+ MSG_WITH_OBJECT = 'Use `with_object` to remove redundant `each`.'
40
+
41
+ RESTRICT_ON_SEND = %i[each each_with_index each_with_object].freeze
42
+
43
+ def on_send(node)
44
+ return unless (redundant_node = redundant_each_method(node))
45
+
46
+ range = range(node)
47
+
48
+ add_offense(range, message: message(node)) do |corrector|
49
+ case node.method_name
50
+ when :each
51
+ remove_redundant_each(corrector, range, redundant_node)
52
+ when :each_with_index
53
+ corrector.replace(node.loc.selector, 'with_index')
54
+ when :each_with_object
55
+ corrector.replace(node.loc.selector, 'with_object')
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
+ def redundant_each_method(node)
64
+ if node.method?(:each) && !node.parent.block_type?
65
+ ancestor_node = node.each_ancestor(:send).detect do |ancestor|
66
+ RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each)
67
+ end
68
+ end
69
+
70
+ ancestor_node || node.each_descendant(:send).detect do |descendant|
71
+ next if descendant.parent.block_type?
72
+
73
+ detected = descendant.method_name.to_s.start_with?('each_') unless node.method?(:each)
74
+
75
+ detected || descendant.method?(:reverse_each)
76
+ end
77
+ end
78
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
79
+
80
+ def range(node)
81
+ if node.method?(:each)
82
+ node.loc.dot.join(node.loc.selector)
83
+ else
84
+ node.loc.selector
85
+ end
86
+ end
87
+
88
+ def message(node)
89
+ case node.method_name
90
+ when :each
91
+ MSG
92
+ when :each_with_index
93
+ MSG_WITH_INDEX
94
+ when :each_with_object
95
+ MSG_WITH_OBJECT
96
+ end
97
+ end
98
+
99
+ def remove_redundant_each(corrector, range, redundant_node)
100
+ corrector.remove(range)
101
+
102
+ if redundant_node.method?(:each_with_index)
103
+ corrector.replace(redundant_node.loc.selector, 'each.with_index')
104
+ elsif redundant_node.method?(:each_with_object)
105
+ corrector.replace(redundant_node.loc.selector, 'each.with_object')
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -40,10 +40,9 @@ module RuboCop
40
40
 
41
41
  # @return [Array<Cop::Cop>]
42
42
  def self.mobilize_cops(cop_classes, config, options = {})
43
- cop_classes = Registry.new(cop_classes.to_a) unless cop_classes.is_a?(Registry)
44
- only = options.fetch(:only, [])
45
- safe = options.fetch(:safe, false)
46
- cop_classes.enabled(config, only, only_safe: safe).map do |cop_class|
43
+ cop_classes = Registry.new(cop_classes.to_a, options) unless cop_classes.is_a?(Registry)
44
+
45
+ cop_classes.enabled(config).map do |cop_class|
47
46
  cop_class.new(config, options)
48
47
  end
49
48
  end
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def accessible_variables
112
- scope_stack.reverse_each.each_with_object([]) do |scope, variables|
112
+ scope_stack.reverse_each.with_object([]) do |scope, variables|
113
113
  variables.concat(scope.variables.values)
114
114
  break variables unless scope.node.block_type?
115
115
  end
@@ -183,9 +183,11 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
183
183
  # rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength
184
184
 
185
185
  def to_table(header, content)
186
- table = ['|===', "| #{header.join(' | ')}\n\n"].join("\n")
186
+ # Specify `[separator=¦]` to prevent the regexp `|` is not used as a table separator.
187
+ # See: https://docs.asciidoctor.org/asciidoc/latest/tables/data-format/#escape-the-cell-separator
188
+ table = ['[separator=¦]', '|===', "| #{header.join(' | ')}\n\n"].join("\n")
187
189
  marked_contents = content.map do |plain_content|
188
- plain_content.map { |c| "| #{c}" }.join("\n")
190
+ plain_content.map { |c| "¦ #{c}" }.join("\n")
189
191
  end
190
192
  table << marked_contents.join("\n\n")
191
193
  table << "\n|===\n"
@@ -4,6 +4,8 @@ module RuboCop
4
4
  module Ext
5
5
  # Extensions to AST::ProcessedSource for our cached comment_config
6
6
  module ProcessedSource
7
+ attr_accessor :registry, :config
8
+
7
9
  def comment_config
8
10
  @comment_config ||= CommentConfig.new(self)
9
11
  end
@@ -12,13 +12,14 @@ module RuboCop
12
12
  # 26 LineLength
13
13
  # 3 OneLineConditional
14
14
  # --
15
- # 29 Total
15
+ # 29 Total in 5 files
16
16
  class OffenseCountFormatter < BaseFormatter
17
17
  attr_reader :offense_counts
18
18
 
19
19
  def started(target_files)
20
20
  super
21
21
  @offense_counts = Hash.new(0)
22
+ @offending_files_count = 0
22
23
  @style_guide_links = {}
23
24
 
24
25
  return unless output.tty?
@@ -43,26 +44,28 @@ module RuboCop
43
44
  if options[:display_style_guide]
44
45
  offenses.each { |o| @style_guide_links[o.cop_name] ||= o.message[/ \(http\S+\)\Z/] }
45
46
  end
47
+ @offending_files_count += 1 unless offenses.empty?
46
48
  @progressbar.increment if instance_variable_defined?(:@progressbar)
47
49
  end
48
50
 
49
51
  def finished(_inspected_files)
50
- report_summary(@offense_counts)
52
+ report_summary(@offense_counts, @offending_files_count)
51
53
  end
52
54
 
53
55
  # rubocop:disable Metrics/AbcSize
54
- def report_summary(offense_counts)
56
+ def report_summary(offense_counts, offending_files_count)
55
57
  per_cop_counts = ordered_offense_counts(offense_counts)
56
58
  total_count = total_offense_count(offense_counts)
57
59
 
58
60
  output.puts
59
61
 
62
+ column_width = total_count.to_s.length + 2
60
63
  per_cop_counts.each do |cop_name, count|
61
- output.puts "#{count.to_s.ljust(total_count.to_s.length + 2)}#{cop_name}" \
64
+ output.puts "#{count.to_s.ljust(column_width)}#{cop_name}" \
62
65
  "#{@style_guide_links[cop_name]}\n"
63
66
  end
64
67
  output.puts '--'
65
- output.puts "#{total_count} Total"
68
+ output.puts "#{total_count} Total in #{offending_files_count} files"
66
69
 
67
70
  output.puts
68
71
  end
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # 26 this/file/is/really/bad.rb
13
13
  # 3 just/ok.rb
14
14
  # --
15
- # 29 Total
15
+ # 29 Total in 2 files
16
16
  class WorstOffendersFormatter < BaseFormatter
17
17
  attr_reader :offense_counts
18
18
 
@@ -36,14 +36,17 @@ module RuboCop
36
36
  def report_summary(offense_counts)
37
37
  per_file_counts = ordered_offense_counts(offense_counts)
38
38
  total_count = total_offense_count(offense_counts)
39
+ file_count = per_file_counts.size
39
40
 
40
41
  output.puts
41
42
 
43
+ column_width = total_count.to_s.length + 2
42
44
  per_file_counts.each do |file_name, count|
43
- output.puts "#{count.to_s.ljust(total_count.to_s.length + 2)}#{file_name}\n"
45
+ output.puts "#{count.to_s.ljust(column_width)}#{file_name}\n"
44
46
  end
47
+
45
48
  output.puts '--'
46
- output.puts "#{total_count} Total"
49
+ output.puts "#{total_count} Total in #{file_count} files"
47
50
 
48
51
  output.puts
49
52
  end
@@ -65,7 +65,7 @@ module RuboCop
65
65
  end
66
66
 
67
67
  def add_check_options(opts) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
68
- section(opts, 'Basic Options') do
68
+ section(opts, 'Basic Options') do # rubocop:disable Metrics/BlockLength
69
69
  option(opts, '-l', '--lint') do
70
70
  @options[:only] ||= []
71
71
  @options[:only] << 'Lint'
@@ -90,6 +90,7 @@ module RuboCop
90
90
  option(opts, '--force-default-config')
91
91
  option(opts, '-s', '--stdin FILE')
92
92
  option(opts, '-P', '--[no-]parallel')
93
+ option(opts, '--raise-cop-error')
93
94
  add_severity_option(opts)
94
95
  end
95
96
  end
@@ -589,7 +590,10 @@ module RuboCop
589
590
  restart_server: 'Restart server process.',
590
591
  start_server: 'Start server process.',
591
592
  stop_server: 'Stop server process.',
592
- server_status: 'Show server status.'
593
+ server_status: 'Show server status.',
594
+ raise_cop_error: ['Raise cop-related errors with cause and location.',
595
+ 'This is used to prevent cops from failing silently.',
596
+ 'Default is false.']
593
597
  }.freeze
594
598
  end
595
599
  end
@@ -28,7 +28,27 @@ module CopHelper
28
28
  file = file.path
29
29
  end
30
30
 
31
- RuboCop::ProcessedSource.new(source, ruby_version, file)
31
+ processed_source = RuboCop::ProcessedSource.new(source, ruby_version, file)
32
+ processed_source.config = configuration
33
+ processed_source.registry = registry
34
+ processed_source
35
+ end
36
+
37
+ def configuration
38
+ @configuration ||= if defined?(config)
39
+ config
40
+ else
41
+ RuboCop::Config.new({}, "#{Dir.pwd}/.rubocop.yml")
42
+ end
43
+ end
44
+
45
+ def registry
46
+ @registry ||= begin
47
+ cops = configuration.keys.map { |cop| RuboCop::Cop::Registry.global.find_by_cop_name(cop) }
48
+ cops << cop_class if defined?(cop_class) && !cops.include?(cop_class)
49
+ cops.compact!
50
+ RuboCop::Cop::Registry.new(cops)
51
+ end
32
52
  end
33
53
 
34
54
  def autocorrect_source_file(source)
@@ -3,14 +3,6 @@
3
3
  require 'tmpdir'
4
4
 
5
5
  RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLength
6
- before do
7
- if RuboCop.const_defined?(:Server)
8
- # Bust server cache to behave as an isolated environment
9
- RuboCop::Server::Cache.cache_root_path = nil
10
- RuboCop::Server::Cache.instance_variable_set(:@project_dir_cache_key, nil)
11
- end
12
- end
13
-
14
6
  around do |example|
15
7
  Dir.mktmpdir do |tmpdir|
16
8
  original_home = Dir.home
@@ -40,14 +32,23 @@ RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLe
40
32
  ENV['HOME'] = original_home
41
33
  ENV['XDG_CONFIG_HOME'] = original_xdg_config_home
42
34
 
43
- if RuboCop.const_defined?(:Server)
44
- RuboCop::Server::Cache.cache_root_path = nil
45
- RuboCop::Server::Cache.instance_variable_set(:@project_dir_cache_key, nil)
46
- end
47
35
  RuboCop::FileFinder.root_level = nil
48
36
  end
49
37
  end
50
38
  end
39
+
40
+ if RuboCop.const_defined?(:Server)
41
+ around do |example|
42
+ RuboCop::Server::Cache.cache_root_path = nil
43
+ RuboCop::Server::Cache.instance_variable_set(:@project_dir_cache_key, nil)
44
+ begin
45
+ example.run
46
+ ensure
47
+ RuboCop::Server::Cache.cache_root_path = nil
48
+ RuboCop::Server::Cache.instance_variable_set(:@project_dir_cache_key, nil)
49
+ end
50
+ end
51
+ end
51
52
  end
52
53
 
53
54
  RSpec.shared_context 'maintain registry' do
@@ -423,17 +423,21 @@ module RuboCop
423
423
  end
424
424
 
425
425
  def get_processed_source(file)
426
- ruby_version = @config_store.for_file(file).target_ruby_version
427
-
428
- if @options[:stdin]
429
- ProcessedSource.new(@options[:stdin], ruby_version, file)
430
- else
431
- begin
432
- ProcessedSource.from_file(file, ruby_version)
433
- rescue Errno::ENOENT
434
- raise RuboCop::Error, "No such file or directory: #{file}"
435
- end
436
- end
426
+ config = @config_store.for_file(file)
427
+ ruby_version = config.target_ruby_version
428
+
429
+ processed_source = if @options[:stdin]
430
+ ProcessedSource.new(@options[:stdin], ruby_version, file)
431
+ else
432
+ begin
433
+ ProcessedSource.from_file(file, ruby_version)
434
+ rescue Errno::ENOENT
435
+ raise RuboCop::Error, "No such file or directory: #{file}"
436
+ end
437
+ end
438
+ processed_source.config = config
439
+ processed_source.registry = mobilized_cop_classes(config)
440
+ processed_source
437
441
  end
438
442
 
439
443
  # A Cop::Team instance is stateful and may change when inspecting.
@@ -41,6 +41,7 @@ module RuboCop
41
41
 
42
42
  pid = fork do
43
43
  Process.daemon(true)
44
+ $stderr.reopen(Cache.stderr_path, 'w')
44
45
  Cache.write_pid_file do
45
46
  read_socket(@server.accept) until @server.closed?
46
47
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.37.1'
6
+ STRING = '1.38.0'
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
@@ -540,6 +540,7 @@ require_relative 'rubocop/cop/style/numbered_parameters'
540
540
  require_relative 'rubocop/cop/style/open_struct_use'
541
541
  require_relative 'rubocop/cop/style/operator_method_call'
542
542
  require_relative 'rubocop/cop/style/redundant_assignment'
543
+ require_relative 'rubocop/cop/style/redundant_each'
543
544
  require_relative 'rubocop/cop/style/redundant_fetch_block'
544
545
  require_relative 'rubocop/cop/style/redundant_file_extension_in_require'
545
546
  require_relative 'rubocop/cop/style/redundant_initialize'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.37.1
4
+ version: 1.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2022-10-24 00:00:00.000000000 Z
13
+ date: 2022-11-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -821,6 +821,7 @@ files:
821
821
  - lib/rubocop/cop/style/redundant_capital_w.rb
822
822
  - lib/rubocop/cop/style/redundant_condition.rb
823
823
  - lib/rubocop/cop/style/redundant_conditional.rb
824
+ - lib/rubocop/cop/style/redundant_each.rb
824
825
  - lib/rubocop/cop/style/redundant_exception.rb
825
826
  - lib/rubocop/cop/style/redundant_fetch_block.rb
826
827
  - lib/rubocop/cop/style/redundant_file_extension_in_require.rb
@@ -984,7 +985,7 @@ metadata:
984
985
  homepage_uri: https://rubocop.org/
985
986
  changelog_uri: https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md
986
987
  source_code_uri: https://github.com/rubocop/rubocop/
987
- documentation_uri: https://docs.rubocop.org/rubocop/1.37/
988
+ documentation_uri: https://docs.rubocop.org/rubocop/1.38/
988
989
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
989
990
  rubygems_mfa_required: 'true'
990
991
  post_install_message: