rubocop 1.88.2 → 1.90.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 +139 -16
- data/exe/rubocop +1 -1
- data/lib/rubocop/cached_data.rb +7 -2
- data/lib/rubocop/cli/command/execute_runner.rb +31 -0
- data/lib/rubocop/cli.rb +2 -4
- data/lib/rubocop/comment_config/directive_range.rb +32 -0
- data/lib/rubocop/comment_config/disable_next.rb +97 -0
- data/lib/rubocop/comment_config.rb +81 -49
- data/lib/rubocop/config.rb +76 -5
- data/lib/rubocop/config_loader.rb +1 -2
- data/lib/rubocop/config_loader_resolver.rb +1 -2
- data/lib/rubocop/config_validator.rb +39 -3
- data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
- data/lib/rubocop/cop/badge.rb +8 -0
- data/lib/rubocop/cop/base.rb +40 -6
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/corrector.rb +4 -4
- 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/internal_affairs/node_matcher_directive.rb +1 -2
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
- data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/class_structure.rb +108 -26
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
- data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
- data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
- 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_continuation_spacing.rb +1 -2
- data/lib/rubocop/cop/layout/line_length.rb +84 -12
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
- 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/rescue_ensure_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +31 -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/ambiguous_range.rb +1 -2
- data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
- data/lib/rubocop/cop/lint/constant_reassignment.rb +18 -21
- data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
- data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
- data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
- data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
- data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
- data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
- data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
- data/lib/rubocop/cop/lint/missing_super.rb +31 -1
- data/lib/rubocop/cop/lint/name_typo.rb +270 -0
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -2
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
- data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
- data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +38 -3
- data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
- data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
- data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
- data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
- data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
- data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
- data/lib/rubocop/cop/lint/void.rb +18 -8
- data/lib/rubocop/cop/lint.rb +178 -0
- data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
- 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 +1 -6
- data/lib/rubocop/cop/migration.rb +13 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
- data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
- 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/hash_shorthand_syntax.rb +1 -2
- data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
- data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
- data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
- data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
- data/lib/rubocop/cop/mixin/range_help.rb +1 -2
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
- data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
- data/lib/rubocop/cop/mixin.rb +82 -77
- data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
- data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
- data/lib/rubocop/cop/naming/method_name.rb +5 -2
- data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
- data/lib/rubocop/cop/naming/variable_number.rb +3 -1
- data/lib/rubocop/cop/naming.rb +31 -0
- data/lib/rubocop/cop/offense.rb +37 -10
- data/lib/rubocop/cop/registry.rb +180 -46
- data/lib/rubocop/cop/security.rb +19 -0
- data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
- data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
- data/lib/rubocop/cop/style/array_first_last.rb +1 -2
- data/lib/rubocop/cop/style/array_intersect.rb +24 -5
- data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
- data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
- data/lib/rubocop/cop/style/case_like_if.rb +2 -4
- data/lib/rubocop/cop/style/class_and_module_children.rb +79 -20
- data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
- data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
- data/lib/rubocop/cop/style/conditional_assignment.rb +23 -2
- data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
- data/lib/rubocop/cop/style/copyright.rb +5 -1
- data/lib/rubocop/cop/style/directive_scope.rb +171 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
- data/lib/rubocop/cop/style/documentation.rb +47 -0
- data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- data/lib/rubocop/cop/style/format_string_token.rb +1 -2
- data/lib/rubocop/cop/style/guard_clause.rb +1 -2
- data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
- data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
- data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -50
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
- 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/multiline_method_signature.rb +1 -2
- 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/negated_if_else_condition.rb +1 -2
- data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
- data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
- data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
- data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
- data/lib/rubocop/cop/style/redundant_assignment.rb +12 -2
- data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
- data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
- data/lib/rubocop/cop/style/redundant_each.rb +1 -2
- data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
- data/lib/rubocop/cop/style/redundant_format.rb +14 -7
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
- data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
- data/lib/rubocop/cop/style/redundant_return.rb +1 -2
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
- data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
- data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
- data/lib/rubocop/cop/style/safe_navigation.rb +11 -2
- data/lib/rubocop/cop/style/sample.rb +11 -6
- data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
- data/lib/rubocop/cop/style/semicolon.rb +2 -4
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +21 -5
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
- data/lib/rubocop/cop/style/static_class.rb +21 -0
- data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
- data/lib/rubocop/cop/style/super_arguments.rb +1 -2
- data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
- data/lib/rubocop/cop/style/time_now.rb +41 -0
- data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
- data/lib/rubocop/cop/style.rb +312 -0
- data/lib/rubocop/cop/team.rb +59 -6
- data/lib/rubocop/cop/util.rb +2 -4
- data/lib/rubocop/cop/variable_force/variable.rb +19 -8
- data/lib/rubocop/cops_documentation_generator.rb +4 -7
- data/lib/rubocop/directive_comment.rb +39 -3
- data/lib/rubocop/feature_loader.rb +0 -6
- data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
- data/lib/rubocop/formatter/html_formatter.rb +2 -4
- data/lib/rubocop/formatter/json_formatter.rb +11 -6
- data/lib/rubocop/formatter/junit_formatter.rb +26 -9
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
- data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
- data/lib/rubocop/formatter/tap_formatter.rb +3 -1
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
- data/lib/rubocop/lockfile.rb +18 -0
- data/lib/rubocop/lsp/diagnostic.rb +4 -8
- data/lib/rubocop/lsp/routes.rb +89 -22
- 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/mcp/server.rb +1 -2
- data/lib/rubocop/options.rb +10 -6
- data/lib/rubocop/path_util.rb +1 -2
- data/lib/rubocop/pending_cops_reporter.rb +1 -6
- data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
- data/lib/rubocop/project_index_loader.rb +16 -0
- data/lib/rubocop/rake_task.rb +1 -2
- data/lib/rubocop/result_cache.rb +6 -4
- data/lib/rubocop/rspec/expect_offense.rb +2 -4
- data/lib/rubocop/rspec/shared_contexts.rb +16 -2
- data/lib/rubocop/runner.rb +71 -43
- data/lib/rubocop/server/cache.rb +11 -7
- data/lib/rubocop/server/cli.rb +2 -4
- data/lib/rubocop/server.rb +14 -0
- data/lib/rubocop/target_finder.rb +4 -1
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/todo_audit.rb +111 -0
- data/lib/rubocop/version.rb +3 -5
- data/lib/rubocop.rb +20 -626
- data/lib/ruby_lsp/rubocop/addon.rb +1 -2
- metadata +28 -5
|
@@ -10,6 +10,32 @@ module RuboCop
|
|
|
10
10
|
# the developer intends to suppress Ruby's method redefinition warnings.
|
|
11
11
|
# See https://bugs.ruby-lang.org/issues/13574.
|
|
12
12
|
#
|
|
13
|
+
# By default the cop can only detect duplicates within a single file.
|
|
14
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed,
|
|
15
|
+
# the cop additionally consults the project-wide index and reports methods
|
|
16
|
+
# whose duplicate definition lives in another file.
|
|
17
|
+
#
|
|
18
|
+
# NOTE: The project index does not record whether a definition in another
|
|
19
|
+
# file is wrapped in a conditional, so a platform-specific redefinition in
|
|
20
|
+
# another file may still be reported. Aliasing the method to itself (see above)
|
|
21
|
+
# before redefining marks the redefinition as intentional and is respected
|
|
22
|
+
# across files. With `AllCops/ActiveSupportExtensionsEnabled: true`, Active
|
|
23
|
+
# Support's redefinition markers (`silence_redefinition_of_method` and
|
|
24
|
+
# `redefine_method`) are honored the same way.
|
|
25
|
+
#
|
|
26
|
+
# NOTE: Methods defined with `define_method` are not recorded in the project
|
|
27
|
+
# index, so a duplicate whose other definition uses `define_method` cannot
|
|
28
|
+
# be detected across files.
|
|
29
|
+
#
|
|
30
|
+
# Cross-file duplicates whose other definition lives in a file matching one
|
|
31
|
+
# of the `AllowedCrossFilePaths` patterns are not reported. This suits files
|
|
32
|
+
# that redefine application methods but are never loaded together with them,
|
|
33
|
+
# such as standalone scripts. Patterns are matched with the same glob (or
|
|
34
|
+
# regexp) semantics as `Exclude`, against the other file's path relative to
|
|
35
|
+
# the directory of the `.rubocop.yml` that configures the cop; absolute
|
|
36
|
+
# patterns are matched against the absolute path. Offenses inside such files
|
|
37
|
+
# themselves are best silenced with an ordinary per-cop `Exclude`.
|
|
38
|
+
#
|
|
13
39
|
# @example
|
|
14
40
|
#
|
|
15
41
|
# # bad
|
|
@@ -118,16 +144,64 @@ module RuboCop
|
|
|
118
144
|
# delegate :foo, to: :bar
|
|
119
145
|
# end
|
|
120
146
|
#
|
|
147
|
+
# # good - Active Support's redefinition markers signal an intentional
|
|
148
|
+
# # redefinition of a method defined in another file
|
|
149
|
+
# silence_redefinition_of_method :foo
|
|
150
|
+
# def foo
|
|
151
|
+
# 1
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# @example AllowedCrossFilePaths: ['script/**/*'] (default: [])
|
|
155
|
+
# # Cross-file duplicates whose other definition lives in a file
|
|
156
|
+
# # matching one of the patterns are not reported.
|
|
157
|
+
#
|
|
158
|
+
# # good - assuming `AppHelper#format` is also defined in
|
|
159
|
+
# # `script/backfill.rb`, which is never loaded with this file
|
|
160
|
+
# class AppHelper
|
|
161
|
+
# def format
|
|
162
|
+
# end
|
|
163
|
+
# end
|
|
164
|
+
#
|
|
165
|
+
# @example DelegatingMethods: ['delegate', 'expose'] (default: ['delegate'])
|
|
166
|
+
# # A project's own `delegate`-shaped macros can be registered so the
|
|
167
|
+
# # methods they define are recognized (with
|
|
168
|
+
# # `AllCops/ActiveSupportExtensionsEnabled: true`).
|
|
169
|
+
#
|
|
170
|
+
# # bad
|
|
171
|
+
# def foo
|
|
172
|
+
# 1
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# expose :foo, to: :bar
|
|
176
|
+
#
|
|
121
177
|
class DuplicateMethods < Base # rubocop:disable Metrics/ClassLength
|
|
178
|
+
include ProjectIndexHelp
|
|
179
|
+
|
|
122
180
|
MSG = 'Method `%<method>s` is defined at both %<defined>s and %<current>s.'
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
181
|
+
|
|
182
|
+
# Method names the cop registers that can be looked up in the project index:
|
|
183
|
+
# a fully qualified namespace followed by `#` (instance) or `.` (singleton)
|
|
184
|
+
# and the method name.
|
|
185
|
+
INDEXABLE_METHOD_NAME =
|
|
186
|
+
/\A(?<owner>[A-Z]\w*(?:::[A-Z]\w*)*)(?<separator>[#.])(?<name>[^#.]+)\z/.freeze
|
|
187
|
+
# No `RESTRICT_ON_SEND`: the delegating method names are configurable
|
|
188
|
+
# (`DelegatingMethods`), so `on_send` must see every call. The handler
|
|
189
|
+
# returns quickly for calls that are not method definitions, and
|
|
190
|
+
# benchmarks show no measurable cost over the previous fixed list.
|
|
126
191
|
|
|
127
192
|
def initialize(config = nil, options = nil)
|
|
128
193
|
super
|
|
129
194
|
@definitions = {}
|
|
130
195
|
@scopes = Hash.new { |hash, key| hash[key] = [] }
|
|
196
|
+
@intentionally_redefined = Set.new
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def on_new_investigation
|
|
200
|
+
# The self-alias trick and Active Support's redefinition markers declare
|
|
201
|
+
# an intentional redefinition only within the file that uses them, so the
|
|
202
|
+
# tracked names do not carry over.
|
|
203
|
+
@intentionally_redefined = Set.new
|
|
204
|
+
super
|
|
131
205
|
end
|
|
132
206
|
|
|
133
207
|
def on_def(node)
|
|
@@ -157,7 +231,11 @@ module RuboCop
|
|
|
157
231
|
def on_alias(node)
|
|
158
232
|
name, original_name = method_alias?(node)
|
|
159
233
|
return unless name && original_name
|
|
160
|
-
|
|
234
|
+
|
|
235
|
+
if name == original_name
|
|
236
|
+
track_intentional_redefinition(node, name)
|
|
237
|
+
return
|
|
238
|
+
end
|
|
161
239
|
return if node.ancestors.any?(&:if_type?)
|
|
162
240
|
|
|
163
241
|
found_instance_method(node, name)
|
|
@@ -168,9 +246,12 @@ module RuboCop
|
|
|
168
246
|
(send nil? :alias_method (sym $_name) (sym $_original_name))
|
|
169
247
|
PATTERN
|
|
170
248
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
|
|
249
|
+
# Matches the argument shape of an Active Support `delegate` call
|
|
250
|
+
# (`delegate :a, :b, to: :target`), regardless of the method name; the
|
|
251
|
+
# name is checked separately against `DelegatingMethods`.
|
|
252
|
+
# @!method delegate_args(node)
|
|
253
|
+
def_node_matcher :delegate_args, <<~PATTERN
|
|
254
|
+
(send nil? _
|
|
174
255
|
({sym str} $_)+
|
|
175
256
|
(hash <(pair (sym :to) {sym str}) ...>)
|
|
176
257
|
)
|
|
@@ -194,6 +275,15 @@ module RuboCop
|
|
|
194
275
|
)
|
|
195
276
|
PATTERN
|
|
196
277
|
|
|
278
|
+
# Matches Active Support's markers of an intentional method redefinition:
|
|
279
|
+
# `silence_redefinition_of_method :name` suppresses Ruby's redefinition
|
|
280
|
+
# warning for a subsequent definition of `name`, and
|
|
281
|
+
# `redefine_method(:name) { ... }` both silences the warning and redefines.
|
|
282
|
+
# @!method active_support_redefinition_marker(node)
|
|
283
|
+
def_node_matcher :active_support_redefinition_marker, <<~PATTERN
|
|
284
|
+
(send nil? {:silence_redefinition_of_method :redefine_method} ({sym str} $_) ...)
|
|
285
|
+
PATTERN
|
|
286
|
+
|
|
197
287
|
# @!method sym_name(node)
|
|
198
288
|
def_node_matcher :sym_name, '(sym $_name)'
|
|
199
289
|
|
|
@@ -204,17 +294,27 @@ module RuboCop
|
|
|
204
294
|
...)
|
|
205
295
|
PATTERN
|
|
206
296
|
|
|
297
|
+
# @!method class_new_block?(node)
|
|
298
|
+
def_node_matcher :class_new_block?, <<~PATTERN
|
|
299
|
+
(block
|
|
300
|
+
(send (const _ :Class) :new ...)
|
|
301
|
+
...)
|
|
302
|
+
PATTERN
|
|
303
|
+
|
|
207
304
|
def on_send(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
208
305
|
name, original_name = alias_method?(node)
|
|
209
306
|
|
|
210
307
|
if name && original_name
|
|
211
|
-
|
|
308
|
+
if name == original_name
|
|
309
|
+
track_intentional_redefinition(node, name)
|
|
310
|
+
return
|
|
311
|
+
end
|
|
212
312
|
return if inside_condition?(node)
|
|
213
313
|
|
|
214
314
|
found_instance_method(node, name)
|
|
215
315
|
elsif (attr = node.attribute_accessor?)
|
|
216
316
|
on_attr(node, *attr)
|
|
217
|
-
elsif
|
|
317
|
+
elsif delegating_method?(node) && (names = delegate_args(node))
|
|
218
318
|
return if inside_condition?(node)
|
|
219
319
|
|
|
220
320
|
on_delegate(node, names)
|
|
@@ -226,6 +326,12 @@ module RuboCop
|
|
|
226
326
|
return if inside_condition?(node)
|
|
227
327
|
|
|
228
328
|
names.each { |name| found_instance_method(node, name) }
|
|
329
|
+
elsif (name = active_support_redefinition_marker(node))
|
|
330
|
+
# `redefine_method` takes a block; scope resolution must start from the
|
|
331
|
+
# block node, since `parent_module_name` cannot see through a block
|
|
332
|
+
# ancestor that is not a module constructor.
|
|
333
|
+
track_intentional_redefinition(node.block_node || node, name) if
|
|
334
|
+
active_support_extensions_enabled?
|
|
229
335
|
end
|
|
230
336
|
end
|
|
231
337
|
|
|
@@ -257,6 +363,19 @@ module RuboCop
|
|
|
257
363
|
current: source_location(node))
|
|
258
364
|
end
|
|
259
365
|
|
|
366
|
+
# Recognizing a delegating call as a method definition is Active
|
|
367
|
+
# Support behavior, so it stays gated on `ActiveSupportExtensionsEnabled`
|
|
368
|
+
# as it was when only `delegate` was supported. `DelegatingMethods` lets
|
|
369
|
+
# a project register its own `delegate`-shaped macros.
|
|
370
|
+
def delegating_method?(node)
|
|
371
|
+
active_support_extensions_enabled? &&
|
|
372
|
+
delegating_methods.include?(node.method_name.to_s)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def delegating_methods
|
|
376
|
+
cop_config.fetch('DelegatingMethods', ['delegate'])
|
|
377
|
+
end
|
|
378
|
+
|
|
260
379
|
def on_delegate(node, method_names)
|
|
261
380
|
name_prefix = delegate_prefix(node)
|
|
262
381
|
|
|
@@ -318,13 +437,27 @@ module RuboCop
|
|
|
318
437
|
parent = anon_block.parent
|
|
319
438
|
return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)
|
|
320
439
|
|
|
321
|
-
if (receiver =
|
|
440
|
+
if (receiver = scope_receiver(parent, anon_block))
|
|
322
441
|
"#{receiver.source}.#{parent.method_name}"
|
|
323
442
|
elsif !parent.begin_type? || parent.parent&.any_block_type?
|
|
324
|
-
|
|
443
|
+
anon_block_identity(anon_block)
|
|
325
444
|
end
|
|
326
445
|
end
|
|
327
446
|
|
|
447
|
+
# When a Class.new block is passed as an argument to a named-receiver
|
|
448
|
+
# method call (e.g. T.cast(Class.new(Base) do ... end, ...)), the
|
|
449
|
+
# receiver-based scope id (e.g. "T.cast") is the same for every call,
|
|
450
|
+
# causing false positives for methods defined in distinct anonymous
|
|
451
|
+
# classes. Return nil so the block falls through to the unique
|
|
452
|
+
# source-location-based scope id.
|
|
453
|
+
# Module.new blocks are excluded because they may be intentionally
|
|
454
|
+
# mixed into the same target via prepend/include/extend.
|
|
455
|
+
def scope_receiver(parent, anon_block)
|
|
456
|
+
return if class_new_block?(anon_block) && parent.call_type?
|
|
457
|
+
|
|
458
|
+
named_receiver(parent)
|
|
459
|
+
end
|
|
460
|
+
|
|
328
461
|
def named_receiver(node)
|
|
329
462
|
receiver = node.receiver
|
|
330
463
|
receiver unless class_or_module_new_block?(receiver)
|
|
@@ -340,27 +473,57 @@ module RuboCop
|
|
|
340
473
|
found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
|
|
341
474
|
end
|
|
342
475
|
|
|
343
|
-
# rubocop:disable Metrics/AbcSize
|
|
344
476
|
def found_method(node, method_name, scope_id: nil)
|
|
345
477
|
key = method_key(node, method_name)
|
|
346
478
|
key = "#{key}@#{scope_id}" if scope_id
|
|
347
479
|
scope = node.each_ancestor(:rescue, :ensure).first&.type
|
|
348
480
|
|
|
349
481
|
if @definitions.key?(key)
|
|
350
|
-
|
|
351
|
-
@definitions[key] = node
|
|
352
|
-
@scopes[scope] << key
|
|
353
|
-
return
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
message = message_for_dup(node, method_name, key)
|
|
357
|
-
|
|
358
|
-
add_offense(location(node), message: message)
|
|
482
|
+
found_redefinition(node, method_name, key, scope)
|
|
359
483
|
else
|
|
360
484
|
@definitions[key] = node
|
|
485
|
+
check_cross_file_duplicate(node, method_name) if scope_id.nil? && scope.nil?
|
|
361
486
|
end
|
|
362
487
|
end
|
|
363
|
-
|
|
488
|
+
|
|
489
|
+
def found_redefinition(node, method_name, key, scope)
|
|
490
|
+
if scope && !@scopes[scope].include?(key)
|
|
491
|
+
@definitions[key] = node
|
|
492
|
+
@scopes[scope] << key
|
|
493
|
+
elsif intentional_cross_file_redefinition?(node, method_name, key) ||
|
|
494
|
+
allowed_cross_file_redefinition?(node, key)
|
|
495
|
+
@definitions[key] = node
|
|
496
|
+
else
|
|
497
|
+
add_offense(location(node), message: message_for_dup(node, method_name, key))
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# `AllowedCrossFilePaths` also covers duplicates detected without the index,
|
|
502
|
+
# when the cop instance carries definitions over from an earlier file of the
|
|
503
|
+
# same run.
|
|
504
|
+
def allowed_cross_file_redefinition?(node, key)
|
|
505
|
+
other_path = @definitions[key].source_range.source_buffer.name
|
|
506
|
+
return false if other_path == node.source_range.source_buffer.name
|
|
507
|
+
|
|
508
|
+
allowed_cross_file_path?(other_path)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# The self-alias trick (`alias foo foo` or `alias_method :foo, :foo` right before
|
|
512
|
+
# a `def`) suppresses Ruby's method redefinition warning, signaling an intentional
|
|
513
|
+
# redefinition of a method defined in another file. Active Support's redefinition
|
|
514
|
+
# markers (tracked when `ActiveSupportExtensionsEnabled` is on) signal the same.
|
|
515
|
+
def intentional_cross_file_redefinition?(node, method_name, key)
|
|
516
|
+
@intentionally_redefined.include?(method_name) &&
|
|
517
|
+
@definitions[key].source_range.source_buffer.name !=
|
|
518
|
+
node.source_range.source_buffer.name
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def track_intentional_redefinition(node, name)
|
|
522
|
+
scope = node.parent_module_name
|
|
523
|
+
return unless scope
|
|
524
|
+
|
|
525
|
+
@intentionally_redefined << "#{humanize_scope(scope)}#{name}"
|
|
526
|
+
end
|
|
364
527
|
|
|
365
528
|
def method_key(node, method_name)
|
|
366
529
|
if (ancestor_def = node.each_ancestor(:any_def).first)
|
|
@@ -439,6 +602,121 @@ module RuboCop
|
|
|
439
602
|
path = smart_path(range.source_buffer.name)
|
|
440
603
|
"#{path}:#{range.line}"
|
|
441
604
|
end
|
|
605
|
+
|
|
606
|
+
def check_cross_file_duplicate(node, method_name)
|
|
607
|
+
return unless project_index
|
|
608
|
+
return if @intentionally_redefined.include?(method_name)
|
|
609
|
+
return if node.each_ancestor(:any_def).any?
|
|
610
|
+
return unless (prior = cross_file_prior_definition(method_name))
|
|
611
|
+
|
|
612
|
+
message = format(MSG, method: method_name,
|
|
613
|
+
defined: index_source_location(prior),
|
|
614
|
+
current: source_location(node))
|
|
615
|
+
add_offense(location(node), message: message)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def cross_file_prior_definition(method_name)
|
|
619
|
+
return unless (match = INDEXABLE_METHOD_NAME.match(method_name))
|
|
620
|
+
|
|
621
|
+
definitions = definitions_in_other_files(
|
|
622
|
+
indexed_definitions(match[:owner], match[:separator], match[:name])
|
|
623
|
+
).reject { |definition| allowed_cross_file_path?(definition.location.to_file_path) }
|
|
624
|
+
return if definitions.empty? || cross_file_self_alias_trick?(definitions)
|
|
625
|
+
return if cross_file_redefinition_marker?(definitions, match[:name])
|
|
626
|
+
|
|
627
|
+
first_indexed_definition(definitions)
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# Cross-file duplicates whose other definition site matches one of the
|
|
631
|
+
# `AllowedCrossFilePaths` patterns are skipped (e.g. standalone scripts that
|
|
632
|
+
# are never loaded together with the code they redefine). Patterns are
|
|
633
|
+
# matched like `Exclude` patterns, against the path relative to the
|
|
634
|
+
# directory of the config file that configures the cop; absolute patterns
|
|
635
|
+
# are matched against the absolute path.
|
|
636
|
+
def allowed_cross_file_path?(path)
|
|
637
|
+
patterns = cop_config.fetch('AllowedCrossFilePaths', [])
|
|
638
|
+
return false if patterns.empty?
|
|
639
|
+
|
|
640
|
+
relative = config.path_relative_to_config(path)
|
|
641
|
+
patterns.any? { |pattern| match_path?(pattern, relative) || match_path?(pattern, path) }
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def indexed_definitions(owner, separator, name)
|
|
645
|
+
namespace = separator == '.' ? "#{owner}::<#{owner.split('::').last}>" : owner
|
|
646
|
+
|
|
647
|
+
if name.match?(/\A\w+=\z/)
|
|
648
|
+
# Rubydex indexes `attr_writer :foo` under `foo` rather than `foo=`, so
|
|
649
|
+
# writer definitions come from both the `foo=` and the `foo` declarations.
|
|
650
|
+
indexed_declaration_definitions(namespace, name) +
|
|
651
|
+
indexed_declaration_definitions(namespace, name.delete_suffix('='))
|
|
652
|
+
.select { |definition| writer_attr_definition?(definition) }
|
|
653
|
+
else
|
|
654
|
+
indexed_declaration_definitions(namespace, name).grep_v(Rubydex::AttrWriterDefinition)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def indexed_declaration_definitions(namespace, name)
|
|
659
|
+
project_index["#{namespace}##{name}()"]&.definitions.to_a
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def writer_attr_definition?(definition)
|
|
663
|
+
definition.is_a?(Rubydex::AttrWriterDefinition) ||
|
|
664
|
+
definition.is_a?(Rubydex::AttrAccessorDefinition)
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# An alias of the method alongside one of its definitions in another file may be
|
|
668
|
+
# the self-alias trick marking an intentional redefinition there, so no offense
|
|
669
|
+
# is registered. A genuine `alias` duplicate is still reported when the alias
|
|
670
|
+
# itself is inspected.
|
|
671
|
+
def cross_file_self_alias_trick?(definitions)
|
|
672
|
+
aliases, others = definitions.partition do |definition|
|
|
673
|
+
definition.is_a?(Rubydex::MethodAliasDefinition)
|
|
674
|
+
end
|
|
675
|
+
alias_paths = aliases.map { |definition| definition.location.to_file_path }
|
|
676
|
+
|
|
677
|
+
others.any? { |definition| alias_paths.include?(definition.location.to_file_path) }
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
# An Active Support redefinition marker alongside one of the definitions in
|
|
681
|
+
# another file marks the redefinition there as intentional, like the
|
|
682
|
+
# self-alias trick. The index does not record the marker's method-name
|
|
683
|
+
# argument, so the other file's source is searched for a marker naming
|
|
684
|
+
# the method.
|
|
685
|
+
def cross_file_redefinition_marker?(definitions, name)
|
|
686
|
+
return false unless active_support_extensions_enabled?
|
|
687
|
+
|
|
688
|
+
definitions.map { |definition| definition.location.to_file_path }.uniq.any? do |path|
|
|
689
|
+
redefinition_marker_in_file?(path, name)
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def redefinition_marker_in_file?(path, name)
|
|
694
|
+
return false unless File.readable?(path)
|
|
695
|
+
|
|
696
|
+
escaped = Regexp.escape(name)
|
|
697
|
+
marker = /\b(?:silence_redefinition_of_method|redefine_method)\s*\(?\s*
|
|
698
|
+
(?::#{escaped}|(["'])#{escaped}\1)(?![\w=!?])/x
|
|
699
|
+
File.read(path).scrub.match?(marker)
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def first_indexed_definition(definitions)
|
|
703
|
+
definitions.find { |definition| !definition.is_a?(Rubydex::MethodAliasDefinition) }
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def index_source_location(definition)
|
|
707
|
+
location = definition.location
|
|
708
|
+
"#{smart_path(location.to_file_path)}:#{location.to_display.start_line}"
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# Internal identity for an anonymous block, used as a scope key.
|
|
712
|
+
# Includes the source range's begin position to distinguish blocks
|
|
713
|
+
# that share the same line (e.g. two Class.new calls separated by `;`).
|
|
714
|
+
# The user-facing offense message still uses `source_location`, which
|
|
715
|
+
# shows only `path:line`.
|
|
716
|
+
def anon_block_identity(anon_block)
|
|
717
|
+
range = anon_block.source_range
|
|
718
|
+
"#{smart_path(range.source_buffer.name)}:#{range.line}:#{range.begin_pos}"
|
|
719
|
+
end
|
|
442
720
|
end
|
|
443
721
|
end
|
|
444
722
|
end
|
|
@@ -28,7 +28,7 @@ module RuboCop
|
|
|
28
28
|
FOR_METHOD = ' Or, if they were intended to be separate method ' \
|
|
29
29
|
'arguments, separate them with a comma.'
|
|
30
30
|
|
|
31
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
31
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
32
32
|
def on_dstr(node)
|
|
33
33
|
each_bad_cons(node) do |lhs_node, rhs_node|
|
|
34
34
|
range = lhs_node.source_range.join(rhs_node.source_range)
|
|
@@ -52,7 +52,6 @@ module RuboCop
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
56
55
|
|
|
57
56
|
private
|
|
58
57
|
|
|
@@ -80,7 +80,7 @@ module RuboCop
|
|
|
80
80
|
alternative: alternative)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
83
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
84
84
|
def ineffective_modifier(node, ignored_methods = nil, modifier = nil, &block)
|
|
85
85
|
node.each_child_node do |child|
|
|
86
86
|
case child.type
|
|
@@ -97,7 +97,6 @@ module RuboCop
|
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
101
100
|
|
|
102
101
|
def access_modifier?(node)
|
|
103
102
|
node.bare_access_modifier? && !node.method?(:module_function)
|
|
@@ -12,6 +12,11 @@ module RuboCop
|
|
|
12
12
|
# exception class handle `StandardError` and its subclasses,
|
|
13
13
|
# but not `Exception` and its subclasses.
|
|
14
14
|
#
|
|
15
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
|
|
16
|
+
# installed, indirect inheritance is also detected: a class whose parent
|
|
17
|
+
# (defined anywhere in the project) ultimately inherits from `Exception`
|
|
18
|
+
# is reported, without autocorrection.
|
|
19
|
+
#
|
|
15
20
|
# @example EnforcedStyle: standard_error (default)
|
|
16
21
|
# # bad
|
|
17
22
|
#
|
|
@@ -39,9 +44,11 @@ module RuboCop
|
|
|
39
44
|
# C = Class.new(RuntimeError)
|
|
40
45
|
class InheritException < Base
|
|
41
46
|
include ConfigurableEnforcedStyle
|
|
47
|
+
include ProjectIndexHelp
|
|
42
48
|
extend AutoCorrector
|
|
43
49
|
|
|
44
50
|
MSG = 'Inherit from `%<prefer>s` instead of `Exception`.'
|
|
51
|
+
INDIRECT_MSG = 'Inherit from `%<prefer>s` instead of `Exception` (inherited via `%<via>s`).'
|
|
45
52
|
PREFERRED_BASE_CLASS = {
|
|
46
53
|
runtime_error: 'RuntimeError',
|
|
47
54
|
standard_error: 'StandardError'
|
|
@@ -57,13 +64,20 @@ module RuboCop
|
|
|
57
64
|
PATTERN
|
|
58
65
|
|
|
59
66
|
def on_class(node)
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
parent_class = node.parent_class
|
|
68
|
+
return unless parent_class
|
|
69
|
+
|
|
70
|
+
if exception_class?(parent_class)
|
|
71
|
+
return if inherit_exception_class_with_omitted_namespace?(node)
|
|
72
|
+
|
|
73
|
+
add_offense(parent_class, message: message(parent_class)) do |corrector|
|
|
74
|
+
corrector.replace(parent_class, preferred_base_class)
|
|
75
|
+
end
|
|
76
|
+
elsif (via = inherits_exception_via(parent_class))
|
|
77
|
+
# No autocorrection: the `Exception` inheritance lives at another
|
|
78
|
+
# class' definition site, possibly in another file.
|
|
79
|
+
message = format(INDIRECT_MSG, prefer: preferred_base_class, via: via)
|
|
80
|
+
add_offense(parent_class, message: message)
|
|
67
81
|
end
|
|
68
82
|
end
|
|
69
83
|
|
|
@@ -88,6 +102,38 @@ module RuboCop
|
|
|
88
102
|
class_node.const_name == 'Exception'
|
|
89
103
|
end
|
|
90
104
|
|
|
105
|
+
# When `AllCops/UseProjectIndex` is enabled, indirect inheritance is
|
|
106
|
+
# detected by walking the parent's indexed ancestry: `Exception` itself
|
|
107
|
+
# is not indexed, so a chain ending in it shows up as an ancestor whose
|
|
108
|
+
# superclass reference is unresolved and literally named `Exception`.
|
|
109
|
+
# Returns the name of that ancestor, or `nil`.
|
|
110
|
+
def inherits_exception_via(parent_class)
|
|
111
|
+
return nil unless project_index && parent_class.const_type?
|
|
112
|
+
|
|
113
|
+
declaration = resolve_constant_in_index(parent_class)
|
|
114
|
+
return nil unless declaration.is_a?(Rubydex::Class)
|
|
115
|
+
|
|
116
|
+
exception_ancestor(declaration)&.name
|
|
117
|
+
rescue StandardError
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def exception_ancestor(declaration)
|
|
122
|
+
declaration.ancestors.find do |ancestor|
|
|
123
|
+
ancestor.is_a?(Rubydex::Class) && exception_superclass_reference?(ancestor)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def exception_superclass_reference?(ancestor)
|
|
128
|
+
ancestor.definitions.any? do |definition|
|
|
129
|
+
next false unless definition.is_a?(Rubydex::ClassDefinition)
|
|
130
|
+
|
|
131
|
+
superclass = definition.superclass
|
|
132
|
+
superclass.is_a?(Rubydex::UnresolvedConstantReference) &&
|
|
133
|
+
superclass.name.delete_prefix('::') == 'Exception'
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
91
137
|
def inherit_exception_class_with_omitted_namespace?(class_node)
|
|
92
138
|
return false if class_node.parent_class.namespace&.cbase_type?
|
|
93
139
|
|
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
|
41
41
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
44
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
45
45
|
def check(node)
|
|
46
46
|
return if node.parent&.regexp_type?
|
|
47
47
|
return unless /(?<!\\)#\{.*\}/.match?(node.source)
|
|
@@ -51,7 +51,6 @@ module RuboCop
|
|
|
51
51
|
|
|
52
52
|
add_offense(node) { |corrector| autocorrect(corrector, node) }
|
|
53
53
|
end
|
|
54
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
55
54
|
|
|
56
55
|
def autocorrect(corrector, node)
|
|
57
56
|
starting_token, ending_token = if node.source.include?('"')
|
|
@@ -88,7 +88,7 @@ module RuboCop
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# rubocop:disable Metrics/AbcSize
|
|
91
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
92
92
|
def on_while_post(node)
|
|
93
93
|
return if node.condition.source == 'true'
|
|
94
94
|
|
|
@@ -102,7 +102,6 @@ module RuboCop
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
|
-
# rubocop:enable Metrics/AbcSize
|
|
106
105
|
|
|
107
106
|
def on_until(node)
|
|
108
107
|
return if node.condition.source == 'false'
|
|
@@ -118,7 +117,7 @@ module RuboCop
|
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
119
|
|
|
121
|
-
# rubocop:disable Metrics/AbcSize
|
|
120
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
122
121
|
def on_until_post(node)
|
|
123
122
|
return if node.condition.source == 'false'
|
|
124
123
|
|
|
@@ -132,7 +131,6 @@ module RuboCop
|
|
|
132
131
|
end
|
|
133
132
|
end
|
|
134
133
|
end
|
|
135
|
-
# rubocop:enable Metrics/AbcSize
|
|
136
134
|
|
|
137
135
|
def on_case(case_node)
|
|
138
136
|
if (cond = case_node.condition)
|
|
@@ -250,10 +248,15 @@ module RuboCop
|
|
|
250
248
|
end
|
|
251
249
|
end
|
|
252
250
|
|
|
253
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
251
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
254
252
|
def correct_if_node(node, cond)
|
|
255
253
|
result = condition_evaluation?(node, cond)
|
|
256
254
|
|
|
255
|
+
# When the branch that survives the literal condition is missing,
|
|
256
|
+
# there is no meaningful correction, so no offense is registered.
|
|
257
|
+
surviving_branch = result ? node.if_branch : node.else_branch
|
|
258
|
+
return if surviving_branch.nil? && (node.elsif? || node.else?)
|
|
259
|
+
|
|
257
260
|
new_node = if node.elsif? && result
|
|
258
261
|
"else\n #{range_with_comments(node.if_branch).source}"
|
|
259
262
|
elsif node.elsif? && !result
|
|
@@ -276,7 +279,6 @@ module RuboCop
|
|
|
276
279
|
ignore_node(node)
|
|
277
280
|
end
|
|
278
281
|
end
|
|
279
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
280
282
|
end
|
|
281
283
|
end
|
|
282
284
|
end
|
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
|
24
24
|
MSG = 'Literal interpolation detected.'
|
|
25
25
|
COMPOSITE = %i[array hash pair irange erange].freeze
|
|
26
26
|
|
|
27
|
-
# rubocop:disable Metrics/AbcSize
|
|
27
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
28
28
|
def on_interpolation(begin_node)
|
|
29
29
|
final_node = begin_node.children.last
|
|
30
30
|
return unless offending?(final_node)
|
|
@@ -50,7 +50,6 @@ module RuboCop
|
|
|
50
50
|
corrector.replace(final_node.parent, replacement)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
# rubocop:enable Metrics/AbcSize
|
|
54
53
|
|
|
55
54
|
private
|
|
56
55
|
|
|
@@ -74,7 +73,7 @@ module RuboCop
|
|
|
74
73
|
node.array_type? && grandparent.regexp_type?
|
|
75
74
|
end
|
|
76
75
|
|
|
77
|
-
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
76
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
78
77
|
def autocorrected_value(node)
|
|
79
78
|
case node.type
|
|
80
79
|
when :int
|
|
@@ -95,7 +94,6 @@ module RuboCop
|
|
|
95
94
|
node.source.gsub('"', '\"')
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
|
-
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
99
97
|
|
|
100
98
|
def handle_special_regexp_chars(begin_node, value)
|
|
101
99
|
parent_node = begin_node.parent
|
|
@@ -155,7 +153,7 @@ module RuboCop
|
|
|
155
153
|
"{#{hash_string}}"
|
|
156
154
|
end
|
|
157
155
|
|
|
158
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
156
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
|
|
159
157
|
def autocorrected_value_in_hash(node)
|
|
160
158
|
case node.type
|
|
161
159
|
when :int
|
|
@@ -163,7 +161,7 @@ module RuboCop
|
|
|
163
161
|
when :float
|
|
164
162
|
node.children.last.to_f.to_s
|
|
165
163
|
when :str
|
|
166
|
-
|
|
164
|
+
escape_string_content(node.value.inspect)
|
|
167
165
|
when :sym
|
|
168
166
|
autocorrected_value_in_hash_for_symbol(node)
|
|
169
167
|
when :array
|
|
@@ -174,7 +172,6 @@ module RuboCop
|
|
|
174
172
|
node.source.gsub('"', '\"')
|
|
175
173
|
end
|
|
176
174
|
end
|
|
177
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
178
175
|
|
|
179
176
|
# Does node print its own source when converted to a string?
|
|
180
177
|
def prints_as_self?(node)
|