rubocop 0.54.0 → 0.55.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 +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
|