rubocop 1.43.0 → 1.44.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 +1 -1
- data/config/default.yml +50 -0
- data/lib/rubocop/config_loader.rb +12 -15
- data/lib/rubocop/cop/corrector.rb +10 -2
- data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +1 -6
- data/lib/rubocop/cop/gemspec/development_dependencies.rb +107 -0
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +11 -3
- data/lib/rubocop/cop/layout/block_end_newline.rb +7 -1
- data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -5
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +6 -9
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +0 -2
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +62 -112
- data/lib/rubocop/cop/lint/else_layout.rb +2 -6
- data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +10 -7
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +11 -1
- data/lib/rubocop/cop/lint/useless_method_definition.rb +3 -3
- data/lib/rubocop/cop/lint/useless_rescue.rb +15 -1
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +9 -1
- data/lib/rubocop/cop/lint/void.rb +17 -10
- data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +2 -5
- data/lib/rubocop/cop/mixin/alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +40 -18
- data/lib/rubocop/cop/mixin/line_length_help.rb +3 -1
- data/lib/rubocop/cop/registry.rb +12 -7
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +18 -10
- data/lib/rubocop/cop/style/block_delimiters.rb +8 -2
- data/lib/rubocop/cop/style/class_and_module_children.rb +2 -9
- data/lib/rubocop/cop/style/comparable_clamp.rb +125 -0
- data/lib/rubocop/cop/style/conditional_assignment.rb +0 -6
- data/lib/rubocop/cop/style/infinite_loop.rb +2 -5
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +114 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -5
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -0
- data/lib/rubocop/cop/style/min_max_comparison.rb +11 -1
- data/lib/rubocop/cop/style/multiline_if_modifier.rb +0 -4
- data/lib/rubocop/cop/style/multiline_memoization.rb +2 -2
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -5
- data/lib/rubocop/cop/style/one_line_conditional.rb +2 -5
- data/lib/rubocop/cop/style/operator_method_call.rb +1 -1
- data/lib/rubocop/cop/style/redundant_conditional.rb +0 -4
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +16 -10
- data/lib/rubocop/cop/style/require_order.rb +2 -9
- data/lib/rubocop/cop/style/semicolon.rb +24 -2
- data/lib/rubocop/cop/variable_force.rb +1 -1
- data/lib/rubocop/formatter.rb +0 -1
- data/lib/rubocop/rspec/expect_offense.rb +5 -3
- data/lib/rubocop/server/cache.rb +3 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +3 -0
- metadata +6 -3
@@ -94,8 +94,8 @@ module RuboCop
|
|
94
94
|
|
95
95
|
# @!method called_on_string?(node)
|
96
96
|
def_node_matcher :called_on_string?, <<~PATTERN
|
97
|
-
{(send {nil? const_type?} _
|
98
|
-
(send
|
97
|
+
{(send {nil? const_type?} _ {str dstr} ...)
|
98
|
+
(send {str dstr} ...)}
|
99
99
|
PATTERN
|
100
100
|
|
101
101
|
def method_with_format_args?(node)
|
@@ -143,11 +143,11 @@ module RuboCop
|
|
143
143
|
return false if node.const_receiver? && !node.receiver.loc.name.is?(KERNEL)
|
144
144
|
return false unless node.method?(name)
|
145
145
|
|
146
|
-
node.arguments.size > 1 && node.first_argument
|
146
|
+
node.arguments.size > 1 && string_type?(node.first_argument)
|
147
147
|
end
|
148
148
|
|
149
149
|
def expected_fields_count(node)
|
150
|
-
return :unknown unless node
|
150
|
+
return :unknown unless string_type?(node)
|
151
151
|
|
152
152
|
format_string = RuboCop::Cop::Utils::FormatString.new(node.source)
|
153
153
|
return 1 if format_string.named_interpolation?
|
@@ -172,10 +172,9 @@ module RuboCop
|
|
172
172
|
def percent?(node)
|
173
173
|
receiver = node.receiver
|
174
174
|
|
175
|
-
percent = node.method?(:%) &&
|
176
|
-
(STRING_TYPES.include?(receiver.type) || node.first_argument.array_type?)
|
175
|
+
percent = node.method?(:%) && (string_type?(receiver) || node.first_argument.array_type?)
|
177
176
|
|
178
|
-
return false if percent &&
|
177
|
+
return false if percent && string_type?(receiver) && heredoc?(node)
|
179
178
|
|
180
179
|
percent
|
181
180
|
end
|
@@ -188,6 +187,10 @@ module RuboCop
|
|
188
187
|
format(MSG, arg_num: num_args_for_format, method: method_name,
|
189
188
|
field_num: num_expected_fields)
|
190
189
|
end
|
190
|
+
|
191
|
+
def string_type?(node)
|
192
|
+
STRING_TYPES.include?(node.type)
|
193
|
+
end
|
191
194
|
end
|
192
195
|
end
|
193
196
|
end
|
@@ -38,6 +38,10 @@ module RuboCop
|
|
38
38
|
MSG = 'Remove unnecessary `require` statement.'
|
39
39
|
RESTRICT_ON_SEND = %i[require].freeze
|
40
40
|
RUBY_22_LOADED_FEATURES = %w[rational complex].freeze
|
41
|
+
PRETTY_PRINT_METHODS = %i[
|
42
|
+
pretty_inspect pretty_print pretty_print_cycle
|
43
|
+
pretty_print_inspect pretty_print_instance_variables
|
44
|
+
].freeze
|
41
45
|
|
42
46
|
# @!method redundant_require_statement?(node)
|
43
47
|
def_node_matcher :redundant_require_statement?, <<~PATTERN
|
@@ -68,12 +72,18 @@ module RuboCop
|
|
68
72
|
feature_name == 'enumerator' ||
|
69
73
|
(target_ruby_version >= 2.1 && feature_name == 'thread') ||
|
70
74
|
(target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
|
71
|
-
(target_ruby_version >= 2.5 && feature_name == 'pp') ||
|
75
|
+
(target_ruby_version >= 2.5 && feature_name == 'pp' && !use_pretty_print_method?) ||
|
72
76
|
(target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
|
73
77
|
(target_ruby_version >= 3.1 && feature_name == 'fiber') ||
|
74
78
|
(target_ruby_version >= 3.2 && feature_name == 'set')
|
75
79
|
end
|
76
80
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
81
|
+
|
82
|
+
def use_pretty_print_method?
|
83
|
+
processed_source.ast.each_descendant(:send).any? do |node|
|
84
|
+
PRETTY_PRINT_METHODS.include?(node.method_name)
|
85
|
+
end
|
86
|
+
end
|
77
87
|
end
|
78
88
|
end
|
79
89
|
end
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
MSG = 'Useless method definition detected.'
|
42
42
|
|
43
43
|
def on_def(node)
|
44
|
-
return if
|
44
|
+
return if use_rest_or_optional_args?(node)
|
45
45
|
return unless delegating?(node.body, node)
|
46
46
|
|
47
47
|
add_offense(node) { |corrector| corrector.remove(node) }
|
@@ -50,8 +50,8 @@ module RuboCop
|
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
|
-
def
|
54
|
-
node.arguments.any? { |arg| arg.optarg_type? || arg.kwoptarg_type? }
|
53
|
+
def use_rest_or_optional_args?(node)
|
54
|
+
node.arguments.any? { |arg| arg.restarg_type? || arg.optarg_type? || arg.kwoptarg_type? }
|
55
55
|
end
|
56
56
|
|
57
57
|
def delegating?(node, def_node)
|
@@ -57,13 +57,27 @@ module RuboCop
|
|
57
57
|
private
|
58
58
|
|
59
59
|
def only_reraising?(resbody_node)
|
60
|
+
return false if use_exception_variable_in_ensure?(resbody_node)
|
61
|
+
|
60
62
|
body = resbody_node.body
|
61
63
|
return false if body.nil? || !body.send_type? || !body.method?(:raise)
|
62
64
|
return true unless body.arguments?
|
63
65
|
return false if body.arguments.size > 1
|
64
66
|
|
65
67
|
exception_name = body.first_argument.source
|
66
|
-
|
68
|
+
|
69
|
+
exception_objects(resbody_node).include?(exception_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def use_exception_variable_in_ensure?(resbody_node)
|
73
|
+
return false unless (exception_variable = resbody_node.exception_variable)
|
74
|
+
return false unless (ensure_node = resbody_node.each_ancestor(:ensure).first)
|
75
|
+
|
76
|
+
ensure_node.body.each_descendant(:lvar).map(&:source).include?(exception_variable.source)
|
77
|
+
end
|
78
|
+
|
79
|
+
def exception_objects(resbody_node)
|
80
|
+
[resbody_node.exception_variable&.source, '$!', '$ERROR_INFO']
|
67
81
|
end
|
68
82
|
end
|
69
83
|
end
|
@@ -98,7 +98,7 @@ module RuboCop
|
|
98
98
|
return unless node.parent
|
99
99
|
|
100
100
|
method_name = sym_node.value
|
101
|
-
definition = node
|
101
|
+
definition = find_method_definition(node, method_name)
|
102
102
|
|
103
103
|
return unless definition
|
104
104
|
return if allowed_arguments(definition.arguments)
|
@@ -106,6 +106,14 @@ module RuboCop
|
|
106
106
|
add_offense(node, message: format(MSG, method_name: method_name))
|
107
107
|
end
|
108
108
|
|
109
|
+
def find_method_definition(node, method_name)
|
110
|
+
node.each_ancestor.lazy.map do |ancestor|
|
111
|
+
ancestor.each_child_node(:def, :block, :numblock).find do |child|
|
112
|
+
method_definition(child, method_name)
|
113
|
+
end
|
114
|
+
end.find(&:itself)
|
115
|
+
end
|
116
|
+
|
109
117
|
# `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
|
110
118
|
def allowed_arguments(arguments)
|
111
119
|
return false if arguments.empty?
|
@@ -46,19 +46,23 @@ module RuboCop
|
|
46
46
|
LIT_MSG = 'Literal `%<lit>s` used in void context.'
|
47
47
|
SELF_MSG = '`self` used in void context.'
|
48
48
|
EXPRESSION_MSG = '`%<expression>s` used in void context.'
|
49
|
-
NONMUTATING_MSG = 'Method `#%<method>s` used in void context. Did you mean `#%<
|
49
|
+
NONMUTATING_MSG = 'Method `#%<method>s` used in void context. Did you mean `#%<suggest>s`?'
|
50
50
|
|
51
51
|
BINARY_OPERATORS = %i[* / % + - == === != < > <= >= <=>].freeze
|
52
52
|
UNARY_OPERATORS = %i[+@ -@ ~ !].freeze
|
53
53
|
OPERATORS = (BINARY_OPERATORS + UNARY_OPERATORS).freeze
|
54
54
|
VOID_CONTEXT_TYPES = %i[def for block].freeze
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
NONMUTATING_METHODS_WITH_BANG_VERSION = %i[capitalize chomp chop compact
|
56
|
+
delete_prefix delete_suffix downcase
|
57
|
+
encode flatten gsub lstrip merge next
|
58
|
+
reject reverse rotate rstrip scrub select
|
59
|
+
shuffle slice sort sort_by squeeze strip sub
|
60
|
+
succ swapcase tr tr_s transform_values
|
61
|
+
unicode_normalize uniq upcase].freeze
|
62
|
+
METHODS_REPLACABLE_BY_EACH = %i[collect map].freeze
|
63
|
+
|
64
|
+
NONMUTATING_METHODS = (NONMUTATING_METHODS_WITH_BANG_VERSION +
|
65
|
+
METHODS_REPLACABLE_BY_EACH).freeze
|
62
66
|
|
63
67
|
def on_block(node)
|
64
68
|
return unless node.body && !node.body.begin_type?
|
@@ -124,9 +128,12 @@ module RuboCop
|
|
124
128
|
end
|
125
129
|
|
126
130
|
def check_nonmutating(node)
|
127
|
-
|
131
|
+
method_name = node.method_name
|
132
|
+
return unless NONMUTATING_METHODS.include?(method_name)
|
128
133
|
|
129
|
-
|
134
|
+
suggestion = METHODS_REPLACABLE_BY_EACH.include?(method_name) ? 'each' : "#{method_name}!"
|
135
|
+
add_offense(node,
|
136
|
+
message: format(NONMUTATING_MSG, method: method_name, suggest: suggestion))
|
130
137
|
end
|
131
138
|
|
132
139
|
def in_void_context?(node)
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# The maximum level of nesting allowed is configurable.
|
14
14
|
class BlockNesting < Base
|
15
|
-
NESTING_BLOCKS = %i[case if while while_post until until_post for resbody].freeze
|
15
|
+
NESTING_BLOCKS = %i[case case_match if while while_post until until_post for resbody].freeze
|
16
16
|
|
17
17
|
exclude_limit 'Max'
|
18
18
|
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
|
36
36
|
MSG = 'Cyclomatic complexity for %<method>s is too high. [%<complexity>d/%<max>d]'
|
37
37
|
COUNTED_NODES = %i[if while until for csend block block_pass
|
38
|
-
rescue when and or or_asgn and_asgn].freeze
|
38
|
+
rescue when in_pattern and or or_asgn and_asgn].freeze
|
39
39
|
|
40
40
|
private
|
41
41
|
|
@@ -25,10 +25,7 @@ module RuboCop
|
|
25
25
|
# > http://c2.com/cgi/wiki?AbcMetric
|
26
26
|
CONDITION_NODES = CyclomaticComplexity::COUNTED_NODES.freeze
|
27
27
|
|
28
|
-
|
29
|
-
ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze
|
30
|
-
|
31
|
-
private_constant :BRANCH_NODES, :CONDITION_NODES, :ARGUMENT_TYPES
|
28
|
+
private_constant :BRANCH_NODES, :CONDITION_NODES
|
32
29
|
|
33
30
|
def self.calculate(node, discount_repeated_attributes: false)
|
34
31
|
new(node, discount_repeated_attributes: discount_repeated_attributes).calculate
|
@@ -129,7 +126,7 @@ module RuboCop
|
|
129
126
|
end
|
130
127
|
|
131
128
|
def argument?(node)
|
132
|
-
|
129
|
+
node.argument_type? && capturing_variable?(node.children.first)
|
133
130
|
end
|
134
131
|
|
135
132
|
def condition?(node)
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
attr_reader :column_delta
|
13
13
|
|
14
14
|
def configured_indentation_width
|
15
|
-
cop_config['IndentationWidth'] || config.for_cop('Layout/IndentationWidth')['Width']
|
15
|
+
cop_config['IndentationWidth'] || config.for_cop('Layout/IndentationWidth')['Width'] || 2
|
16
16
|
end
|
17
17
|
|
18
18
|
def indentation(node)
|
@@ -46,21 +46,22 @@ module RuboCop
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
# rubocop:disable Metrics/AbcSize
|
50
|
-
def register_offense(node, message, replacement)
|
49
|
+
def register_offense(node, message, replacement) # rubocop:disable Metrics/AbcSize
|
51
50
|
add_offense(node.value, message: message) do |corrector|
|
52
51
|
if (def_node = def_node_that_require_parentheses(node))
|
53
|
-
|
52
|
+
last_argument = def_node.last_argument
|
53
|
+
if last_argument.nil? || !last_argument.hash_type?
|
54
|
+
next corrector.replace(node, replacement)
|
55
|
+
end
|
56
|
+
|
57
|
+
white_spaces = range_between(def_node.selector.end_pos,
|
54
58
|
def_node.first_argument.source_range.begin_pos)
|
55
59
|
corrector.replace(white_spaces, '(')
|
56
|
-
|
57
|
-
last_argument = def_node.arguments.last
|
58
60
|
corrector.insert_after(last_argument, ')') if node == last_argument.pairs.last
|
59
61
|
end
|
60
62
|
corrector.replace(node, replacement)
|
61
63
|
end
|
62
64
|
end
|
63
|
-
# rubocop:enable Metrics/AbcSize
|
64
65
|
|
65
66
|
def ignore_mixed_hash_shorthand_syntax?(hash_node)
|
66
67
|
target_ruby_version <= 3.0 || enforced_shorthand_syntax != 'consistent' ||
|
@@ -87,31 +88,34 @@ module RuboCop
|
|
87
88
|
end
|
88
89
|
|
89
90
|
def require_hash_value_for_around_hash_literal?(node)
|
90
|
-
return false unless (
|
91
|
+
return false unless (method_dispatch_node = find_ancestor_method_dispatch_node(node))
|
91
92
|
|
92
|
-
!node.parent.braces? &&
|
93
|
-
|
93
|
+
!node.parent.braces? &&
|
94
|
+
!use_element_of_hash_literal_as_receiver?(method_dispatch_node, node.parent) &&
|
95
|
+
use_modifier_form_without_parenthesized_method_call?(method_dispatch_node)
|
94
96
|
end
|
95
97
|
|
96
98
|
def def_node_that_require_parentheses(node)
|
97
99
|
last_pair = node.parent.pairs.last
|
98
100
|
return unless last_pair.key.source == last_pair.value.source
|
99
|
-
return unless (
|
100
|
-
return unless without_parentheses_call_expr_follows?(
|
101
|
+
return unless (method_dispatch_node = find_ancestor_method_dispatch_node(node))
|
102
|
+
return unless without_parentheses_call_expr_follows?(method_dispatch_node)
|
101
103
|
|
102
|
-
def_node = node.each_ancestor(:send, :csend).first
|
104
|
+
def_node = node.each_ancestor(:send, :csend, :super, :yield).first
|
103
105
|
|
104
|
-
def_node unless def_node && def_node.arguments.empty?
|
106
|
+
DefNode.new(def_node) unless def_node && def_node.arguments.empty?
|
105
107
|
end
|
106
108
|
|
107
|
-
def
|
108
|
-
ancestor = node.parent.parent
|
109
|
+
def find_ancestor_method_dispatch_node(node)
|
110
|
+
return unless (ancestor = node.parent.parent)
|
111
|
+
return unless ancestor.call_type? || ancestor.super_type? || ancestor.yield_type?
|
112
|
+
return if brackets?(ancestor)
|
109
113
|
|
110
|
-
ancestor
|
114
|
+
ancestor
|
111
115
|
end
|
112
116
|
|
113
|
-
def brackets?(
|
114
|
-
|
117
|
+
def brackets?(method_dispatch_node)
|
118
|
+
method_dispatch_node.method?(:[]) || method_dispatch_node.method?(:[]=)
|
115
119
|
end
|
116
120
|
|
117
121
|
def use_element_of_hash_literal_as_receiver?(ancestor, parent)
|
@@ -189,6 +193,24 @@ module RuboCop
|
|
189
193
|
register_offense(pair_node, OMIT_HASH_VALUE_MSG, replacement)
|
190
194
|
end
|
191
195
|
end
|
196
|
+
|
197
|
+
DefNode = Struct.new(:node) do
|
198
|
+
def selector
|
199
|
+
if node.loc.respond_to?(:selector)
|
200
|
+
node.loc.selector
|
201
|
+
else
|
202
|
+
node.loc.keyword
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def first_argument
|
207
|
+
node.first_argument
|
208
|
+
end
|
209
|
+
|
210
|
+
def last_argument
|
211
|
+
node.last_argument
|
212
|
+
end
|
213
|
+
end
|
192
214
|
end
|
193
215
|
end
|
194
216
|
# rubocop:enable Metrics/ModuleLength
|
@@ -4,6 +4,8 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
# Help methods for determining if a line is too long.
|
6
6
|
module LineLengthHelp
|
7
|
+
include Alignment
|
8
|
+
|
7
9
|
private
|
8
10
|
|
9
11
|
def ignore_cop_directives?
|
@@ -85,7 +87,7 @@ module RuboCop
|
|
85
87
|
|
86
88
|
def tab_indentation_width
|
87
89
|
config.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
|
88
|
-
|
90
|
+
configured_indentation_width
|
89
91
|
end
|
90
92
|
|
91
93
|
def uri_regexp
|
data/lib/rubocop/cop/registry.rb
CHANGED
@@ -41,6 +41,11 @@ module RuboCop
|
|
41
41
|
@global = new
|
42
42
|
end
|
43
43
|
|
44
|
+
def self.qualified_cop?(name)
|
45
|
+
badge = Badge.parse(name)
|
46
|
+
global.qualify_badge(badge).first == badge
|
47
|
+
end
|
48
|
+
|
44
49
|
attr_reader :options
|
45
50
|
|
46
51
|
def initialize(cops = [], options = {})
|
@@ -158,6 +163,13 @@ module RuboCop
|
|
158
163
|
'RedundantCopDisableDirective'
|
159
164
|
end
|
160
165
|
|
166
|
+
def qualify_badge(badge)
|
167
|
+
clear_enrollment_queue
|
168
|
+
@departments
|
169
|
+
.map { |department, _| badge.with_department(department) }
|
170
|
+
.select { |potential_badge| registered?(potential_badge) }
|
171
|
+
end
|
172
|
+
|
161
173
|
# @return [Hash{String => Array<Class>}]
|
162
174
|
def to_h
|
163
175
|
clear_enrollment_queue
|
@@ -282,13 +294,6 @@ module RuboCop
|
|
282
294
|
self.class.new(cops)
|
283
295
|
end
|
284
296
|
|
285
|
-
def qualify_badge(badge)
|
286
|
-
clear_enrollment_queue
|
287
|
-
@departments
|
288
|
-
.map { |department, _| badge.with_department(department) }
|
289
|
-
.select { |potential_badge| registered?(potential_badge) }
|
290
|
-
end
|
291
|
-
|
292
297
|
def resolve_badge(given_badge, real_badge, source_path)
|
293
298
|
unless given_badge.match?(real_badge)
|
294
299
|
path = PathUtil.smart_path(source_path)
|
@@ -115,9 +115,7 @@ module RuboCop
|
|
115
115
|
def_node = find_corresponding_def_node(node)
|
116
116
|
return unless def_node
|
117
117
|
|
118
|
-
|
119
|
-
remove_node(corrector, node)
|
120
|
-
insert_def(corrector, node, def_node.source)
|
118
|
+
replace_def(corrector, node, def_node)
|
121
119
|
when :inline
|
122
120
|
remove_node(corrector, node)
|
123
121
|
select_grouped_def_nodes(node).each do |grouped_def_node|
|
@@ -173,7 +171,9 @@ module RuboCop
|
|
173
171
|
end
|
174
172
|
|
175
173
|
def find_argument_less_modifier_node(node)
|
176
|
-
node.parent
|
174
|
+
return unless (parent = node.parent)
|
175
|
+
|
176
|
+
parent.each_child_node(:send).find do |child|
|
177
177
|
child.method?(node.method_name) && child.arguments.empty?
|
178
178
|
end
|
179
179
|
end
|
@@ -184,17 +184,21 @@ module RuboCop
|
|
184
184
|
end.select(&:def_type?)
|
185
185
|
end
|
186
186
|
|
187
|
-
def
|
188
|
-
source =
|
187
|
+
def replace_def(corrector, node, def_node)
|
188
|
+
source = def_source(node, def_node)
|
189
189
|
argument_less_modifier_node = find_argument_less_modifier_node(node)
|
190
190
|
if argument_less_modifier_node
|
191
191
|
corrector.insert_after(argument_less_modifier_node, "\n\n#{source}")
|
192
|
+
elsif (ancestor = node.each_ancestor(:block, :class, :module).first)
|
193
|
+
|
194
|
+
corrector.insert_before(ancestor.loc.end, "#{node.method_name}\n\n#{source}\n")
|
192
195
|
else
|
193
|
-
corrector.
|
194
|
-
|
195
|
-
"#{node.method_name}\n\n#{source}\n"
|
196
|
-
)
|
196
|
+
corrector.replace(node, "#{node.method_name}\n\n#{source}")
|
197
|
+
return
|
197
198
|
end
|
199
|
+
|
200
|
+
remove_node(corrector, def_node)
|
201
|
+
remove_node(corrector, node)
|
198
202
|
end
|
199
203
|
|
200
204
|
def insert_inline_modifier(corrector, node, modifier_name)
|
@@ -204,6 +208,10 @@ module RuboCop
|
|
204
208
|
def remove_node(corrector, node)
|
205
209
|
corrector.remove(range_with_comments_and_lines(node))
|
206
210
|
end
|
211
|
+
|
212
|
+
def def_source(node, def_node)
|
213
|
+
[*processed_source.ast_with_comments[node].map(&:text), def_node.source].join("\n")
|
214
|
+
end
|
207
215
|
end
|
208
216
|
end
|
209
217
|
end
|
@@ -299,8 +299,8 @@ module RuboCop
|
|
299
299
|
|
300
300
|
def move_comment_before_block(corrector, comment, block_node, closing_brace)
|
301
301
|
range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
|
302
|
-
|
303
|
-
corrector
|
302
|
+
corrector.remove(range_with_surrounding_space(comment.loc.expression, side: :right))
|
303
|
+
remove_trailing_whitespace(corrector, range, comment)
|
304
304
|
corrector.insert_after(range, "\n")
|
305
305
|
|
306
306
|
corrector.insert_before(block_node, "#{comment.text}\n")
|
@@ -313,6 +313,12 @@ module RuboCop
|
|
313
313
|
end_of_chain(node.parent)
|
314
314
|
end
|
315
315
|
|
316
|
+
def remove_trailing_whitespace(corrector, range, comment)
|
317
|
+
range_of_trailing = range.end.join(comment.loc.expression.begin)
|
318
|
+
|
319
|
+
corrector.remove(range_of_trailing) if range_of_trailing.source.match?(/\A\s+\z/)
|
320
|
+
end
|
321
|
+
|
316
322
|
def with_block?(node)
|
317
323
|
node.respond_to?(:block_node) && node.block_node
|
318
324
|
end
|
@@ -31,6 +31,7 @@ module RuboCop
|
|
31
31
|
#
|
32
32
|
# The compact style is only forced for classes/modules with one child.
|
33
33
|
class ClassAndModuleChildren < Base
|
34
|
+
include Alignment
|
34
35
|
include ConfigurableEnforcedStyle
|
35
36
|
include RangeHelp
|
36
37
|
extend AutoCorrector
|
@@ -59,7 +60,7 @@ module RuboCop
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def nest_definition(corrector, node)
|
62
|
-
padding = (
|
63
|
+
padding = indentation(node) + leading_spaces(node)
|
63
64
|
padding_for_trailing_end = padding.sub(' ' * node.loc.end.column, '')
|
64
65
|
|
65
66
|
replace_namespace_keyword(corrector, node)
|
@@ -124,10 +125,6 @@ module RuboCop
|
|
124
125
|
corrector.remove(range)
|
125
126
|
end
|
126
127
|
|
127
|
-
def configured_indentation_width
|
128
|
-
config.for_badge(Layout::IndentationWidth.badge).fetch('Width', 2)
|
129
|
-
end
|
130
|
-
|
131
128
|
def unindent(corrector, node)
|
132
129
|
return if node.body.children.last.nil?
|
133
130
|
|
@@ -141,10 +138,6 @@ module RuboCop
|
|
141
138
|
node.source_range.source_line[/\A\s*/]
|
142
139
|
end
|
143
140
|
|
144
|
-
def indent_width
|
145
|
-
@config.for_cop('Layout/IndentationWidth')['Width'] || 2
|
146
|
-
end
|
147
|
-
|
148
141
|
def check_style(node, body)
|
149
142
|
return if node.identifier.children[0]&.cbase_type?
|
150
143
|
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Enforces the use of `Comparable#clamp` instead of comparison by minimum and maximum.
|
7
|
+
#
|
8
|
+
# This cop supports autocorrection for `if/elsif/else` bad style only.
|
9
|
+
# Because `ArgumentError` occurs if the minimum and maximum of `clamp` arguments are reversed.
|
10
|
+
# When these are variables, it is not possible to determine which is the minimum and maximum:
|
11
|
+
#
|
12
|
+
# [source,ruby]
|
13
|
+
# ----
|
14
|
+
# [1, [2, 3].max].min # => 1
|
15
|
+
# 1.clamp(3, 1) # => min argument must be smaller than max argument (ArgumentError)
|
16
|
+
# ----
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
# # bad
|
21
|
+
# [[x, low].max, high].min
|
22
|
+
#
|
23
|
+
# # bad
|
24
|
+
# if x < low
|
25
|
+
# low
|
26
|
+
# elsif high < x
|
27
|
+
# high
|
28
|
+
# else
|
29
|
+
# x
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# x.clamp(low, high)
|
34
|
+
#
|
35
|
+
class ComparableClamp < Base
|
36
|
+
include Alignment
|
37
|
+
extend AutoCorrector
|
38
|
+
extend TargetRubyVersion
|
39
|
+
|
40
|
+
minimum_target_ruby_version 2.4
|
41
|
+
|
42
|
+
MSG = 'Use `%<prefer>s` instead of `if/elsif/else`.'
|
43
|
+
MSG_MIN_MAX = 'Use `Comparable#clamp` instead.'
|
44
|
+
RESTRICT_ON_SEND = %i[min max].freeze
|
45
|
+
|
46
|
+
# @!method if_elsif_else_condition?(node)
|
47
|
+
def_node_matcher :if_elsif_else_condition?, <<~PATTERN
|
48
|
+
{
|
49
|
+
(if (send _x :< _min) _min (if (send _max :< _x) _max _x))
|
50
|
+
(if (send _min :> _x) _min (if (send _max :< _x) _max _x))
|
51
|
+
(if (send _x :< _min) _min (if (send _x :> _max) _max _x))
|
52
|
+
(if (send _min :> _x) _min (if (send _x :> _max) _max _x))
|
53
|
+
(if (send _max :< _x) _max (if (send _x :< _min) _min _x))
|
54
|
+
(if (send _x :> _max) _max (if (send _x :< _min) _min _x))
|
55
|
+
(if (send _max :< _x) _max (if (send _min :> _x) _min _x))
|
56
|
+
(if (send _x :> _max) _max (if (send _min :> _x) _min _x))
|
57
|
+
}
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
# @!method array_min_max?(node)
|
61
|
+
def_node_matcher :array_min_max?, <<~PATTERN
|
62
|
+
{
|
63
|
+
(send
|
64
|
+
(array
|
65
|
+
(send (array _ _) :max) _) :min)
|
66
|
+
(send
|
67
|
+
(array
|
68
|
+
_ (send (array _ _) :max)) :min)
|
69
|
+
(send
|
70
|
+
(array
|
71
|
+
(send (array _ _) :min) _) :max)
|
72
|
+
(send
|
73
|
+
(array
|
74
|
+
_ (send (array _ _) :min)) :max)
|
75
|
+
}
|
76
|
+
PATTERN
|
77
|
+
|
78
|
+
def on_if(node)
|
79
|
+
return unless if_elsif_else_condition?(node)
|
80
|
+
|
81
|
+
if_body, elsif_body, else_body = *node.branches
|
82
|
+
|
83
|
+
else_body_source = else_body.source
|
84
|
+
|
85
|
+
if min_condition?(node.condition, else_body_source)
|
86
|
+
min = if_body.source
|
87
|
+
max = elsif_body.source
|
88
|
+
else
|
89
|
+
min = elsif_body.source
|
90
|
+
max = if_body.source
|
91
|
+
end
|
92
|
+
|
93
|
+
prefer = "#{else_body_source}.clamp(#{min}, #{max})"
|
94
|
+
|
95
|
+
add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
|
96
|
+
autocorrect(corrector, node, prefer)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_send(node)
|
101
|
+
return unless array_min_max?(node)
|
102
|
+
|
103
|
+
add_offense(node, message: MSG_MIN_MAX)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def autocorrect(corrector, node, prefer)
|
109
|
+
if node.elsif?
|
110
|
+
corrector.insert_before(node, "else\n")
|
111
|
+
corrector.replace(node, "#{indentation(node)}#{prefer}")
|
112
|
+
else
|
113
|
+
corrector.replace(node, prefer)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def min_condition?(if_condition, else_body)
|
118
|
+
lhs, op, rhs = *if_condition
|
119
|
+
|
120
|
+
(lhs.source == else_body && op == :<) || (rhs.source == else_body && op == :>)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|