rubocop 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|