rubocop 0.54.0 → 0.55.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 +5 -1
- data/config/default.yml +17 -2
- data/config/enabled.yml +13 -0
- data/lib/rubocop.rb +4 -0
- data/lib/rubocop/ast/node/mixin/binary_operator_node.rb +20 -0
- data/lib/rubocop/cli.rb +6 -2
- data/lib/rubocop/cop/commissioner.rb +21 -25
- data/lib/rubocop/cop/layout/end_of_line.rb +33 -0
- data/lib/rubocop/cop/layout/space_inside_parens.rb +64 -5
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +20 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +80 -0
- data/lib/rubocop/cop/lint/shadowed_argument.rb +3 -0
- data/lib/rubocop/cop/lint/void.rb +20 -9
- data/lib/rubocop/cop/metrics/block_length.rb +17 -1
- data/lib/rubocop/cop/metrics/line_length.rb +2 -3
- data/lib/rubocop/cop/mixin/percent_literal.rb +9 -8
- data/lib/rubocop/cop/performance/end_with.rb +2 -1
- data/lib/rubocop/cop/performance/regexp_match.rb +43 -7
- data/lib/rubocop/cop/performance/start_with.rb +2 -1
- data/lib/rubocop/cop/performance/unneeded_sort.rb +130 -0
- data/lib/rubocop/cop/rails/http_status.rb +19 -16
- data/lib/rubocop/cop/rails/inverse_of.rb +29 -22
- data/lib/rubocop/cop/rails/read_write_attribute.rb +9 -2
- data/lib/rubocop/cop/style/array_join.rb +1 -1
- data/lib/rubocop/cop/style/class_vars.rb +5 -4
- data/lib/rubocop/cop/style/commented_keyword.rb +2 -3
- data/lib/rubocop/cop/style/empty_line_after_guard_clause.rb +39 -8
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +22 -11
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +5 -0
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negated_while.rb +18 -0
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +11 -0
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +17 -0
- data/lib/rubocop/cop/style/option_hash.rb +6 -0
- data/lib/rubocop/cop/style/single_line_block_params.rb +20 -0
- data/lib/rubocop/cop/style/special_global_vars.rb +52 -0
- data/lib/rubocop/cop/style/string_literals.rb +1 -1
- data/lib/rubocop/cop/style/unpack_first.rb +0 -2
- data/lib/rubocop/formatter/auto_gen_config_formatter.rb +16 -0
- data/lib/rubocop/formatter/formatter_set.rb +14 -13
- data/lib/rubocop/node_pattern.rb +2 -2
- data/lib/rubocop/options.rb +1 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +13 -4
@@ -95,6 +95,9 @@ module RuboCop
|
|
95
95
|
argument.assignments.reduce(true) do |location_known, assignment|
|
96
96
|
assignment_node = assignment.meta_assignment_node || assignment.node
|
97
97
|
|
98
|
+
# Shorthand assignments always use their arguments
|
99
|
+
next false if assignment_node.shorthand_asgn?
|
100
|
+
|
98
101
|
node_within_block_or_conditional =
|
99
102
|
node_within_block_or_conditional?(assignment_node.parent,
|
100
103
|
argument.scope.node)
|
@@ -72,13 +72,20 @@ module RuboCop
|
|
72
72
|
UNARY_OPERATORS = %i[+@ -@ ~ !].freeze
|
73
73
|
OPERATORS = (BINARY_OPERATORS + UNARY_OPERATORS).freeze
|
74
74
|
VOID_CONTEXT_TYPES = %i[def for block].freeze
|
75
|
-
NONMUTATING_METHODS = %i[capitalize chomp chop collect compact
|
75
|
+
NONMUTATING_METHODS = %i[capitalize chomp chop collect compact
|
76
|
+
delete_prefix delete_suffix downcase
|
76
77
|
encode flatten gsub lstrip map next reject
|
77
78
|
reverse rotate rstrip scrub select shuffle
|
78
79
|
slice sort sort_by squeeze strip sub succ
|
79
80
|
swapcase tr tr_s transform_values
|
80
81
|
unicode_normalize uniq upcase].freeze
|
81
82
|
|
83
|
+
def on_block(node)
|
84
|
+
return unless node.body && !node.body.begin_type?
|
85
|
+
return unless in_void_context?(node.body)
|
86
|
+
check_expression(node.body)
|
87
|
+
end
|
88
|
+
|
82
89
|
def on_begin(node)
|
83
90
|
check_begin(node)
|
84
91
|
end
|
@@ -90,17 +97,21 @@ module RuboCop
|
|
90
97
|
expressions = *node
|
91
98
|
expressions = expressions.drop_last(1) unless in_void_context?(node)
|
92
99
|
expressions.each do |expr|
|
93
|
-
|
94
|
-
check_literal(expr)
|
95
|
-
check_var(expr)
|
96
|
-
check_self(expr)
|
97
|
-
check_defined(expr)
|
98
|
-
if cop_config['CheckForMethodsWithNoSideEffects']
|
99
|
-
check_nonmutating(expr)
|
100
|
-
end
|
100
|
+
check_expression(expr)
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
104
|
+
def check_expression(expr)
|
105
|
+
check_void_op(expr)
|
106
|
+
check_literal(expr)
|
107
|
+
check_var(expr)
|
108
|
+
check_self(expr)
|
109
|
+
check_defined(expr)
|
110
|
+
return unless cop_config['CheckForMethodsWithNoSideEffects']
|
111
|
+
|
112
|
+
check_nonmutating(expr)
|
113
|
+
end
|
114
|
+
|
104
115
|
def check_void_op(node)
|
105
116
|
return unless node.send_type? && OPERATORS.include?(node.method_name)
|
106
117
|
|
@@ -13,12 +13,28 @@ module RuboCop
|
|
13
13
|
LABEL = 'Block'.freeze
|
14
14
|
|
15
15
|
def on_block(node)
|
16
|
-
return if
|
16
|
+
return if excluded_method?(node)
|
17
17
|
check_code_length(node)
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
+
def excluded_method?(node)
|
23
|
+
node_receiver = node.receiver && node.receiver.source.gsub(/\s+/, '')
|
24
|
+
node_method = String(node.method_name)
|
25
|
+
|
26
|
+
excluded_methods.any? do |config|
|
27
|
+
receiver, method = config.split('.')
|
28
|
+
|
29
|
+
unless method
|
30
|
+
method = receiver
|
31
|
+
receiver = node_receiver
|
32
|
+
end
|
33
|
+
|
34
|
+
method == node_method && receiver == node_receiver
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
22
38
|
def excluded_methods
|
23
39
|
cop_config['ExcludedMethods'] || []
|
24
40
|
end
|
@@ -76,11 +76,10 @@ module RuboCop
|
|
76
76
|
|
77
77
|
def extract_heredocs(ast)
|
78
78
|
return [] unless ast
|
79
|
-
ast.each_node.
|
80
|
-
next unless node.location.is_a?(Parser::Source::Map::Heredoc)
|
79
|
+
ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
|
81
80
|
body = node.location.heredoc_body
|
82
81
|
delimiter = node.location.heredoc_end.source.strip
|
83
|
-
|
82
|
+
[body.first_line...body.last_line, delimiter]
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
@@ -61,11 +61,12 @@ module RuboCop
|
|
61
61
|
def autocorrect_multiline_words(node, escape, delimiters)
|
62
62
|
base_line_number = node.first_line
|
63
63
|
previous_line_number = base_line_number
|
64
|
-
contents = node.children.map do |word_node|
|
64
|
+
contents = node.children.map.with_index do |word_node, index|
|
65
65
|
line_breaks = line_breaks(word_node,
|
66
66
|
node.source,
|
67
67
|
previous_line_number,
|
68
|
-
base_line_number
|
68
|
+
base_line_number,
|
69
|
+
index)
|
69
70
|
previous_line_number = word_node.first_line
|
70
71
|
content = escaped_content(word_node, escape, delimiters)
|
71
72
|
line_breaks + content
|
@@ -85,14 +86,14 @@ module RuboCop
|
|
85
86
|
end.join(' ')
|
86
87
|
end
|
87
88
|
|
88
|
-
def line_breaks(node, source,
|
89
|
+
def line_breaks(node, source, previous_line_num, base_line_num, node_idx)
|
89
90
|
source_in_lines = source.split("\n")
|
90
|
-
if node.first_line ==
|
91
|
-
node.first_line ==
|
91
|
+
if node.first_line == previous_line_num
|
92
|
+
node_idx.zero? && node.first_line == base_line_num ? '' : ' '
|
92
93
|
else
|
93
|
-
|
94
|
-
|
95
|
-
lines = source_in_lines[
|
94
|
+
begin_line_num = previous_line_num - base_line_num + 1
|
95
|
+
end_line_num = node.first_line - base_line_num + 1
|
96
|
+
lines = source_in_lines[begin_line_num...end_line_num]
|
96
97
|
"\n" + lines.join("\n").split(node.source).first
|
97
98
|
end
|
98
99
|
end
|
@@ -8,6 +8,7 @@ module RuboCop
|
|
8
8
|
#
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
|
+
# 'abc'.match?(/bc\Z/)
|
11
12
|
# 'abc' =~ /bc\Z/
|
12
13
|
# 'abc'.match(/bc\Z/)
|
13
14
|
#
|
@@ -19,7 +20,7 @@ module RuboCop
|
|
19
20
|
SINGLE_QUOTE = "'".freeze
|
20
21
|
|
21
22
|
def_node_matcher :redundant_regex?, <<-PATTERN
|
22
|
-
{(send $!nil? {:match :=~} (regexp (str $#literal_at_end?) (regopt)))
|
23
|
+
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
|
23
24
|
(send (regexp (str $#literal_at_end?) (regopt)) {:match :=~} $_)}
|
24
25
|
PATTERN
|
25
26
|
|
@@ -19,6 +19,13 @@ module RuboCop
|
|
19
19
|
#
|
20
20
|
# # bad
|
21
21
|
# def foo
|
22
|
+
# if x !~ /re/
|
23
|
+
# do_something
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # bad
|
28
|
+
# def foo
|
22
29
|
# if x.match(/re/)
|
23
30
|
# do_something
|
24
31
|
# end
|
@@ -40,6 +47,13 @@ module RuboCop
|
|
40
47
|
#
|
41
48
|
# # good
|
42
49
|
# def foo
|
50
|
+
# if !x.match?(/re/)
|
51
|
+
# do_something
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# # good
|
56
|
+
# def foo
|
43
57
|
# if x =~ /re/
|
44
58
|
# do_something(Regexp.last_match)
|
45
59
|
# end
|
@@ -63,6 +77,9 @@ module RuboCop
|
|
63
77
|
|
64
78
|
minimum_target_ruby_version 2.4
|
65
79
|
|
80
|
+
# Constants are included in this list because it is unlikely that
|
81
|
+
# someone will store `nil` as a constant and then use it for comparison
|
82
|
+
TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
|
66
83
|
MSG =
|
67
84
|
'Use `match?` instead of `%<current>s` when `MatchData` ' \
|
68
85
|
'is not used.'.freeze
|
@@ -75,7 +92,7 @@ module RuboCop
|
|
75
92
|
PATTERN
|
76
93
|
|
77
94
|
def_node_matcher :match_operator?, <<-PATTERN
|
78
|
-
(send !nil? :=~ !nil?)
|
95
|
+
(send !nil? {:=~ :!~} !nil?)
|
79
96
|
PATTERN
|
80
97
|
|
81
98
|
def_node_matcher :match_threequals?, <<-PATTERN
|
@@ -128,8 +145,8 @@ module RuboCop
|
|
128
145
|
if match_method?(node)
|
129
146
|
corrector.replace(node.loc.selector, 'match?')
|
130
147
|
elsif match_operator?(node) || match_threequals?(node)
|
131
|
-
recv,
|
132
|
-
correct_operator(corrector, recv, arg)
|
148
|
+
recv, oper, arg = *node
|
149
|
+
correct_operator(corrector, recv, arg, oper)
|
133
150
|
elsif match_with_lvasgn?(node)
|
134
151
|
recv, arg = *node
|
135
152
|
correct_operator(corrector, recv, arg)
|
@@ -214,13 +231,32 @@ module RuboCop
|
|
214
231
|
].include?(sym)
|
215
232
|
end
|
216
233
|
|
217
|
-
def correct_operator(corrector, recv, arg)
|
234
|
+
def correct_operator(corrector, recv, arg, oper = nil)
|
235
|
+
op_range = correction_range(recv, arg)
|
236
|
+
|
237
|
+
if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
|
238
|
+
corrector.replace(op_range, '.match?(')
|
239
|
+
elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
|
240
|
+
corrector.replace(op_range, '.match?(')
|
241
|
+
swap_receiver_and_arg(corrector, recv, arg)
|
242
|
+
else
|
243
|
+
corrector.replace(op_range, '&.match?(')
|
244
|
+
end
|
245
|
+
|
246
|
+
corrector.insert_after(arg.loc.expression, ')')
|
247
|
+
corrector.insert_before(recv.loc.expression, '!') if oper == :!~
|
248
|
+
end
|
249
|
+
|
250
|
+
def swap_receiver_and_arg(corrector, recv, arg)
|
251
|
+
corrector.replace(recv.loc.expression, arg.source)
|
252
|
+
corrector.replace(arg.loc.expression, recv.source)
|
253
|
+
end
|
254
|
+
|
255
|
+
def correction_range(recv, arg)
|
218
256
|
buffer = processed_source.buffer
|
219
257
|
op_begin_pos = recv.loc.expression.end_pos
|
220
258
|
op_end_pos = arg.loc.expression.begin_pos
|
221
|
-
|
222
|
-
corrector.replace(op_range, '.match?(')
|
223
|
-
corrector.insert_after(arg.loc.expression, ')')
|
259
|
+
Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
|
224
260
|
end
|
225
261
|
end
|
226
262
|
end
|
@@ -8,6 +8,7 @@ module RuboCop
|
|
8
8
|
#
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
|
+
# 'abc'.match?(/\Aab/)
|
11
12
|
# 'abc' =~ /\Aab/
|
12
13
|
# 'abc'.match(/\Aab/)
|
13
14
|
#
|
@@ -19,7 +20,7 @@ module RuboCop
|
|
19
20
|
SINGLE_QUOTE = "'".freeze
|
20
21
|
|
21
22
|
def_node_matcher :redundant_regex?, <<-PATTERN
|
22
|
-
{(send $!nil? {:match :=~} (regexp (str $#literal_at_start?) (regopt)))
|
23
|
+
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
|
23
24
|
(send (regexp (str $#literal_at_start?) (regopt)) {:match :=~} $_)}
|
24
25
|
PATTERN
|
25
26
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify instances of sorting and then taking
|
7
|
+
# only the first or last element.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# [].sort.first
|
12
|
+
# [].sort_by(&:length).last
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# [].min
|
16
|
+
# [].max_by(&:length)
|
17
|
+
class UnneededSort < Cop
|
18
|
+
include RangeHelp
|
19
|
+
|
20
|
+
MSG = 'Use `%<suggestion>s` instead of '\
|
21
|
+
'`%<sorter>s...%<accessor_source>s`.'.freeze
|
22
|
+
|
23
|
+
def_node_matcher :unneeded_sort?, <<-MATCHER
|
24
|
+
{
|
25
|
+
(send $(send _ $:sort ...) ${:last :first})
|
26
|
+
(send $(send _ $:sort ...) ${:[] :at :slice} {(int 0) (int -1)})
|
27
|
+
|
28
|
+
(send $(send _ $:sort_by _) ${:last :first})
|
29
|
+
(send $(send _ $:sort_by _) ${:[] :at :slice} {(int 0) (int -1)})
|
30
|
+
|
31
|
+
(send (block $(send _ ${:sort_by :sort}) ...) ${:last :first})
|
32
|
+
(send
|
33
|
+
(block $(send _ ${:sort_by :sort}) ...)
|
34
|
+
${:[] :at :slice} {(int 0) (int -1)}
|
35
|
+
)
|
36
|
+
}
|
37
|
+
MATCHER
|
38
|
+
|
39
|
+
def on_send(node)
|
40
|
+
unneeded_sort?(node) do |sort_node, sorter, accessor|
|
41
|
+
range = range_between(
|
42
|
+
sort_node.loc.selector.begin_pos,
|
43
|
+
node.loc.expression.end_pos
|
44
|
+
)
|
45
|
+
|
46
|
+
add_offense(node,
|
47
|
+
location: range,
|
48
|
+
message: message(node,
|
49
|
+
sorter,
|
50
|
+
accessor))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def autocorrect(node)
|
55
|
+
sort_node, sorter, accessor = unneeded_sort?(node)
|
56
|
+
|
57
|
+
lambda do |corrector|
|
58
|
+
# Remove accessor, e.g. `first` or `[-1]`.
|
59
|
+
corrector.remove(
|
60
|
+
range_between(
|
61
|
+
accessor_start(node),
|
62
|
+
node.loc.expression.end_pos
|
63
|
+
)
|
64
|
+
)
|
65
|
+
|
66
|
+
# Replace "sort" or "sort_by" with the appropriate min/max method.
|
67
|
+
corrector.replace(
|
68
|
+
sort_node.loc.selector,
|
69
|
+
suggestion(sorter, accessor, arg_value(node))
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def message(node, sorter, accessor)
|
77
|
+
accessor_source = range_between(
|
78
|
+
node.loc.selector.begin_pos,
|
79
|
+
node.loc.expression.end_pos
|
80
|
+
).source
|
81
|
+
|
82
|
+
format(MSG,
|
83
|
+
suggestion: suggestion(sorter,
|
84
|
+
accessor,
|
85
|
+
arg_value(node)),
|
86
|
+
sorter: sorter,
|
87
|
+
accessor_source: accessor_source)
|
88
|
+
end
|
89
|
+
|
90
|
+
def suggestion(sorter, accessor, arg)
|
91
|
+
base(accessor, arg) + suffix(sorter)
|
92
|
+
end
|
93
|
+
|
94
|
+
def base(accessor, arg)
|
95
|
+
if accessor == :first || (arg && arg.zero?)
|
96
|
+
'min'
|
97
|
+
elsif accessor == :last || arg == -1
|
98
|
+
'max'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def suffix(sorter)
|
103
|
+
if sorter == :sort
|
104
|
+
''
|
105
|
+
elsif sorter == :sort_by
|
106
|
+
'_by'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def arg_node(node)
|
111
|
+
node.arguments.first
|
112
|
+
end
|
113
|
+
|
114
|
+
def arg_value(node)
|
115
|
+
arg_node(node).nil? ? nil : arg_node(node).node_parts.first
|
116
|
+
end
|
117
|
+
|
118
|
+
# This gets the start of the accessor whether it has a dot
|
119
|
+
# (e.g. `.first`) or doesn't (e.g. `[0]`)
|
120
|
+
def accessor_start(node)
|
121
|
+
if node.loc.dot
|
122
|
+
node.loc.dot.begin_pos
|
123
|
+
else
|
124
|
+
node.loc.selector.begin_pos
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -5,7 +5,7 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
# Enforces use of symbolic or numeric value to define HTTP status.
|
7
7
|
#
|
8
|
-
# @example
|
8
|
+
# @example EnforcedStyle: symbolic (default)
|
9
9
|
# # bad
|
10
10
|
# render :foo, status: 200
|
11
11
|
# render json: { foo: 'bar' }, status: 200
|
@@ -18,7 +18,7 @@ module RuboCop
|
|
18
18
|
# render plain: 'foo/bar', status: :not_modified
|
19
19
|
# redirect_to root_url, status: :moved_permanently
|
20
20
|
#
|
21
|
-
# @example
|
21
|
+
# @example EnforcedStyle: numeric
|
22
22
|
# # bad
|
23
23
|
# render :foo, status: :ok
|
24
24
|
# render json: { foo: 'bar' }, status: :not_found
|
@@ -43,24 +43,20 @@ module RuboCop
|
|
43
43
|
|
44
44
|
def_node_matcher :http_status, <<-PATTERN
|
45
45
|
{
|
46
|
-
(send nil? {:render :redirect_to}
|
47
|
-
|
48
|
-
(hash
|
49
|
-
(pair
|
50
|
-
(sym :status)
|
51
|
-
${int sym})))
|
52
|
-
(send nil? {:render}
|
53
|
-
(hash
|
54
|
-
_
|
55
|
-
(pair
|
56
|
-
(sym :status)
|
57
|
-
${int sym})))
|
46
|
+
(send nil? {:render :redirect_to} _ $hash)
|
47
|
+
(send nil? {:render :redirect_to} $hash)
|
58
48
|
}
|
59
49
|
PATTERN
|
60
50
|
|
51
|
+
def_node_matcher :status_pair?, <<-PATTERN
|
52
|
+
(pair (sym :status) ${int sym})
|
53
|
+
PATTERN
|
54
|
+
|
61
55
|
def on_send(node)
|
62
|
-
http_status(node) do |
|
63
|
-
|
56
|
+
http_status(node) do |hash_node|
|
57
|
+
status = status_code(hash_node)
|
58
|
+
return unless status
|
59
|
+
checker = checker_class.new(status)
|
64
60
|
return unless checker.offensive?
|
65
61
|
add_offense(checker.node, message: checker.message)
|
66
62
|
end
|
@@ -79,6 +75,13 @@ module RuboCop
|
|
79
75
|
|
80
76
|
private
|
81
77
|
|
78
|
+
def status_code(node)
|
79
|
+
node.each_pair.each do |pair|
|
80
|
+
status_pair?(pair) { |code| return code }
|
81
|
+
end
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
82
85
|
def checker_class
|
83
86
|
case style
|
84
87
|
when :symbolic
|