rubocop 1.4.0 → 1.5.2

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/assets/logo.png +0 -0
  4. data/assets/output.html.erb +261 -0
  5. data/config/default.yml +49 -9
  6. data/lib/rubocop.rb +4 -0
  7. data/lib/rubocop/cli.rb +5 -1
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +108 -0
  9. data/lib/rubocop/config_loader.rb +1 -1
  10. data/lib/rubocop/config_loader_resolver.rb +5 -1
  11. data/lib/rubocop/config_obsoletion.rb +21 -3
  12. data/lib/rubocop/config_validator.rb +8 -1
  13. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  14. data/lib/rubocop/cop/generator.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  16. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  17. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  19. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +12 -0
  20. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  21. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  22. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  23. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  24. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  25. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +7 -2
  26. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  27. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  28. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  29. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  30. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  31. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  32. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  33. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -2
  34. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  35. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  36. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  37. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  38. data/lib/rubocop/cop/style/and_or.rb +10 -0
  39. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  40. data/lib/rubocop/cop/style/format_string.rb +8 -3
  41. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  42. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  43. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  44. data/lib/rubocop/cop/style/redundant_argument.rb +3 -1
  45. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  46. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  47. data/lib/rubocop/cop/style/sole_nested_conditional.rb +59 -3
  48. data/lib/rubocop/cop/style/string_concatenation.rb +7 -1
  49. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  50. data/lib/rubocop/core_ext/hash.rb +20 -0
  51. data/lib/rubocop/ext/regexp_node.rb +29 -12
  52. data/lib/rubocop/ext/regexp_parser.rb +20 -9
  53. data/lib/rubocop/version.rb +1 -1
  54. metadata +23 -5
@@ -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
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
@@ -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
 
@@ -104,6 +104,8 @@ module RuboCop
104
104
  def on_arg(node)
105
105
  @node = node
106
106
  name, = *node
107
+ return if allowed_identifier?(name)
108
+
107
109
  check_name(node, name, node.loc.name)
108
110
  end
109
111
  alias on_lvasgn on_arg
@@ -139,7 +141,7 @@ module RuboCop
139
141
  end
140
142
 
141
143
  def allowed_identifier?(name)
142
- allowed_identifiers.include?(name.to_s)
144
+ allowed_identifiers.include?(name.to_s.delete('@'))
143
145
  end
144
146
 
145
147
  def allowed_identifiers
@@ -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)
@@ -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
@@ -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,10 +36,12 @@ 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)
43
+ return if node.receiver.nil?
44
+ return if node.arguments.count != 1
43
45
  return unless redundant_argument?(node)
44
46
 
45
47
  add_offense(node, message: format(MSG, arg: node.arguments.first.source))
@@ -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
@@ -80,14 +80,30 @@ module RuboCop
80
80
  delimiters.include?(char)
81
81
  end
82
82
 
83
- def each_escape(node)
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
86
-
87
- if expr.type == :set
88
- char_class_depth + (event == :enter ? 1 : -1)
89
- else
90
- char_class_depth
83
+ if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0')
84
+ def each_escape(node)
85
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
86
+ yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
87
+
88
+ if expr.type == :set
89
+ char_class_depth + (event == :enter ? 1 : -1)
90
+ else
91
+ char_class_depth
92
+ end
93
+ end
94
+ end
95
+ # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
96
+ # It's for compatibility with regexp_arser 1.8 and will never be maintained.
97
+ else
98
+ def each_escape(node)
99
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
+ yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
101
+
102
+ if expr.type == :set
103
+ char_class_depth + (event == :enter ? 1 : -1)
104
+ else
105
+ char_class_depth
106
+ end
91
107
  end
92
108
  end
93
109
  end
@@ -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,57 @@ 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
+ corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
72
+
73
+ and_operator = if_branch.unless? ? ' && !' : ' && '
74
+ if if_branch.modifier_form?
75
+ correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
76
+ else
77
+ correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
78
+ end
79
+
80
+ correct_for_comment(corrector, node, if_branch)
81
+ end
82
+
83
+ def correct_for_gurad_condition_style(corrector, node, if_branch, and_operator)
84
+ condition = if_branch.condition
85
+ corrector.insert_after(node.condition, replacement_condition(and_operator, condition))
86
+
87
+ range = range_between(if_branch.loc.keyword.begin_pos, condition.source_range.end_pos)
88
+ corrector.remove(range_with_surrounding_space(range: range, newlines: false))
89
+ corrector.remove(if_branch.loc.keyword)
90
+ end
91
+
92
+ def correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
93
+ range = range_between(
94
+ node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
95
+ )
96
+ corrector.replace(range, and_operator)
97
+ corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
98
+ corrector.wrap(if_branch.condition, '(', ')') if if_branch.condition.or_type?
99
+ end
100
+
101
+ def correct_for_comment(corrector, node, if_branch)
102
+ comments = processed_source.comments_before_line(if_branch.source_range.line)
103
+ comment_text = comments.map(&:text).join("\n") << "\n"
104
+
105
+ corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
106
+ end
107
+
108
+ def replacement_condition(and_operator, condition)
109
+ if condition.or_type?
110
+ "#{and_operator}(#{condition.source})"
111
+ else
112
+ "#{and_operator}#{condition.source}"
113
+ end
114
+ end
115
+
60
116
  def allow_modifier?
61
117
  cop_config['AllowModifier']
62
118
  end