rubocop 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -49,7 +49,18 @@ module RuboCop
49
49
  # const_set(:LIST, [])
50
50
  # end
51
51
  # end
52
+ #
53
+ # @example AllowedMethods: ['enums']
54
+ # # good
55
+ # class TestEnum < T::Enum
56
+ # enums do
57
+ # Foo = new("foo")
58
+ # end
59
+ # end
60
+ #
52
61
  class ConstantDefinitionInBlock < Base
62
+ include AllowedMethods
63
+
53
64
  MSG = 'Do not define constants this way within a block.'
54
65
 
55
66
  def_node_matcher :constant_assigned_in_block?, <<~PATTERN
@@ -61,13 +72,23 @@ module RuboCop
61
72
  PATTERN
62
73
 
63
74
  def on_casgn(node)
64
- add_offense(node) if constant_assigned_in_block?(node)
75
+ return if !constant_assigned_in_block?(node) || allowed_method?(method_name(node))
76
+
77
+ add_offense(node)
65
78
  end
66
79
 
67
80
  def on_class(node)
68
- add_offense(node) if module_defined_in_block?(node)
81
+ return if !module_defined_in_block?(node) || allowed_method?(method_name(node))
82
+
83
+ add_offense(node)
69
84
  end
70
85
  alias on_module on_class
86
+
87
+ private
88
+
89
+ def method_name(node)
90
+ node.ancestors.find(&:block_type?).send_node.method_name
91
+ end
71
92
  end
72
93
  end
73
94
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # This cop checks for calls to debugger or pry.
7
+ # The cop can be configured to define which methods and receivers must be fixed.
7
8
  #
8
9
  # @example
9
10
  #
@@ -35,33 +36,11 @@ module RuboCop
35
36
  class Debugger < Base
36
37
  MSG = 'Remove debugger entry point `%<source>s`.'
37
38
 
38
- RESTRICT_ON_SEND = %i[
39
- debugger byebug remote_byebug pry remote_pry pry_remote console rescue
40
- save_and_open_page save_and_open_screenshot irb
41
- ].freeze
42
-
43
- def_node_matcher :kernel?, <<~PATTERN
44
- {
45
- (const nil? :Kernel)
46
- (const (cbase) :Kernel)
47
- }
48
- PATTERN
49
-
50
- def_node_matcher :debugger_call?, <<~PATTERN
51
- {(send {nil? #kernel?} {:debugger :byebug :remote_byebug} ...)
52
- (send (send {#kernel? nil?} :binding)
53
- {:pry :remote_pry :pry_remote :console} ...)
54
- (send (const {nil? (cbase)} :Pry) :rescue ...)
55
- (send nil? {:save_and_open_page
56
- :save_and_open_screenshot} ...)}
57
- PATTERN
58
-
59
- def_node_matcher :binding_irb_call?, <<~PATTERN
60
- (send (send {#kernel? nil?} :binding) :irb ...)
61
- PATTERN
39
+ RESTRICT_ON_SEND = [].freeze
62
40
 
63
41
  def on_send(node)
64
- return unless debugger_call?(node) || binding_irb?(node)
42
+ return unless debugger_method?(node.method_name)
43
+ return if !node.receiver.nil? && !debugger_receiver?(node)
65
44
 
66
45
  add_offense(node)
67
46
  end
@@ -72,8 +51,19 @@ module RuboCop
72
51
  format(MSG, source: node.source)
73
52
  end
74
53
 
75
- def binding_irb?(node)
76
- binding_irb_call?(node)
54
+ def debugger_method?(name)
55
+ cop_config.fetch('DebuggerMethods', []).include?(name.to_s)
56
+ end
57
+
58
+ def debugger_receiver?(node)
59
+ receiver = case node.receiver
60
+ when RuboCop::AST::SendNode
61
+ node.receiver.method_name
62
+ when RuboCop::AST::ConstNode
63
+ node.receiver.const_name
64
+ end
65
+
66
+ cop_config.fetch('DebuggerReceivers', []).include?(receiver.to_s)
77
67
  end
78
68
  end
79
69
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks that there are no repeated bodies
7
+ # within `if/unless`, `case-when` and `rescue` constructs.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # if foo
12
+ # do_foo
13
+ # do_something_else
14
+ # elsif bar
15
+ # do_foo
16
+ # do_something_else
17
+ # end
18
+ #
19
+ # # good
20
+ # if foo || bar
21
+ # do_foo
22
+ # do_something_else
23
+ # end
24
+ #
25
+ # # bad
26
+ # case x
27
+ # when foo
28
+ # do_foo
29
+ # when bar
30
+ # do_foo
31
+ # else
32
+ # do_something_else
33
+ # end
34
+ #
35
+ # # good
36
+ # case x
37
+ # when foo, bar
38
+ # do_foo
39
+ # else
40
+ # do_something_else
41
+ # end
42
+ #
43
+ # # bad
44
+ # begin
45
+ # do_something
46
+ # rescue FooError
47
+ # handle_error
48
+ # rescue BarError
49
+ # handle_error
50
+ # end
51
+ #
52
+ # # good
53
+ # begin
54
+ # do_something
55
+ # rescue FooError, BarError
56
+ # handle_error
57
+ # end
58
+ #
59
+ class DuplicateBranch < Base
60
+ include RescueNode
61
+
62
+ MSG = 'Duplicate branch body detected.'
63
+
64
+ def on_branching_statement(node)
65
+ branches = node.branches.compact
66
+ branches.each_with_object(Set.new) do |branch, previous|
67
+ add_offense(offense_range(branch)) unless previous.add?(branch)
68
+ end
69
+ end
70
+ alias on_if on_branching_statement
71
+ alias on_case on_branching_statement
72
+ alias on_rescue on_branching_statement
73
+
74
+ private
75
+
76
+ def offense_range(duplicate_branch)
77
+ parent = duplicate_branch.parent
78
+
79
+ if parent.respond_to?(:else_branch) &&
80
+ parent.else_branch.equal?(duplicate_branch)
81
+ if parent.if_type? && parent.ternary?
82
+ duplicate_branch.source_range
83
+ else
84
+ parent.loc.else
85
+ end
86
+ else
87
+ parent.source_range
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -31,22 +31,12 @@ module RuboCop
31
31
  MSG = 'Duplicate `when` condition detected.'
32
32
 
33
33
  def on_case(case_node)
34
- case_node.when_branches.each_with_object([]) do |when_node, previous|
34
+ case_node.when_branches.each_with_object(Set.new) do |when_node, previous|
35
35
  when_node.each_condition do |condition|
36
- next unless repeated_condition?(previous, condition)
37
-
38
- add_offense(condition)
36
+ add_offense(condition) unless previous.add?(condition)
39
37
  end
40
-
41
- previous.push(when_node.conditions)
42
38
  end
43
39
  end
44
-
45
- private
46
-
47
- def repeated_condition?(previous, condition)
48
- previous.any? { |c| c.include?(condition) }
49
- end
50
40
  end
51
41
  end
52
42
  end
@@ -7,6 +7,8 @@ module RuboCop
7
7
  # Such empty blocks are typically an oversight or we should provide a comment
8
8
  # be clearer what we're aiming for.
9
9
  #
10
+ # Empty lambdas are ignored by default.
11
+ #
10
12
  # @example
11
13
  # # bad
12
14
  # items.each { |item| }
@@ -30,11 +32,28 @@ module RuboCop
30
32
  #
31
33
  # items.each { |item| } # TODO: implement later (inline comment)
32
34
  #
35
+ # @example AllowEmptyLambdas: true (default)
36
+ # # good
37
+ # allow(subject).to receive(:callable).and_return(-> {})
38
+ #
39
+ # placeholder = lambda do
40
+ # end
41
+ # (callable || placeholder).call
42
+ #
43
+ # @example AllowEmptyLambdas: false
44
+ # # bad
45
+ # allow(subject).to receive(:callable).and_return(-> {})
46
+ #
47
+ # placeholder = lambda do
48
+ # end
49
+ # (callable || placeholder).call
50
+ #
33
51
  class EmptyBlock < Base
34
52
  MSG = 'Empty block detected.'
35
53
 
36
54
  def on_block(node)
37
55
  return if node.body
56
+ return if allow_empty_lambdas? && node.lambda?
38
57
  return if cop_config['AllowComments'] && allow_comment?(node)
39
58
 
40
59
  add_offense(node)
@@ -49,6 +68,10 @@ module RuboCop
49
68
  !line_comment || !comment_disables_cop?(line_comment.loc.expression.source)
50
69
  end
51
70
 
71
+ def allow_empty_lambdas?
72
+ cop_config['AllowEmptyLambdas']
73
+ end
74
+
52
75
  def comment_disables_cop?(comment)
53
76
  regexp_pattern = "# rubocop : (disable|todo) ([^,],)* (all|#{cop_name})"
54
77
  Regexp.new(regexp_pattern.gsub(' ', '\s*')).match?(comment)
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for classes and metaclasses without a body.
7
+ # Such empty classes and metaclasses are typically an oversight or we should provide a comment
8
+ # to be clearer what we're aiming for.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # class Foo
13
+ # end
14
+ #
15
+ # class Bar
16
+ # class << self
17
+ # end
18
+ # end
19
+ #
20
+ # class << obj
21
+ # end
22
+ #
23
+ # # good
24
+ # class Foo
25
+ # def do_something
26
+ # # ... code
27
+ # end
28
+ # end
29
+ #
30
+ # class Bar
31
+ # class << self
32
+ # attr_reader :bar
33
+ # end
34
+ # end
35
+ #
36
+ # class << obj
37
+ # attr_reader :bar
38
+ # end
39
+ #
40
+ # @example AllowComments: false (default)
41
+ # # bad
42
+ # class Foo
43
+ # # TODO: implement later
44
+ # end
45
+ #
46
+ # class Bar
47
+ # class << self
48
+ # # TODO: implement later
49
+ # end
50
+ # end
51
+ #
52
+ # class << obj
53
+ # # TODO: implement later
54
+ # end
55
+ #
56
+ # @example AllowComments: true
57
+ # # good
58
+ # class Foo
59
+ # # TODO: implement later
60
+ # end
61
+ #
62
+ # class Bar
63
+ # class << self
64
+ # # TODO: implement later
65
+ # end
66
+ # end
67
+ #
68
+ # class << obj
69
+ # # TODO: implement later
70
+ # end
71
+ #
72
+ class EmptyClass < Base
73
+ CLASS_MSG = 'Empty class detected.'
74
+ METACLASS_MSG = 'Empty metaclass detected.'
75
+
76
+ def on_class(node)
77
+ add_offense(node, message: CLASS_MSG) unless body_or_allowed_comment_lines?(node) ||
78
+ node.parent_class
79
+ end
80
+
81
+ def on_sclass(node)
82
+ add_offense(node, message: METACLASS_MSG) unless body_or_allowed_comment_lines?(node)
83
+ end
84
+
85
+ private
86
+
87
+ def body_or_allowed_comment_lines?(node)
88
+ node.body || (cop_config['AllowComments'] && comment_lines?(node))
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -27,9 +27,7 @@ module RuboCop
27
27
 
28
28
  def on_interpolation(begin_node)
29
29
  final_node = begin_node.children.last
30
- return unless final_node
31
- return if special_keyword?(final_node)
32
- return unless prints_as_self?(final_node)
30
+ return unless offending?(final_node)
33
31
 
34
32
  # %W and %I split the content into words before expansion
35
33
  # treating each interpolation as a word component, so
@@ -48,6 +46,14 @@ module RuboCop
48
46
 
49
47
  private
50
48
 
49
+ def offending?(node)
50
+ node &&
51
+ !special_keyword?(node) &&
52
+ prints_as_self?(node) &&
53
+ # Special case for Layout/TrailingWhitespace
54
+ !(space_literal?(node) && ends_heredoc_line?(node))
55
+ end
56
+
51
57
  def special_keyword?(node)
52
58
  # handle strings like __FILE__
53
59
  (node.str_type? && !node.loc.respond_to?(:begin)) ||
@@ -99,6 +105,18 @@ module RuboCop
99
105
  node.children.all? { |child| prints_as_self?(child) })
100
106
  end
101
107
 
108
+ def space_literal?(node)
109
+ node.str_type? && node.value.blank?
110
+ end
111
+
112
+ def ends_heredoc_line?(node)
113
+ grandparent = node.parent.parent
114
+ return false unless grandparent&.dstr_type? && grandparent&.heredoc?
115
+
116
+ line = processed_source.lines[node.last_line - 1]
117
+ line.size == node.loc.last_column + 1
118
+ end
119
+
102
120
  def in_array_percent_literal?(node)
103
121
  parent = node.parent
104
122
  return false unless parent.dstr_type? || parent.dsym_type?
@@ -5,6 +5,10 @@ module RuboCop
5
5
  module Lint
6
6
  # This cop checks for uses of `begin...end while/until something`.
7
7
  #
8
+ # The cop is marked as unsafe because behaviour can change in some cases, including
9
+ # if a local variable inside the loop body is accessed outside of it, or if the
10
+ # loop body raises a `StopIteration` exception (which `Kernel#loop` rescues).
11
+ #
8
12
  # @example
9
13
  #
10
14
  # # bad
@@ -88,31 +88,34 @@ module RuboCop
88
88
  begin_pos = reposition(source, begin_pos, -1)
89
89
  end_pos = reposition(source, end_pos, 1)
90
90
 
91
- comma_pos =
92
- if source[begin_pos - 1] == ','
93
- :before
94
- elsif source[end_pos] == ','
95
- :after
96
- else
97
- :none
98
- end
99
-
100
- range_to_remove(begin_pos, end_pos, comma_pos, comment)
91
+ range_to_remove(begin_pos, end_pos, comment)
101
92
  end
102
93
 
103
- def range_to_remove(begin_pos, end_pos, comma_pos, comment)
94
+ def range_to_remove(begin_pos, end_pos, comment)
104
95
  start = comment_start(comment)
96
+ source = comment.loc.expression.source
105
97
 
106
- case comma_pos
107
- when :before
108
- range_between(start + begin_pos - 1, start + end_pos)
109
- when :after
110
- range_between(start + begin_pos, start + end_pos + 1)
98
+ if source[begin_pos - 1] == ','
99
+ range_with_comma_before(start, begin_pos, end_pos)
100
+ elsif source[end_pos] == ','
101
+ range_with_comma_after(comment, start, begin_pos, end_pos)
111
102
  else
112
103
  range_between(start, comment.loc.expression.end_pos)
113
104
  end
114
105
  end
115
106
 
107
+ def range_with_comma_before(start, begin_pos, end_pos)
108
+ range_between(start + begin_pos - 1, start + end_pos)
109
+ end
110
+
111
+ # If the list of cops is comma-separated, but without a empty space after the comma,
112
+ # we should **not** remove the prepending empty space, thus begin_pos += 1
113
+ def range_with_comma_after(comment, start, begin_pos, end_pos)
114
+ begin_pos += 1 if comment.loc.expression.source[end_pos + 1] != ' '
115
+
116
+ range_between(start + begin_pos, start + end_pos + 1)
117
+ end
118
+
116
119
  def all_or_name(name)
117
120
  name == 'all' ? 'all cops' : name
118
121
  end