rubocop 1.88.0 → 1.88.1
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/config/default.yml +3 -1
- data/lib/rubocop/cop/bundler/gem_comment.rb +3 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
- data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
- data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
- data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
- data/lib/rubocop/cop/layout/block_alignment.rb +17 -0
- data/lib/rubocop/cop/layout/class_structure.rb +7 -3
- data/lib/rubocop/cop/layout/condition_position.rb +13 -3
- data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
- data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
- data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +32 -8
- data/lib/rubocop/cop/metrics/method_length.rb +1 -1
- data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
- data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
- data/lib/rubocop/cop/naming/file_name.rb +4 -3
- data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
- data/lib/rubocop/cop/security/io_methods.rb +1 -1
- data/lib/rubocop/cop/security/marshal_load.rb +1 -1
- data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
- data/lib/rubocop/cop/style/dir_empty.rb +4 -0
- data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
- data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
- data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
- data/lib/rubocop/cop/style/empty_literal.rb +7 -2
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
- data/lib/rubocop/cop/style/env_home.rb +4 -0
- data/lib/rubocop/cop/style/even_odd.rb +11 -1
- data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
- data/lib/rubocop/cop/style/file_null.rb +4 -2
- data/lib/rubocop/cop/style/format_string.rb +13 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
- data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
- data/lib/rubocop/cop/style/inline_comment.rb +1 -1
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
- data/lib/rubocop/cop/style/lambda.rb +7 -1
- data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
- data/lib/rubocop/cop/style/map_into_array.rb +1 -1
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +6 -2
- data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
- data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
- data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
- data/lib/rubocop/cop/style/option_hash.rb +1 -1
- data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
- data/lib/rubocop/cop/style/parallel_assignment.rb +11 -2
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
- data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
- data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
- data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
- data/lib/rubocop/cop/style/redundant_format.rb +28 -0
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
- data/lib/rubocop/cop/style/redundant_self.rb +9 -0
- data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
- data/lib/rubocop/cop/style/semicolon.rb +4 -4
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
- data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
- data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
- data/lib/rubocop/runner.rb +5 -3
- data/lib/rubocop/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04f5790c0be94705978732f52d462bd3afe919498e1e6be410fcfece0faaf4cd
|
|
4
|
+
data.tar.gz: 87c79acdf7db812d889bc2464a61d9921db70daf4e6c11117be879d7d9b64991
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8491483d605d97e0c746f171fdebf6b5b009a0e1e01392764a9fbf12208dad6a7fe8e8529f32d3a2b1ae37e39f150b689638cd7dbc901040f27b6cdf1981ab5
|
|
7
|
+
data.tar.gz: 7aa878cbe87d967607a65f37a1b3eafd47507d7a9c54c3bede0398f689527254fa4d65460d3af11d233244eb53eb7058bd5b69326588d7b3d3ed0fbb864afef8
|
data/config/default.yml
CHANGED
|
@@ -2844,7 +2844,7 @@ Metrics/PerceivedComplexity:
|
|
|
2844
2844
|
Description: 'Checks that the perceived complexity of methods is not higher than the configured maximum.'
|
|
2845
2845
|
Enabled: true
|
|
2846
2846
|
VersionAdded: '0.25'
|
|
2847
|
-
VersionChanged: '
|
|
2847
|
+
VersionChanged: '1.88'
|
|
2848
2848
|
AllowedMethods: []
|
|
2849
2849
|
AllowedPatterns: []
|
|
2850
2850
|
Max: 8
|
|
@@ -4658,7 +4658,9 @@ Style/MagicCommentFormat:
|
|
|
4658
4658
|
Style/MapCompactWithConditionalBlock:
|
|
4659
4659
|
Description: 'Prefer `select` or `reject` over `map { ... }.compact`.'
|
|
4660
4660
|
Enabled: pending
|
|
4661
|
+
SafeAutoCorrect: false
|
|
4661
4662
|
VersionAdded: '1.30'
|
|
4663
|
+
VersionChanged: '1.88'
|
|
4662
4664
|
|
|
4663
4665
|
Style/MapIntoArray:
|
|
4664
4666
|
Description: 'Checks for usages of `each` with `<<`, `push`, or `append` which can be replaced by `map`.'
|
|
@@ -163,7 +163,9 @@ module RuboCop
|
|
|
163
163
|
def gem_options(node)
|
|
164
164
|
return [] unless node.last_argument&.hash_type?
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
# Only literal keys carry an option name to check; a non-literal key
|
|
167
|
+
# (e.g. a variable or method call) has no `value` and must be skipped.
|
|
168
|
+
node.last_argument.keys.filter_map { |key| key.value if key.type?(:sym, :str) }
|
|
167
169
|
end
|
|
168
170
|
end
|
|
169
171
|
end
|
|
@@ -27,7 +27,7 @@ module RuboCop
|
|
|
27
27
|
if block_node.arguments?
|
|
28
28
|
format(CORRECTION_WITH_ARGUMENTS,
|
|
29
29
|
collection: collection_node.source,
|
|
30
|
-
variables: argument_node.children.
|
|
30
|
+
variables: argument_node.children.map(&:source).join(', '))
|
|
31
31
|
else
|
|
32
32
|
format(CORRECTION_WITHOUT_ARGUMENTS, enumerable: collection_node.source)
|
|
33
33
|
end
|
|
@@ -86,7 +86,13 @@ module RuboCop
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def lambda_arg_string
|
|
89
|
-
|
|
89
|
+
# Block-local (shadow) arguments are separated from regular arguments by a
|
|
90
|
+
# `;`; joining everything with `,` would turn them into extra parameters
|
|
91
|
+
# and change the lambda's arity.
|
|
92
|
+
regular, shadow = arguments.children.partition { |arg| !arg.shadowarg_type? }
|
|
93
|
+
arg_string = regular.map(&:source).join(', ')
|
|
94
|
+
arg_string += "; #{shadow.map(&:source).join(', ')}" unless shadow.empty?
|
|
95
|
+
arg_string
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
def needs_separating_space?
|
|
@@ -18,7 +18,14 @@ module RuboCop
|
|
|
18
18
|
current_range = declaration_with_comment(node)
|
|
19
19
|
previous_range = declaration_with_comment(previous_declaration)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
lambda do |corrector|
|
|
22
|
+
if current_range.source.end_with?("\n")
|
|
23
|
+
corrector.swap(current_range, previous_range)
|
|
24
|
+
else
|
|
25
|
+
corrector.insert_before(previous_range, "#{current_range.source}\n")
|
|
26
|
+
corrector.remove(current_range)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
private
|
|
@@ -143,7 +143,10 @@ module RuboCop
|
|
|
143
143
|
#{block_var}.metadata['rubygems_mfa_required'] = 'true'
|
|
144
144
|
RUBY
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
# Scope the search to the current spec block. Searching the whole file
|
|
147
|
+
# would, for a second `Gem::Specification.new` block, insert the directive
|
|
148
|
+
# into the first block, leaving this block uncorrected and looping forever.
|
|
149
|
+
if (last_assignment = metadata_assignment(node).to_a.last)
|
|
147
150
|
corrector.insert_after(last_assignment, "\n#{require_mfa_directive}")
|
|
148
151
|
else
|
|
149
152
|
corrector.insert_before(node.loc.end, "#{require_mfa_directive}\n")
|
|
@@ -281,15 +281,32 @@ module RuboCop
|
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
+
# rubocop:disable Metrics/AbcSize
|
|
284
285
|
def do_line_begins_inside_argument?(node, do_loc)
|
|
285
286
|
line_begin_pos = do_loc.begin_pos - do_loc.column
|
|
286
287
|
first_char_pos = line_begin_pos + (do_loc.source_line =~ /\S/)
|
|
288
|
+
return false unless inside_parentheses?(node, first_char_pos)
|
|
287
289
|
|
|
288
290
|
(node.send_node.arguments + node.arguments).any? do |argument|
|
|
289
291
|
argument.source_range.begin_pos <= first_char_pos &&
|
|
290
292
|
first_char_pos < argument.source_range.end_pos
|
|
291
293
|
end
|
|
292
294
|
end
|
|
295
|
+
# rubocop:enable Metrics/AbcSize
|
|
296
|
+
|
|
297
|
+
# The continuation line indentation is only an unmeaningful alignment target when
|
|
298
|
+
# it is dictated by an opening delimiter, i.e. the line begins inside `(` or `[`.
|
|
299
|
+
# For a bare argument list without parentheses the indentation is the author's
|
|
300
|
+
# deliberate alignment, so the anchor must not move to the method dispatch line.
|
|
301
|
+
def inside_parentheses?(node, pos)
|
|
302
|
+
preceding_tokens = processed_source.tokens.select do |token|
|
|
303
|
+
token.begin_pos >= node.source_range.begin_pos && token.begin_pos < pos
|
|
304
|
+
end
|
|
305
|
+
opens = preceding_tokens.count { |token| token.left_parens? || token.left_bracket? }
|
|
306
|
+
closes = preceding_tokens.count { |token| token.right_parens? || token.right_bracket? }
|
|
307
|
+
|
|
308
|
+
opens > closes
|
|
309
|
+
end
|
|
293
310
|
end
|
|
294
311
|
end
|
|
295
312
|
end
|
|
@@ -278,10 +278,14 @@ module RuboCop
|
|
|
278
278
|
|
|
279
279
|
return [] unless class_def
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
# Only a multi-statement body (`begin`/`kwbegin`) wraps several elements; any
|
|
282
|
+
# single statement (`def`, `send`, `csend`, `if`, ...) is itself the sole element.
|
|
283
|
+
# Exploding such a node into its children would yield non-node values (e.g. a
|
|
284
|
+
# method-name `Symbol` from a `csend`) and crash later checks.
|
|
285
|
+
if class_def.type?(:begin, :kwbegin)
|
|
284
286
|
class_def.children.compact
|
|
287
|
+
else
|
|
288
|
+
[class_def]
|
|
285
289
|
end
|
|
286
290
|
end
|
|
287
291
|
|
|
@@ -44,16 +44,26 @@ module RuboCop
|
|
|
44
44
|
message = message(condition)
|
|
45
45
|
|
|
46
46
|
add_offense(condition, message: message) do |corrector|
|
|
47
|
-
range = range_by_whole_lines(condition.source_range, include_final_newline: true)
|
|
48
|
-
|
|
49
47
|
corrector.insert_after(condition.parent.loc.keyword, " #{condition.source}")
|
|
50
|
-
corrector.remove(
|
|
48
|
+
corrector.remove(removal_range(node, condition))
|
|
51
49
|
end
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
def message(condition)
|
|
55
53
|
format(MSG, keyword: condition.parent.keyword)
|
|
56
54
|
end
|
|
55
|
+
|
|
56
|
+
# When a body statement shares the condition's line (e.g. `while\n cond; body\nend`),
|
|
57
|
+
# removing the whole line would delete the body too. In that case only remove the
|
|
58
|
+
# condition and its trailing separator, preserving the body statement.
|
|
59
|
+
def removal_range(node, condition)
|
|
60
|
+
body = node.body
|
|
61
|
+
if body && body.source_range.line == condition.source_range.last_line
|
|
62
|
+
range_between(condition.source_range.begin_pos, body.source_range.begin_pos)
|
|
63
|
+
else
|
|
64
|
+
range_by_whole_lines(condition.source_range, include_final_newline: true)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
57
67
|
end
|
|
58
68
|
end
|
|
59
69
|
end
|
|
@@ -95,8 +95,7 @@ module RuboCop
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def autocorrect(corrector, node)
|
|
98
|
-
|
|
99
|
-
range = if previous_token && same_line?(node, previous_token)
|
|
98
|
+
range = if inline_comment?(node)
|
|
100
99
|
range_with_surrounding_space(node.source_range, newlines: false)
|
|
101
100
|
else
|
|
102
101
|
range_by_whole_lines(node.source_range, include_final_newline: true)
|
|
@@ -138,14 +137,13 @@ module RuboCop
|
|
|
138
137
|
cop_config['AllowMarginComment']
|
|
139
138
|
end
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
index.zero? ? nil : processed_source.tokens[index - 1]
|
|
140
|
+
# A comment is inline when code precedes it on the same line. Detecting this
|
|
141
|
+
# from the source (rather than the token stream) is required because, for a
|
|
142
|
+
# comment trailing a heredoc opener, the preceding token is the heredoc end on
|
|
143
|
+
# a later line, which would wrongly trigger whole-line removal of the opener.
|
|
144
|
+
def inline_comment?(comment)
|
|
145
|
+
preceding_source = processed_source.lines[comment.loc.line - 1][0...comment.loc.column]
|
|
146
|
+
!preceding_source.strip.empty?
|
|
149
147
|
end
|
|
150
148
|
end
|
|
151
149
|
end
|
|
@@ -274,7 +274,20 @@ module RuboCop
|
|
|
274
274
|
end
|
|
275
275
|
|
|
276
276
|
def end_loc(node)
|
|
277
|
-
node.source_range.end
|
|
277
|
+
end_location = node.source_range.end
|
|
278
|
+
trailing_heredoc_end(node, end_location) || end_location
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# For an endless method whose body is a heredoc (e.g. `def a = <<~TEXT`), the
|
|
282
|
+
# node's source range ends at the heredoc opening line, before the heredoc body.
|
|
283
|
+
# Use the heredoc's closing delimiter so the def's real end is located after the
|
|
284
|
+
# heredoc and blank-line insertion does not land inside the heredoc body.
|
|
285
|
+
def trailing_heredoc_end(node, end_location)
|
|
286
|
+
heredocs = node.each_descendant(:any_str).select(&:heredoc?)
|
|
287
|
+
return if heredocs.empty?
|
|
288
|
+
|
|
289
|
+
heredoc_end = heredocs.map { |heredoc| heredoc.loc.heredoc_end }.max_by(&:end_pos)
|
|
290
|
+
heredoc_end if heredoc_end.end_pos > end_location.end_pos
|
|
278
291
|
end
|
|
279
292
|
|
|
280
293
|
def autocorrect_remove_lines(corrector, newline_pos, count)
|
|
@@ -28,10 +28,10 @@ module RuboCop
|
|
|
28
28
|
# # bad
|
|
29
29
|
# hash = {
|
|
30
30
|
# key: :value
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
31
|
+
# }
|
|
32
|
+
# in_a_method_call({
|
|
33
|
+
# foo: :bar
|
|
34
|
+
# })
|
|
35
35
|
# takes_multi_pairs_hash(x: {
|
|
36
36
|
# a: 1,
|
|
37
37
|
# b: 2
|
|
@@ -42,13 +42,12 @@ module RuboCop
|
|
|
42
42
|
# })
|
|
43
43
|
#
|
|
44
44
|
# # good
|
|
45
|
-
# special_inside_parentheses
|
|
46
45
|
# hash = {
|
|
47
46
|
# key: :value
|
|
48
47
|
# }
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
48
|
+
# in_a_method_call({
|
|
49
|
+
# foo: :bar
|
|
50
|
+
# })
|
|
52
51
|
# takes_multi_pairs_hash(x: {
|
|
53
52
|
# a: 1,
|
|
54
53
|
# b: 2
|
|
@@ -67,17 +66,17 @@ module RuboCop
|
|
|
67
66
|
# # bad
|
|
68
67
|
# hash = {
|
|
69
68
|
# key: :value
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
69
|
+
# }
|
|
70
|
+
# in_a_method_call({
|
|
71
|
+
# foo: :bar
|
|
72
|
+
# })
|
|
74
73
|
#
|
|
75
74
|
# # good
|
|
76
75
|
# hash = {
|
|
77
76
|
# key: :value
|
|
78
77
|
# }
|
|
79
|
-
#
|
|
80
|
-
#
|
|
78
|
+
# in_a_method_call({
|
|
79
|
+
# foo: :bar
|
|
81
80
|
# })
|
|
82
81
|
#
|
|
83
82
|
#
|
|
@@ -25,6 +25,17 @@ module RuboCop
|
|
|
25
25
|
# end
|
|
26
26
|
# end
|
|
27
27
|
#
|
|
28
|
+
# @example
|
|
29
|
+
# # bad
|
|
30
|
+
# value = (
|
|
31
|
+
# foo - bar
|
|
32
|
+
# )
|
|
33
|
+
#
|
|
34
|
+
# # good
|
|
35
|
+
# value = (
|
|
36
|
+
# foo - bar
|
|
37
|
+
# )
|
|
38
|
+
#
|
|
28
39
|
# @example AllowedPatterns: ['^\s*module']
|
|
29
40
|
# # bad
|
|
30
41
|
# module A
|
|
@@ -95,6 +106,16 @@ module RuboCop
|
|
|
95
106
|
check_indentation(node.loc.end, node.children.first)
|
|
96
107
|
end
|
|
97
108
|
|
|
109
|
+
def on_begin(node)
|
|
110
|
+
# Only a parenthesized grouping expression (e.g. `(\n foo\n)`) has
|
|
111
|
+
# explicit delimiters. Indent the body one step from the line
|
|
112
|
+
# the opening parenthesis is on, but only when the closing parenthesis is
|
|
113
|
+
# first on its line (a body on the opening line is skipped downstream).
|
|
114
|
+
return unless parentheses?(node) && begins_its_line?(node.loc.end)
|
|
115
|
+
|
|
116
|
+
check_indentation(opening_line_start(node.loc.begin), node.children.first)
|
|
117
|
+
end
|
|
118
|
+
|
|
98
119
|
def on_block(node)
|
|
99
120
|
end_loc = node.loc.end
|
|
100
121
|
|
|
@@ -188,6 +209,13 @@ module RuboCop
|
|
|
188
209
|
AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
|
|
189
210
|
end
|
|
190
211
|
|
|
212
|
+
# Returns a range at the first non-space column of the line the opening parenthesis is on,
|
|
213
|
+
# used as the base to indent the body from.
|
|
214
|
+
def opening_line_start(begin_loc)
|
|
215
|
+
column = begin_loc.source_line =~ /\S/
|
|
216
|
+
source_range(processed_source.buffer, begin_loc.line, column)
|
|
217
|
+
end
|
|
218
|
+
|
|
191
219
|
def check_members(base, members)
|
|
192
220
|
check_indentation(base, select_check_member(members.first))
|
|
193
221
|
|
|
@@ -204,10 +204,14 @@ module RuboCop
|
|
|
204
204
|
|
|
205
205
|
def autocorrect(corrector, range, right_operand)
|
|
206
206
|
range_source = range.source
|
|
207
|
+
# Match the operator exactly, not by substring, so compound assignments
|
|
208
|
+
# like `**=` and `/=` are not mistaken for `**` and `/` (which would drop
|
|
209
|
+
# the `=` and silently change the program's behavior).
|
|
210
|
+
operator = range_source.strip
|
|
207
211
|
|
|
208
|
-
if
|
|
212
|
+
if operator == '**' && !space_around_exponent_operator?
|
|
209
213
|
corrector.replace(range, '**')
|
|
210
|
-
elsif
|
|
214
|
+
elsif operator == '/' && !space_around_slash_operator?(right_operand)
|
|
211
215
|
corrector.replace(range, '/')
|
|
212
216
|
elsif range_source.end_with?("\n")
|
|
213
217
|
corrector.replace(range, " #{range_source.strip}\n")
|
|
@@ -78,13 +78,25 @@ module RuboCop
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def allowed_construct?(asgn_node)
|
|
81
|
-
asgn_node.begin_type?
|
|
81
|
+
return true if asgn_node.begin_type?
|
|
82
|
+
|
|
83
|
+
conditional_assignment?(asgn_node) || discarded_assignment?(asgn_node)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
def conditional_assignment?(asgn_node)
|
|
85
87
|
!asgn_node.loc.operator
|
|
86
88
|
end
|
|
87
89
|
|
|
90
|
+
# An assignment that is a statement of a multi-statement `begin`
|
|
91
|
+
# (e.g. `(foo = bar; baz)`) has its value discarded, so it is not used
|
|
92
|
+
# as the condition. Wrapping it in parentheses would only conflict with
|
|
93
|
+
# `Style/RedundantParentheses`, so it is left alone.
|
|
94
|
+
def discarded_assignment?(asgn_node)
|
|
95
|
+
parent = asgn_node.parent
|
|
96
|
+
|
|
97
|
+
parent&.begin_type? && parent.children.size > 1
|
|
98
|
+
end
|
|
99
|
+
|
|
88
100
|
def skip_children?(asgn_node)
|
|
89
101
|
(asgn_node.call_type? && !asgn_node.assignment_method?) ||
|
|
90
102
|
empty_condition?(asgn_node) ||
|
|
@@ -109,9 +109,11 @@ module RuboCop
|
|
|
109
109
|
when :optarg
|
|
110
110
|
send_arg.source == def_arg_name.to_s
|
|
111
111
|
when :kwoptarg, :kwarg
|
|
112
|
-
send_arg
|
|
112
|
+
keyword_hash_argument?(send_arg) &&
|
|
113
113
|
send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) }
|
|
114
114
|
when :kwrestarg
|
|
115
|
+
return false unless keyword_hash_argument?(send_arg)
|
|
116
|
+
|
|
115
117
|
send_arg.each_child_node(:kwsplat, :forwarded_kwrestarg).any? do |child|
|
|
116
118
|
child.source == def_arg.source
|
|
117
119
|
end
|
|
@@ -120,6 +122,10 @@ module RuboCop
|
|
|
120
122
|
end
|
|
121
123
|
end
|
|
122
124
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
125
|
+
|
|
126
|
+
def keyword_hash_argument?(send_arg)
|
|
127
|
+
send_arg.hash_type? && !send_arg.braces?
|
|
128
|
+
end
|
|
123
129
|
end
|
|
124
130
|
end
|
|
125
131
|
end
|
|
@@ -44,9 +44,7 @@ module RuboCop
|
|
|
44
44
|
|
|
45
45
|
def on_regexp(node)
|
|
46
46
|
RuboCop::Util.silence_warnings do
|
|
47
|
-
node.parsed_tree
|
|
48
|
-
detect_offenses(node, expr)
|
|
49
|
-
end
|
|
47
|
+
detect_offenses_in_tree(node, node.parsed_tree)
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
50
|
|
|
@@ -55,19 +53,43 @@ module RuboCop
|
|
|
55
53
|
return if node.each_descendant(:dstr).any?
|
|
56
54
|
|
|
57
55
|
regexp_constructor(node) do |text|
|
|
58
|
-
parse_regexp(text.value)
|
|
59
|
-
detect_offenses(text, expr)
|
|
60
|
-
end
|
|
56
|
+
detect_offenses_in_tree(text, parse_regexp(text.value))
|
|
61
57
|
end
|
|
62
58
|
end
|
|
63
59
|
|
|
64
60
|
private
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
# When a character class opens with a bare `]` (e.g. `[^]]`), `regexp_parser` parses
|
|
63
|
+
# `[^]` / `[]` as an empty set and reports the closing `]` as a separate literal.
|
|
64
|
+
# Ruby treats that `]` as the end of the class, not as an unescaped bracket,
|
|
65
|
+
# so the first `]` following an empty set must be skipped.
|
|
66
|
+
def detect_offenses_in_tree(node, tree)
|
|
67
|
+
return unless tree
|
|
68
|
+
|
|
69
|
+
skip_class_closer = false
|
|
70
|
+
tree.each_expression do |expr|
|
|
71
|
+
if empty_character_set?(expr)
|
|
72
|
+
skip_class_closer = true
|
|
73
|
+
elsif expr.type?(:literal)
|
|
74
|
+
skip_class_closer = detect_offenses(node, expr, skip_class_closer)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def empty_character_set?(expr)
|
|
80
|
+
expr.type?(:set) && expr.expressions.empty?
|
|
81
|
+
end
|
|
68
82
|
|
|
83
|
+
def detect_offenses(node, expr, skip_class_closer)
|
|
69
84
|
expr.text.scan(/(?<!\\)\]/) do
|
|
70
85
|
pos = Regexp.last_match.begin(0)
|
|
86
|
+
|
|
87
|
+
# The first `]` following an empty `[^]` / `[]` set closes the character class.
|
|
88
|
+
if skip_class_closer
|
|
89
|
+
skip_class_closer = false
|
|
90
|
+
next
|
|
91
|
+
end
|
|
92
|
+
|
|
71
93
|
# If the unescaped bracket is the first character of the regexp, Ruby does not warn.
|
|
72
94
|
# `pos` is relative to the sub-expression, so add its start offset (`expr.ts`).
|
|
73
95
|
next if (expr.ts + pos).zero?
|
|
@@ -78,6 +100,8 @@ module RuboCop
|
|
|
78
100
|
corrector.replace(location, '\]')
|
|
79
101
|
end
|
|
80
102
|
end
|
|
103
|
+
|
|
104
|
+
skip_class_closer
|
|
81
105
|
end
|
|
82
106
|
|
|
83
107
|
def range_at_index(node, index, offset)
|
|
@@ -58,7 +58,7 @@ module RuboCop
|
|
|
58
58
|
return unless node.method?(:define_method)
|
|
59
59
|
|
|
60
60
|
method_name = node.send_node.first_argument
|
|
61
|
-
return if method_name
|
|
61
|
+
return if method_name&.basic_literal? && allowed?(method_name.value)
|
|
62
62
|
|
|
63
63
|
check_code_length(node)
|
|
64
64
|
end
|
|
@@ -12,9 +12,15 @@ module RuboCop
|
|
|
12
12
|
# nodes count. In contrast to the CyclomaticComplexity cop, this cop
|
|
13
13
|
# considers `else` nodes as adding complexity.
|
|
14
14
|
#
|
|
15
|
+
# A `case`/`in` branch whose pattern is a simple literal (e.g. `in 1`, `in "red"`, `in 1..10`)
|
|
16
|
+
# or a constant/type (e.g. `in Integer`) and has no guard is just as easy to read as a `when`
|
|
17
|
+
# branch, so it is discounted the same way. Branches with structural patterns (e.g. array,
|
|
18
|
+
# hash, or find patterns), bindings, alternatives, or a guard add the full complexity of
|
|
19
|
+
# a decision point.
|
|
20
|
+
#
|
|
15
21
|
# @example
|
|
16
22
|
#
|
|
17
|
-
# def
|
|
23
|
+
# def example_1 # 1
|
|
18
24
|
# if cond # 1
|
|
19
25
|
# case var # 2 (0.8 + 4 * 0.2, rounded)
|
|
20
26
|
# when 1 then func_one
|
|
@@ -26,33 +32,58 @@ module RuboCop
|
|
|
26
32
|
# do_something until a && b # 2
|
|
27
33
|
# end # ===
|
|
28
34
|
# end # 7 complexity points
|
|
35
|
+
#
|
|
36
|
+
# def example_2 # 1
|
|
37
|
+
# case color # 1 (3 * 0.2, rounded)
|
|
38
|
+
# in "red" then func_red
|
|
39
|
+
# in "blue" then func_blue
|
|
40
|
+
# in "green" then func_green
|
|
41
|
+
# end # ===
|
|
42
|
+
# end # 2 complexity points
|
|
29
43
|
class PerceivedComplexity < CyclomaticComplexity
|
|
30
44
|
MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
|
|
31
45
|
|
|
32
|
-
COUNTED_NODES = (
|
|
46
|
+
COUNTED_NODES = (
|
|
47
|
+
CyclomaticComplexity::COUNTED_NODES - %i[when in_pattern] + %i[case case_match]
|
|
48
|
+
).freeze
|
|
33
49
|
|
|
34
50
|
private
|
|
35
51
|
|
|
52
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
36
53
|
def complexity_score_for(node)
|
|
37
54
|
case node.type
|
|
38
55
|
when :case
|
|
39
|
-
# If cond is nil, that means each when has an expression that
|
|
40
|
-
#
|
|
41
|
-
# if/elsif/elsif... so the when nodes count.
|
|
56
|
+
# If cond is nil, that means each when has an expression that evaluates to true or
|
|
57
|
+
# false. It's just an alternative to if/elsif/elsif... so the when nodes count.
|
|
42
58
|
nb_branches = node.when_branches.length + (node.else_branch ? 1 : 0)
|
|
43
59
|
if node.condition.nil?
|
|
44
60
|
nb_branches
|
|
45
61
|
else
|
|
46
|
-
# Otherwise, the case node gets 0.8 complexity points and each
|
|
47
|
-
# when gets 0.2.
|
|
62
|
+
# Otherwise, the case node gets 0.8 complexity points and each when gets 0.2.
|
|
48
63
|
((nb_branches * 0.2) + 0.8).round
|
|
49
64
|
end
|
|
65
|
+
when :case_match
|
|
66
|
+
# Simple `in` branches are discounted like `when`, while structural patterns keep
|
|
67
|
+
# the full complexity of a decision point.
|
|
68
|
+
score = node.in_pattern_branches.sum { |branch| simple_in_pattern?(branch) ? 0.2 : 1 }
|
|
69
|
+
score += 0.2 if node.else_branch
|
|
70
|
+
score.round
|
|
50
71
|
when :if
|
|
51
72
|
node.else? && !node.elsif? ? 2 : 1
|
|
52
73
|
else
|
|
53
74
|
super
|
|
54
75
|
end
|
|
55
76
|
end
|
|
77
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
78
|
+
|
|
79
|
+
def simple_in_pattern?(in_pattern_node)
|
|
80
|
+
# `in_pattern_node.children[1]` is the guard (`if`/`unless`), or `nil`.
|
|
81
|
+
return false unless in_pattern_node.children[1].nil?
|
|
82
|
+
|
|
83
|
+
# A scalar literal, a literal range, or a constant/type is as easy to read as a `when`.
|
|
84
|
+
pattern = in_pattern_node.pattern
|
|
85
|
+
pattern.literal? || pattern.const_type?
|
|
86
|
+
end
|
|
56
87
|
end
|
|
57
88
|
end
|
|
58
89
|
end
|
|
@@ -182,10 +182,18 @@ module RuboCop
|
|
|
182
182
|
return ":\"#{value.source}\"" if value.dsym_type?
|
|
183
183
|
return "\"#{value.source}\"" if value.dstr_type?
|
|
184
184
|
return ":#{value.source}" if value.sym_type?
|
|
185
|
+
# The element of a `%w` array can contain characters that are special
|
|
186
|
+
# inside a single-quoted string (e.g. a `'`), so escape them rather than
|
|
187
|
+
# wrapping the raw source.
|
|
188
|
+
return to_single_quoted(value.value) if value.str_type?
|
|
185
189
|
|
|
186
190
|
"'#{value.source}'"
|
|
187
191
|
end
|
|
188
192
|
|
|
193
|
+
def to_single_quoted(string)
|
|
194
|
+
"'#{string.gsub(/['\\]/) { |character| "\\#{character}" }}'"
|
|
195
|
+
end
|
|
196
|
+
|
|
189
197
|
def except_key(node)
|
|
190
198
|
key_arg = node.argument_list.first.source
|
|
191
199
|
body, = extract_body_if_negated(node.body)
|
|
@@ -94,6 +94,10 @@ module RuboCop
|
|
|
94
94
|
|
|
95
95
|
return unless captures.use_transformed_argname?
|
|
96
96
|
|
|
97
|
+
# A splat transforming expression (e.g. `[k, *v]`) can't be used as a
|
|
98
|
+
# standalone block return value, so the rewrite would produce invalid Ruby.
|
|
99
|
+
return if captures.transforming_body_expr.splat_type?
|
|
100
|
+
|
|
97
101
|
message = "Prefer `#{new_method_name}` over `#{match_desc}`."
|
|
98
102
|
add_offense(node, message: message) do |corrector|
|
|
99
103
|
correction = prepare_correction(node)
|
|
@@ -203,9 +203,10 @@ module RuboCop
|
|
|
203
203
|
|
|
204
204
|
def match_acronym?(expected, name)
|
|
205
205
|
expected = expected.to_s
|
|
206
|
-
name = name.to_s
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
name = allowed_acronyms.reduce(name.to_s) do |result, acronym|
|
|
207
|
+
result.gsub(acronym, acronym.capitalize)
|
|
208
|
+
end
|
|
209
|
+
expected == name
|
|
209
210
|
end
|
|
210
211
|
|
|
211
212
|
def to_namespace(path) # rubocop:disable Metrics/AbcSize
|