rubocop 1.3.0 → 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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +75 -16
  4. data/lib/rubocop.rb +5 -0
  5. data/lib/rubocop/cli.rb +5 -1
  6. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +67 -0
  8. data/lib/rubocop/config_loader.rb +12 -4
  9. data/lib/rubocop/config_loader_resolver.rb +5 -1
  10. data/lib/rubocop/config_obsoletion.rb +21 -3
  11. data/lib/rubocop/config_regeneration.rb +1 -1
  12. data/lib/rubocop/config_validator.rb +8 -1
  13. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  14. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  15. data/lib/rubocop/cop/generator.rb +2 -9
  16. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  17. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  18. data/lib/rubocop/cop/layout/class_structure.rb +15 -3
  19. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +80 -10
  20. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  21. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  23. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  24. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  25. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +4 -1
  26. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  27. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  28. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  29. data/lib/rubocop/cop/lint/to_enum_arguments.rb +6 -15
  30. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  31. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +20 -6
  32. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  33. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  34. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  35. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  36. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  37. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  38. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  39. data/lib/rubocop/cop/mixin/configurable_numbering.rb +4 -3
  40. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  41. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  42. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  43. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  44. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  45. data/lib/rubocop/cop/style/and_or.rb +10 -0
  46. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  47. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +8 -1
  48. data/lib/rubocop/cop/style/documentation.rb +12 -1
  49. data/lib/rubocop/cop/style/format_string.rb +8 -3
  50. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  51. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  52. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  53. data/lib/rubocop/cop/style/negated_if_else_condition.rb +3 -1
  54. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  55. data/lib/rubocop/cop/style/redundant_argument.rb +75 -0
  56. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  57. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  58. data/lib/rubocop/cop/style/sole_nested_conditional.rb +49 -3
  59. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  60. data/lib/rubocop/cop/util.rb +1 -1
  61. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  62. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  63. data/lib/rubocop/core_ext/hash.rb +20 -0
  64. data/lib/rubocop/ext/regexp_node.rb +5 -10
  65. data/lib/rubocop/ext/regexp_parser.rb +2 -9
  66. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  67. data/lib/rubocop/options.rb +5 -0
  68. data/lib/rubocop/rake_task.rb +2 -2
  69. data/lib/rubocop/runner.rb +1 -1
  70. data/lib/rubocop/target_finder.rb +1 -1
  71. data/lib/rubocop/target_ruby.rb +12 -4
  72. data/lib/rubocop/version.rb +1 -1
  73. metadata +12 -10
  74. data/bin/console +0 -10
  75. data/bin/rubocop-profile +0 -32
  76. data/bin/setup +0 -7
@@ -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 = {}
@@ -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
- snake_case: /(?:\D|_\d+)$/,
12
- normalcase: /(?:\D|[^_\d]\d+)$/,
13
- non_integer: /\D$/
12
+ snake_case: /(?:\D|_\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
@@ -11,6 +11,12 @@ module RuboCop
11
11
  include Metrics::Utils::RepeatedCsendDiscount
12
12
  extend NodePattern::Macros
13
13
 
14
+ # Ensure cops that include `MethodComplexity` have the config
15
+ # `attr_accessor`s that `ignored_method?` needs.
16
+ def self.included(base)
17
+ base.extend(IgnoredMethods::Config)
18
+ end
19
+
14
20
  def on_def(node)
15
21
  return if ignored_method?(node.method_name)
16
22
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  # b c { block }. <-- b is indented relative to a
32
32
  # d <-- d is indented relative to a
33
33
  def left_hand_side(lhs)
34
- lhs = lhs.parent while lhs.parent&.send_type?
34
+ lhs = lhs.parent while lhs.parent&.send_type? && lhs.parent.loc.dot
35
35
  lhs
36
36
  end
37
37
 
@@ -16,9 +16,7 @@ module RuboCop
16
16
  end
17
17
 
18
18
  def find_visibility_start(node)
19
- node.left_siblings
20
- .reverse
21
- .find(&method(:visibility_block?))
19
+ node.left_siblings.reverse.find { |sibling| visibility_block?(sibling) }
22
20
  end
23
21
 
24
22
  # Navigate to find the last protected method
@@ -72,6 +72,8 @@ module RuboCop
72
72
  end
73
73
 
74
74
  corrector.replace(node.loc.operator, node.alternate_operator)
75
+
76
+ keep_operator_precedence(corrector, node)
75
77
  end
76
78
  end
77
79
 
@@ -123,6 +125,14 @@ module RuboCop
123
125
  corrector.wrap(node, '(', ')')
124
126
  end
125
127
 
128
+ def keep_operator_precedence(corrector, node)
129
+ if node.or_type? && node.parent&.and_type?
130
+ corrector.wrap(node, '(', ')')
131
+ elsif node.and_type? && node.rhs.or_type?
132
+ corrector.wrap(node.rhs, '(', ')')
133
+ end
134
+ end
135
+
126
136
  def correctable_send?(node)
127
137
  !node.parenthesized? && node.arguments? && !node.method?(:[])
128
138
  end
@@ -55,13 +55,18 @@ module RuboCop
55
55
  padding = ((' ' * indent_width) + leading_spaces(node)).to_s
56
56
  padding_for_trailing_end = padding.sub(' ' * node.loc.end.column, '')
57
57
 
58
- replace_keyword_with_module(corrector, node)
58
+ replace_namespace_keyword(corrector, node)
59
59
  split_on_double_colon(corrector, node, padding)
60
60
  add_trailing_end(corrector, node, padding_for_trailing_end)
61
61
  end
62
62
 
63
- def replace_keyword_with_module(corrector, node)
64
- corrector.replace(node.loc.keyword, 'module')
63
+ def replace_namespace_keyword(corrector, node)
64
+ class_definition = node.left_sibling&.each_node(:class)&.find do |class_node|
65
+ class_node.identifier == node.identifier.namespace
66
+ end
67
+ namespace_keyword = class_definition ? 'class' : 'module'
68
+
69
+ corrector.replace(node.loc.keyword, namespace_keyword)
65
70
  end
66
71
 
67
72
  def split_on_double_colon(corrector, node, padding)
@@ -68,6 +68,12 @@ module RuboCop
68
68
  # end
69
69
  # EOT
70
70
  # )
71
+ #
72
+ # # bad - interpolated string without comment
73
+ # class_eval("def #{unsafe_method}!(*params); end")
74
+ #
75
+ # # good - with inline comment or replace it with block comment using heredoc
76
+ # class_eval("def #{unsafe_method}!(*params); end # def capitalize!(*params); end")
71
77
  class DocumentDynamicEvalDefinition < Base
72
78
  BLOCK_COMMENT_REGEXP = /^\s*#(?!{)/.freeze
73
79
  COMMENT_REGEXP = /\s*#(?!{).*/.freeze
@@ -79,7 +85,8 @@ module RuboCop
79
85
  arg_node = node.first_argument
80
86
 
81
87
  return unless arg_node&.dstr_type? && interpolated?(arg_node)
82
- return if inline_comment_docs?(arg_node) || comment_block_docs?(arg_node)
88
+ return if inline_comment_docs?(arg_node) ||
89
+ arg_node.heredoc? && comment_block_docs?(arg_node)
83
90
 
84
91
  add_offense(node.loc.selector)
85
92
  end
@@ -55,6 +55,11 @@ module RuboCop
55
55
  # Public = Class.new
56
56
  # end
57
57
  #
58
+ # # Macro calls
59
+ # module Namespace
60
+ # extend Foo
61
+ # end
62
+ #
58
63
  class Documentation < Base
59
64
  include DocumentationComment
60
65
 
@@ -83,15 +88,21 @@ module RuboCop
83
88
  return if documentation_comment?(node) || nodoc_comment?(node)
84
89
  return if compact_namespace?(node) &&
85
90
  nodoc_comment?(outer_module(node).first)
91
+ return if macro_only?(body)
86
92
 
87
93
  add_offense(node.loc.keyword, message: format(MSG, type: type))
88
94
  end
89
95
 
96
+ def macro_only?(body)
97
+ body.respond_to?(:macro?) && body.macro? ||
98
+ body.respond_to?(:children) && body.children&.all? { |child| macro_only?(child) }
99
+ end
100
+
90
101
  def namespace?(node)
91
102
  return false unless node
92
103
 
93
104
  if node.begin_type?
94
- node.children.all?(&method(:constant_declaration?))
105
+ node.children.all? { |child| constant_declaration?(child) }
95
106
  else
96
107
  constant_definition?(node)
97
108
  end
@@ -111,15 +111,20 @@ module RuboCop
111
111
  format = format_arg.source
112
112
 
113
113
  args = if param_args.one?
114
- arg = param_args.last
115
-
116
- arg.hash_type? ? "{ #{arg.source} }" : arg.source
114
+ format_single_parameter(param_args.last)
117
115
  else
118
116
  "[#{param_args.map(&:source).join(', ')}]"
119
117
  end
120
118
 
121
119
  corrector.replace(node, "#{format} % #{args}")
122
120
  end
121
+
122
+ def format_single_parameter(arg)
123
+ source = arg.source
124
+ return "{ #{source} }" if arg.hash_type?
125
+
126
+ arg.send_type? && arg.operator_method? && !arg.parenthesized? ? "(#{source})" : source
127
+ end
123
128
  end
124
129
  end
125
130
  end