rubocop 0.92.0 → 0.93.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +32 -0
  4. data/lib/rubocop.rb +3 -1
  5. data/lib/rubocop/cached_data.rb +2 -1
  6. data/lib/rubocop/cop/correctors/line_break_corrector.rb +2 -2
  7. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -10
  8. data/lib/rubocop/cop/layout/dot_position.rb +6 -9
  9. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -7
  10. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  11. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -11
  12. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +0 -4
  13. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -0
  14. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +15 -1
  15. data/lib/rubocop/cop/lint/boolean_symbol.rb +3 -0
  16. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +37 -0
  17. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -0
  18. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  19. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +45 -0
  20. data/lib/rubocop/cop/metrics/block_length.rb +3 -1
  21. data/lib/rubocop/cop/metrics/class_length.rb +8 -6
  22. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  23. data/lib/rubocop/cop/offense.rb +15 -2
  24. data/lib/rubocop/cop/style/access_modifier_declarations.rb +6 -2
  25. data/lib/rubocop/cop/style/accessor_grouping.rb +3 -0
  26. data/lib/rubocop/cop/style/case_like_if.rb +20 -4
  27. data/lib/rubocop/cop/style/class_equality_comparison.rb +49 -0
  28. data/lib/rubocop/cop/style/combinable_loops.rb +8 -1
  29. data/lib/rubocop/cop/style/comment_annotation.rb +6 -0
  30. data/lib/rubocop/cop/style/explicit_block_argument.rb +6 -2
  31. data/lib/rubocop/cop/style/for.rb +0 -4
  32. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  33. data/lib/rubocop/cop/style/method_def_parentheses.rb +0 -4
  34. data/lib/rubocop/cop/style/nested_ternary_operator.rb +2 -0
  35. data/lib/rubocop/cop/style/raise_args.rb +0 -3
  36. data/lib/rubocop/cop/style/redundant_begin.rb +26 -8
  37. data/lib/rubocop/cop/style/redundant_condition.rb +5 -1
  38. data/lib/rubocop/cop/style/redundant_interpolation.rb +6 -1
  39. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +39 -24
  40. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -15
  41. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  42. data/lib/rubocop/cop/variable_force/branch.rb +0 -4
  43. data/lib/rubocop/ext/regexp_node.rb +20 -4
  44. data/lib/rubocop/result_cache.rb +8 -2
  45. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  46. data/lib/rubocop/runner.rb +4 -4
  47. data/lib/rubocop/target_finder.rb +23 -25
  48. data/lib/rubocop/version.rb +1 -1
  49. metadata +10 -8
  50. data/lib/rubocop/cop/mixin/regexp_literal_help.rb +0 -43
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # HEREDOC
30
30
  # end # 5 points
31
31
  #
32
+ #
33
+ # NOTE: This cop also applies for `Struct` definitions.
32
34
  class ClassLength < Base
33
35
  include CodeLength
34
36
 
@@ -37,17 +39,17 @@ module RuboCop
37
39
  end
38
40
 
39
41
  def on_casgn(node)
40
- class_definition?(node) do
41
- check_code_length(node)
42
+ if node.parent&.assignment?
43
+ block_node = node.parent.children[1]
44
+ else
45
+ _scope, _name, block_node = *node
42
46
  end
47
+
48
+ check_code_length(block_node) if block_node.class_definition?
43
49
  end
44
50
 
45
51
  private
46
52
 
47
- def_node_matcher :class_definition?, <<~PATTERN
48
- (casgn nil? _ (block (send (const {nil? cbase} :Class) :new) ...))
49
- PATTERN
50
-
51
53
  def message(length, max_length)
52
54
  format('Class has too many lines. [%<length>d/%<max>d]',
53
55
  length: length,
@@ -137,7 +137,7 @@ module RuboCop
137
137
  end
138
138
 
139
139
  # Internal helper class to hold autocorrect data
140
- Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
140
+ Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do
141
141
  def self.from_each_with_object(node, match)
142
142
  new(match, node, 0, 0)
143
143
  end
@@ -63,10 +63,23 @@ module RuboCop
63
63
  attr_reader :corrector
64
64
 
65
65
  PseudoSourceRange = Struct.new(:line, :column, :source_line, :begin_pos,
66
- :end_pos)
66
+ :end_pos) do
67
+ alias_method :first_line, :line
68
+ alias_method :last_line, :line
69
+ alias_method :last_column, :column
70
+
71
+ def column_range
72
+ column...last_column
73
+ end
74
+
75
+ def size
76
+ end_pos - begin_pos
77
+ end
78
+ alias_method :length, :size
79
+ end
67
80
  private_constant :PseudoSourceRange
68
81
 
69
- NO_LOCATION = PseudoSourceRange.new(1, 0, '', 0, 1).freeze
82
+ NO_LOCATION = PseudoSourceRange.new(1, 0, '', 0, 0).freeze
70
83
 
71
84
  # @api private
72
85
  def initialize(severity, location, message, cop_name, # rubocop:disable Metrics/ParameterLists
@@ -83,8 +83,8 @@ module RuboCop
83
83
 
84
84
  def on_send(node)
85
85
  return unless node.access_modifier?
86
- return if node.parent.pair_type?
87
- return if cop_config['AllowModifiersOnSymbols'] && access_modifier_with_symbol?(node)
86
+ return if node.parent&.pair_type?
87
+ return if allow_modifiers_on_symbols?(node)
88
88
 
89
89
  if offense?(node)
90
90
  add_offense(node.loc.selector) if opposite_style_detected
@@ -95,6 +95,10 @@ module RuboCop
95
95
 
96
96
  private
97
97
 
98
+ def allow_modifiers_on_symbols?(node)
99
+ cop_config['AllowModifiersOnSymbols'] && access_modifier_with_symbol?(node)
100
+ end
101
+
98
102
  def offense?(node)
99
103
  (group_style? && access_modifier_is_inlined?(node)) ||
100
104
  (inline_style? && access_modifier_is_not_inlined?(node))
@@ -7,6 +7,9 @@ module RuboCop
7
7
  # By default it enforces accessors to be placed in grouped declarations,
8
8
  # but it can be configured to enforce separating them in multiple declarations.
9
9
  #
10
+ # Note: `Sorbet` is not compatible with "grouped" style. Consider "separated" style
11
+ # or disabling this cop.
12
+ #
10
13
  # @example EnforcedStyle: grouped (default)
11
14
  # # bad
12
15
  # class Foo
@@ -42,6 +42,8 @@ module RuboCop
42
42
  convertible = true
43
43
 
44
44
  branch_conditions(node).each do |branch_condition|
45
+ return false if regexp_with_working_captures?(branch_condition)
46
+
45
47
  conditions << []
46
48
  convertible = collect_conditions(branch_condition, target, conditions.last)
47
49
  break unless convertible
@@ -49,9 +51,7 @@ module RuboCop
49
51
 
50
52
  return unless convertible
51
53
 
52
- add_offense(node) do |corrector|
53
- autocorrect(corrector, node)
54
- end
54
+ add_offense(node) { |corrector| autocorrect(corrector, node) }
55
55
  end
56
56
 
57
57
  private
@@ -107,7 +107,7 @@ module RuboCop
107
107
  when :include?, :cover?
108
108
  receiver = deparenthesize(node.receiver)
109
109
  node.arguments.first if receiver.range_type?
110
- when :match, :match?
110
+ when :match, :match?, :=~
111
111
  find_target_in_match_node(node)
112
112
  end
113
113
  end
@@ -230,6 +230,22 @@ module RuboCop
230
230
  def indent(node)
231
231
  ' ' * node.loc.column
232
232
  end
233
+
234
+ # Named captures work with `=~` (if regexp is on lhs) and with `match` (both sides)
235
+ def regexp_with_working_captures?(node)
236
+ case node.type
237
+ when :match_with_lvasgn
238
+ lhs, _rhs = *node
239
+ node.loc.selector.source == '=~' && regexp_with_named_captures?(lhs)
240
+ when :send
241
+ lhs, method, rhs = *node
242
+ method == :match && [lhs, rhs].any? { |n| regexp_with_named_captures?(n) }
243
+ end
244
+ end
245
+
246
+ def regexp_with_named_captures?(node)
247
+ node.regexp_type? && node.each_capture(named: true).count.positive?
248
+ end
233
249
  end
234
250
  end
235
251
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop enforces the use of `Object#instance_of?` instead of class comparison
7
+ # for equality.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # var.class == Date
12
+ # var.class.equal?(Date)
13
+ # var.class.eql?(Date)
14
+ # var.class.name == 'Date'
15
+ #
16
+ # # good
17
+ # var.instance_of?(Date)
18
+ #
19
+ class ClassEqualityComparison < Base
20
+ include RangeHelp
21
+ include IgnoredMethods
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `Object.instance_of?` instead of comparing classes.'
25
+
26
+ RESTRICT_ON_SEND = %i[== equal? eql?].freeze
27
+
28
+ def_node_matcher :class_comparison_candidate?, <<~PATTERN
29
+ (send
30
+ {$(send _ :class) (send $(send _ :class) :name)}
31
+ {:== :equal? :eql?} $_)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ def_node = node.each_ancestor(:def, :defs).first
36
+ return if def_node && ignored_method?(def_node.method_name)
37
+
38
+ class_comparison_candidate?(node) do |receiver_node, class_node|
39
+ range = range_between(receiver_node.loc.selector.begin_pos, node.source_range.end_pos)
40
+
41
+ add_offense(range) do |corrector|
42
+ corrector.replace(range, "instance_of?(#{class_node.source})")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -49,6 +49,12 @@ module RuboCop
49
49
  # end
50
50
  # end
51
51
  #
52
+ # # good
53
+ # def method
54
+ # each_slice(2) { |slice| do_something(slice) }
55
+ # each_slice(3) { |slice| do_something(slice) }
56
+ # end
57
+ #
52
58
  class CombinableLoops < Base
53
59
  MSG = 'Combine this loop with the previous loop.'
54
60
 
@@ -76,7 +82,8 @@ module RuboCop
76
82
  def same_collection_looping?(node, sibling)
77
83
  sibling&.block_type? &&
78
84
  sibling.send_node.method?(node.method_name) &&
79
- sibling.send_node.receiver == node.send_node.receiver
85
+ sibling.send_node.receiver == node.send_node.receiver &&
86
+ sibling.send_node.arguments == node.send_node.arguments
80
87
  end
81
88
  end
82
89
  end
@@ -6,6 +6,12 @@ module RuboCop
6
6
  # This cop checks that comment annotation keywords are written according
7
7
  # to guidelines.
8
8
  #
9
+ # NOTE: With a multiline comment block (where each line is only a
10
+ # comment), only the first line will be able to register an offense, even
11
+ # if an annotation keyword starts another line. This is done to prevent
12
+ # incorrect registering of keywords (eg. `review`) inside a paragraph as an
13
+ # annotation.
14
+ #
9
15
  # @example
10
16
  # # bad
11
17
  # # TODO make better
@@ -57,12 +57,16 @@ module RuboCop
57
57
  yielding_block?(block_node) do |send_node, block_args, yield_args|
58
58
  return unless yielding_arguments?(block_args, yield_args)
59
59
 
60
+ def_node = block_node.each_ancestor(:def, :defs).first
61
+ # if `yield` is being called outside of a method context, ignore
62
+ # this is not a valid ruby pattern, but can happen in haml or erb,
63
+ # so this can cause crashes in haml_lint
64
+ return unless def_node
65
+
60
66
  add_offense(block_node) do |corrector|
61
67
  corrector.remove(block_body_range(block_node, send_node))
62
68
 
63
69
  add_block_argument(send_node, corrector)
64
-
65
- def_node = block_node.each_ancestor(:def, :defs).first
66
70
  add_block_argument(def_node, corrector) if @def_nodes.add?(def_node)
67
71
  end
68
72
  end
@@ -49,8 +49,6 @@ module RuboCop
49
49
 
50
50
  def on_for(node)
51
51
  if style == :each
52
- return unless opposite_style_detected
53
-
54
52
  add_offense(node, message: PREFER_EACH) do |corrector|
55
53
  ForToEachCorrector.new(node).call(corrector)
56
54
  end
@@ -63,8 +61,6 @@ module RuboCop
63
61
  return unless suspect_enumerable?(node)
64
62
 
65
63
  if style == :for
66
- return unless opposite_style_detected
67
-
68
64
  add_offense(node, message: PREFER_FOR) do |corrector|
69
65
  EachToForCorrector.new(node).call(corrector)
70
66
  end
@@ -33,7 +33,7 @@ module RuboCop
33
33
  #
34
34
  # # bad
35
35
  # format('%<greeting>s', greeting: 'Hello')
36
- # format('%{greeting}', 'Hello')
36
+ # format('%{greeting}', greeting: 'Hello')
37
37
  #
38
38
  # # good
39
39
  # format('%s', 'Hello')
@@ -140,16 +140,12 @@ module RuboCop
140
140
  def missing_parentheses(node)
141
141
  location = node.arguments.source_range
142
142
 
143
- return unless unexpected_style_detected(:require_no_parentheses)
144
-
145
143
  add_offense(location, message: MSG_MISSING) do |corrector|
146
144
  correct_definition(node, corrector)
147
145
  end
148
146
  end
149
147
 
150
148
  def unwanted_parentheses(args)
151
- return unless unexpected_style_detected(:require_parentheses)
152
-
153
149
  add_offense(args, message: MSG_PRESENT) do |corrector|
154
150
  # offense is registered on args node when parentheses are unwanted
155
151
  correct_arguments(args, corrector)
@@ -49,6 +49,8 @@ module RuboCop
49
49
  end
50
50
 
51
51
  def remove_parentheses(source)
52
+ return source unless source.start_with?('(')
53
+
52
54
  source.gsub(/\A\(/, '').gsub(/\)\z/, '')
53
55
  end
54
56
  end
@@ -84,8 +84,6 @@ module RuboCop
84
84
 
85
85
  def check_compact(node)
86
86
  if node.arguments.size > 1
87
- return unless opposite_style_detected
88
-
89
87
  add_offense(node, message: format(COMPACT_MSG, method: node.method_name)) do |corrector|
90
88
  replacement = correction_exploded_to_compact(node)
91
89
 
@@ -103,7 +101,6 @@ module RuboCop
103
101
 
104
102
  return unless first_arg.send_type? && first_arg.method?(:new)
105
103
  return if acceptable_exploded_args?(first_arg.arguments)
106
- return unless opposite_style_detected
107
104
 
108
105
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
109
106
  replacement = correction_compact_to_exploded(node)
@@ -28,6 +28,14 @@ module RuboCop
28
28
  # end
29
29
  #
30
30
  # # bad
31
+ # begin
32
+ # do_something
33
+ # end
34
+ #
35
+ # # good
36
+ # do_something
37
+ #
38
+ # # bad
31
39
  # # When using Ruby 2.5 or later.
32
40
  # do_something do
33
41
  # begin
@@ -60,7 +68,9 @@ module RuboCop
60
68
  MSG = 'Redundant `begin` block detected.'
61
69
 
62
70
  def on_def(node)
63
- check(node)
71
+ return unless node.body&.kwbegin_type?
72
+
73
+ register_offense(node.body)
64
74
  end
65
75
  alias on_defs on_def
66
76
 
@@ -69,18 +79,26 @@ module RuboCop
69
79
 
70
80
  return if node.send_node.lambda_literal?
71
81
  return if node.braces?
82
+ return unless node.body&.kwbegin_type?
72
83
 
73
- check(node)
84
+ register_offense(node.body)
74
85
  end
75
86
 
76
- private
87
+ def on_kwbegin(node)
88
+ return if node.parent&.assignment?
77
89
 
78
- def check(node)
79
- return unless node.body&.kwbegin_type?
90
+ first_child = node.children.first
91
+ return if first_child.rescue_type? || first_child.ensure_type?
92
+
93
+ register_offense(node)
94
+ end
95
+
96
+ private
80
97
 
81
- add_offense(node.body.loc.begin) do |corrector|
82
- corrector.remove(node.body.loc.begin)
83
- corrector.remove(node.body.loc.end)
98
+ def register_offense(node)
99
+ add_offense(node.loc.begin) do |corrector|
100
+ corrector.remove(node.loc.begin)
101
+ corrector.remove(node.loc.end)
84
102
  end
85
103
  end
86
104
  end
@@ -75,7 +75,7 @@ module RuboCop
75
75
  def offense?(node)
76
76
  condition, if_branch, else_branch = *node
77
77
 
78
- return false if use_if_branch?(else_branch)
78
+ return false if use_if_branch?(else_branch) || use_hash_key_assignment?(else_branch)
79
79
 
80
80
  condition == if_branch && !node.elsif? && (
81
81
  node.ternary? ||
@@ -88,6 +88,10 @@ module RuboCop
88
88
  else_branch&.if_type?
89
89
  end
90
90
 
91
+ def use_hash_key_assignment?(else_branch)
92
+ else_branch&.send_type? && else_branch&.method?(:[]=)
93
+ end
94
+
91
95
  def else_source(else_branch)
92
96
  if require_parentheses?(else_branch)
93
97
  "(#{else_branch.source})"
@@ -51,7 +51,12 @@ module RuboCop
51
51
  end
52
52
 
53
53
  def single_variable_interpolation?(node)
54
- node.children.one? && variable_interpolation?(node.children.first)
54
+ return false unless node.children.one?
55
+
56
+ first_child = node.children.first
57
+
58
+ variable_interpolation?(first_child) ||
59
+ first_child.send_type? && !first_child.operator_method?
55
60
  end
56
61
 
57
62
  def interpolation?(node)
@@ -22,32 +22,14 @@ module RuboCop
22
22
  # # good
23
23
  # r = /[ab]/
24
24
  class RedundantRegexpCharacterClass < Base
25
- include MatchRange
26
- include RegexpLiteralHelp
27
25
  extend AutoCorrector
28
26
 
27
+ REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS = '.*+?{}()|$'.chars.freeze
29
28
  MSG_REDUNDANT_CHARACTER_CLASS = 'Redundant single-element character class, ' \
30
29
  '`%<char_class>s` can be replaced with `%<element>s`.'
31
30
 
32
- PATTERN = /
33
- (
34
- (?<!\\) # No \-prefix (i.e. not escaped)
35
- \[ # Literal [
36
- (?!\#\{) # Not (the start of) an interpolation
37
- (?: # Either...
38
- \\[^b] | # Any escaped character except b (which would change behaviour)
39
- [^.*+?{}()|$] | # or one that doesn't require escaping outside the character class
40
- \\[upP]\{[^}]+\} # or a unicode code-point or property
41
- )
42
- (?<!\\) # No \-prefix (i.e. not escaped)
43
- \] # Literal ]
44
- )
45
- /x.freeze
46
-
47
31
  def on_regexp(node)
48
32
  each_redundant_character_class(node) do |loc|
49
- next if whitespace_in_free_space_mode?(node, loc)
50
-
51
33
  add_offense(
52
34
  loc, message: format(
53
35
  MSG_REDUNDANT_CHARACTER_CLASS,
@@ -63,19 +45,52 @@ module RuboCop
63
45
  private
64
46
 
65
47
  def each_redundant_character_class(node)
66
- pattern_source(node).scan(PATTERN) do
67
- yield match_range(node.loc.begin.end, Regexp.last_match)
48
+ each_single_element_character_class(node) do |char_class|
49
+ next unless redundant_single_element_character_class?(node, char_class)
50
+
51
+ yield node.loc.begin.adjust(begin_pos: 1 + char_class.ts, end_pos: char_class.te)
52
+ end
53
+ end
54
+
55
+ def each_single_element_character_class(node)
56
+ node.parsed_tree&.each_expression do |expr|
57
+ next if expr.type != :set || expr.expressions.size != 1
58
+ next if expr.negative?
59
+ next if %i[set posixclass nonposixclass].include?(expr.expressions.first.type)
60
+
61
+ yield expr
68
62
  end
69
63
  end
70
64
 
65
+ def redundant_single_element_character_class?(node, char_class)
66
+ class_elem = char_class.expressions.first.text
67
+
68
+ non_redundant =
69
+ whitespace_in_free_space_mode?(node, class_elem) ||
70
+ backslash_b?(class_elem) ||
71
+ requires_escape_outside_char_class?(class_elem)
72
+
73
+ !non_redundant
74
+ end
75
+
71
76
  def without_character_class(loc)
72
77
  loc.source[1..-2]
73
78
  end
74
79
 
75
- def whitespace_in_free_space_mode?(node, loc)
76
- return false unless freespace_mode_regexp?(node)
80
+ def whitespace_in_free_space_mode?(node, elem)
81
+ return false unless node.extended?
82
+
83
+ /\s/.match?(elem)
84
+ end
85
+
86
+ def backslash_b?(elem)
87
+ # \b's behaviour is different inside and outside of a character class, matching word
88
+ # boundaries outside but backspace (0x08) when inside.
89
+ elem == '\b'
90
+ end
77
91
 
78
- /\[\s\]/.match?(loc.source)
92
+ def requires_escape_outside_char_class?(elem)
93
+ REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS.include?(elem)
79
94
  end
80
95
  end
81
96
  end