rubocop 1.72.2 → 1.74.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 +3 -3
- data/config/default.yml +56 -15
- data/config/internal_affairs.yml +20 -0
- data/lib/rubocop/config_loader.rb +0 -1
- data/lib/rubocop/config_loader_resolver.rb +4 -3
- data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -3
- data/lib/rubocop/config_obsoletion.rb +1 -1
- data/lib/rubocop/config_validator.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/example_description.rb +3 -1
- data/lib/rubocop/cop/internal_affairs/node_type_group.rb +91 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +4 -4
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +26 -1
- data/lib/rubocop/cop/layout/line_length.rb +3 -3
- data/lib/rubocop/cop/lint/duplicate_methods.rb +0 -14
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +14 -64
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -6
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -6
- data/lib/rubocop/cop/lint/literal_as_condition.rb +103 -9
- data/lib/rubocop/cop/lint/mixed_case_range.rb +2 -2
- data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +2 -2
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +0 -21
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +32 -5
- data/lib/rubocop/cop/lint/return_in_void_context.rb +4 -11
- data/lib/rubocop/cop/lint/shared_mutable_default.rb +12 -1
- data/lib/rubocop/cop/lint/useless_constant_scoping.rb +2 -11
- data/lib/rubocop/cop/lint/void.rb +6 -0
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_subset.rb +19 -4
- data/lib/rubocop/cop/mixin/range_help.rb +12 -0
- data/lib/rubocop/cop/mixin/target_ruby_version.rb +1 -1
- data/lib/rubocop/cop/mixin/trailing_comma.rb +12 -0
- data/lib/rubocop/cop/naming/variable_name.rb +64 -6
- data/lib/rubocop/cop/style/accessor_grouping.rb +19 -5
- data/lib/rubocop/cop/style/class_and_module_children.rb +29 -7
- data/lib/rubocop/cop/style/commented_keyword.rb +10 -3
- data/lib/rubocop/cop/style/comparable_between.rb +75 -0
- data/lib/rubocop/cop/style/double_negation.rb +1 -1
- data/lib/rubocop/cop/style/endless_method.rb +163 -18
- data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -7
- data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
- data/lib/rubocop/cop/style/format_string_token.rb +38 -11
- data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -2
- data/lib/rubocop/cop/style/inverse_methods.rb +8 -5
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +13 -7
- data/lib/rubocop/cop/style/line_end_concatenation.rb +10 -4
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -3
- data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -1
- data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -9
- data/lib/rubocop/cop/style/redundant_condition.rb +45 -0
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +14 -4
- data/lib/rubocop/cop/style/redundant_format.rb +23 -11
- data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +3 -0
- data/lib/rubocop/cop/style/single_line_methods.rb +3 -3
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +0 -6
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
- data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +47 -6
- data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +48 -6
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/utils/format_string.rb +5 -2
- data/lib/rubocop/cops_documentation_generator.rb +12 -1
- data/lib/rubocop/directive_comment.rb +1 -1
- data/lib/rubocop/ext/regexp_node.rb +0 -1
- data/lib/rubocop/plugin/load_error.rb +1 -1
- data/lib/rubocop/plugin.rb +9 -2
- data/lib/rubocop/rspec/shared_contexts.rb +15 -0
- data/lib/rubocop/rspec/support.rb +1 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +1 -1
- metadata +6 -5
- data/lib/rubocop/cop/utils/regexp_ranges.rb +0 -113
@@ -23,7 +23,7 @@ module RuboCop
|
|
23
23
|
(call _ _)
|
24
24
|
(args
|
25
25
|
$(arg _key)
|
26
|
-
(arg _))
|
26
|
+
$(arg _))
|
27
27
|
{
|
28
28
|
$(send
|
29
29
|
{(lvar _key) $_ _ | _ $_ (lvar _key)})
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def extracts_hash_subset?(block)
|
70
|
-
block_with_first_arg_check?(block) do |key_arg, send_node, method|
|
70
|
+
block_with_first_arg_check?(block) do |key_arg, value_arg, send_node, method|
|
71
71
|
# Only consider methods that have one argument
|
72
72
|
return false unless send_node.arguments.one?
|
73
73
|
|
@@ -76,15 +76,22 @@ module RuboCop
|
|
76
76
|
|
77
77
|
case method
|
78
78
|
when :include?, :exclude?
|
79
|
-
send_node
|
79
|
+
slices_key?(send_node, :first_argument, key_arg, value_arg)
|
80
80
|
when :in?
|
81
|
-
send_node
|
81
|
+
slices_key?(send_node, :receiver, key_arg, value_arg)
|
82
82
|
else
|
83
83
|
true
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
+
def slices_key?(send_node, method, key_arg, value_arg)
|
89
|
+
return false if using_value_variable?(send_node, value_arg)
|
90
|
+
|
91
|
+
node = method == :receiver ? send_node.receiver : send_node.first_argument
|
92
|
+
node.source == key_arg.source
|
93
|
+
end
|
94
|
+
|
88
95
|
def range_include?(send_node)
|
89
96
|
# When checking `include?`, `exclude?` and `in?` for offenses, if the receiver
|
90
97
|
# or first argument is a range, an offense should not be registered.
|
@@ -97,6 +104,14 @@ module RuboCop
|
|
97
104
|
receiver.range_type?
|
98
105
|
end
|
99
106
|
|
107
|
+
def using_value_variable?(send_node, value_arg)
|
108
|
+
# If the receiver of `include?` or `exclude?`, or the first argument of `in?` is the
|
109
|
+
# hash value block argument, an offense should not be registered.
|
110
|
+
# ie. `v.include?(k)` or `k.in?(v)`
|
111
|
+
(send_node.receiver.lvar_type? && send_node.receiver.name == value_arg.name) ||
|
112
|
+
(send_node.first_argument.lvar_type? && send_node.first_argument.name == value_arg.name)
|
113
|
+
end
|
114
|
+
|
100
115
|
def supported_subset_method?(method)
|
101
116
|
if active_support_extensions_enabled?
|
102
117
|
ACTIVE_SUPPORT_SUBSET_METHODS.include?(method)
|
@@ -34,6 +34,18 @@ module RuboCop
|
|
34
34
|
range_between(node.loc.begin.end_pos, node.loc.end.begin_pos)
|
35
35
|
end
|
36
36
|
|
37
|
+
# A range containing the first to the last argument
|
38
|
+
# of a method call or method definition.
|
39
|
+
# def foo(a, b:)
|
40
|
+
# ^^^^^
|
41
|
+
# bar(1, 2, 3, &blk)
|
42
|
+
# ^^^^^^^^^^^^^
|
43
|
+
# baz { |x, y:, z:| }
|
44
|
+
# ^^^^^^^^^
|
45
|
+
def arguments_range(node)
|
46
|
+
node.first_argument.source_range.join(node.last_argument.source_range)
|
47
|
+
end
|
48
|
+
|
37
49
|
def range_between(start_pos, end_pos)
|
38
50
|
Parser::Source::Range.new(processed_source.buffer, start_pos, end_pos)
|
39
51
|
end
|
@@ -4,6 +4,7 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
# Common methods shared by Style/TrailingCommaInArguments,
|
6
6
|
# Style/TrailingCommaInArrayLiteral and Style/TrailingCommaInHashLiteral
|
7
|
+
# rubocop:disable Metrics/ModuleLength
|
7
8
|
module TrailingComma
|
8
9
|
include ConfigurableEnforcedStyle
|
9
10
|
include RangeHelp
|
@@ -57,6 +58,8 @@ module RuboCop
|
|
57
58
|
', unless each item is on its own line'
|
58
59
|
when :consistent_comma
|
59
60
|
', unless items are split onto multiple lines'
|
61
|
+
when :diff_comma
|
62
|
+
', unless that item immediately precedes a newline'
|
60
63
|
else
|
61
64
|
''
|
62
65
|
end
|
@@ -68,6 +71,8 @@ module RuboCop
|
|
68
71
|
multiline?(node) && no_elements_on_same_line?(node)
|
69
72
|
when :consistent_comma
|
70
73
|
multiline?(node) && !method_name_and_arguments_on_same_line?(node)
|
74
|
+
when :diff_comma
|
75
|
+
multiline?(node) && last_item_precedes_newline?(node)
|
71
76
|
else
|
72
77
|
false
|
73
78
|
end
|
@@ -130,6 +135,12 @@ module RuboCop
|
|
130
135
|
range1.last_line == range2.line
|
131
136
|
end
|
132
137
|
|
138
|
+
def last_item_precedes_newline?(node)
|
139
|
+
after_last_item =
|
140
|
+
range_between(node.children.last.source_range.end_pos, node.loc.end.begin_pos)
|
141
|
+
after_last_item.source =~ /\A,?\s*\n/
|
142
|
+
end
|
143
|
+
|
133
144
|
def avoid_comma(kind, comma_begin_pos, extra_info)
|
134
145
|
range = range_between(comma_begin_pos, comma_begin_pos + 1)
|
135
146
|
article = kind.include?('array') ? 'an' : 'a'
|
@@ -205,5 +216,6 @@ module RuboCop
|
|
205
216
|
false
|
206
217
|
end
|
207
218
|
end
|
219
|
+
# rubocop:enable Metrics/ModuleLength
|
208
220
|
end
|
209
221
|
end
|
@@ -3,8 +3,15 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Naming
|
6
|
-
#
|
7
|
-
#
|
6
|
+
# Checks that the configured style (snake_case or camelCase) is used for all variable names.
|
7
|
+
# This includes local variables, instance variables, class variables, method arguments
|
8
|
+
# (positional, keyword, rest or block), and block arguments.
|
9
|
+
#
|
10
|
+
# The cop can also be configured to forbid using specific names for variables, using
|
11
|
+
# `ForbiddenIdentifiers` or `ForbiddenPatterns`. In addition to the above, this applies
|
12
|
+
# to global variables as well.
|
13
|
+
#
|
14
|
+
# Method definitions and method calls are not affected by this cop.
|
8
15
|
#
|
9
16
|
# @example EnforcedStyle: snake_case (default)
|
10
17
|
# # bad
|
@@ -26,7 +33,21 @@ module RuboCop
|
|
26
33
|
#
|
27
34
|
# @example AllowedPatterns: ['_v\d+\z']
|
28
35
|
# # good (with EnforcedStyle: camelCase)
|
29
|
-
#
|
36
|
+
# release_v1 = true
|
37
|
+
#
|
38
|
+
# @example ForbiddenIdentifiers: ['fooBar']
|
39
|
+
# # bad (in all cases)
|
40
|
+
# fooBar = 1
|
41
|
+
# @fooBar = 1
|
42
|
+
# @@fooBar = 1
|
43
|
+
# $fooBar = 1
|
44
|
+
#
|
45
|
+
# @example ForbiddenPatterns: ['_v\d+\z']
|
46
|
+
# # bad (in all cases)
|
47
|
+
# release_v1 = true
|
48
|
+
# @release_v1 = true
|
49
|
+
# @@release_v1 = true
|
50
|
+
# $release_v1 = true
|
30
51
|
#
|
31
52
|
class VariableName < Base
|
32
53
|
include AllowedIdentifiers
|
@@ -34,16 +55,21 @@ module RuboCop
|
|
34
55
|
include AllowedPattern
|
35
56
|
|
36
57
|
MSG = 'Use %<style>s for variable names.'
|
58
|
+
MSG_FORBIDDEN = '`%<identifier>s` is forbidden, use another name instead.'
|
37
59
|
|
38
60
|
def valid_name?(node, name, given_style = style)
|
39
61
|
super || matches_allowed_pattern?(name)
|
40
62
|
end
|
41
63
|
|
42
64
|
def on_lvasgn(node)
|
43
|
-
return unless node.name
|
44
|
-
return if allowed_identifier?(
|
65
|
+
return unless (name = node.name)
|
66
|
+
return if allowed_identifier?(name)
|
45
67
|
|
46
|
-
|
68
|
+
if forbidden_name?(name)
|
69
|
+
register_forbidden_name(node)
|
70
|
+
else
|
71
|
+
check_name(node, name, node.loc.name)
|
72
|
+
end
|
47
73
|
end
|
48
74
|
alias on_ivasgn on_lvasgn
|
49
75
|
alias on_cvasgn on_lvasgn
|
@@ -56,11 +82,43 @@ module RuboCop
|
|
56
82
|
alias on_blockarg on_lvasgn
|
57
83
|
alias on_lvar on_lvasgn
|
58
84
|
|
85
|
+
# Only forbidden names are checked for global variable assignment
|
86
|
+
def on_gvasgn(node)
|
87
|
+
return unless (name = node.name)
|
88
|
+
return unless forbidden_name?(name)
|
89
|
+
|
90
|
+
register_forbidden_name(node)
|
91
|
+
end
|
92
|
+
|
59
93
|
private
|
60
94
|
|
61
95
|
def message(style)
|
62
96
|
format(MSG, style: style)
|
63
97
|
end
|
98
|
+
|
99
|
+
def forbidden_identifiers
|
100
|
+
cop_config.fetch('ForbiddenIdentifiers', [])
|
101
|
+
end
|
102
|
+
|
103
|
+
def forbidden_patterns
|
104
|
+
cop_config.fetch('ForbiddenPatterns', [])
|
105
|
+
end
|
106
|
+
|
107
|
+
def matches_forbidden_pattern?(name)
|
108
|
+
forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def forbidden_name?(name)
|
112
|
+
name = name.to_s.delete(SIGILS)
|
113
|
+
|
114
|
+
(forbidden_identifiers.any? && forbidden_identifiers.include?(name)) ||
|
115
|
+
(forbidden_patterns.any? && matches_forbidden_pattern?(name))
|
116
|
+
end
|
117
|
+
|
118
|
+
def register_forbidden_name(node)
|
119
|
+
message = format(MSG_FORBIDDEN, identifier: node.name)
|
120
|
+
add_offense(node.loc.name, message: message)
|
121
|
+
end
|
64
122
|
end
|
65
123
|
end
|
66
124
|
end
|
@@ -139,12 +139,16 @@ module RuboCop
|
|
139
139
|
style == :separated
|
140
140
|
end
|
141
141
|
|
142
|
+
def groupable_sibling_accessor?(node, sibling)
|
143
|
+
sibling.attribute_accessor? &&
|
144
|
+
sibling.method?(node.method_name) &&
|
145
|
+
node_visibility(sibling) == node_visibility(node) &&
|
146
|
+
groupable_accessor?(sibling) && !previous_line_comment?(sibling)
|
147
|
+
end
|
148
|
+
|
142
149
|
def groupable_sibling_accessors(send_node)
|
143
150
|
send_node.parent.each_child_node(:send).select do |sibling|
|
144
|
-
|
145
|
-
sibling.method?(send_node.method_name) &&
|
146
|
-
node_visibility(sibling) == node_visibility(send_node) &&
|
147
|
-
groupable_accessor?(sibling) && !previous_line_comment?(sibling)
|
151
|
+
groupable_sibling_accessor?(send_node, sibling)
|
148
152
|
end
|
149
153
|
end
|
150
154
|
|
@@ -155,13 +159,23 @@ module RuboCop
|
|
155
159
|
|
156
160
|
def preferred_accessors(node)
|
157
161
|
if grouped_style?
|
162
|
+
return if skip_for_grouping?(node)
|
163
|
+
|
158
164
|
accessors = groupable_sibling_accessors(node)
|
159
|
-
|
165
|
+
if node.loc == accessors.first.loc || skip_for_grouping?(accessors.first)
|
166
|
+
group_accessors(node, accessors)
|
167
|
+
end
|
160
168
|
else
|
161
169
|
separate_accessors(node)
|
162
170
|
end
|
163
171
|
end
|
164
172
|
|
173
|
+
# Group after constants
|
174
|
+
def skip_for_grouping?(node)
|
175
|
+
node.right_siblings.any?(&:casgn_type?) &&
|
176
|
+
node.right_siblings.any? { |n| n.send_type? && groupable_sibling_accessor?(node, n) }
|
177
|
+
end
|
178
|
+
|
165
179
|
def group_accessors(node, accessors)
|
166
180
|
accessor_names = accessors.flat_map { |accessor| accessor.arguments.map(&:source) }.uniq
|
167
181
|
|
@@ -3,14 +3,26 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Style
|
6
|
-
# Checks
|
7
|
-
#
|
6
|
+
# Checks that namespaced classes and modules are defined with a consistent style.
|
7
|
+
#
|
8
|
+
# With `nested` style, classes and modules should be defined separately (one constant
|
9
|
+
# on each line, without `::`). With `compact` style, classes and modules should be
|
10
|
+
# defined with fully qualified names (using `::` for namespaces).
|
11
|
+
#
|
12
|
+
# NOTE: The style chosen will affect `Module.nesting` for the class or module. Using
|
13
|
+
# `nested` style will result in each level being added, whereas `compact` style will
|
14
|
+
# only include the fully qualified class or module name.
|
15
|
+
#
|
16
|
+
# By default, `EnforcedStyle` applies to both classes and modules. If desired, separate
|
17
|
+
# styles can be defined for classes and modules by using `EnforcedStyleForClasses` and
|
18
|
+
# `EnforcedStyleForModules` respectively. If not set, or set to nil, the `EnforcedStyle`
|
19
|
+
# value will be used.
|
8
20
|
#
|
9
21
|
# @safety
|
10
22
|
# Autocorrection is unsafe.
|
11
23
|
#
|
12
|
-
# Moving from compact to nested children requires knowledge of whether the
|
13
|
-
# outer parent is a module or a class. Moving from nested to compact requires
|
24
|
+
# Moving from `compact` to `nested` children requires knowledge of whether the
|
25
|
+
# outer parent is a module or a class. Moving from `nested` to `compact` requires
|
14
26
|
# verification that the outer parent is defined elsewhere. RuboCop does not
|
15
27
|
# have the knowledge to perform either operation safely and thus requires
|
16
28
|
# manual oversight.
|
@@ -42,16 +54,18 @@ module RuboCop
|
|
42
54
|
def on_class(node)
|
43
55
|
return if node.parent_class && style != :nested
|
44
56
|
|
45
|
-
check_style(node, node.body)
|
57
|
+
check_style(node, node.body, style_for_classes)
|
46
58
|
end
|
47
59
|
|
48
60
|
def on_module(node)
|
49
|
-
check_style(node, node.body)
|
61
|
+
check_style(node, node.body, style_for_modules)
|
50
62
|
end
|
51
63
|
|
52
64
|
private
|
53
65
|
|
54
66
|
def nest_or_compact(corrector, node)
|
67
|
+
style = node.class_type? ? style_for_classes : style_for_modules
|
68
|
+
|
55
69
|
if style == :nested
|
56
70
|
nest_definition(corrector, node)
|
57
71
|
else
|
@@ -141,7 +155,7 @@ module RuboCop
|
|
141
155
|
node.source_range.source_line[/\A\s*/]
|
142
156
|
end
|
143
157
|
|
144
|
-
def check_style(node, body)
|
158
|
+
def check_style(node, body, style)
|
145
159
|
return if node.identifier.namespace&.cbase_type?
|
146
160
|
|
147
161
|
if style == :nested
|
@@ -183,6 +197,14 @@ module RuboCop
|
|
183
197
|
def compact_node_name?(node)
|
184
198
|
node.identifier.source.include?('::')
|
185
199
|
end
|
200
|
+
|
201
|
+
def style_for_classes
|
202
|
+
cop_config['EnforcedStyleForClasses'] || style
|
203
|
+
end
|
204
|
+
|
205
|
+
def style_for_modules
|
206
|
+
cop_config['EnforcedStyleForModules'] || style
|
207
|
+
end
|
186
208
|
end
|
187
209
|
end
|
188
210
|
end
|
@@ -9,8 +9,8 @@ module RuboCop
|
|
9
9
|
# These keywords are: `class`, `module`, `def`, `begin`, `end`.
|
10
10
|
#
|
11
11
|
# Note that some comments
|
12
|
-
# (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
|
13
|
-
#
|
12
|
+
# (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`),
|
13
|
+
# RBS::Inline annotation, and Steep annotation (`steep:ignore`) are allowed.
|
14
14
|
#
|
15
15
|
# Autocorrection removes comments from `end` keyword and keeps comments
|
16
16
|
# for `class`, `module`, `def` and `begin` above the keyword.
|
@@ -57,9 +57,11 @@ module RuboCop
|
|
57
57
|
|
58
58
|
REGEXP = /(?<keyword>\S+).*#/.freeze
|
59
59
|
|
60
|
-
SUBCLASS_DEFINITION = /\A\s*class\s
|
60
|
+
SUBCLASS_DEFINITION = /\A\s*class\s+(\w|::)+\s*<\s*(\w|::)+/.freeze
|
61
61
|
METHOD_DEFINITION = /\A\s*def\s/.freeze
|
62
62
|
|
63
|
+
STEEP_REGEXP = /#\ssteep:ignore(\s|\z)/.freeze
|
64
|
+
|
63
65
|
def on_new_investigation
|
64
66
|
processed_source.comments.each do |comment|
|
65
67
|
next unless offensive?(comment) && (match = source_line(comment).match(REGEXP))
|
@@ -86,6 +88,7 @@ module RuboCop
|
|
86
88
|
def offensive?(comment)
|
87
89
|
line = source_line(comment)
|
88
90
|
return false if rbs_inline_annotation?(line, comment)
|
91
|
+
return false if steep_annotation?(comment)
|
89
92
|
|
90
93
|
KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
|
91
94
|
ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
|
@@ -105,6 +108,10 @@ module RuboCop
|
|
105
108
|
false
|
106
109
|
end
|
107
110
|
end
|
111
|
+
|
112
|
+
def steep_annotation?(comment)
|
113
|
+
comment.text.match?(STEEP_REGEXP)
|
114
|
+
end
|
108
115
|
end
|
109
116
|
end
|
110
117
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for logical comparison which can be replaced with `Comparable#between?`.
|
7
|
+
#
|
8
|
+
# NOTE: `Comparable#between?` is on average slightly slower than logical comparison,
|
9
|
+
# although the difference generally isn't observable. If you require maximum
|
10
|
+
# performance, consider using logical comparison.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# x >= min && x <= max
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# x <= max && x >= min
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# x.between?(min, max)
|
22
|
+
#
|
23
|
+
class ComparableBetween < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Prefer `%<prefer>s` over logical comparison.'
|
27
|
+
|
28
|
+
# @!method logical_comparison_between_by_min_first?(node)
|
29
|
+
def_node_matcher :logical_comparison_between_by_min_first?, <<~PATTERN
|
30
|
+
(and
|
31
|
+
(send
|
32
|
+
{$_value :>= $_min | $_min :<= $_value})
|
33
|
+
(send
|
34
|
+
{$_value :<= $_max | $_max :>= $_value}))
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
# @!method logical_comparison_between_by_max_first?(node)
|
38
|
+
def_node_matcher :logical_comparison_between_by_max_first?, <<~PATTERN
|
39
|
+
(and
|
40
|
+
(send
|
41
|
+
{$_value :<= $_max | $_max :>= $_value})
|
42
|
+
(send
|
43
|
+
{$_value :>= $_min | $_min :<= $_value}))
|
44
|
+
PATTERN
|
45
|
+
|
46
|
+
def on_and(node)
|
47
|
+
logical_comparison_between_by_min_first?(node) do |*args|
|
48
|
+
min_and_value, max_and_value = args.each_slice(2).to_a
|
49
|
+
|
50
|
+
register_offense(node, min_and_value, max_and_value)
|
51
|
+
end
|
52
|
+
|
53
|
+
logical_comparison_between_by_max_first?(node) do |*args|
|
54
|
+
max_and_value, min_and_value = args.each_slice(2).to_a
|
55
|
+
|
56
|
+
register_offense(node, min_and_value, max_and_value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def register_offense(node, min_and_value, max_and_value)
|
63
|
+
value = (min_and_value & max_and_value).first
|
64
|
+
min = min_and_value.find { _1 != value }
|
65
|
+
max = max_and_value.find { _1 != value }
|
66
|
+
|
67
|
+
prefer = "#{value.source}.between?(#{min.source}, #{max.source})"
|
68
|
+
add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
|
69
|
+
corrector.replace(node, prefer)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|