rubocop 1.2.0 → 1.3.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -10
  3. data/config/default.yml +59 -7
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/config_loader.rb +7 -6
  6. data/lib/rubocop/cop/bundler/duplicated_gem.rb +3 -3
  7. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  8. data/lib/rubocop/cop/commissioner.rb +1 -1
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  10. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  11. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  12. data/lib/rubocop/cop/generator.rb +1 -1
  13. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  14. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  15. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +23 -2
  16. data/lib/rubocop/cop/lint/debugger.rb +17 -27
  17. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  18. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  19. data/lib/rubocop/cop/lint/empty_block.rb +23 -0
  20. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  21. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +21 -3
  22. data/lib/rubocop/cop/lint/loop.rb +4 -0
  23. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  24. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  25. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  26. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  27. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  28. data/lib/rubocop/cop/naming/variable_number.rb +16 -0
  29. data/lib/rubocop/cop/style/and_or.rb +1 -3
  30. data/lib/rubocop/cop/style/collection_compact.rb +6 -0
  31. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +100 -5
  32. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  33. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  34. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  35. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  36. data/lib/rubocop/cop/style/multiple_comparison.rb +3 -2
  37. data/lib/rubocop/cop/style/negated_if_else_condition.rb +7 -2
  38. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  39. data/lib/rubocop/cop/style/static_class.rb +97 -0
  40. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  41. data/lib/rubocop/target_ruby.rb +57 -1
  42. data/lib/rubocop/version.rb +1 -1
  43. metadata +9 -5
@@ -140,11 +140,10 @@ module RuboCop
140
140
  rescued_groups.each_cons(2).all? do |x, y|
141
141
  if x.include?(Exception)
142
142
  false
143
- elsif y.include?(Exception)
144
- true
145
- elsif x.none? || y.none?
146
- # consider sorted if a group is empty or only contains
147
- # `nil`s
143
+ elsif y.include?(Exception) ||
144
+ # consider sorted if a group is empty or only contains
145
+ # `nil`s
146
+ x.none? || y.none?
148
147
  true
149
148
  else
150
149
  (x <=> y || 0) <= 0
@@ -54,11 +54,9 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def delegating?(node, def_node)
57
- if node.nil?
58
- false
59
- elsif node.zsuper_type?
57
+ if node&.zsuper_type?
60
58
  true
61
- elsif node.super_type?
59
+ elsif node&.super_type?
62
60
  node.arguments.map(&:source) == def_node.arguments.map(&:source)
63
61
  else
64
62
  false
@@ -103,7 +103,7 @@ module RuboCop
103
103
  # hashes wrapped in a set of curly braces like {foo: 1}.
104
104
  # That is, not a kwargs hash. For method calls, this ensures
105
105
  # the method call is made with parens.
106
- starts_with_bracket = node.loc.begin
106
+ starts_with_bracket = !node.hash_type? || node.loc.begin
107
107
 
108
108
  # If the call has a second argument, we can insert a line
109
109
  # break before the second argument and the rest of the
@@ -19,7 +19,8 @@ module RuboCop
19
19
  def non_eligible_node?(node)
20
20
  node.modifier_form? ||
21
21
  node.nonempty_line_count > 3 ||
22
- processed_source.line_with_comment?(node.loc.last_line)
22
+ processed_source.line_with_comment?(node.loc.last_line) ||
23
+ (first_line_comment(node) && code_after(node))
23
24
  end
24
25
 
25
26
  def non_eligible_body?(body)
@@ -41,11 +42,9 @@ module RuboCop
41
42
 
42
43
  def length_in_modifier_form(node)
43
44
  keyword_element = node.loc.keyword
44
- end_element = node.loc.end
45
45
  code_before = keyword_element.source_line[0...keyword_element.column]
46
- code_after = end_element.source_line[end_element.last_column..-1]
47
46
  expression = to_modifier_form(node)
48
- line_length("#{code_before}#{expression}#{code_after}")
47
+ line_length("#{code_before}#{expression}#{code_after(node)}")
49
48
  end
50
49
 
51
50
  def to_modifier_form(node)
@@ -64,6 +63,12 @@ module RuboCop
64
63
  comment_source unless comment_disables_cop?(comment_source)
65
64
  end
66
65
 
66
+ def code_after(node)
67
+ end_element = node.loc.end
68
+ code = end_element.source_line[end_element.last_column..-1]
69
+ code unless code.empty?
70
+ end
71
+
67
72
  def parenthesize?(node)
68
73
  # Parenthesize corrected expression if changing to modifier-if form
69
74
  # would change the meaning of the parent expression
@@ -92,6 +92,10 @@ module RuboCop
92
92
  # # good
93
93
  # :some_sym_1
94
94
  #
95
+ # @example AllowedIdentifier: [capture3]
96
+ # # good
97
+ # expect(Open3).to receive(:capture3)
98
+ #
95
99
  class VariableNumber < Base
96
100
  include ConfigurableNumbering
97
101
 
@@ -108,12 +112,16 @@ module RuboCop
108
112
 
109
113
  def on_def(node)
110
114
  @node = node
115
+ return if allowed_identifier?(node.method_name)
116
+
111
117
  check_name(node, node.method_name, node.loc.name) if cop_config['CheckMethodNames']
112
118
  end
113
119
  alias on_defs on_def
114
120
 
115
121
  def on_sym(node)
116
122
  @node = node
123
+ return if allowed_identifier?(node.value)
124
+
117
125
  check_name(node, node.value, node) if cop_config['CheckSymbols']
118
126
  end
119
127
 
@@ -129,6 +137,14 @@ module RuboCop
129
137
 
130
138
  format(MSG, style: style, identifier_type: identifier_type)
131
139
  end
140
+
141
+ def allowed_identifier?(name)
142
+ allowed_identifiers.include?(name.to_s)
143
+ end
144
+
145
+ def allowed_identifiers
146
+ cop_config.fetch('AllowedIdentifiers', [])
147
+ end
132
148
  end
133
149
  end
134
150
  end
@@ -66,9 +66,7 @@ module RuboCop
66
66
  node.each_child_node do |expr|
67
67
  if expr.send_type?
68
68
  correct_send(expr, corrector)
69
- elsif expr.return_type?
70
- correct_other(expr, corrector)
71
- elsif expr.assignment?
69
+ elsif expr.return_type? || expr.assignment?
72
70
  correct_other(expr, corrector)
73
71
  end
74
72
  end
@@ -6,6 +6,12 @@ module RuboCop
6
6
  # This cop checks for places where custom logic on rejection nils from arrays
7
7
  # and hashes can be replaced with `{Array,Hash}#{compact,compact!}`.
8
8
  #
9
+ # It is marked as unsafe by default because false positives may occur in the
10
+ # nil check of block arguments to the receiver object.
11
+ # For example, `[[1, 2], [3, nil]].reject { |first, second| second.nil? }`
12
+ # and `[[1, 2], [3, nil]].compact` are not compatible. This will work fine
13
+ # when the receiver is a hash object.
14
+ #
9
15
  # @example
10
16
  # # bad
11
17
  # array.reject { |e| e.nil? }
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # end
26
26
  # end
27
27
  #
28
- # # good
28
+ # # good, inline comments in heredoc
29
29
  # UNSAFE_STRING_METHODS.each do |unsafe_method|
30
30
  # if 'String'.respond_to?(unsafe_method)
31
31
  # class_eval <<-EOT, __FILE__, __LINE__ + 1
@@ -41,25 +41,120 @@ module RuboCop
41
41
  # end
42
42
  # end
43
43
  #
44
+ # # good, block comments in heredoc
45
+ # class_eval <<-EOT, __FILE__, __LINE__ + 1
46
+ # # def capitalize!(*params)
47
+ # # @dirty = true
48
+ # # super
49
+ # # end
50
+ #
51
+ # def #{unsafe_method}!(*params)
52
+ # @dirty = true
53
+ # super
54
+ # end
55
+ # EOT
56
+ #
57
+ # # good, block comments before heredoc
58
+ # class_eval(
59
+ # # def capitalize!(*params)
60
+ # # @dirty = true
61
+ # # super
62
+ # # end
63
+ #
64
+ # <<-EOT, __FILE__, __LINE__ + 1
65
+ # def #{unsafe_method}!(*params)
66
+ # @dirty = true
67
+ # super
68
+ # end
69
+ # EOT
70
+ # )
44
71
  class DocumentDynamicEvalDefinition < Base
72
+ BLOCK_COMMENT_REGEXP = /^\s*#(?!{)/.freeze
73
+ COMMENT_REGEXP = /\s*#(?!{).*/.freeze
45
74
  MSG = 'Add a comment block showing its appearance if interpolated.'
46
75
 
47
76
  RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
48
77
 
49
78
  def on_send(node)
50
79
  arg_node = node.first_argument
51
- return unless arg_node&.dstr_type?
52
80
 
53
- add_offense(node.loc.selector) unless comment_docs?(arg_node)
81
+ return unless arg_node&.dstr_type? && interpolated?(arg_node)
82
+ return if inline_comment_docs?(arg_node) || comment_block_docs?(arg_node)
83
+
84
+ add_offense(node.loc.selector)
54
85
  end
55
86
 
56
87
  private
57
88
 
58
- def comment_docs?(node)
89
+ def interpolated?(arg_node)
90
+ arg_node.each_child_node(:begin).any?
91
+ end
92
+
93
+ def inline_comment_docs?(node)
59
94
  node.each_child_node(:begin).all? do |begin_node|
60
95
  source_line = processed_source.lines[begin_node.first_line - 1]
61
- source_line.match?(/\s*#[^{]+/)
96
+ source_line.match?(COMMENT_REGEXP)
97
+ end
98
+ end
99
+
100
+ def comment_block_docs?(arg_node)
101
+ comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span)
102
+ .concat(preceding_comment_blocks(arg_node.parent))
103
+
104
+ return if comments.none?
105
+
106
+ regexp = comment_regexp(arg_node)
107
+ comments.any? { |comment| regexp.match?(comment) } || regexp.match?(comments.join)
108
+ end
109
+
110
+ def preceding_comment_blocks(node)
111
+ # Collect comments in the method call, but outside the heredoc
112
+ comments = processed_source.each_comment_in_lines(node.loc.expression.line_span)
113
+
114
+ comments.each_with_object({}) do |comment, hash|
115
+ merge_adjacent_comments(comment.text, comment.loc.line, hash)
116
+ end.values
117
+ end
118
+
119
+ def heredoc_comment_blocks(heredoc_body)
120
+ # Collect comments inside the heredoc
121
+ line_range = (heredoc_body.begin - 1)..(heredoc_body.end - 1)
122
+ lines = processed_source.lines[line_range]
123
+
124
+ lines.each_with_object({}).with_index(line_range.begin) do |(line, hash), index|
125
+ merge_adjacent_comments(line, index, hash)
126
+ end.values
127
+ end
128
+
129
+ def merge_adjacent_comments(line, index, hash)
130
+ # Combine adjacent comment lines into a single string
131
+ return unless (line = line.dup.gsub!(BLOCK_COMMENT_REGEXP, ''))
132
+
133
+ hash[index] = if hash.keys.last == index - 1
134
+ [hash.delete(index - 1), line].join("\n")
135
+ else
136
+ line
137
+ end
138
+ end
139
+
140
+ def comment_regexp(arg_node)
141
+ # Replace the interpolations with wildcards
142
+ regexp_parts = arg_node.child_nodes.map do |n|
143
+ n.begin_type? ? /.+/ : source_to_regexp(n.source)
62
144
  end
145
+
146
+ Regexp.new(regexp_parts.join)
147
+ end
148
+
149
+ def source_to_regexp(source)
150
+ # Get the source in the heredoc being `eval`ed, without any comments
151
+ # and turn it into a regexp
152
+ return /\s+/ if source.blank?
153
+
154
+ source = source.gsub(COMMENT_REGEXP, '')
155
+ return if source.blank?
156
+
157
+ /\s*#{Regexp.escape(source.strip)}/
63
158
  end
64
159
  end
65
160
  end
@@ -3,8 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop checks for identical lines at the beginning or end of
7
- # each branch of a conditional statement.
6
+ # This cop checks for identical expressions at the beginning or end of
7
+ # each branch of a conditional expression. Such expressions should normally
8
+ # be placed outside the conditional expression - before or after it.
9
+ #
10
+ # NOTE: The cop is poorly named and some people might think that it actually
11
+ # checks for duplicated conditional branches. The name will probably be changed
12
+ # in a future major RuboCop release.
8
13
  #
9
14
  # @example
10
15
  # # bad
@@ -59,6 +59,9 @@ module RuboCop
59
59
  # end
60
60
  #
61
61
  class IfInsideElse < Base
62
+ include RangeHelp
63
+ extend AutoCorrector
64
+
62
65
  MSG = 'Convert `if` nested inside `else` to `elsif`.'
63
66
 
64
67
  def on_if(node)
@@ -69,11 +72,44 @@ module RuboCop
69
72
  return unless else_branch&.if_type? && else_branch&.if?
70
73
  return if allow_if_modifier_in_else_branch?(else_branch)
71
74
 
72
- add_offense(else_branch.loc.keyword)
75
+ add_offense(else_branch.loc.keyword) do |corrector|
76
+ autocorrect(corrector, else_branch)
77
+ end
73
78
  end
74
79
 
75
80
  private
76
81
 
82
+ def autocorrect(corrector, node)
83
+ if node.modifier_form?
84
+ correct_to_elsif_from_modifier_form(corrector, node)
85
+ end_range = node.parent.loc.end
86
+ else
87
+ correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
88
+ end_range = node.loc.end
89
+ end
90
+ corrector.remove(range_by_whole_lines(end_range, include_final_newline: true))
91
+ corrector.remove(
92
+ range_by_whole_lines(node.if_branch.source_range, include_final_newline: true)
93
+ )
94
+ end
95
+
96
+ def correct_to_elsif_from_modifier_form(corrector, node)
97
+ corrector.replace(node.parent.loc.else, <<~RUBY.chop)
98
+ elsif #{node.condition.source}
99
+ #{indent(node.if_branch)}#{node.if_branch.source}
100
+ end
101
+ RUBY
102
+ end
103
+
104
+ def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
105
+ corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
106
+ if_condition_range = range_between(
107
+ node.loc.keyword.begin_pos, condition.source_range.end_pos
108
+ )
109
+ corrector.replace(if_condition_range, node.if_branch.source)
110
+ corrector.remove(condition)
111
+ end
112
+
77
113
  def allow_if_modifier_in_else_branch?(else_branch)
78
114
  allow_if_modifier? && else_branch&.modifier_form?
79
115
  end
@@ -21,14 +21,18 @@ module RuboCop
21
21
  # Foo.do_something
22
22
  # end
23
23
  #
24
- # do_something_in_a_method_with_a_long_name(arg) if long_condition
24
+ # do_something_with_a_long_name(arg) if long_condition_that_prevents_code_fit_on_single_line
25
25
  #
26
26
  # # good
27
27
  # do_stuff(bar) if condition
28
28
  # Foo.do_something unless qux.empty?
29
29
  #
30
- # if long_condition
31
- # do_something_in_a_method_with_a_long_name(arg)
30
+ # if long_condition_that_prevents_code_fit_on_single_line
31
+ # do_something_with_a_long_name(arg)
32
+ # end
33
+ #
34
+ # if short_condition # a long comment that makes it too long if it were just a single line
35
+ # do_something
32
36
  # end
33
37
  class IfUnlessModifier < Base
34
38
  include StatementModifier
@@ -5,6 +5,10 @@ module RuboCop
5
5
  module Style
6
6
  # Use `Kernel#loop` for infinite loops.
7
7
  #
8
+ # This cop is marked as unsafe as the rule does not necessarily
9
+ # apply if the body might raise a `StopIteration` exception; contrary to
10
+ # other infinite loops, `Kernel#loop` silently rescues that and returns `nil`.
11
+ #
8
12
  # @example
9
13
  # # bad
10
14
  # while true
@@ -48,6 +48,7 @@ module RuboCop
48
48
 
49
49
  def on_new_investigation
50
50
  @compared_elements = []
51
+ @allowed_method_comparison = false
51
52
  end
52
53
 
53
54
  def on_or(node)
@@ -55,6 +56,7 @@ module RuboCop
55
56
 
56
57
  return unless node == root_of_or_node
57
58
  return unless nested_variable_comparison?(root_of_or_node)
59
+ return if @allowed_method_comparison
58
60
 
59
61
  add_offense(node) do |corrector|
60
62
  elements = @compared_elements.join(', ')
@@ -95,8 +97,7 @@ module RuboCop
95
97
  return [variable_name(var1), variable_name(var2)]
96
98
  end
97
99
  if (var, obj = simple_comparison_lhs?(node)) || (obj, var = simple_comparison_rhs?(node))
98
- return [] if allow_method_comparison? && obj.send_type?
99
-
100
+ @allowed_method_comparison = true if allow_method_comparison? && obj.send_type?
100
101
  @compared_elements << obj.source
101
102
  return [variable_name(var)]
102
103
  end
@@ -28,6 +28,7 @@ module RuboCop
28
28
  # x ? do_something_else : do_something
29
29
  #
30
30
  class NegatedIfElseCondition < Base
31
+ include RangeHelp
31
32
  extend AutoCorrector
32
33
 
33
34
  MSG = 'Invert the negated condition and swap the %<type>s branches.'
@@ -90,8 +91,12 @@ module RuboCop
90
91
  end
91
92
 
92
93
  def swap_branches(corrector, node)
93
- corrector.replace(node.if_branch, node.else_branch.source)
94
- corrector.replace(node.else_branch, node.if_branch.source)
94
+ if node.if_branch.nil?
95
+ corrector.remove(range_by_whole_lines(node.loc.else, include_final_newline: true))
96
+ else
97
+ corrector.replace(node.if_branch, node.else_branch.source)
98
+ corrector.replace(node.else_branch, node.if_branch.source)
99
+ end
95
100
  end
96
101
  end
97
102
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for lambdas that always return nil, which can be replaced
7
+ # with an empty lambda instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # -> { nil }
12
+ #
13
+ # lambda do
14
+ # next nil
15
+ # end
16
+ #
17
+ # # good
18
+ # -> {}
19
+ #
20
+ # lambda do
21
+ # end
22
+ #
23
+ # -> (x) { nil if x }
24
+ #
25
+ class NilLambda < Base
26
+ extend AutoCorrector
27
+ include RangeHelp
28
+
29
+ MSG = 'Use an empty lambda instead of always returning nil.'
30
+
31
+ def_node_matcher :nil_return?, <<~PATTERN
32
+ { ({return next break} nil) (nil) }
33
+ PATTERN
34
+
35
+ def on_block(node)
36
+ return unless node.lambda?
37
+ return unless nil_return?(node.body)
38
+
39
+ add_offense(node) do |corrector|
40
+ range = if node.single_line?
41
+ range_with_surrounding_space(range: node.body.loc.expression)
42
+ else
43
+ range_by_whole_lines(node.body.loc.expression, include_final_newline: true)
44
+ end
45
+
46
+ corrector.remove(range)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end