rubocop 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -5
  3. data/config/default.yml +52 -3
  4. data/lib/rubocop.rb +8 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/comment_config.rb +1 -1
  7. data/lib/rubocop/cop/bundler/duplicated_gem.rb +23 -3
  8. data/lib/rubocop/cop/commissioner.rb +9 -9
  9. data/lib/rubocop/cop/corrector.rb +3 -1
  10. data/lib/rubocop/cop/force.rb +1 -1
  11. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  13. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  14. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  15. data/lib/rubocop/cop/lint/debugger.rb +2 -3
  16. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  17. data/lib/rubocop/cop/lint/empty_block.rb +46 -0
  18. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  19. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +17 -3
  20. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  21. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  22. data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
  23. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
  24. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  25. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  26. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  27. data/lib/rubocop/cop/offense.rb +3 -3
  28. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  29. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +67 -0
  30. data/lib/rubocop/cop/style/multiple_comparison.rb +54 -7
  31. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  32. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  33. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  34. data/lib/rubocop/cop/team.rb +6 -1
  35. data/lib/rubocop/cop/util.rb +1 -1
  36. data/lib/rubocop/ext/regexp_node.rb +10 -7
  37. data/lib/rubocop/ext/regexp_parser.rb +77 -0
  38. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  39. data/lib/rubocop/magic_comment.rb +2 -2
  40. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  41. data/lib/rubocop/version.rb +1 -1
  42. metadata +13 -5
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for blocks without a body.
7
+ # Such empty blocks are typically an oversight or we should provide a comment
8
+ # be clearer what we're aiming for.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # items.each { |item| }
13
+ #
14
+ # # good
15
+ # items.each { |item| puts item }
16
+ #
17
+ # @example AllowComments: true (default)
18
+ # # good
19
+ # items.each do |item|
20
+ # # TODO: implement later (inner comment)
21
+ # end
22
+ #
23
+ # items.each { |item| } # TODO: implement later (inline comment)
24
+ #
25
+ # @example AllowComments: false
26
+ # # bad
27
+ # items.each do |item|
28
+ # # TODO: implement later (inner comment)
29
+ # end
30
+ #
31
+ # items.each { |item| } # TODO: implement later (inline comment)
32
+ #
33
+ class EmptyBlock < Base
34
+ MSG = 'Empty block detected.'
35
+
36
+ def on_block(node)
37
+ return if node.body
38
+ return if cop_config['AllowComments'] &&
39
+ processed_source.contains_comment?(node.source_range)
40
+
41
+ add_offense(node)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,8 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # This cop looks for uses of flip-flop operator.
7
- # flip-flop operator is deprecated since Ruby 2.6.0.
6
+ # This cop looks for uses of flip-flop operator
7
+ # based on the Ruby Style Guide.
8
+ #
9
+ # Here is the history of flip-flops in Ruby.
10
+ # flip-flop operator is deprecated in Ruby 2.6.0 and
11
+ # the deprecation has been reverted by Ruby 2.7.0 and
12
+ # backported to Ruby 2.6.
13
+ # See: https://bugs.ruby-lang.org/issues/5400
8
14
  #
9
15
  # @example
10
16
  # # bad
@@ -31,12 +31,18 @@ module RuboCop
31
31
  return if special_keyword?(final_node)
32
32
  return unless prints_as_self?(final_node)
33
33
 
34
+ # %W and %I split the content into words before expansion
35
+ # treating each interpolation as a word component, so
36
+ # interpolation should not be removed if the expanded value
37
+ # contains a space character.
38
+ expanded_value = autocorrected_value(final_node)
39
+ return if in_array_percent_literal?(begin_node) &&
40
+ /\s/.match?(expanded_value)
41
+
34
42
  add_offense(final_node) do |corrector|
35
43
  return if final_node.dstr_type? # nested, fixed in next iteration
36
44
 
37
- value = autocorrected_value(final_node)
38
-
39
- corrector.replace(final_node.parent, value)
45
+ corrector.replace(final_node.parent, expanded_value)
40
46
  end
41
47
  end
42
48
 
@@ -92,6 +98,14 @@ module RuboCop
92
98
  (COMPOSITE.include?(node.type) &&
93
99
  node.children.all? { |child| prints_as_self?(child) })
94
100
  end
101
+
102
+ def in_array_percent_literal?(node)
103
+ parent = node.parent
104
+ return false unless parent.dstr_type? || parent.dsym_type?
105
+
106
+ grandparent = parent.parent
107
+ grandparent&.array_type? && grandparent&.percent_literal?
108
+ end
95
109
  end
96
110
  end
97
111
  end
@@ -7,6 +7,17 @@ module RuboCop
7
7
  # number conversion can cause unexpected error if auto type conversion
8
8
  # fails. Cop prefer parsing with number class instead.
9
9
  #
10
+ # Conversion with `Integer`, `Float`, etc. will raise an `ArgumentError`
11
+ # if given input that is not numeric (eg. an empty string), whereas
12
+ # `to_i`, etc. will try to convert regardless of input (`''.to_i => 0`).
13
+ # As such, this cop is disabled by default because it's not necessarily
14
+ # always correct to raise if a value is not numeric.
15
+ #
16
+ # NOTE: Some values cannot be converted properly using one of the `Kernel`
17
+ # method (for instance, `Time` and `DateTime` values are allowed by this
18
+ # cop by default). Similarly, Rails' duration methods do not work well
19
+ # with `Integer()` and can be ignored with `IgnoredMethods`.
20
+ #
10
21
  # @example
11
22
  #
12
23
  # # bad
@@ -20,8 +31,19 @@ module RuboCop
20
31
  # Integer('10', 10)
21
32
  # Float('10.2')
22
33
  # Complex('10')
34
+ #
35
+ # @example IgnoredMethods: [minutes]
36
+ #
37
+ # # good
38
+ # 10.minutes.to_i
39
+ #
40
+ # @example IgnoredClasses: [Time, DateTime] (default)
41
+ #
42
+ # # good
43
+ # Time.now.to_datetime.to_i
23
44
  class NumberConversion < Base
24
45
  extend AutoCorrector
46
+ include IgnoredMethods
25
47
 
26
48
  CONVERSION_METHOD_CLASS_MAPPING = {
27
49
  to_i: "#{Integer.name}(%<number_object>s, 10)",
@@ -38,13 +60,9 @@ module RuboCop
38
60
  (send $_ ${:to_i :to_f :to_c})
39
61
  PATTERN
40
62
 
41
- def_node_matcher :datetime?, <<~PATTERN
42
- (send (const {nil? (cbase)} {:Time :DateTime}) ...)
43
- PATTERN
44
-
45
63
  def on_send(node)
46
64
  to_method(node) do |receiver, to_method|
47
- next if receiver.nil? || date_time_object?(receiver)
65
+ next if receiver.nil? || ignore_receiver?(receiver)
48
66
 
49
67
  message = format(
50
68
  MSG,
@@ -60,18 +78,33 @@ module RuboCop
60
78
 
61
79
  private
62
80
 
63
- def date_time_object?(node)
64
- child = node
65
- while child&.send_type?
66
- return true if datetime? child
81
+ def correct_method(node, receiver)
82
+ format(CONVERSION_METHOD_CLASS_MAPPING[node.method_name],
83
+ number_object: receiver.source)
84
+ end
67
85
 
68
- child = child.children[0]
86
+ def ignore_receiver?(receiver)
87
+ if receiver.send_type? && ignored_method?(receiver.method_name)
88
+ true
89
+ elsif (receiver = top_receiver(receiver))
90
+ receiver.const_type? && ignored_class?(receiver.const_name)
91
+ else
92
+ false
69
93
  end
70
94
  end
71
95
 
72
- def correct_method(node, receiver)
73
- format(CONVERSION_METHOD_CLASS_MAPPING[node.method_name],
74
- number_object: receiver.source)
96
+ def top_receiver(node)
97
+ receiver = node
98
+ receiver = receiver.receiver until receiver.receiver.nil?
99
+ receiver
100
+ end
101
+
102
+ def ignored_classes
103
+ cop_config.fetch('IgnoredClasses', [])
104
+ end
105
+
106
+ def ignored_class?(name)
107
+ ignored_classes.include?(name.to_s)
75
108
  end
76
109
  end
77
110
  end
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # puts $1 # => foo
20
20
  #
21
21
  class OutOfRangeRegexpRef < Base
22
- MSG = 'Do not use out of range reference for the Regexp.'
22
+ MSG = '$%<backref>s is out of range (%<count>s regexp capture %<group>s detected).'
23
23
 
24
24
  REGEXP_RECEIVER_METHODS = %i[=~ === match].to_set.freeze
25
25
  REGEXP_ARGUMENT_METHODS = %i[=~ match grep gsub gsub! sub sub! [] slice slice! index rindex
@@ -35,14 +35,13 @@ module RuboCop
35
35
  check_regexp(node.children.first)
36
36
  end
37
37
 
38
- def on_send(node)
38
+ def after_send(node)
39
39
  @valid_ref = nil
40
40
 
41
- if node.receiver&.regexp_type?
42
- check_regexp(node.receiver)
43
- elsif node.first_argument&.regexp_type? \
44
- && REGEXP_ARGUMENT_METHODS.include?(node.method_name)
41
+ if regexp_first_argument?(node)
45
42
  check_regexp(node.first_argument)
43
+ elsif regexp_receiver?(node)
44
+ check_regexp(node.receiver)
46
45
  end
47
46
  end
48
47
 
@@ -56,9 +55,16 @@ module RuboCop
56
55
 
57
56
  def on_nth_ref(node)
58
57
  backref, = *node
59
- return if @valid_ref.nil?
58
+ return if @valid_ref.nil? || backref <= @valid_ref
59
+
60
+ message = format(
61
+ MSG,
62
+ backref: backref,
63
+ count: @valid_ref.zero? ? 'no' : @valid_ref,
64
+ group: @valid_ref == 1 ? 'group' : 'groups'
65
+ )
60
66
 
61
- add_offense(node) if backref > @valid_ref
67
+ add_offense(node, message: message)
62
68
  end
63
69
 
64
70
  private
@@ -73,6 +79,19 @@ module RuboCop
73
79
  node.each_capture(named: false).count
74
80
  end
75
81
  end
82
+
83
+ def regexp_first_argument?(send_node)
84
+ send_node.first_argument&.regexp_type? \
85
+ && REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
86
+ end
87
+
88
+ def regexp_receiver?(send_node)
89
+ send_node.receiver&.regexp_type?
90
+ end
91
+
92
+ def nth_ref_receiver?(send_node)
93
+ send_node.receiver&.nth_ref_type?
94
+ end
76
95
  end
77
96
  end
78
97
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop ensures that `to_enum`/`enum_for`, called for the current method,
7
+ # has correct arguments.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def method(x, y = 1)
12
+ # return to_enum(__method__, x) # `y` is missing
13
+ # end
14
+ #
15
+ # # good
16
+ # def method(x, y = 1)
17
+ # return to_enum(__method__, x, y)
18
+ # end
19
+ #
20
+ # # bad
21
+ # def method(required:)
22
+ # return to_enum(:method, required: something) # `required` has incorrect value
23
+ # end
24
+ #
25
+ # # good
26
+ # def method(required:)
27
+ # return to_enum(:method, required: required)
28
+ # end
29
+ #
30
+ class ToEnumArguments < Base
31
+ MSG = 'Ensure you correctly provided all the arguments.'
32
+
33
+ RESTRICT_ON_SEND = %i[to_enum enum_for].freeze
34
+
35
+ def_node_matcher :enum_conversion_call?, <<~PATTERN
36
+ (send {nil? self} {:to_enum :enum_for} $_ $...)
37
+ PATTERN
38
+
39
+ def_node_matcher :method_name?, <<~PATTERN
40
+ {(send nil? :__method__) (sym %1)}
41
+ PATTERN
42
+
43
+ def_node_matcher :passing_keyword_arg?, <<~PATTERN
44
+ (pair (sym %1) (lvar %1))
45
+ PATTERN
46
+
47
+ def on_send(node)
48
+ def_node = node.each_ancestor(:def, :defs).first
49
+ return unless def_node
50
+
51
+ enum_conversion_call?(node) do |method_node, arguments|
52
+ add_offense(node) unless method_name?(method_node, def_node.method_name) &&
53
+ arguments_match?(arguments, def_node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def arguments_match?(arguments, def_node)
60
+ index = 0
61
+
62
+ def_node.arguments.reject(&:blockarg_type?).all? do |def_arg|
63
+ send_arg = arguments[index]
64
+ case def_arg.type
65
+ when :arg, :restarg, :optarg
66
+ index += 1
67
+ end
68
+
69
+ send_arg && argument_match?(send_arg, def_arg)
70
+ end
71
+ end
72
+
73
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
74
+ def argument_match?(send_arg, def_arg)
75
+ def_arg_name = def_arg.children[0]
76
+
77
+ case def_arg.type
78
+ when :arg, :restarg
79
+ send_arg.source == def_arg.source
80
+ when :optarg
81
+ send_arg.source == def_arg_name.to_s
82
+ when :kwoptarg, :kwarg
83
+ send_arg.hash_type? &&
84
+ send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) }
85
+ when :kwrestarg
86
+ send_arg.each_child_node(:kwsplat).any? { |child| child.source == def_arg.source }
87
+ when :forward_arg
88
+ send_arg.forwarded_args_type?
89
+ end
90
+ end
91
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Looks for `reduce` or `inject` blocks where the value returned (implicitly or
7
+ # explicitly) does not include the accumulator. A block is considered valid as
8
+ # long as at least one return value includes the accumulator.
9
+ #
10
+ # If the accumulator is not included in the return value, then the entire
11
+ # block will just return a transformation of the last element value, and
12
+ # could be rewritten as such without a loop.
13
+ #
14
+ # Also catches instances where an index of the accumulator is returned, as
15
+ # this may change the type of object being retained.
16
+ #
17
+ # NOTE: For the purpose of reducing false positives, this cop only flags
18
+ # returns in `reduce` blocks where the element is the only variable in
19
+ # the expression (since we will not be able to tell what other variables
20
+ # relate to via static analysis).
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # (1..4).reduce(0) do |acc, el|
26
+ # el * 2
27
+ # end
28
+ #
29
+ # # bad, may raise a NoMethodError after the first iteration
30
+ # %w(a b c).reduce({}) do |acc, letter|
31
+ # acc[letter] = true
32
+ # end
33
+ #
34
+ # # good
35
+ # (1..4).reduce(0) do |acc, el|
36
+ # acc + el * 2
37
+ # end
38
+ #
39
+ # # good, element is returned but modified using the accumulator
40
+ # values.reduce do |acc, el|
41
+ # el << acc
42
+ # el
43
+ # end
44
+ #
45
+ # # good, returns the accumulator instead of the index
46
+ # %w(a b c).reduce({}) do |acc, letter|
47
+ # acc[letter] = true
48
+ # acc
49
+ # end
50
+ #
51
+ # # good, at least one branch returns the accumulator
52
+ # values.reduce(nil) do |result, value|
53
+ # break result if something?
54
+ # value
55
+ # end
56
+ #
57
+ # # ignored as the return value cannot be determined
58
+ # enum.reduce do |acc, el|
59
+ # x = foo(acc, el)
60
+ # bar(x)
61
+ # end
62
+ class UnmodifiedReduceAccumulator < Base
63
+ MSG = 'Ensure the accumulator `%<accum>s` will be modified by `%<method>s`.'
64
+ MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
65
+
66
+ def_node_matcher :reduce_with_block?, <<~PATTERN
67
+ (block (send _recv {:reduce :inject} ...) (args arg+) ...)
68
+ PATTERN
69
+
70
+ def_node_matcher :accumulator_index?, <<~PATTERN
71
+ (send (lvar %1) {:[] :[]=} ...)
72
+ PATTERN
73
+
74
+ def_node_search :element_modified?, <<~PATTERN
75
+ {
76
+ (send _receiver !{:[] :[]=} <`(lvar %1) `_ ...>) # method(el, ...)
77
+ (send (lvar %1) _message <{ivar gvar cvar lvar send} ...>) # el.method(...)
78
+ (lvasgn %1 _) # el = ...
79
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1) ... _) # el += ...
80
+ }
81
+ PATTERN
82
+
83
+ def_node_matcher :lvar_used?, <<~PATTERN
84
+ {
85
+ (lvar %1)
86
+ (lvasgn %1 ...)
87
+ (send (lvar %1) :<< ...)
88
+ (dstr (begin (lvar %1)))
89
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1))
90
+ }
91
+ PATTERN
92
+
93
+ def_node_search :expression_values, <<~PATTERN
94
+ {
95
+ (%RuboCop::AST::Node::VARIABLES $_)
96
+ (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_ ...)
97
+ (send (%RuboCop::AST::Node::VARIABLES $_) :<< ...)
98
+ $(send _ _)
99
+ (dstr (begin {(%RuboCop::AST::Node::VARIABLES $_)}))
100
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_) ...)
101
+ }
102
+ PATTERN
103
+
104
+ def on_block(node)
105
+ return unless reduce_with_block?(node)
106
+
107
+ check_return_values(node)
108
+ end
109
+
110
+ private
111
+
112
+ # Return values in a block are either the value given to next,
113
+ # the last line of a multiline block, or the only line of the block
114
+ def return_values(block_body_node)
115
+ nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
116
+
117
+ block_body_node.each_descendant(:next, :break) do |n|
118
+ # Ignore `next`/`break` inside an inner block
119
+ next if n.each_ancestor(:block).first != block_body_node.parent
120
+ next unless n.first_argument
121
+
122
+ nodes << n.first_argument
123
+ end
124
+
125
+ nodes
126
+ end
127
+
128
+ def check_return_values(block_node)
129
+ return_values = return_values(block_node.body)
130
+ accumulator_name = block_arg_name(block_node, 0)
131
+ element_name = block_arg_name(block_node, 1)
132
+ message_opts = { method: block_node.method_name, accum: accumulator_name }
133
+
134
+ if (node = returned_accumulator_index(return_values, accumulator_name))
135
+ add_offense(node, message: format(MSG_INDEX, message_opts))
136
+ elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
137
+ return_values.each do |return_val|
138
+ unless acceptable_return?(return_val, element_name)
139
+ add_offense(return_val, message: format(MSG, message_opts))
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def block_arg_name(node, index)
146
+ node.arguments[index].node_parts[0]
147
+ end
148
+
149
+ # Look for an index of the accumulator being returned
150
+ # This is always an offense, in order to try to catch potential exceptions
151
+ # due to type mismatches
152
+ def returned_accumulator_index(return_values, accumulator_name)
153
+ return_values.detect { |val| accumulator_index?(val, accumulator_name) }
154
+ end
155
+
156
+ def potential_offense?(return_values, block_body, element_name, accumulator_name)
157
+ !(element_modified?(block_body, element_name) ||
158
+ returns_accumulator_anywhere?(return_values, accumulator_name))
159
+ end
160
+
161
+ # If the accumulator is used in any return value, the node is acceptable since
162
+ # the accumulator has a chance to change each iteration
163
+ def returns_accumulator_anywhere?(return_values, accumulator_name)
164
+ return_values.any? { |node| lvar_used?(node, accumulator_name) }
165
+ end
166
+
167
+ # Determine if a return value is acceptable for the purposes of this cop
168
+ # If it is an expression containing the accumulator, it is acceptable
169
+ # Otherwise, it is only unacceptable if it contains the iterated element, since we
170
+ # otherwise do not have enough information to prevent false positives.
171
+ def acceptable_return?(return_val, element_name)
172
+ vars = expression_values(return_val).uniq
173
+ return true if vars.none? || (vars - [element_name]).any?
174
+
175
+ false
176
+ end
177
+
178
+ # Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
179
+ def allowed_type?(parent_node)
180
+ !parent_node.dstr_type?
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end