rubocop 1.82.0 → 1.84.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/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/config/default.yml +44 -0
- data/lib/rubocop/cli/command/lsp.rb +1 -1
- data/lib/rubocop/cli.rb +2 -1
- data/lib/rubocop/comment_config.rb +1 -0
- data/lib/rubocop/cop/autocorrect_logic.rb +2 -0
- data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +20 -2
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
- data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
- data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
- data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
- data/lib/rubocop/cop/layout/class_structure.rb +12 -5
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +32 -1
- data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
- data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +35 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +111 -7
- data/lib/rubocop/cop/layout/line_length.rb +6 -2
- data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -1
- data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
- data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
- data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +8 -8
- data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
- data/lib/rubocop/cop/lint/duplicate_methods.rb +57 -5
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
- data/lib/rubocop/cop/lint/literal_as_condition.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -1
- data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
- data/lib/rubocop/cop/lint/to_json.rb +12 -16
- data/lib/rubocop/cop/lint/useless_assignment.rb +44 -16
- data/lib/rubocop/cop/lint/useless_or.rb +1 -1
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +1 -1
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -0
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +4 -4
- data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
- data/lib/rubocop/cop/mixin/trailing_comma.rb +5 -1
- data/lib/rubocop/cop/naming/predicate_prefix.rb +11 -11
- data/lib/rubocop/cop/offense.rb +2 -1
- data/lib/rubocop/cop/security/json_load.rb +1 -1
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -2
- data/lib/rubocop/cop/style/documentation.rb +6 -6
- data/lib/rubocop/cop/style/documentation_method.rb +8 -8
- data/lib/rubocop/cop/style/empty_class_definition.rb +144 -0
- data/lib/rubocop/cop/style/guard_clause.rb +7 -4
- data/lib/rubocop/cop/style/hash_lookup_method.rb +94 -0
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
- data/lib/rubocop/cop/style/lambda_call.rb +8 -8
- data/lib/rubocop/cop/style/module_member_existence_check.rb +56 -13
- data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -0
- data/lib/rubocop/cop/style/negative_array_index.rb +218 -0
- data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
- data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
- data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
- data/lib/rubocop/cop/style/reverse_find.rb +51 -0
- data/lib/rubocop/cop/team.rb +3 -3
- data/lib/rubocop/cop/variable_force/branch.rb +28 -4
- data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
- data/lib/rubocop/formatter/formatter_set.rb +1 -1
- data/lib/rubocop/formatter/tap_formatter.rb +5 -2
- data/lib/rubocop/remote_config.rb +5 -2
- data/lib/rubocop/result_cache.rb +38 -27
- data/lib/rubocop/rspec/shared_contexts.rb +4 -0
- data/lib/rubocop/rspec/support.rb +1 -0
- data/lib/rubocop/runner.rb +4 -0
- data/lib/rubocop/target_ruby.rb +3 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +4 -0
- metadata +11 -7
|
@@ -77,7 +77,7 @@ module RuboCop
|
|
|
77
77
|
|
|
78
78
|
def on_case(node)
|
|
79
79
|
node.when_branches.each do |when_branch|
|
|
80
|
-
when_branch.
|
|
80
|
+
when_branch.conditions.each do |condition|
|
|
81
81
|
next if !float?(condition) || literal_safe?(condition)
|
|
82
82
|
|
|
83
83
|
add_offense(condition, message: MSG_CASE)
|
|
@@ -157,7 +157,7 @@ module RuboCop
|
|
|
157
157
|
|
|
158
158
|
check_case(case_match_node)
|
|
159
159
|
else
|
|
160
|
-
case_match_node.
|
|
160
|
+
case_match_node.in_pattern_branches.each do |in_pattern_node|
|
|
161
161
|
next unless in_pattern_node.condition.literal?
|
|
162
162
|
|
|
163
163
|
add_offense(in_pattern_node)
|
|
@@ -77,7 +77,7 @@ module RuboCop
|
|
|
77
77
|
PERCENT_CAPITAL_W = '%W'
|
|
78
78
|
PERCENT_I = '%i'
|
|
79
79
|
PERCENT_CAPITAL_I = '%I'
|
|
80
|
-
ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
|
|
80
|
+
ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn casgn].freeze
|
|
81
81
|
|
|
82
82
|
# @!method array_new?(node)
|
|
83
83
|
def_node_matcher :array_new?, <<~PATTERN
|
|
@@ -26,7 +26,23 @@ module RuboCop
|
|
|
26
26
|
'and it may be unexpected.'
|
|
27
27
|
RESTRICT_ON_SEND = %i[new].freeze
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# This is based on `Struct.instance_methods.sort` in Ruby 4.0.0.
|
|
30
|
+
STRUCT_METHOD_NAMES = %i[
|
|
31
|
+
! != !~ <=> == === [] []= __id__ __send__ all? any? chain chunk chunk_while class clone
|
|
32
|
+
collect collect_concat compact count cycle deconstruct deconstruct_keys
|
|
33
|
+
define_singleton_method detect dig display drop drop_while dup each each_cons each_entry
|
|
34
|
+
each_pair each_slice each_with_index each_with_object entries enum_for eql? equal? extend
|
|
35
|
+
filter filter_map find find_all find_index first flat_map freeze frozen? grep grep_v
|
|
36
|
+
group_by hash include? inject inspect instance_eval instance_exec instance_of?
|
|
37
|
+
instance_variable_defined? instance_variable_get instance_variable_set instance_variables
|
|
38
|
+
is_a? itself kind_of? lazy length map max max_by member? members method methods
|
|
39
|
+
min min_by minmax minmax_by nil? none? object_id one? partition private_methods
|
|
40
|
+
protected_methods public_method public_methods public_send reduce reject
|
|
41
|
+
remove_instance_variable respond_to? reverse_each select send singleton_class
|
|
42
|
+
singleton_method singleton_methods size slice_after slice_before slice_when sort sort_by
|
|
43
|
+
sum take take_while tally tap then to_a to_enum to_h to_s to_set uniq values values_at
|
|
44
|
+
yield_self zip
|
|
45
|
+
].freeze
|
|
30
46
|
STRUCT_MEMBER_NAME_TYPES = %i[sym str].freeze
|
|
31
47
|
|
|
32
48
|
# @!method struct_new(node)
|
|
@@ -5,27 +5,23 @@ module RuboCop
|
|
|
5
5
|
module Lint
|
|
6
6
|
# Checks to make sure `#to_json` includes an optional argument.
|
|
7
7
|
# When overriding `#to_json`, callers may invoke JSON
|
|
8
|
-
# generation via `JSON.generate(your_obj)`.
|
|
8
|
+
# generation via `JSON.generate(your_obj)`. Since `JSON#generate` allows
|
|
9
9
|
# for an optional argument, your method should too.
|
|
10
10
|
#
|
|
11
11
|
# @example
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# def to_json
|
|
17
|
-
# JSON.generate([x, y])
|
|
18
|
-
# end
|
|
12
|
+
# # bad - incorrect arity
|
|
13
|
+
# def to_json
|
|
14
|
+
# JSON.generate([x, y])
|
|
15
|
+
# end
|
|
19
16
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
17
|
+
# # good - preserving args
|
|
18
|
+
# def to_json(*args)
|
|
19
|
+
# JSON.generate([x, y], *args)
|
|
20
|
+
# end
|
|
24
21
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# end
|
|
22
|
+
# # good - discarding args
|
|
23
|
+
# def to_json(*_args)
|
|
24
|
+
# JSON.generate([x, y])
|
|
29
25
|
# end
|
|
30
26
|
#
|
|
31
27
|
class ToJSON < Base
|
|
@@ -52,32 +52,39 @@ module RuboCop
|
|
|
52
52
|
scope.variables.each_value { |variable| check_for_unused_assignments(variable) }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
56
55
|
def check_for_unused_assignments(variable)
|
|
57
56
|
return if variable.should_be_unused?
|
|
58
57
|
|
|
59
58
|
variable.assignments.reverse_each do |assignment|
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
check_for_unused_assignment(variable, assignment)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
def check_for_unused_assignment(variable, assignment)
|
|
64
|
+
assignment_node = assignment.node
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
# In cases like `x = 1, y = 2`, where removing a variable would cause a syntax error,
|
|
68
|
-
# and where changing `x ||= 1` to `x = 1` would cause `NameError`,
|
|
69
|
-
# the autocorrect will be skipped, even if the variable is unused.
|
|
70
|
-
if sequential_assignment?(assignment_node) || assignment_node.parent&.or_asgn_type?
|
|
71
|
-
next
|
|
72
|
-
end
|
|
66
|
+
return if ignored_assignment?(variable, assignment_node, assignment)
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
message = message_for_useless_assignment(assignment)
|
|
69
|
+
range = offense_range(assignment)
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
add_offense(range, message: message) do |corrector|
|
|
72
|
+
# In cases like `x = 1, y = 2`, where removing a variable would cause a syntax error,
|
|
73
|
+
# and where changing `x ||= 1` to `x = 1` would cause `NameError`,
|
|
74
|
+
# the autocorrect will be skipped, even if the variable is unused.
|
|
75
|
+
next if sequential_assignment?(assignment_node) ||
|
|
76
|
+
assignment_node.parent&.or_asgn_type?
|
|
77
|
+
|
|
78
|
+
autocorrect(corrector, assignment)
|
|
78
79
|
end
|
|
80
|
+
|
|
81
|
+
ignore_node(assignment_node) if chained_assignment?(assignment_node)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def ignored_assignment?(variable, assignment_node, assignment)
|
|
85
|
+
assignment.used? || part_of_ignored_node?(assignment_node) ||
|
|
86
|
+
variable_in_loop_condition?(assignment_node, variable)
|
|
79
87
|
end
|
|
80
|
-
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
81
88
|
|
|
82
89
|
def message_for_useless_assignment(assignment)
|
|
83
90
|
variable = assignment.variable
|
|
@@ -208,6 +215,27 @@ module RuboCop
|
|
|
208
215
|
def remove_local_variable_assignment_part(corrector, node)
|
|
209
216
|
corrector.replace(node, node.expression.source)
|
|
210
217
|
end
|
|
218
|
+
|
|
219
|
+
def variable_in_loop_condition?(assignment_node, variable)
|
|
220
|
+
return false if assignment_node.each_ancestor(:any_def).any?
|
|
221
|
+
|
|
222
|
+
loop_node = assignment_node.each_ancestor.find do |ancestor|
|
|
223
|
+
ancestor.type?(*VariableForce::LOOP_TYPES)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
return false unless loop_node.respond_to?(:condition)
|
|
227
|
+
|
|
228
|
+
condition_node = loop_node.condition
|
|
229
|
+
variable_name = variable.name
|
|
230
|
+
|
|
231
|
+
return true if condition_node.lvar_type? && condition_node.children.first == variable_name
|
|
232
|
+
|
|
233
|
+
condition_node.each_descendant(:lvar) do |lvar_node|
|
|
234
|
+
return true if lvar_node.children.first == variable_name
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
false
|
|
238
|
+
end
|
|
211
239
|
end
|
|
212
240
|
end
|
|
213
241
|
end
|
|
@@ -11,7 +11,7 @@ module RuboCop
|
|
|
11
11
|
#
|
|
12
12
|
# @safety
|
|
13
13
|
# As shown in the examples below, there are generally two possible ways to correct the
|
|
14
|
-
# offense, but this cop
|
|
14
|
+
# offense, but this cop's autocorrection always chooses the option that preserves the
|
|
15
15
|
# current behavior. While this does not change how the code behaves, that option is not
|
|
16
16
|
# necessarily the appropriate fix in every situation. For this reason, the autocorrection
|
|
17
17
|
# provided by this cop is considered unsafe.
|
|
@@ -125,7 +125,7 @@ module RuboCop
|
|
|
125
125
|
return if dispatch_node.assignment_method?
|
|
126
126
|
return if dispatch_node.parenthesized?
|
|
127
127
|
return if dispatch_node.parent && parentheses?(dispatch_node.parent)
|
|
128
|
-
return if last_expression?(dispatch_node) && !
|
|
128
|
+
return if last_expression?(dispatch_node) && !requires_parentheses_context?(dispatch_node)
|
|
129
129
|
|
|
130
130
|
def_node = node.each_ancestor(:call, :super, :yield).first
|
|
131
131
|
|
|
@@ -164,11 +164,11 @@ module RuboCop
|
|
|
164
164
|
!assignment_node.right_sibling
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
-
def
|
|
168
|
-
parent =
|
|
167
|
+
def requires_parentheses_context?(node)
|
|
168
|
+
parent = node.parent
|
|
169
169
|
return false unless parent
|
|
170
170
|
|
|
171
|
-
parent.type?(:call, :super, :yield)
|
|
171
|
+
parent.type?(:call, :if, :super, :until, :while, :yield)
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def breakdown_value_types_of_hash(hash_node)
|
|
@@ -8,8 +8,8 @@ module RuboCop
|
|
|
8
8
|
MSG = 'Space missing after %<token>s.'
|
|
9
9
|
|
|
10
10
|
def on_new_investigation
|
|
11
|
-
each_missing_space(processed_source.tokens) do |token|
|
|
12
|
-
add_offense(token.pos, message: format(MSG, token: kind
|
|
11
|
+
each_missing_space(processed_source.tokens) do |token, kind|
|
|
12
|
+
add_offense(token.pos, message: format(MSG, token: kind)) do |corrector|
|
|
13
13
|
PunctuationCorrector.add_space(corrector, token)
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -19,11 +19,12 @@ module RuboCop
|
|
|
19
19
|
|
|
20
20
|
def each_missing_space(tokens)
|
|
21
21
|
tokens.each_cons(2) do |token1, token2|
|
|
22
|
-
|
|
22
|
+
kind = kind(token1, token2)
|
|
23
|
+
next unless kind
|
|
23
24
|
next unless space_missing?(token1, token2)
|
|
24
25
|
next unless space_required_before?(token2)
|
|
25
26
|
|
|
26
|
-
yield token1
|
|
27
|
+
yield token1, kind
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -95,12 +95,16 @@ module RuboCop
|
|
|
95
95
|
node.multiline? && !allowed_multiline_argument?(node)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# rubocop:disable Metrics/AbcSize
|
|
98
99
|
def method_name_and_arguments_on_same_line?(node)
|
|
99
100
|
return false if !node.call_type? || node.last_line != node.last_argument.last_line
|
|
100
101
|
return true if node.last_argument.hash_type? && node.last_argument.braces?
|
|
101
102
|
|
|
102
|
-
node.loc.selector
|
|
103
|
+
line = node.loc.selector&.line || node.loc.line
|
|
104
|
+
|
|
105
|
+
line == node.last_argument.last_line
|
|
103
106
|
end
|
|
107
|
+
# rubocop:enable Metrics/AbcSize
|
|
104
108
|
|
|
105
109
|
# A single argument with the closing bracket on the same line as the end
|
|
106
110
|
# of the argument is not considered multiline, even if the argument
|
|
@@ -63,17 +63,17 @@ module RuboCop
|
|
|
63
63
|
# end
|
|
64
64
|
#
|
|
65
65
|
# @example UseSorbetSigs: false (default)
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
66
|
+
# # bad
|
|
67
|
+
# sig { returns(String) }
|
|
68
|
+
# def is_this_thing_on
|
|
69
|
+
# "yes"
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# # good - Sorbet signature is not evaluated
|
|
73
|
+
# sig { returns(String) }
|
|
74
|
+
# def is_this_thing_on?
|
|
75
|
+
# "yes"
|
|
76
|
+
# end
|
|
77
77
|
#
|
|
78
78
|
# @example UseSorbetSigs: true
|
|
79
79
|
# # bad
|
data/lib/rubocop/cop/offense.rb
CHANGED
|
@@ -139,7 +139,8 @@ module RuboCop
|
|
|
139
139
|
# @return [Parser::Source::Range]
|
|
140
140
|
# the range of the code that is highlighted
|
|
141
141
|
def highlighted_area
|
|
142
|
-
Parser::Source::
|
|
142
|
+
source_buffer = Parser::Source::Buffer.new(location.source_buffer.name, source: source_line)
|
|
143
|
+
Parser::Source::Range.new(source_buffer, column, column + column_length)
|
|
143
144
|
end
|
|
144
145
|
|
|
145
146
|
# @api private
|
|
@@ -326,8 +326,7 @@ module RuboCop
|
|
|
326
326
|
argument_less_modifier_node = find_argument_less_modifier_node(node)
|
|
327
327
|
if argument_less_modifier_node
|
|
328
328
|
corrector.insert_after(argument_less_modifier_node, "\n\n#{source}")
|
|
329
|
-
elsif (ancestor = node.each_ancestor(:class, :module).first)
|
|
330
|
-
|
|
329
|
+
elsif (ancestor = node.each_ancestor(:class, :module, :sclass).first)
|
|
331
330
|
corrector.insert_before(ancestor.loc.end, "#{node.method_name}\n\n#{source}\n")
|
|
332
331
|
else
|
|
333
332
|
corrector.replace(node, "#{node.method_name}\n\n#{source}")
|
|
@@ -62,12 +62,12 @@ module RuboCop
|
|
|
62
62
|
#
|
|
63
63
|
# @example AllowedConstants: ['ClassMethods']
|
|
64
64
|
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
65
|
+
# # good
|
|
66
|
+
# module A
|
|
67
|
+
# module ClassMethods
|
|
68
|
+
# # ...
|
|
69
|
+
# end
|
|
70
|
+
# end
|
|
71
71
|
#
|
|
72
72
|
class Documentation < Base
|
|
73
73
|
include DocumentationComment
|
|
@@ -97,14 +97,14 @@ module RuboCop
|
|
|
97
97
|
#
|
|
98
98
|
# @example AllowedMethods: ['method_missing', 'respond_to_missing?']
|
|
99
99
|
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
100
|
+
# # good
|
|
101
|
+
# class Foo
|
|
102
|
+
# def method_missing(name, *args)
|
|
103
|
+
# end
|
|
104
|
+
#
|
|
105
|
+
# def respond_to_missing?(symbol, include_private)
|
|
106
|
+
# end
|
|
107
|
+
# end
|
|
108
108
|
#
|
|
109
109
|
class DocumentationMethod < Base
|
|
110
110
|
include DocumentationComment
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Enforces consistent style for empty class definitions.
|
|
7
|
+
#
|
|
8
|
+
# This cop can enforce either a two-line class definition or `Class.new`
|
|
9
|
+
# for classes with no body.
|
|
10
|
+
#
|
|
11
|
+
# The supported styles are:
|
|
12
|
+
#
|
|
13
|
+
# * class_definition (default) - prefer two-line class definition over `Class.new`
|
|
14
|
+
# * class_new - prefer `Class.new` over class definition
|
|
15
|
+
#
|
|
16
|
+
# @example EnforcedStyle: class_definition (default)
|
|
17
|
+
# # bad
|
|
18
|
+
# FooError = Class.new(StandardError)
|
|
19
|
+
#
|
|
20
|
+
# # okish
|
|
21
|
+
# class FooError < StandardError; end
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# class FooError < StandardError
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example EnforcedStyle: class_new
|
|
28
|
+
# # bad
|
|
29
|
+
# class FooError < StandardError
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # bad
|
|
33
|
+
# class FooError < StandardError; end
|
|
34
|
+
#
|
|
35
|
+
# # good
|
|
36
|
+
# FooError = Class.new(StandardError)
|
|
37
|
+
#
|
|
38
|
+
class EmptyClassDefinition < Base
|
|
39
|
+
include ConfigurableEnforcedStyle
|
|
40
|
+
include Alignment
|
|
41
|
+
include RangeHelp
|
|
42
|
+
extend AutoCorrector
|
|
43
|
+
|
|
44
|
+
MSG_CLASS_DEFINITION =
|
|
45
|
+
'Prefer a two-line class definition over `Class.new` for classes with no body.'
|
|
46
|
+
MSG_CLASS_NEW = 'Prefer `Class.new` over class definition for classes with no body.'
|
|
47
|
+
|
|
48
|
+
# @!method class_new_assignment?(node)
|
|
49
|
+
def_node_matcher :class_new_assignment?, <<~PATTERN
|
|
50
|
+
(casgn _ _ (send (const _ :Class) :new ...))
|
|
51
|
+
PATTERN
|
|
52
|
+
|
|
53
|
+
def on_casgn(node)
|
|
54
|
+
return unless style == :class_definition
|
|
55
|
+
return unless node.expression
|
|
56
|
+
|
|
57
|
+
class_new_node = find_class_new_node(node.expression)
|
|
58
|
+
return if chained_with_any_method?(node.expression, class_new_node)
|
|
59
|
+
return if variable_parent_class?(class_new_node)
|
|
60
|
+
|
|
61
|
+
add_offense(node, message: MSG_CLASS_DEFINITION) do |corrector|
|
|
62
|
+
autocorrect_class_new(corrector, node)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def on_class(node)
|
|
67
|
+
return unless style == :class_new
|
|
68
|
+
return unless empty_class?(node)
|
|
69
|
+
|
|
70
|
+
add_offense(node, message: MSG_CLASS_NEW) do |corrector|
|
|
71
|
+
autocorrect_class_definition(corrector, node)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def autocorrect_class_new(corrector, node)
|
|
78
|
+
indent = ' ' * node.loc.column
|
|
79
|
+
class_name = node.name
|
|
80
|
+
class_new_node = find_class_new_node(node.expression)
|
|
81
|
+
parent_class = extract_parent_class(class_new_node)
|
|
82
|
+
|
|
83
|
+
replacement = if parent_class
|
|
84
|
+
"class #{class_name} < #{parent_class}\n#{indent}end"
|
|
85
|
+
else
|
|
86
|
+
"class #{class_name}\n#{indent}end"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
corrector.replace(node, replacement)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def autocorrect_class_definition(corrector, node)
|
|
93
|
+
source_line = processed_source.buffer.source_line(node.loc.line)
|
|
94
|
+
indent = source_line[/\A */]
|
|
95
|
+
class_name = node.identifier.source
|
|
96
|
+
parent_class = node.parent_class&.source
|
|
97
|
+
range = range_by_whole_lines(node.source_range, include_final_newline: true)
|
|
98
|
+
|
|
99
|
+
replacement = if parent_class
|
|
100
|
+
"#{indent}#{class_name} = Class.new(#{parent_class})\n"
|
|
101
|
+
else
|
|
102
|
+
"#{indent}#{class_name} = Class.new\n"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
corrector.replace(range, replacement)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def extract_parent_class(class_new_node)
|
|
109
|
+
first_arg = class_new_node.first_argument
|
|
110
|
+
first_arg&.source
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def variable_parent_class?(class_new_node)
|
|
114
|
+
first_arg = class_new_node.first_argument
|
|
115
|
+
return false unless first_arg
|
|
116
|
+
|
|
117
|
+
!first_arg.const_type?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def find_class_new_node(node)
|
|
121
|
+
return nil unless node.send_type?
|
|
122
|
+
return nil unless node.receiver&.const_type?
|
|
123
|
+
|
|
124
|
+
return node if node.receiver.const_name.to_sym == :Class && node.method?(:new)
|
|
125
|
+
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def chained_with_any_method?(expression_node, class_new_node)
|
|
130
|
+
return true unless expression_node == class_new_node
|
|
131
|
+
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def empty_class?(node)
|
|
136
|
+
body = node.body
|
|
137
|
+
return true unless body
|
|
138
|
+
|
|
139
|
+
body.begin_type? && body.children.empty?
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -227,12 +227,15 @@ module RuboCop
|
|
|
227
227
|
remove_whole_lines(corrector, node.loc.end)
|
|
228
228
|
return unless node.else?
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
if leave_branch
|
|
231
|
+
remove_whole_lines(corrector, leave_branch.source_range)
|
|
232
|
+
corrector.insert_after(
|
|
233
|
+
heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
231
237
|
remove_whole_lines(corrector, node.loc.else)
|
|
232
238
|
remove_whole_lines(corrector, range_of_branch_to_remove(node, guard))
|
|
233
|
-
corrector.insert_after(
|
|
234
|
-
heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
|
|
235
|
-
)
|
|
236
239
|
end
|
|
237
240
|
|
|
238
241
|
def range_of_branch_to_remove(node, guard)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Enforces the use of either `Hash#[]` or `Hash#fetch` for hash lookup.
|
|
7
|
+
#
|
|
8
|
+
# This cop can be configured to prefer either bracket-style (`[]`)
|
|
9
|
+
# or fetch-style lookup. It is disabled by default.
|
|
10
|
+
#
|
|
11
|
+
# When enforcing `fetch` style, only single-argument bracket access is flagged.
|
|
12
|
+
# When enforcing `brackets` style, only `fetch` calls with a single key
|
|
13
|
+
# argument are flagged (not those with default values or blocks).
|
|
14
|
+
#
|
|
15
|
+
# @safety
|
|
16
|
+
# This cop is unsafe because `Hash#[]` and `Hash#fetch` have different
|
|
17
|
+
# semantics. `Hash#[]` returns `nil` for missing keys, while `Hash#fetch`
|
|
18
|
+
# raises a `KeyError`. Replacing one with the other can change program
|
|
19
|
+
# behavior in cases where the key is missing.
|
|
20
|
+
#
|
|
21
|
+
# Additionally, it cannot be guaranteed that the receiver is a `Hash`
|
|
22
|
+
# or responds to the replacement method.
|
|
23
|
+
#
|
|
24
|
+
# @example EnforcedStyle: brackets (default)
|
|
25
|
+
# # bad
|
|
26
|
+
# hash.fetch(key)
|
|
27
|
+
#
|
|
28
|
+
# # good
|
|
29
|
+
# hash[key]
|
|
30
|
+
#
|
|
31
|
+
# # good - fetch with default value is allowed
|
|
32
|
+
# hash.fetch(key, default)
|
|
33
|
+
#
|
|
34
|
+
# # good - fetch with block is allowed
|
|
35
|
+
# hash.fetch(key) { default }
|
|
36
|
+
#
|
|
37
|
+
# @example EnforcedStyle: fetch
|
|
38
|
+
# # bad
|
|
39
|
+
# hash[key]
|
|
40
|
+
#
|
|
41
|
+
# # good
|
|
42
|
+
# hash.fetch(key)
|
|
43
|
+
#
|
|
44
|
+
class HashLookupMethod < Base
|
|
45
|
+
include ConfigurableEnforcedStyle
|
|
46
|
+
extend AutoCorrector
|
|
47
|
+
|
|
48
|
+
BRACKET_MSG = 'Use `Hash#[]` instead of `Hash#fetch`.'
|
|
49
|
+
FETCH_MSG = 'Use `Hash#fetch` instead of `Hash#[]`.'
|
|
50
|
+
|
|
51
|
+
RESTRICT_ON_SEND = %i[[] fetch].freeze
|
|
52
|
+
|
|
53
|
+
def on_send(node)
|
|
54
|
+
if offense_for_brackets?(node)
|
|
55
|
+
add_offense(node.loc.selector, message: BRACKET_MSG) do |corrector|
|
|
56
|
+
correct_fetch_to_brackets(corrector, node)
|
|
57
|
+
end
|
|
58
|
+
elsif offense_for_fetch?(node)
|
|
59
|
+
add_offense(node, message: FETCH_MSG) do |corrector|
|
|
60
|
+
correct_brackets_to_fetch(corrector, node)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
alias on_csend on_send
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def offense_for_brackets?(node)
|
|
69
|
+
style == :brackets && node.receiver && node.method?(:fetch) && node.arguments.one? &&
|
|
70
|
+
!node.block_literal?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def offense_for_fetch?(node)
|
|
74
|
+
style == :fetch && node.method?(:[]) && node.arguments.one?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def correct_fetch_to_brackets(corrector, node)
|
|
78
|
+
receiver = node.receiver.source
|
|
79
|
+
key = node.first_argument.source
|
|
80
|
+
replacement = "#{receiver}[#{key}]"
|
|
81
|
+
replacement = "(#{replacement})" if node.csend_type?
|
|
82
|
+
corrector.replace(node, replacement)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def correct_brackets_to_fetch(corrector, node)
|
|
86
|
+
receiver = node.receiver.source
|
|
87
|
+
key = node.first_argument.source
|
|
88
|
+
operator = node.csend_type? ? '&.' : '.'
|
|
89
|
+
corrector.replace(node, "#{receiver}#{operator}fetch(#{key})")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|