rubocop 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +47 -7
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/cli.rb +5 -1
  6. data/lib/rubocop/cli/command/suggest_extensions.rb +67 -0
  7. data/lib/rubocop/config_loader.rb +1 -1
  8. data/lib/rubocop/config_loader_resolver.rb +5 -1
  9. data/lib/rubocop/config_obsoletion.rb +21 -3
  10. data/lib/rubocop/config_validator.rb +8 -1
  11. data/lib/rubocop/cop/generator.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  13. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  14. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  15. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  16. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  17. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +8 -5
  18. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  19. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  20. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  21. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  22. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  23. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  24. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  25. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  26. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  27. data/lib/rubocop/cop/style/and_or.rb +10 -0
  28. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  29. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  30. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  31. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  32. data/lib/rubocop/cop/style/redundant_argument.rb +1 -1
  33. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  34. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  35. data/lib/rubocop/cop/style/sole_nested_conditional.rb +49 -3
  36. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  37. data/lib/rubocop/core_ext/hash.rb +20 -0
  38. data/lib/rubocop/ext/regexp_node.rb +5 -10
  39. data/lib/rubocop/ext/regexp_parser.rb +2 -9
  40. data/lib/rubocop/version.rb +1 -1
  41. metadata +11 -7
@@ -40,7 +40,7 @@ module RuboCop
40
40
  # do_something
41
41
  # end
42
42
  #
43
- class NoReturnInBeginEndBlocks < Cop
43
+ class NoReturnInBeginEndBlocks < Base
44
44
  MSG = 'Do not `return` in `begin..end` blocks in assignment contexts.'
45
45
 
46
46
  def on_lvasgn(node)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for a block that is known to need more positional
7
+ # block arguments than are given (by default this is configured for
8
+ # `Enumerable` methods needing 2 arguments). Optional arguments are allowed,
9
+ # although they don't generally make sense as the default value will
10
+ # be used. Blocks that have no receiver, or take splatted arguments
11
+ # (ie. `*args`) are always accepted.
12
+ #
13
+ # Keyword arguments (including `**kwargs`) do not get counted towards
14
+ # this, as they are not used by the methods in question.
15
+ #
16
+ # NOTE: This cop matches for method names only and hence cannot tell apart
17
+ # methods with same name in different classes.
18
+ #
19
+ # Method names and their expected arity can be configured like this:
20
+ #
21
+ # Methods:
22
+ # inject: 2
23
+ # reduce: 2
24
+ #
25
+ # @example
26
+ # # bad
27
+ # values.reduce {}
28
+ # values.min { |a| a }
29
+ # values.sort { |a; b| a + b }
30
+ #
31
+ # # good
32
+ # values.reduce { |memo, obj| memo << obj }
33
+ # values.min { |a, b| a <=> b }
34
+ # values.sort { |*x| x[0] <=> x[1] }
35
+ #
36
+ class UnexpectedBlockArity < Base
37
+ MSG = '`%<method>s` expects at least %<expected>i positional arguments, got %<actual>i.'
38
+
39
+ def on_block(node)
40
+ return if acceptable?(node)
41
+
42
+ expected = expected_arity(node.method_name)
43
+ actual = arg_count(node)
44
+ return if actual >= expected
45
+
46
+ message = format(MSG, method: node.method_name, expected: expected, actual: actual)
47
+ add_offense(node, message: message)
48
+ end
49
+
50
+ alias on_numblock on_block
51
+
52
+ private
53
+
54
+ def methods
55
+ cop_config.fetch('Methods', [])
56
+ end
57
+
58
+ def acceptable?(node)
59
+ !(included_method?(node.method_name) && node.receiver)
60
+ end
61
+
62
+ def included_method?(name)
63
+ methods.key?(name.to_s)
64
+ end
65
+
66
+ def expected_arity(method)
67
+ cop_config['Methods'][method.to_s]
68
+ end
69
+
70
+ def arg_count(node)
71
+ return node.children[1] if node.numblock_type? # the maximum numbered param for the block
72
+
73
+ # Only `arg`, `optarg` and `mlhs` (destructuring) count as arguments that
74
+ # can be used. Keyword arguments are not used for these methods so are
75
+ # ignored.
76
+ node.arguments.count do |arg|
77
+ return Float::INFINITY if arg.restarg_type?
78
+
79
+ arg.arg_type? || arg.optarg_type? || arg.mlhs_type?
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -12,8 +12,7 @@ module RuboCop
12
12
  # could be rewritten as such without a loop.
13
13
  #
14
14
  # Also catches instances where an index of the accumulator is returned, as
15
- # this may change the type of object being retained. As well, detects when
16
- # fewer than 2 block arguments are specified.
15
+ # this may change the type of object being retained.
17
16
  #
18
17
  # NOTE: For the purpose of reducing false positives, this cop only flags
19
18
  # returns in `reduce` blocks where the element is the only variable in
@@ -68,7 +67,10 @@ module RuboCop
68
67
  MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
69
68
 
70
69
  def_node_matcher :reduce_with_block?, <<~PATTERN
71
- (block (send _recv {:reduce :inject} ...) args ...)
70
+ {
71
+ (block (send _recv {:reduce :inject} ...) args ...)
72
+ (numblock (send _recv {:reduce :inject} ...) ...)
73
+ }
72
74
  PATTERN
73
75
 
74
76
  def_node_matcher :accumulator_index?, <<~PATTERN
@@ -107,10 +109,11 @@ module RuboCop
107
109
 
108
110
  def on_block(node)
109
111
  return unless reduce_with_block?(node)
110
- return unless node.arguments.length >= 2
112
+ return unless node.argument_list.length >= 2
111
113
 
112
114
  check_return_values(node)
113
115
  end
116
+ alias on_numblock on_block
114
117
 
115
118
  private
116
119
 
@@ -148,7 +151,7 @@ module RuboCop
148
151
  end
149
152
 
150
153
  def block_arg_name(node, index)
151
- node.arguments[index].node_parts[0]
154
+ node.argument_list[index].name
152
155
  end
153
156
 
154
157
  # Look for an index of the accumulator being returned, except where the index
@@ -7,6 +7,27 @@ module RuboCop
7
7
  # configured maximum. The ABC size is based on assignments, branches
8
8
  # (method calls), and conditions. See http://c2.com/cgi/wiki?AbcMetric
9
9
  # and https://en.wikipedia.org/wiki/ABC_Software_Metric.
10
+ #
11
+ # You can have repeated "attributes" calls count as a single "branch".
12
+ # For this purpose, attributes are any method with no argument; no attempt
13
+ # is meant to distinguish actual `attr_reader` from other methods.
14
+ #
15
+ # @example CountRepeatedAttributes: false (default is true)
16
+ #
17
+ # # `model` and `current_user`, refenced 3 times each,
18
+ # # are each counted as only 1 branch each if
19
+ # # `CountRepeatedAttributes` is set to 'false'
20
+ #
21
+ # def search
22
+ # @posts = model.active.visible_by(current_user)
23
+ # .search(params[:q])
24
+ # @posts = model.some_process(@posts, current_user)
25
+ # @posts = model.another_process(@posts, current_user)
26
+ #
27
+ # render 'pages/search/page'
28
+ # end
29
+ #
30
+ # This cop also takes into account `IgnoredMethods` (defaults to `[]`)
10
31
  class AbcSize < Base
11
32
  include MethodComplexity
12
33
 
@@ -16,7 +37,10 @@ module RuboCop
16
37
  private
17
38
 
18
39
  def complexity(node)
19
- Utils::AbcSizeCalculator.calculate(node)
40
+ Utils::AbcSizeCalculator.calculate(
41
+ node,
42
+ discount_repeated_attributes: !cop_config['CountRepeatedAttributes']
43
+ )
20
44
  end
21
45
  end
22
46
  end
@@ -12,6 +12,10 @@ module RuboCop
12
12
  # Available are: 'array', 'hash', and 'heredoc'. Each literal
13
13
  # will be counted as one line regardless of its actual size.
14
14
  #
15
+ #
16
+ # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
17
+ # for backwards compatibility. Please use `IgnoredMethods` instead.
18
+ #
15
19
  # @example CountAsOne: ['array', 'heredoc']
16
20
  #
17
21
  # something do
@@ -33,11 +37,15 @@ module RuboCop
33
37
  # NOTE: This cop does not apply for `Struct` definitions.
34
38
  class BlockLength < Base
35
39
  include CodeLength
40
+ include IgnoredMethods
41
+
42
+ ignored_methods deprecated_key: 'ExcludedMethods'
36
43
 
37
44
  LABEL = 'Block'
38
45
 
39
46
  def on_block(node)
40
- return if excluded_method?(node)
47
+ return if ignored_method?(node.method_name)
48
+ return if method_receiver_excluded?(node)
41
49
  return if node.class_constructor? || node.struct_constructor?
42
50
 
43
51
  check_code_length(node)
@@ -45,11 +53,13 @@ module RuboCop
45
53
 
46
54
  private
47
55
 
48
- def excluded_method?(node)
56
+ def method_receiver_excluded?(node)
49
57
  node_receiver = node.receiver&.source&.gsub(/\s+/, '')
50
58
  node_method = String(node.method_name)
51
59
 
52
- excluded_methods.any? do |config|
60
+ ignored_methods.any? do |config|
61
+ next unless config.is_a?(String)
62
+
53
63
  receiver, method = config.split('.')
54
64
 
55
65
  unless method
@@ -61,10 +71,6 @@ module RuboCop
61
71
  end
62
72
  end
63
73
 
64
- def excluded_methods
65
- cop_config['ExcludedMethods'] || []
66
- end
67
-
68
74
  def cop_label
69
75
  LABEL
70
76
  end
@@ -11,6 +11,9 @@ module RuboCop
11
11
  # Available are: 'array', 'hash', and 'heredoc'. Each literal
12
12
  # will be counted as one line regardless of its actual size.
13
13
  #
14
+ # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
15
+ # for backwards compatibility. Please use `IgnoredMethods` instead.
16
+ #
14
17
  # @example CountAsOne: ['array', 'heredoc']
15
18
  #
16
19
  # def m
@@ -31,12 +34,14 @@ module RuboCop
31
34
  #
32
35
  class MethodLength < Base
33
36
  include CodeLength
37
+ include IgnoredMethods
38
+
39
+ ignored_methods deprecated_key: 'ExcludedMethods'
34
40
 
35
41
  LABEL = 'Method'
36
42
 
37
43
  def on_def(node)
38
- excluded_methods = cop_config['ExcludedMethods']
39
- return if excluded_methods.any? { |m| m.match? String(node.method_name) }
44
+ return if ignored_method?(node.method_name)
40
45
 
41
46
  check_code_length(node)
42
47
  end
@@ -4,17 +4,76 @@ module RuboCop
4
4
  module Cop
5
5
  module Metrics
6
6
  # This cop checks for methods with too many parameters.
7
+ #
7
8
  # The maximum number of parameters is configurable.
8
- # Keyword arguments can optionally be excluded from the total count.
9
+ # Keyword arguments can optionally be excluded from the total count,
10
+ # as they add less complexity than positional or optional parameters.
11
+ #
12
+ # @example Max: 3
13
+ # # good
14
+ # def foo(a, b, c = 1)
15
+ # end
16
+ #
17
+ # @example Max: 2
18
+ # # bad
19
+ # def foo(a, b, c = 1)
20
+ # end
21
+ #
22
+ # @example CountKeywordArgs: true (default)
23
+ # # counts keyword args towards the maximum
24
+ #
25
+ # # bad (assuming Max is 3)
26
+ # def foo(a, b, c, d: 1)
27
+ # end
28
+ #
29
+ # # good (assuming Max is 3)
30
+ # def foo(a, b, c: 1)
31
+ # end
32
+ #
33
+ # @example CountKeywordArgs: false
34
+ # # don't count keyword args towards the maximum
35
+ #
36
+ # # good (assuming Max is 3)
37
+ # def foo(a, b, c, d: 1)
38
+ # end
39
+ #
40
+ # This cop also checks for the maximum number of optional parameters.
41
+ # This can be configured using the `MaxOptionalParameters` config option.
42
+ #
43
+ # @example MaxOptionalParameters: 3 (default)
44
+ # # good
45
+ # def foo(a = 1, b = 2, c = 3)
46
+ # end
47
+ #
48
+ # @example MaxOptionalParameters: 2
49
+ # # bad
50
+ # def foo(a = 1, b = 2, c = 3)
51
+ # end
52
+ #
9
53
  class ParameterLists < Base
10
54
  include ConfigurableMax
11
55
 
12
56
  MSG = 'Avoid parameter lists longer than %<max>d parameters. ' \
13
57
  '[%<count>d/%<max>d]'
58
+ OPTIONAL_PARAMETERS_MSG = 'Method has too many optional parameters. [%<count>d/%<max>d]'
14
59
 
15
60
  NAMED_KEYWORD_TYPES = %i[kwoptarg kwarg].freeze
16
61
  private_constant :NAMED_KEYWORD_TYPES
17
62
 
63
+ def on_def(node)
64
+ optargs = node.arguments.select(&:optarg_type?)
65
+ return if optargs.count <= max_optional_parameters
66
+
67
+ message = format(
68
+ OPTIONAL_PARAMETERS_MSG,
69
+ max: max_optional_parameters,
70
+ count: optargs.count
71
+ )
72
+
73
+ add_offense(node, message: message)
74
+ end
75
+ alias on_defs on_def
76
+
18
77
  def on_args(node)
19
78
  count = args_count(node)
20
79
  return unless count > max_params
@@ -44,6 +103,10 @@ module RuboCop
44
103
  cop_config['Max']
45
104
  end
46
105
 
106
+ def max_optional_parameters
107
+ cop_config['MaxOptionalParameters']
108
+ end
109
+
47
110
  def count_keyword_args?
48
111
  cop_config['CountKeywordArgs']
49
112
  end
@@ -13,6 +13,7 @@ module RuboCop
13
13
  class AbcSizeCalculator
14
14
  include IteratingBlock
15
15
  include RepeatedCsendDiscount
16
+ prepend RepeatedAttributeDiscount
16
17
 
17
18
  # > Branch -- an explicit forward program branch out of scope -- a
18
19
  # > function call, class method call ..
@@ -24,8 +25,8 @@ module RuboCop
24
25
  # > http://c2.com/cgi/wiki?AbcMetric
25
26
  CONDITION_NODES = CyclomaticComplexity::COUNTED_NODES.freeze
26
27
 
27
- def self.calculate(node)
28
- new(node).calculate
28
+ def self.calculate(node, discount_repeated_attributes: false)
29
+ new(node, discount_repeated_attributes: discount_repeated_attributes).calculate
29
30
  end
30
31
 
31
32
  # TODO: move to rubocop-ast
@@ -42,14 +43,8 @@ module RuboCop
42
43
  end
43
44
 
44
45
  def calculate
45
- @node.each_node do |child|
46
- @assignment += 1 if assignment?(child)
47
-
48
- if branch?(child)
49
- evaluate_branch_nodes(child)
50
- elsif condition?(child)
51
- evaluate_condition_node(child)
52
- end
46
+ visit_depth_last(@node) do |child|
47
+ calculate_node(child)
53
48
  end
54
49
 
55
50
  [
@@ -80,6 +75,21 @@ module RuboCop
80
75
 
81
76
  private
82
77
 
78
+ def visit_depth_last(node, &block)
79
+ node.each_child_node { |child| visit_depth_last(child, &block) }
80
+ yield node
81
+ end
82
+
83
+ def calculate_node(node)
84
+ @assignment += 1 if assignment?(node)
85
+
86
+ if branch?(node)
87
+ evaluate_branch_nodes(node)
88
+ elsif condition?(node)
89
+ evaluate_condition_node(node)
90
+ end
91
+ end
92
+
83
93
  def assignment?(node)
84
94
  return compound_assignment(node) if node.masgn_type? || node.shorthand_asgn?
85
95
 
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Metrics
6
+ module Utils
7
+ # @api private
8
+ #
9
+ # Identifies repetitions `{c}send` calls with no arguments:
10
+ #
11
+ # foo.bar
12
+ # foo.bar # => repeated
13
+ # foo.bar.baz.qux # => inner send repeated
14
+ # foo.bar.baz.other # => both inner send repeated
15
+ # foo.bar(2) # => not repeated
16
+ #
17
+ # It also invalidates sequences if a receiver is reassigned:
18
+ #
19
+ # xx.foo.bar
20
+ # xx.foo.baz # => inner send repeated
21
+ # self.xx = any # => invalidates everything so far
22
+ # xx.foo.baz # => no repetition
23
+ # self.xx.foo.baz # => all repeated
24
+ #
25
+ module RepeatedAttributeDiscount
26
+ extend NodePattern::Macros
27
+ include RuboCop::AST::Sexp
28
+
29
+ # Plug into the calculator
30
+ def initialize(node, discount_repeated_attributes: false)
31
+ super(node)
32
+ return unless discount_repeated_attributes
33
+
34
+ self_attributes = {} # Share hash for `(send nil? :foo)` and `(send (self) :foo)`
35
+ @known_attributes = {
36
+ s(:self) => self_attributes,
37
+ nil => self_attributes
38
+ }
39
+ # example after running `obj = foo.bar; obj.baz.qux`
40
+ # { nil => {foo: {bar: {}}},
41
+ # s(self) => same hash ^,
42
+ # s(:lvar, :obj) => {baz: {qux: {}}}
43
+ # }
44
+ end
45
+
46
+ def discount_repeated_attributes?
47
+ defined?(@known_attributes)
48
+ end
49
+
50
+ def evaluate_branch_nodes(node)
51
+ return if discount_repeated_attributes? && discount_repeated_attribute?(node)
52
+
53
+ super
54
+ end
55
+
56
+ def calculate_node(node)
57
+ update_repeated_attribute(node) if discount_repeated_attributes?
58
+ super
59
+ end
60
+
61
+ private
62
+
63
+ def_node_matcher :attribute_call?, <<~PATTERN
64
+ ( {csend send} _receiver _method # and no parameters
65
+ )
66
+ PATTERN
67
+
68
+ def discount_repeated_attribute?(send_node)
69
+ return false unless attribute_call?(send_node)
70
+
71
+ repeated = true
72
+ find_attributes(send_node) do |hash, lookup|
73
+ return false if hash.nil?
74
+
75
+ repeated = false
76
+ hash[lookup] = {}
77
+ end
78
+
79
+ repeated
80
+ end
81
+
82
+ def update_repeated_attribute(node)
83
+ return unless (receiver, method = setter_to_getter(node))
84
+
85
+ calls = find_attributes(receiver) { return }
86
+ if method # e.g. `self.foo = 42`
87
+ calls.delete(method)
88
+ else # e.g. `var = 42`
89
+ calls.clear
90
+ end
91
+ end
92
+
93
+ def_node_matcher :root_node?, <<~PATTERN
94
+ { nil? | self # e.g. receiver of `my_method` or `self.my_attr`
95
+ | lvar | ivar | cvar | gvar # e.g. receiver of `var.my_method`
96
+ | const } # e.g. receiver of `MyConst.foo.bar`
97
+ PATTERN
98
+
99
+ # Returns the "known_attributes" for the `node` by walking the receiver tree
100
+ # If at any step the subdirectory does not exist, it is yielded with the
101
+ # associated key (method_name)
102
+ # If the node is not a series of `(c)send` calls with no arguments,
103
+ # then `nil` is yielded
104
+ def find_attributes(node, &block)
105
+ if attribute_call?(node)
106
+ calls = find_attributes(node.receiver, &block)
107
+ value = node.method_name
108
+ elsif root_node?(node)
109
+ calls = @known_attributes
110
+ value = node
111
+ else
112
+ return yield nil
113
+ end
114
+
115
+ calls.fetch(value) do
116
+ yield [calls, value]
117
+ end
118
+ end
119
+
120
+ VAR_SETTER_TO_GETTER = {
121
+ lvasgn: :lvar,
122
+ ivasgn: :ivar,
123
+ cvasgn: :cvar,
124
+ gvasgn: :gvar
125
+ }.freeze
126
+
127
+ # @returns `[receiver, method | nil]` for the given setter `node`
128
+ # or `nil` if it is not a setter.
129
+ def setter_to_getter(node)
130
+ if (type = VAR_SETTER_TO_GETTER[node.type])
131
+ # (lvasgn :my_var (int 42)) => [(lvar my_var), nil]
132
+ [s(type, node.children.first), nil]
133
+ elsif node.shorthand_asgn? # (or-asgn (send _receiver :foo) _value)
134
+ # (or-asgn (send _receiver :foo) _value) => [_receiver, :foo]
135
+ node.children.first.children
136
+ elsif node.respond_to?(:setter_method?) && node.setter_method?
137
+ # (send _receiver :foo= (int 42) ) => [_receiver, :foo]
138
+ method_name = node.method_name[0...-1].to_sym
139
+ [node.receiver, method_name]
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end