rubocop 1.4.2 → 1.5.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 (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