rubocop 1.88.2 → 1.89.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/config/default.yml +67 -11
- data/lib/rubocop/comment_config.rb +35 -19
- data/lib/rubocop/config.rb +53 -0
- data/lib/rubocop/config_validator.rb +38 -1
- data/lib/rubocop/cop/badge.rb +8 -0
- data/lib/rubocop/cop/base.rb +6 -4
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
- data/lib/rubocop/cop/gemspec.rb +22 -0
- data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
- data/lib/rubocop/cop/generator.rb +4 -0
- data/lib/rubocop/cop/layout/class_structure.rb +8 -2
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
- data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
- data/lib/rubocop/cop/layout/line_length.rb +85 -12
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
- data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
- data/lib/rubocop/cop/layout.rb +114 -0
- data/lib/rubocop/cop/lazy_loader.rb +41 -0
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
- data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
- data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
- data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
- data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
- data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
- data/lib/rubocop/cop/lint/missing_super.rb +31 -1
- data/lib/rubocop/cop/lint/name_typo.rb +223 -0
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +37 -1
- data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/void.rb +17 -6
- data/lib/rubocop/cop/lint.rb +176 -0
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics.rb +31 -0
- data/lib/rubocop/cop/migration/department_name.rb +0 -4
- data/lib/rubocop/cop/migration.rb +13 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
- data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +42 -6
- data/lib/rubocop/cop/mixin.rb +78 -77
- data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
- data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
- data/lib/rubocop/cop/naming/variable_number.rb +3 -1
- data/lib/rubocop/cop/naming.rb +31 -0
- data/lib/rubocop/cop/offense.rb +10 -6
- data/lib/rubocop/cop/registry.rb +153 -43
- data/lib/rubocop/cop/security.rb +19 -0
- data/lib/rubocop/cop/style/array_intersect.rb +24 -5
- data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
- data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
- data/lib/rubocop/cop/style/copyright.rb +6 -1
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
- data/lib/rubocop/cop/style/documentation.rb +47 -0
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -48
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
- data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
- data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
- data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
- data/lib/rubocop/cop/style/redundant_format.rb +12 -3
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
- data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
- data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
- data/lib/rubocop/cop/style/static_class.rb +21 -0
- data/lib/rubocop/cop/style.rb +310 -0
- data/lib/rubocop/cop/team.rb +48 -3
- data/lib/rubocop/cop/variable_force/variable.rb +18 -6
- data/lib/rubocop/cops_documentation_generator.rb +1 -1
- data/lib/rubocop/directive_comment.rb +2 -0
- data/lib/rubocop/feature_loader.rb +0 -6
- data/lib/rubocop/lsp/routes.rb +41 -3
- data/lib/rubocop/lsp/runtime.rb +4 -0
- data/lib/rubocop/lsp/server.rb +4 -0
- data/lib/rubocop/lsp/stdin_runner.rb +20 -0
- data/lib/rubocop/pending_cops_reporter.rb +1 -6
- data/lib/rubocop/project_index_loader.rb +16 -0
- data/lib/rubocop/result_cache.rb +6 -4
- data/lib/rubocop/rspec/shared_contexts.rb +14 -2
- data/lib/rubocop/runner.rb +45 -20
- data/lib/rubocop/server/cache.rb +10 -5
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +17 -626
- metadata +18 -3
|
@@ -12,6 +12,17 @@ module RuboCop
|
|
|
12
12
|
# Specific cops can be allowed with the `AllowedCops` configuration. Note that
|
|
13
13
|
# if this configuration is set, `rubocop:disable all` is still disallowed.
|
|
14
14
|
#
|
|
15
|
+
# Alternatively, specific cops can be disallowed with the `DisallowedCops`
|
|
16
|
+
# configuration. When `DisallowedCops` is set, only directives for the listed
|
|
17
|
+
# cops (and `all`) will be flagged. This is useful when you want
|
|
18
|
+
# to protect a small set of critical cops from being disabled rather than
|
|
19
|
+
# allowlisting all other cops. `AllowedCops` and `DisallowedCops` should not
|
|
20
|
+
# both be set at the same time; if `DisallowedCops` is set, it takes precedence.
|
|
21
|
+
#
|
|
22
|
+
# This cop cannot be disabled via directive comments when it is explicitly
|
|
23
|
+
# enabled with `Enabled: true`. This prevents users from bypassing the cop
|
|
24
|
+
# with `# rubocop:disable Style/DisableCopsWithinSourceCodeDirective`.
|
|
25
|
+
#
|
|
15
26
|
# @example
|
|
16
27
|
# # bad
|
|
17
28
|
# # rubocop:disable Metrics/AbcSize
|
|
@@ -30,6 +41,17 @@ module RuboCop
|
|
|
30
41
|
# end
|
|
31
42
|
# # rubocop:enable Metrics/AbcSize
|
|
32
43
|
#
|
|
44
|
+
# @example DisallowedCops: [Lint/Void]
|
|
45
|
+
# # bad
|
|
46
|
+
# # rubocop:disable Lint/Void
|
|
47
|
+
# foo
|
|
48
|
+
# # rubocop:enable Lint/Void
|
|
49
|
+
#
|
|
50
|
+
# # good
|
|
51
|
+
# # rubocop:disable Metrics/AbcSize
|
|
52
|
+
# foo
|
|
53
|
+
# # rubocop:enable Metrics/AbcSize
|
|
54
|
+
#
|
|
33
55
|
class DisableCopsWithinSourceCodeDirective < Base
|
|
34
56
|
extend AutoCorrector
|
|
35
57
|
|
|
@@ -40,7 +62,7 @@ module RuboCop
|
|
|
40
62
|
def on_new_investigation
|
|
41
63
|
processed_source.comments.each do |comment|
|
|
42
64
|
directive_cops = directive_cops(comment)
|
|
43
|
-
disallowed_cops = directive_cops
|
|
65
|
+
disallowed_cops = compute_disallowed_cops(directive_cops)
|
|
44
66
|
|
|
45
67
|
next unless disallowed_cops.any?
|
|
46
68
|
|
|
@@ -50,8 +72,20 @@ module RuboCop
|
|
|
50
72
|
|
|
51
73
|
private
|
|
52
74
|
|
|
75
|
+
def compute_disallowed_cops(directive_cops)
|
|
76
|
+
if disallowed_cops_config.any?
|
|
77
|
+
if directive_cops.include?('all')
|
|
78
|
+
directive_cops
|
|
79
|
+
else
|
|
80
|
+
directive_cops.uniq.select { |cop| disallowed_cops_config.include?(cop) }
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
directive_cops.reject { |cop| allowed_cops.include?(cop) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
53
87
|
def register_offense(comment, directive_cops, disallowed_cops)
|
|
54
|
-
message = if any_cops_allowed?
|
|
88
|
+
message = if any_cops_allowed? || disallowed_cops_config.any?
|
|
55
89
|
format(MSG_FOR_COPS, cops: "`#{disallowed_cops.join('`, `')}`")
|
|
56
90
|
else
|
|
57
91
|
MSG
|
|
@@ -75,12 +109,16 @@ module RuboCop
|
|
|
75
109
|
end
|
|
76
110
|
|
|
77
111
|
def allowed_cops
|
|
78
|
-
Array(cop_config['AllowedCops'])
|
|
112
|
+
@allowed_cops ||= Array(cop_config['AllowedCops']).to_set
|
|
79
113
|
end
|
|
80
114
|
|
|
81
115
|
def any_cops_allowed?
|
|
82
116
|
allowed_cops.any?
|
|
83
117
|
end
|
|
118
|
+
|
|
119
|
+
def disallowed_cops_config
|
|
120
|
+
@disallowed_cops_config ||= Array(cop_config['DisallowedCops']).to_set
|
|
121
|
+
end
|
|
84
122
|
end
|
|
85
123
|
end
|
|
86
124
|
end
|
|
@@ -13,6 +13,11 @@ module RuboCop
|
|
|
13
13
|
# a `#:nodoc:` comment next to it. Likewise, `#:nodoc: all` does the
|
|
14
14
|
# same for all its children.
|
|
15
15
|
#
|
|
16
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
|
|
17
|
+
# installed, a reopened class or module is not reported when any of its
|
|
18
|
+
# other definition sites (in the same or another file) carries a
|
|
19
|
+
# documentation comment.
|
|
20
|
+
#
|
|
16
21
|
# @example
|
|
17
22
|
# # bad
|
|
18
23
|
# class Person
|
|
@@ -71,10 +76,15 @@ module RuboCop
|
|
|
71
76
|
#
|
|
72
77
|
class Documentation < Base
|
|
73
78
|
include DocumentationComment
|
|
79
|
+
include ProjectIndexHelp
|
|
74
80
|
include RangeHelp
|
|
75
81
|
|
|
76
82
|
MSG = 'Missing top-level documentation comment for `%<type>s %<identifier>s`.'
|
|
77
83
|
|
|
84
|
+
# Comments that configure tooling rather than document the code.
|
|
85
|
+
DIRECTIVE_COMMENT_REGEXP = /\A\#\s*(rubocop:|frozen_string_literal:|encoding:|
|
|
86
|
+
shareable_constant_value:|typed:|-\*-)/x.freeze
|
|
87
|
+
|
|
78
88
|
# @!method constant_definition?(node)
|
|
79
89
|
def_node_matcher :constant_definition?, '{class module casgn}'
|
|
80
90
|
|
|
@@ -109,12 +119,49 @@ module RuboCop
|
|
|
109
119
|
return if constant_allowed?(node)
|
|
110
120
|
return if nodoc_self_or_outer_module?(node)
|
|
111
121
|
return if include_statement_only?(body)
|
|
122
|
+
return if documented_elsewhere?(node)
|
|
123
|
+
|
|
124
|
+
add_documentation_offense(node)
|
|
125
|
+
end
|
|
112
126
|
|
|
127
|
+
def add_documentation_offense(node)
|
|
113
128
|
range = range_between(node.source_range.begin_pos, node.loc.name.end_pos)
|
|
114
129
|
message = format(MSG, type: node.type, identifier: identifier(node))
|
|
115
130
|
add_offense(range, message: message)
|
|
116
131
|
end
|
|
117
132
|
|
|
133
|
+
# A reopened class or module is typically documented at a single
|
|
134
|
+
# definition site. When `AllCops/UseProjectIndex` is enabled, the
|
|
135
|
+
# documentation requirement is satisfied by a comment on any other
|
|
136
|
+
# definition of the same class or module.
|
|
137
|
+
def documented_elsewhere?(node)
|
|
138
|
+
return false unless project_index
|
|
139
|
+
|
|
140
|
+
declaration = resolve_constant_in_index(node.identifier)
|
|
141
|
+
return false unless declaration.is_a?(Rubydex::Namespace)
|
|
142
|
+
|
|
143
|
+
declaration.definitions.any? do |definition|
|
|
144
|
+
other_definition?(definition, node) && documenting_comments?(definition)
|
|
145
|
+
end
|
|
146
|
+
rescue StandardError
|
|
147
|
+
false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def other_definition?(definition, node)
|
|
151
|
+
location = definition.location
|
|
152
|
+
return false unless location.uri.start_with?(FILE_URI_PREFIX)
|
|
153
|
+
|
|
154
|
+
!same_file?(location.to_file_path, processed_source.file_path) ||
|
|
155
|
+
location.to_display.start_line != node.first_line
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def documenting_comments?(definition)
|
|
159
|
+
definition.comments.to_a.any? do |comment|
|
|
160
|
+
text = comment.string.strip
|
|
161
|
+
!text.empty? && !DIRECTIVE_COMMENT_REGEXP.match?(text)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
118
165
|
def nodoc_self_or_outer_module?(node)
|
|
119
166
|
nodoc_comment?(node) ||
|
|
120
167
|
(compact_namespace?(node) && nodoc_comment?(outer_module(node).first))
|
|
@@ -206,16 +206,6 @@ module RuboCop
|
|
|
206
206
|
body.each_descendant(:str).any?(&:heredoc?)
|
|
207
207
|
end
|
|
208
208
|
|
|
209
|
-
def correct_to_multiline(corrector, node)
|
|
210
|
-
replacement = <<~RUBY.strip
|
|
211
|
-
def #{receiver(node)}#{node.method_name}#{arguments(node)}
|
|
212
|
-
#{node.body.source}
|
|
213
|
-
end
|
|
214
|
-
RUBY
|
|
215
|
-
|
|
216
|
-
corrector.replace(node, replacement)
|
|
217
|
-
end
|
|
218
|
-
|
|
219
209
|
def endless_replacement(node)
|
|
220
210
|
<<~RUBY.strip
|
|
221
211
|
def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
|
|
@@ -72,10 +72,8 @@ module RuboCop
|
|
|
72
72
|
# do_something
|
|
73
73
|
# end
|
|
74
74
|
#
|
|
75
|
-
class IfUnlessModifier < Base
|
|
75
|
+
class IfUnlessModifier < Base
|
|
76
76
|
include StatementModifier
|
|
77
|
-
include LineLengthHelp
|
|
78
|
-
include AllowedPattern
|
|
79
77
|
include RangeHelp
|
|
80
78
|
include CommentsHelp
|
|
81
79
|
extend AutoCorrector
|
|
@@ -83,6 +81,10 @@ module RuboCop
|
|
|
83
81
|
MSG_USE_MODIFIER = 'Favor modifier `%<keyword>s` usage when having a ' \
|
|
84
82
|
'single-line body. Another good alternative is ' \
|
|
85
83
|
'the usage of control flow `&&`/`||`.'
|
|
84
|
+
MSG_USE_MODIFIER_PARENS = 'Favor modifier `%<keyword>s` usage when having a ' \
|
|
85
|
+
'single-line body. Wrap the expression in parentheses ' \
|
|
86
|
+
'to keep the current behavior, as it is part of a ' \
|
|
87
|
+
'larger expression.'
|
|
86
88
|
MSG_USE_NORMAL = 'Modifier form of `%<keyword>s` makes the line too long.'
|
|
87
89
|
|
|
88
90
|
def self.autocorrect_incompatible_with
|
|
@@ -143,7 +145,7 @@ module RuboCop
|
|
|
143
145
|
|
|
144
146
|
def message(node)
|
|
145
147
|
if single_line_as_modifier?(node) && !named_capture_in_condition?(node)
|
|
146
|
-
MSG_USE_MODIFIER
|
|
148
|
+
parenthesize?(node) ? MSG_USE_MODIFIER_PARENS : MSG_USE_MODIFIER
|
|
147
149
|
elsif too_long_due_to_modifier?(node)
|
|
148
150
|
MSG_USE_NORMAL
|
|
149
151
|
end
|
|
@@ -188,52 +190,9 @@ module RuboCop
|
|
|
188
190
|
max_line_length.between?(source_length - comment.source_range.length, source_length)
|
|
189
191
|
end
|
|
190
192
|
|
|
191
|
-
def allowed_patterns
|
|
192
|
-
line_length_config = config.for_cop('Layout/LineLength')
|
|
193
|
-
line_length_config['AllowedPatterns'] || line_length_config['IgnoredPatterns'] || []
|
|
194
|
-
end
|
|
195
|
-
|
|
196
193
|
def too_long_single_line?(node)
|
|
197
|
-
return false unless max_line_length
|
|
198
|
-
|
|
199
194
|
range = node.source_range
|
|
200
|
-
|
|
201
|
-
return false unless line_length_enabled_at_line?(range.first_line)
|
|
202
|
-
|
|
203
|
-
line = range.source_line
|
|
204
|
-
return false if line_length(line) <= max_line_length
|
|
205
|
-
|
|
206
|
-
too_long_line_based_on_config?(range, line)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def too_long_line_based_on_config?(range, line)
|
|
210
|
-
return false if matches_allowed_pattern?(line)
|
|
211
|
-
|
|
212
|
-
too_long = too_long_line_based_on_allow_cop_directives?(range, line)
|
|
213
|
-
return too_long unless too_long == :undetermined
|
|
214
|
-
|
|
215
|
-
too_long_line_based_on_allow_uri?(line)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def too_long_line_based_on_allow_cop_directives?(range, line)
|
|
219
|
-
if allow_cop_directives? && directive_on_source_line?(range.line - 1)
|
|
220
|
-
return line_length_without_directive(line) > max_line_length
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
:undetermined
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def too_long_line_based_on_allow_uri?(line)
|
|
227
|
-
if allow_uri?
|
|
228
|
-
uri_range = find_excessive_range(line, :uri)
|
|
229
|
-
return false if uri_range && allowed_position?(line, uri_range)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
true
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def line_length_enabled_at_line?(line)
|
|
236
|
-
processed_source.comment_config.cop_enabled_at_line?('Layout/LineLength', line)
|
|
195
|
+
range.single_line? && !acceptable_line_length?(range.source_line, range.first_line)
|
|
237
196
|
end
|
|
238
197
|
|
|
239
198
|
def named_capture_in_condition?(node)
|
|
@@ -8,11 +8,26 @@ module RuboCop
|
|
|
8
8
|
# rubocop:disable Metrics/ModuleLength, Metrics/CyclomaticComplexity
|
|
9
9
|
module OmitParentheses
|
|
10
10
|
include RangeHelp
|
|
11
|
+
include ReparsedEquivalence
|
|
11
12
|
|
|
12
13
|
TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
|
|
13
14
|
OMIT_MSG = 'Omit parentheses for method calls with arguments.'
|
|
14
15
|
private_constant :OMIT_MSG
|
|
15
16
|
|
|
17
|
+
def on_investigation_end
|
|
18
|
+
# Each candidate's exact correction is verified by reparsing before
|
|
19
|
+
# the offense is registered, so an omission that would change how
|
|
20
|
+
# the code parses is never reported or offered.
|
|
21
|
+
verified_by_reparse(@pending_omit_offenses || []).each do |node|
|
|
22
|
+
add_offense(offense_range(node), message: OMIT_MSG) do |corrector|
|
|
23
|
+
autocorrect(corrector, node)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
@pending_omit_offenses = []
|
|
27
|
+
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
|
|
16
31
|
private
|
|
17
32
|
|
|
18
33
|
def omit_parentheses(node) # rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -26,9 +41,11 @@ module RuboCop
|
|
|
26
41
|
return if allowed_camel_case_method_call?(node)
|
|
27
42
|
return if allowed_string_interpolation_method_call?(node)
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
(@pending_omit_offenses ||= []) << node
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def apply_reparse_correction(corrector, node)
|
|
48
|
+
autocorrect(corrector, node)
|
|
32
49
|
end
|
|
33
50
|
|
|
34
51
|
def autocorrect(corrector, node)
|
|
@@ -28,6 +28,11 @@ module RuboCop
|
|
|
28
28
|
# delegator.upcase # => FOO
|
|
29
29
|
# ----
|
|
30
30
|
#
|
|
31
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
|
|
32
|
+
# installed, `respond_to_missing?` defined in another definition of the
|
|
33
|
+
# same class or module (e.g. a reopening in another file) also
|
|
34
|
+
# satisfies the check.
|
|
35
|
+
#
|
|
31
36
|
# @example
|
|
32
37
|
# # bad
|
|
33
38
|
# def method_missing(name, *args)
|
|
@@ -52,11 +57,14 @@ module RuboCop
|
|
|
52
57
|
# end
|
|
53
58
|
#
|
|
54
59
|
class MissingRespondToMissing < Base
|
|
60
|
+
include ProjectIndexHelp
|
|
61
|
+
|
|
55
62
|
MSG = 'When using `method_missing`, define `respond_to_missing?`.'
|
|
56
63
|
|
|
57
64
|
def on_def(node)
|
|
58
65
|
return unless node.method?(:method_missing)
|
|
59
66
|
return if implements_respond_to_missing?(node)
|
|
67
|
+
return if respond_to_missing_elsewhere?(node)
|
|
60
68
|
|
|
61
69
|
add_offense(node)
|
|
62
70
|
end
|
|
@@ -64,6 +72,28 @@ module RuboCop
|
|
|
64
72
|
|
|
65
73
|
private
|
|
66
74
|
|
|
75
|
+
# When `AllCops/UseProjectIndex` is enabled, `respond_to_missing?`
|
|
76
|
+
# defined in another definition of the same class or module (e.g. a
|
|
77
|
+
# reopening in another file) also satisfies the check. Ancestors are
|
|
78
|
+
# not consulted: overriding `method_missing` warrants a matching
|
|
79
|
+
# `respond_to_missing?` for the same class.
|
|
80
|
+
def respond_to_missing_elsewhere?(node)
|
|
81
|
+
return false unless project_index
|
|
82
|
+
return false unless (namespace_node = node.each_ancestor(:class, :module).first)
|
|
83
|
+
|
|
84
|
+
declaration = resolve_constant_in_index(namespace_node.identifier)
|
|
85
|
+
return false unless declaration.is_a?(Rubydex::Namespace)
|
|
86
|
+
|
|
87
|
+
scope = singleton_definition?(node) ? indexed_singleton_of(declaration) : declaration
|
|
88
|
+
!scope&.member('respond_to_missing?()').nil?
|
|
89
|
+
rescue StandardError
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def singleton_definition?(node)
|
|
94
|
+
node.defs_type? || node.each_ancestor(:sclass, :class, :module).first&.sclass_type?
|
|
95
|
+
end
|
|
96
|
+
|
|
67
97
|
def implements_respond_to_missing?(node)
|
|
68
98
|
scope = enclosing_scope(node)
|
|
69
99
|
search_root = scope || node.parent
|
|
@@ -36,7 +36,13 @@ module RuboCop
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def non_modifier_then?(node)
|
|
39
|
-
node.multiline? && node
|
|
39
|
+
node.multiline? && own_then?(node) && node.loc.begin.line != node.if_branch&.loc&.line
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Prism gives an `elsif` with no `then` of its own the previous branch's `then` location,
|
|
43
|
+
# which lies outside the node.
|
|
44
|
+
def own_then?(node)
|
|
45
|
+
node.then? && node.source_range.contains?(node.loc.begin)
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
end
|
|
@@ -70,48 +70,59 @@ module RuboCop
|
|
|
70
70
|
(send $_ :== ${lvar call})
|
|
71
71
|
PATTERN
|
|
72
72
|
|
|
73
|
-
# rubocop:disable Metrics/AbcSize
|
|
74
73
|
def on_or(node)
|
|
75
|
-
|
|
76
|
-
return unless node == root_of_or_node
|
|
74
|
+
return unless node == root_of_or_node(node)
|
|
77
75
|
return unless nested_comparison?(node)
|
|
78
|
-
|
|
79
|
-
return unless (variable, values = find_offending_var(node))
|
|
76
|
+
return unless (variable, values, skipped = find_offending_var(node))
|
|
80
77
|
return if values.size < comparisons_threshold
|
|
81
78
|
|
|
82
79
|
range = offense_range(values)
|
|
80
|
+
# Bail when an allowed method comparison sits between the collected
|
|
81
|
+
# values: collapsing them into a single `include?` would drop it.
|
|
82
|
+
return if skipped_within_range?(skipped, range)
|
|
83
83
|
|
|
84
84
|
add_offense(range) do |corrector|
|
|
85
|
-
|
|
86
|
-
argument = variable.lvar_type? ? variable_name(variable) : variable.source
|
|
87
|
-
prefer_method = "[#{elements}].include?(#{argument})"
|
|
88
|
-
|
|
89
|
-
corrector.replace(range, prefer_method)
|
|
85
|
+
corrector.replace(range, preferred_method(variable, values))
|
|
90
86
|
end
|
|
91
87
|
end
|
|
92
|
-
# rubocop:enable Metrics/AbcSize
|
|
93
88
|
|
|
94
89
|
private
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
def skipped_within_range?(skipped, range)
|
|
92
|
+
skipped.any? { |node_part| range.contains?(node_part.source_range) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def preferred_method(variable, values)
|
|
96
|
+
elements = values.map(&:source).join(', ')
|
|
97
|
+
argument = variable.lvar_type? ? variable_name(variable) : variable.source
|
|
98
|
+
"[#{elements}].include?(#{argument})"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def find_offending_var(node, variables = Set.new, values = [], skipped = [])
|
|
98
102
|
if node.or_type?
|
|
99
|
-
find_offending_var(node.lhs, variables, values)
|
|
100
|
-
find_offending_var(node.rhs, variables, values)
|
|
103
|
+
find_offending_var(node.lhs, variables, values, skipped)
|
|
104
|
+
find_offending_var(node.rhs, variables, values, skipped)
|
|
101
105
|
elsif simple_double_comparison?(node)
|
|
102
106
|
return
|
|
103
|
-
elsif (
|
|
104
|
-
|
|
107
|
+
elsif (comparison = simple_comparison(node))
|
|
108
|
+
collect_comparison(node, comparison, variables, values, skipped)
|
|
109
|
+
end
|
|
105
110
|
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
[variables.first, values, skipped] if variables.any?
|
|
112
|
+
end
|
|
108
113
|
|
|
109
|
-
|
|
114
|
+
def collect_comparison(node, comparison, variables, values, skipped)
|
|
115
|
+
var, obj = comparison
|
|
116
|
+
if allow_method_comparison? && obj.call_type?
|
|
117
|
+
skipped << node
|
|
118
|
+
return
|
|
110
119
|
end
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
variables << var
|
|
122
|
+
return if variables.size > 1
|
|
123
|
+
|
|
124
|
+
values << obj
|
|
113
125
|
end
|
|
114
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
115
126
|
|
|
116
127
|
def offense_range(values)
|
|
117
128
|
values.first.parent.source_range.begin.join(values.last.parent.source_range.end)
|
|
@@ -89,6 +89,9 @@ module RuboCop
|
|
|
89
89
|
# end
|
|
90
90
|
# end.freeze
|
|
91
91
|
#
|
|
92
|
+
# # good - `Data.define` declares an immutable value type
|
|
93
|
+
# CONST = Data.define(:foo, :bar)
|
|
94
|
+
#
|
|
92
95
|
# @example
|
|
93
96
|
# # Magic comment - shareable_constant_value: literal
|
|
94
97
|
#
|
|
@@ -319,6 +322,8 @@ module RuboCop
|
|
|
319
322
|
(const _ _)
|
|
320
323
|
(send (const {nil? cbase} :Struct) :new ...)
|
|
321
324
|
(block (send (const {nil? cbase} :Struct) :new ...) ...)
|
|
325
|
+
(send (const {nil? cbase} :Data) :define ...)
|
|
326
|
+
(block (send (const {nil? cbase} :Data) :define ...) ...)
|
|
322
327
|
(send _ :freeze)
|
|
323
328
|
(send {float int} {:+ :- :* :** :/ :% :<<} _)
|
|
324
329
|
(send _ {:+ :- :* :** :/ :%} {float int})
|
|
@@ -91,8 +91,9 @@ module RuboCop
|
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
94
95
|
def range_with_length_subtraction?(range_node, array_receiver)
|
|
95
|
-
return false unless range_node
|
|
96
|
+
return false unless range_node&.range_type?
|
|
96
97
|
|
|
97
98
|
range_end = range_node.end
|
|
98
99
|
range_start = range_node.begin
|
|
@@ -107,6 +108,7 @@ module RuboCop
|
|
|
107
108
|
|
|
108
109
|
receivers_match_strict?(length_receiver, array_receiver)
|
|
109
110
|
end
|
|
111
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
110
112
|
|
|
111
113
|
def handle_range_pattern(receiver, range_node, index_arg)
|
|
112
114
|
range_end = range_node.end
|
|
@@ -5,6 +5,9 @@ module RuboCop
|
|
|
5
5
|
module Style
|
|
6
6
|
# Checks for redundant assignment before returning.
|
|
7
7
|
#
|
|
8
|
+
# When there are comments between the assignment and reference,
|
|
9
|
+
# the cop will report an offense but it will not autocorrect.
|
|
10
|
+
#
|
|
8
11
|
# @example
|
|
9
12
|
# # bad
|
|
10
13
|
# def test
|
|
@@ -99,6 +102,8 @@ module RuboCop
|
|
|
99
102
|
def check_begin_node(node)
|
|
100
103
|
if (assignment = redundant_assignment?(node))
|
|
101
104
|
add_offense(assignment) do |corrector|
|
|
105
|
+
next if comments_between_assignment_and_reference?(assignment)
|
|
106
|
+
|
|
102
107
|
expression = assignment.children[1]
|
|
103
108
|
corrector.replace(assignment, expression.source)
|
|
104
109
|
corrector.remove(assignment.right_sibling)
|
|
@@ -108,6 +113,12 @@ module RuboCop
|
|
|
108
113
|
check_branch(last_expr)
|
|
109
114
|
end
|
|
110
115
|
end
|
|
116
|
+
|
|
117
|
+
def comments_between_assignment_and_reference?(assignment)
|
|
118
|
+
line_span = assignment.source_range.line..assignment.right_sibling.source_range.line
|
|
119
|
+
|
|
120
|
+
processed_source.each_comment_in_lines(line_span).any?
|
|
121
|
+
end
|
|
111
122
|
end
|
|
112
123
|
end
|
|
113
124
|
end
|
|
@@ -14,6 +14,11 @@ module RuboCop
|
|
|
14
14
|
# to prevent conflicting rules. This is because it respects user configurations
|
|
15
15
|
# that want to enable `Lint/ConstantResolution` cop which is disabled by default.
|
|
16
16
|
#
|
|
17
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
|
|
18
|
+
# installed, a leading `::` inside a namespace is also reported when the
|
|
19
|
+
# constant provably resolves to the same declaration with and without
|
|
20
|
+
# the base, i.e. nothing in the surrounding nesting shadows it.
|
|
21
|
+
#
|
|
17
22
|
# @example
|
|
18
23
|
# # bad
|
|
19
24
|
# ::Const
|
|
@@ -41,13 +46,14 @@ module RuboCop
|
|
|
41
46
|
# ::Const
|
|
42
47
|
# end
|
|
43
48
|
class RedundantConstantBase < Base
|
|
49
|
+
include ProjectIndexHelp
|
|
44
50
|
extend AutoCorrector
|
|
45
51
|
|
|
46
52
|
MSG = 'Remove redundant `::`.'
|
|
47
53
|
|
|
48
54
|
def on_cbase(node)
|
|
49
55
|
return if lint_constant_resolution_cop_enabled?
|
|
50
|
-
return unless bad?(node)
|
|
56
|
+
return unless bad?(node) || provably_unshadowed?(node)
|
|
51
57
|
|
|
52
58
|
add_offense(node) do |corrector|
|
|
53
59
|
corrector.remove(node)
|
|
@@ -56,6 +62,36 @@ module RuboCop
|
|
|
56
62
|
|
|
57
63
|
private
|
|
58
64
|
|
|
65
|
+
# When `AllCops/UseProjectIndex` is enabled, `::` inside a namespace is
|
|
66
|
+
# also redundant when the first constant segment resolves to the same
|
|
67
|
+
# declaration with and without the base, i.e. nothing in the
|
|
68
|
+
# surrounding nesting shadows it.
|
|
69
|
+
def provably_unshadowed?(node)
|
|
70
|
+
return false unless project_index
|
|
71
|
+
return false unless (segment = first_constant_segment(node))
|
|
72
|
+
|
|
73
|
+
nesting = module_nesting_ancestors_of(node)
|
|
74
|
+
.map { |ancestor| ancestor.identifier.const_name }.reverse
|
|
75
|
+
|
|
76
|
+
!nesting.empty? && same_resolution?(segment, nesting)
|
|
77
|
+
rescue StandardError
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def same_resolution?(segment, nesting)
|
|
82
|
+
relative = project_index.resolve_constant(segment, nesting)
|
|
83
|
+
absolute = project_index.resolve_constant(segment, [])
|
|
84
|
+
|
|
85
|
+
!relative.nil? && !absolute.nil? && relative.name == absolute.name
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def first_constant_segment(node)
|
|
89
|
+
parent = node.parent
|
|
90
|
+
return nil unless parent&.const_type?
|
|
91
|
+
|
|
92
|
+
parent.short_name.to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
59
95
|
def lint_constant_resolution_cop_enabled?
|
|
60
96
|
lint_constant_resolution_config.fetch('Enabled', false)
|
|
61
97
|
end
|
|
@@ -41,6 +41,10 @@ module RuboCop
|
|
|
41
41
|
return unless name_node.value.end_with?('.rb')
|
|
42
42
|
|
|
43
43
|
extension_range = extension_range(name_node)
|
|
44
|
+
# A backslash right before the extension means the path is escaped;
|
|
45
|
+
# dropping the extension would either produce invalid Ruby (a trailing
|
|
46
|
+
# backslash escaping the closing quote) or a confusing path, so skip it.
|
|
47
|
+
return if escaped_extension?(name_node, extension_range)
|
|
44
48
|
|
|
45
49
|
add_offense(extension_range) do |corrector|
|
|
46
50
|
corrector.remove(extension_range)
|
|
@@ -55,6 +59,11 @@ module RuboCop
|
|
|
55
59
|
|
|
56
60
|
range_between(end_of_path_string - 4, end_of_path_string - 1)
|
|
57
61
|
end
|
|
62
|
+
|
|
63
|
+
def escaped_extension?(name_node, extension_range)
|
|
64
|
+
offset = extension_range.begin_pos - name_node.source_range.begin_pos
|
|
65
|
+
name_node.source[0...offset].end_with?('\\')
|
|
66
|
+
end
|
|
58
67
|
end
|
|
59
68
|
end
|
|
60
69
|
end
|
|
@@ -182,8 +182,8 @@ module RuboCop
|
|
|
182
182
|
def unknown_variable_width?(sequence, arguments)
|
|
183
183
|
return false unless sequence.variable_width?
|
|
184
184
|
|
|
185
|
-
argument = arguments
|
|
186
|
-
!numeric?(argument)
|
|
185
|
+
argument = positional_argument(arguments, sequence.variable_width_argument_number)
|
|
186
|
+
argument.nil? || !numeric?(argument)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
# rubocop:disable Metrics/AbcSize
|
|
@@ -195,7 +195,7 @@ module RuboCop
|
|
|
195
195
|
arguments.delete_at(sequence.variable_width_argument_number - 1)
|
|
196
196
|
arguments.shift
|
|
197
197
|
elsif sequence.arg_number
|
|
198
|
-
arguments
|
|
198
|
+
positional_argument(arguments, sequence.arg_number.to_i)
|
|
199
199
|
else
|
|
200
200
|
arguments.shift
|
|
201
201
|
end
|
|
@@ -220,6 +220,15 @@ module RuboCop
|
|
|
220
220
|
end
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
+
# `Array#[]` raises `RangeError` for an index beyond the 64-bit range
|
|
224
|
+
# (e.g. `format('%*9999999999999999999999$d', 1)`) instead of returning `nil`,
|
|
225
|
+
# so the index needs to be checked before accessing the array.
|
|
226
|
+
def positional_argument(arguments, number)
|
|
227
|
+
index = number - 1
|
|
228
|
+
|
|
229
|
+
arguments[index] if index < arguments.size
|
|
230
|
+
end
|
|
231
|
+
|
|
223
232
|
def numeric?(argument)
|
|
224
233
|
argument.type?(:numeric, :str) ||
|
|
225
234
|
rational_number?(argument) ||
|