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
@@ -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