rubocop 1.4.1 → 1.6.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +54 -11
  4. data/config/obsoletion.yml +196 -0
  5. data/lib/rubocop.rb +14 -0
  6. data/lib/rubocop/cli.rb +5 -1
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +80 -0
  8. data/lib/rubocop/config_loader.rb +1 -1
  9. data/lib/rubocop/config_loader_resolver.rb +5 -1
  10. data/lib/rubocop/config_obsoletion.rb +65 -247
  11. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  12. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  13. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  14. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  15. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  17. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  18. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  19. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  20. data/lib/rubocop/config_validator.rb +18 -4
  21. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  22. data/lib/rubocop/cop/base.rb +17 -15
  23. data/lib/rubocop/cop/cop.rb +2 -2
  24. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  25. data/lib/rubocop/cop/generator.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  27. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  28. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  29. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  30. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  31. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +12 -0
  32. data/lib/rubocop/cop/layout/line_length.rb +6 -16
  33. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  34. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  35. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  36. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  37. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  38. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +7 -2
  39. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  40. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  41. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  42. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  43. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  44. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  45. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  46. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  47. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -2
  48. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  49. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  50. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  51. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  52. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  53. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  54. data/lib/rubocop/cop/style/and_or.rb +10 -0
  55. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  56. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  57. data/lib/rubocop/cop/style/float_division.rb +44 -1
  58. data/lib/rubocop/cop/style/format_string.rb +8 -3
  59. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -0
  60. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  61. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  62. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  63. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  64. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  65. data/lib/rubocop/cop/style/redundant_argument.rb +17 -2
  66. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  67. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  68. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  69. data/lib/rubocop/cop/style/sole_nested_conditional.rb +65 -3
  70. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  71. data/lib/rubocop/cop/style/string_concatenation.rb +26 -1
  72. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  73. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  74. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  75. data/lib/rubocop/core_ext/hash.rb +20 -0
  76. data/lib/rubocop/ext/regexp_node.rb +29 -12
  77. data/lib/rubocop/ext/regexp_parser.rb +20 -9
  78. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  79. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  80. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  81. data/lib/rubocop/lockfile.rb +40 -0
  82. data/lib/rubocop/version.rb +1 -1
  83. metadata +32 -5
@@ -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
@@ -6,7 +6,12 @@ module RuboCop
6
6
  module Utils
7
7
  # @api private
8
8
  #
9
- # Helps to calculate code length for the provided node.
9
+ # Identifies repetitions `&.` on the same variable:
10
+ #
11
+ # my_var&.foo
12
+ # my_var&.bar # => repeated
13
+ # my_var = baz # => reset
14
+ # my_var&.qux # => not repeated
10
15
  module RepeatedCsendDiscount
11
16
  def reset_repeated_csend
12
17
  @repeated_csend = {}
@@ -71,7 +71,7 @@ module RuboCop
71
71
  end
72
72
 
73
73
  def qualified_legacy_cop_name(cop_name)
74
- legacy_cop_names = RuboCop::ConfigObsoletion::OBSOLETE_COPS.keys
74
+ legacy_cop_names = RuboCop::ConfigObsoletion.legacy_cop_names
75
75
 
76
76
  legacy_cop_names.detect do |legacy_cop_name|
77
77
  legacy_cop_name.split('/')[1] == cop_name
@@ -7,10 +7,11 @@ module RuboCop
7
7
  module ConfigurableNumbering
8
8
  include ConfigurableFormatting
9
9
 
10
+ implicit_param = /\A_\d+\z/
10
11
  FORMATS = {
11
12
  snake_case: /(?:\D|_\d+|\A\d+)\z/,
12
- normalcase: /(?:\D|[^_\d]\d+|\A\d+)\z/,
13
- non_integer: /(\D|\A\d+)\z/
13
+ normalcase: /(?:\D|[^_\d]\d+|\A\d+)\z|#{implicit_param}/,
14
+ non_integer: /(\D|\A\d+)\z|#{implicit_param}/
14
15
  }.freeze
15
16
  end
16
17
  end
@@ -2,7 +2,15 @@
2
2
 
3
3
  module RuboCop
4
4
  module Cop
5
- # Common functionality for enforcing a specific superclass
5
+ # Common functionality for enforcing a specific superclass.
6
+ #
7
+ # IMPORTANT: RuboCop core depended on this module when it supported Rails department.
8
+ # Rails department has been extracted to RuboCop Rails gem.
9
+ # This module is deprecated and will be removed by RuboCop 2.0.
10
+ # It will not be updated to `RuboCop::Cop::Base` v1 API to maintain compatibility
11
+ # with existing RuboCop Rails 2.8 or lower.
12
+ #
13
+ # @api private
6
14
  module EnforceSuperclass
7
15
  def self.included(base)
8
16
  base.def_node_matcher :class_definition, <<~PATTERN
@@ -4,15 +4,48 @@ module RuboCop
4
4
  module Cop
5
5
  # This module encapsulates the ability to ignore certain methods when
6
6
  # parsing.
7
+ # Cops that use `IgnoredMethods` can accept either strings or regexes to match
8
+ # against.
7
9
  module IgnoredMethods
8
- private
10
+ # Configuration for IgnoredMethods. It is added to classes that include
11
+ # the module so that configuration can be set using the `ignored_methods`
12
+ # class macro.
13
+ module Config
14
+ attr_accessor :deprecated_key
15
+
16
+ def ignored_methods(**config)
17
+ self.deprecated_key = config[:deprecated_key]
18
+ end
19
+ end
20
+
21
+ def self.included(base)
22
+ base.extend(Config)
23
+ end
9
24
 
10
25
  def ignored_method?(name)
11
- ignored_methods.include?(name.to_s)
26
+ ignored_methods.any? do |value|
27
+ case value
28
+ when Regexp
29
+ value.match? String(name)
30
+ else
31
+ value == String(name)
32
+ end
33
+ end
12
34
  end
13
35
 
14
36
  def ignored_methods
15
- cop_config.fetch('IgnoredMethods', [])
37
+ keys = %w[IgnoredMethods]
38
+ keys << deprecated_key if deprecated_key
39
+
40
+ cop_config.slice(*keys).values.reduce(&:concat)
41
+ end
42
+
43
+ private
44
+
45
+ def deprecated_key
46
+ return unless self.class.respond_to?(:deprecated_key)
47
+
48
+ self.class.deprecated_key&.to_s
16
49
  end
17
50
  end
18
51
  end