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
@@ -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 = {}
@@ -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
 
@@ -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)
@@ -17,26 +17,61 @@ module RuboCop
17
17
  include OnNormalIfUnless
18
18
  extend AutoCorrector
19
19
 
20
- MSG = 'Do not use if x; Use the ternary operator instead.'
20
+ MSG_IF_ELSE = 'Do not use `if %<expr>s;` - use `if/else` instead.'
21
+ MSG_TERNARY = 'Do not use `if %<expr>s;` - use a ternary operator instead.'
21
22
 
22
23
  def on_normal_if_unless(node)
23
24
  return unless node.else_branch
25
+ return if node.parent&.if_type?
24
26
 
25
27
  beginning = node.loc.begin
26
28
  return unless beginning&.is?(';')
27
29
 
28
- add_offense(node) do |corrector|
29
- corrector.replace(node, correct_to_ternary(node))
30
+ message = node.else_branch.if_type? ? MSG_IF_ELSE : MSG_TERNARY
31
+
32
+ add_offense(node, message: format(message, expr: node.condition.source)) do |corrector|
33
+ corrector.replace(node, autocorrect(node))
30
34
  end
31
35
  end
32
36
 
33
37
  private
34
38
 
35
- def correct_to_ternary(node)
39
+ def autocorrect(node)
40
+ return correct_elsif(node) if node.else_branch.if_type?
41
+
36
42
  else_code = node.else_branch ? node.else_branch.source : 'nil'
37
43
 
38
44
  "#{node.condition.source} ? #{node.if_branch.source} : #{else_code}"
39
45
  end
46
+
47
+ def correct_elsif(node)
48
+ <<~RUBY.chop
49
+ if #{node.condition.source}
50
+ #{node.if_branch.source}
51
+ #{build_else_branch(node.else_branch).chop}
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ def build_else_branch(second_condition)
57
+ result = <<~RUBY
58
+ elsif #{second_condition.condition.source}
59
+ #{second_condition.if_branch.source}
60
+ RUBY
61
+
62
+ if second_condition.else_branch
63
+ result += if second_condition.else_branch.if_type?
64
+ build_else_branch(second_condition.else_branch)
65
+ else
66
+ <<~RUBY
67
+ else
68
+ #{second_condition.else_branch.source}
69
+ RUBY
70
+ end
71
+ end
72
+
73
+ result
74
+ end
40
75
  end
41
76
  end
42
77
  end
@@ -21,21 +21,30 @@ module RuboCop
21
21
  def on_send(node)
22
22
  return unless !node.arguments? && node.parenthesized?
23
23
  return if ineligible_node?(node)
24
+ return if default_argument?(node)
24
25
  return if ignored_method?(node.method_name)
25
26
  return if same_name_assignment?(node)
26
27
 
28
+ register_offense(node)
29
+ end
30
+
31
+ private
32
+
33
+ def register_offense(node)
27
34
  add_offense(offense_range(node)) do |corrector|
28
35
  corrector.remove(node.loc.begin)
29
36
  corrector.remove(node.loc.end)
30
37
  end
31
38
  end
32
39
 
33
- private
34
-
35
40
  def ineligible_node?(node)
36
41
  node.camel_case_method? || node.implicit_call? || node.prefix_not?
37
42
  end
38
43
 
44
+ def default_argument?(node)
45
+ node.parent&.optarg_type?
46
+ end
47
+
39
48
  def same_name_assignment?(node)
40
49
  any_assignment?(node) do |asgn_node|
41
50
  next variable_in_mass_assignment?(node.method_name, asgn_node) if asgn_node.masgn_type?
@@ -27,12 +27,13 @@ module RuboCop
27
27
  # # bad
28
28
  # 10_000_00 # typical representation of $10,000 in cents
29
29
  #
30
- class NumericLiterals < Cop
30
+ class NumericLiterals < Base
31
31
  # The parameter is called MinDigits (meaning the minimum number of
32
32
  # digits for which an offense can be registered), but essentially it's
33
33
  # a Max parameter (the maximum number of something that's allowed).
34
34
  include ConfigurableMax
35
35
  include IntegerNode
36
+ extend AutoCorrector
36
37
 
37
38
  MSG = 'Use underscores(_) as thousands separator and ' \
38
39
  'separate every 3 digits with them.'
@@ -46,12 +47,6 @@ module RuboCop
46
47
  check(node)
47
48
  end
48
49
 
49
- def autocorrect(node)
50
- lambda do |corrector|
51
- corrector.replace(node, format_number(node))
52
- end
53
- end
54
-
55
50
  private
56
51
 
57
52
  def max_parameter_name
@@ -67,11 +62,19 @@ module RuboCop
67
62
 
68
63
  case int
69
64
  when /^\d+$/
70
- add_offense(node) { self.max = int.size + 1 }
65
+ return unless (self.max = int.size + 1)
66
+
67
+ register_offense(node)
71
68
  when /\d{4}/, short_group_regex
72
- add_offense(node) do
73
- self.config_to_allow_offenses = { 'Enabled' => false }
74
- end
69
+ return unless (self.config_to_allow_offenses = { 'Enabled' => false })
70
+
71
+ register_offense(node)
72
+ end
73
+ end
74
+
75
+ def register_offense(node)
76
+ add_offense(node) do |corrector|
77
+ corrector.replace(node, format_number(node))
75
78
  end
76
79
  end
77
80
 
@@ -36,7 +36,7 @@ module RuboCop
36
36
  # string.split
37
37
  # "first second".split
38
38
  # A.foo
39
- class RedundantArgument < Cop
39
+ class RedundantArgument < Base
40
40
  MSG = 'Argument %<arg>s is redundant because it is implied by default.'
41
41
 
42
42
  def on_send(node)
@@ -131,7 +131,8 @@ module RuboCop
131
131
  end
132
132
 
133
133
  def without_argument_parentheses_method?(node)
134
- node.send_type? && !node.arguments.empty? && !node.parenthesized?
134
+ node.send_type? &&
135
+ !node.arguments.empty? && !node.parenthesized? && !node.operator_method?
135
136
  end
136
137
  end
137
138
  end
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  def each_escape(node)
84
84
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
85
- yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
85
+ yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
86
86
 
87
87
  if expr.type == :set
88
88
  char_class_depth + (event == :enter ? 1 : -1)
@@ -33,17 +33,22 @@ module RuboCop
33
33
  # end
34
34
  #
35
35
  class SoleNestedConditional < Base
36
+ include RangeHelp
37
+ extend AutoCorrector
38
+
36
39
  MSG = 'Consider merging nested conditions into '\
37
40
  'outer `%<conditional_type>s` conditions.'
38
41
 
39
42
  def on_if(node)
40
43
  return if node.ternary? || node.else? || node.elsif?
41
44
 
42
- branch = node.if_branch
43
- return unless offending_branch?(branch)
45
+ if_branch = node.if_branch
46
+ return unless offending_branch?(if_branch)
44
47
 
45
48
  message = format(MSG, conditional_type: node.keyword)
46
- add_offense(branch.loc.keyword, message: message)
49
+ add_offense(if_branch.loc.keyword, message: message) do |corrector|
50
+ autocorrect(corrector, node, if_branch)
51
+ end
47
52
  end
48
53
 
49
54
  private
@@ -57,6 +62,47 @@ module RuboCop
57
62
  !(branch.modifier_form? && allow_modifier?)
58
63
  end
59
64
 
65
+ def autocorrect(corrector, node, if_branch)
66
+ if node.unless?
67
+ corrector.replace(node.loc.keyword, 'if')
68
+ corrector.insert_before(node.condition, '!')
69
+ end
70
+
71
+ and_operator = if_branch.unless? ? ' && !' : ' && '
72
+ if if_branch.modifier_form?
73
+ correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
74
+ else
75
+ correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
76
+ end
77
+
78
+ correct_for_comment(corrector, node, if_branch)
79
+ end
80
+
81
+ def correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
82
+ corrector.insert_after(node.condition, "#{and_operator}#{if_branch.condition.source}")
83
+
84
+ range = range_between(
85
+ if_branch.loc.keyword.begin_pos, if_branch.condition.source_range.end_pos
86
+ )
87
+ corrector.remove(range_with_surrounding_space(range: range, newlines: false))
88
+ corrector.remove(if_branch.loc.keyword)
89
+ end
90
+
91
+ def correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
92
+ range = range_between(
93
+ node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
94
+ )
95
+ corrector.replace(range, and_operator)
96
+ corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
97
+ end
98
+
99
+ def correct_for_comment(corrector, node, if_branch)
100
+ comments = processed_source.comments_before_line(if_branch.source_range.line)
101
+ comment_text = comments.map(&:text).join("\n") << "\n"
102
+
103
+ corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
104
+ end
105
+
60
106
  def allow_modifier?
61
107
  cop_config['AllowModifier']
62
108
  end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # @example
9
9
  # # bad
10
10
  # something.map { |s| s.upcase }
11
+ # something.map { _1.upcase }
11
12
  #
12
13
  # # good
13
14
  # something.map(&:upcase)
@@ -22,9 +23,9 @@ module RuboCop
22
23
 
23
24
  def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'
24
25
  def_node_matcher :symbol_proc?, <<~PATTERN
25
- (block
26
+ ({block numblock}
26
27
  ${(send ...) (super ...) zsuper}
27
- $(args (arg _var))
28
+ ${(args (arg _)) %Integer}
28
29
  (send (lvar _var) $_))
29
30
  PATTERN
30
31
 
@@ -40,11 +41,12 @@ module RuboCop
40
41
  return if proc_node?(dispatch_node)
41
42
  return if %i[lambda proc].include?(dispatch_node.method_name)
42
43
  return if ignored_method?(dispatch_node.method_name)
43
- return if destructuring_block_argument?(arguments_node)
44
+ return if node.block_type? && destructuring_block_argument?(arguments_node)
44
45
 
45
46
  register_offense(node, method_name, dispatch_node.method_name)
46
47
  end
47
48
  end
49
+ alias on_numblock on_block
48
50
 
49
51
  def destructuring_block_argument?(argument_node)
50
52
  argument_node.one? && argument_node.source.include?(',')
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extensions to the core Hash class
4
+ class Hash
5
+ unless method_defined?(:slice)
6
+ # Adds `Hash#slice` for Ruby 2.4.
7
+ # Returns a hash containing a subset of keys. If a given key is not
8
+ # in the hash, it will not be returned.
9
+ #
10
+ # @return [Hash] hash containing only the keys given.
11
+ #
12
+ # @example
13
+ # { one: 1, two: 2 }.slice(:two, :three) #=> { two: 2 }
14
+ def slice(*keys)
15
+ h = {}
16
+ keys.each { |k| h[k] = self[k] if key?(k) }
17
+ h
18
+ end
19
+ end
20
+ end
@@ -19,18 +19,13 @@ module RuboCop
19
19
  super
20
20
 
21
21
  str = with_interpolations_blanked
22
- begin
23
- @parsed_tree = Regexp::Parser.parse(str, options: options)
22
+ @parsed_tree = begin
23
+ Regexp::Parser.parse(str, options: options)
24
24
  rescue StandardError
25
- @parsed_tree = nil
26
- else
27
- origin = loc.begin.end
28
- source = @parsed_tree.to_s
29
- @parsed_tree.each_expression(true) do |e|
30
- e.origin = origin
31
- e.source = source
32
- end
25
+ nil
33
26
  end
27
+ origin = loc.begin.end
28
+ @parsed_tree&.each_expression(true) { |e| e.origin = origin }
34
29
  end
35
30
 
36
31
  def each_capture(named: ANY)