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.
- checksums.yaml +4 -4
- data/README.md +16 -10
- data/config/default.yml +59 -7
- data/lib/rubocop.rb +4 -0
- data/lib/rubocop/config_loader.rb +7 -6
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +3 -3
- data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
- data/lib/rubocop/cop/commissioner.rb +1 -1
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
- data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
- data/lib/rubocop/cop/generator.rb +1 -1
- data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
- data/lib/rubocop/cop/layout/line_length.rb +8 -1
- data/lib/rubocop/cop/lint/constant_definition_in_block.rb +23 -2
- data/lib/rubocop/cop/lint/debugger.rb +17 -27
- data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
- data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
- data/lib/rubocop/cop/lint/empty_block.rb +23 -0
- data/lib/rubocop/cop/lint/empty_class.rb +93 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +21 -3
- data/lib/rubocop/cop/lint/loop.rb +4 -0
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
- data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
- data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
- data/lib/rubocop/cop/naming/variable_number.rb +16 -0
- data/lib/rubocop/cop/style/and_or.rb +1 -3
- data/lib/rubocop/cop/style/collection_compact.rb +6 -0
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +100 -5
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
- data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
- data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
- data/lib/rubocop/cop/style/multiple_comparison.rb +3 -2
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +7 -2
- data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
- data/lib/rubocop/cop/style/static_class.rb +97 -0
- data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
- data/lib/rubocop/target_ruby.rb +57 -1
- data/lib/rubocop/version.rb +1 -1
- 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
|
-
|
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
|
-
|
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 =
|
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
|
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
|
76
|
-
|
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(
|
34
|
+
case_node.when_branches.each_with_object(Set.new) do |when_node, previous|
|
35
35
|
when_node.each_condition do |condition|
|
36
|
-
|
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
|
-
|
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,
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|