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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Lint
|
|
6
|
+
# Checks for private instance methods that are not referenced anywhere
|
|
7
|
+
# in the project.
|
|
8
|
+
#
|
|
9
|
+
# The check is powered by the project-wide index, so it only runs when
|
|
10
|
+
# `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
|
|
11
|
+
# Without the index the cop does nothing.
|
|
12
|
+
#
|
|
13
|
+
# A method counts as referenced when a call with its name appears anywhere
|
|
14
|
+
# in the indexed project (regardless of the receiver), when it is the source
|
|
15
|
+
# of an `alias`, or when its name appears in the same file as a symbol or
|
|
16
|
+
# inside a string literal (covering `send(:name)` and declarative DSLs like
|
|
17
|
+
# `before_action :name`). An interpolated symbol or string with a literal
|
|
18
|
+
# prefix (e.g. `send(:"format_#{type}")`) counts as a reference to every
|
|
19
|
+
# method whose name starts with that prefix; fully dynamic names cannot
|
|
20
|
+
# be detected. Methods defined in classes or modules with descendants are
|
|
21
|
+
# not checked, since they may be invoked through `super` or inherited
|
|
22
|
+
# dispatch.
|
|
23
|
+
#
|
|
24
|
+
# Methods whose names are composed by a framework (e.g. Rails
|
|
25
|
+
# `attribute_method_suffix` generating `attribute?` methods) can be
|
|
26
|
+
# excluded from the check with `AllowedNames` (exact names) or
|
|
27
|
+
# `AllowedPatterns` (regular expressions).
|
|
28
|
+
#
|
|
29
|
+
# The cop is disabled by default because symbol-based references from
|
|
30
|
+
# *other* files (e.g. a Rails callback declared in a concern) cannot be
|
|
31
|
+
# detected and would be reported as false positives. It is best suited
|
|
32
|
+
# for occasional dead-code sweeps rather than permanent enforcement.
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# # bad - `helper` is never referenced anywhere in the project
|
|
36
|
+
# class Service
|
|
37
|
+
# def call
|
|
38
|
+
# do_something
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# private
|
|
42
|
+
#
|
|
43
|
+
# def helper
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# # good
|
|
48
|
+
# class Service
|
|
49
|
+
# def call
|
|
50
|
+
# do_something(helper)
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# private
|
|
54
|
+
#
|
|
55
|
+
# def helper
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# @example AllowedNames: ['attribute?']
|
|
60
|
+
# # good - the name is explicitly allowed
|
|
61
|
+
# class Contact
|
|
62
|
+
# private
|
|
63
|
+
#
|
|
64
|
+
# def attribute?
|
|
65
|
+
# end
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# @example AllowedPatterns: ['_hook\z']
|
|
69
|
+
# # good - the name matches an allowed pattern
|
|
70
|
+
# class Service
|
|
71
|
+
# private
|
|
72
|
+
#
|
|
73
|
+
# def before_save_hook
|
|
74
|
+
# end
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
class UnusedPrivateMethod < Base
|
|
78
|
+
include AllowedPattern
|
|
79
|
+
include ProjectIndexHelp
|
|
80
|
+
|
|
81
|
+
MSG = 'Private method `%<method>s` appears to be unused.'
|
|
82
|
+
|
|
83
|
+
IDENTIFIER_PATTERN = /[a-zA-Z_]\w*[?!=]?/.freeze
|
|
84
|
+
NAME_PREFIX_PATTERN = /[a-zA-Z_]\w*\z/.freeze
|
|
85
|
+
|
|
86
|
+
# Methods invoked implicitly by the Ruby runtime.
|
|
87
|
+
IMPLICITLY_INVOKED_METHODS = %i[
|
|
88
|
+
initialize initialize_copy initialize_clone initialize_dup
|
|
89
|
+
method_missing respond_to_missing? marshal_dump marshal_load
|
|
90
|
+
coerce instance_variables_to_inspect
|
|
91
|
+
inherited included extended prepended
|
|
92
|
+
append_features extend_object prepend_features
|
|
93
|
+
const_missing const_added
|
|
94
|
+
method_added method_removed method_undefined
|
|
95
|
+
singleton_method_added singleton_method_removed singleton_method_undefined
|
|
96
|
+
].to_set.freeze
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
# The reference-name set is a property of the index, not of the cop
|
|
100
|
+
# instance, so all per-file instances share one computation. A
|
|
101
|
+
# single-entry cache (instead of a hash keyed by index) avoids
|
|
102
|
+
# retaining stale graphs in long-lived processes.
|
|
103
|
+
attr_accessor :cached_reference_names
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_new_investigation
|
|
107
|
+
@literal_names = nil
|
|
108
|
+
@literal_name_prefixes = nil
|
|
109
|
+
super
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def on_def(node)
|
|
113
|
+
return unless project_index
|
|
114
|
+
|
|
115
|
+
declaration = checkable_declaration(node)
|
|
116
|
+
return unless declaration
|
|
117
|
+
return if referenced?(node.method_name)
|
|
118
|
+
return if owner_with_descendants?(declaration) || override?(declaration, node.method_name)
|
|
119
|
+
|
|
120
|
+
message = format(MSG, method: node.method_name)
|
|
121
|
+
add_offense(node.loc.keyword.join(node.loc.name), message: message)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def checkable_declaration(node)
|
|
127
|
+
return nil if IMPLICITLY_INVOKED_METHODS.include?(node.method_name)
|
|
128
|
+
return nil if allowed_name?(node.method_name)
|
|
129
|
+
return nil if node.each_ancestor(:any_def, :any_block, :sclass).any?
|
|
130
|
+
return nil unless (namespace_node = node.each_ancestor(:class, :module).first)
|
|
131
|
+
|
|
132
|
+
declaration = method_declaration(node, namespace_node)
|
|
133
|
+
declaration if declaration&.private?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def allowed_name?(method_name)
|
|
137
|
+
name = method_name.to_s
|
|
138
|
+
|
|
139
|
+
cop_config.fetch('AllowedNames', []).include?(name) || matches_allowed_pattern?(name)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def method_declaration(node, namespace_node)
|
|
143
|
+
namespace = resolve_constant_in_index(namespace_node.identifier)
|
|
144
|
+
return nil unless namespace.is_a?(Rubydex::Namespace)
|
|
145
|
+
|
|
146
|
+
namespace.member("#{node.method_name}()")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def referenced?(method_name)
|
|
150
|
+
name = method_name.to_s
|
|
151
|
+
|
|
152
|
+
referenced_names.include?(name) || literal_names.include?(name) ||
|
|
153
|
+
literal_name_prefixes.any? { |prefix| name.start_with?(prefix) }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def referenced_names
|
|
157
|
+
index, names = self.class.cached_reference_names
|
|
158
|
+
return names if index.equal?(project_index)
|
|
159
|
+
|
|
160
|
+
compute_referenced_names.tap do |computed|
|
|
161
|
+
self.class.cached_reference_names = [project_index, computed]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def compute_referenced_names
|
|
166
|
+
project_index.method_references
|
|
167
|
+
.to_set { |reference| reference.name.delete_suffix('()') }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Symbol literals and identifier-like tokens inside string literals in the
|
|
171
|
+
# current file, covering `send(:name)`, declarative DSLs like
|
|
172
|
+
# `before_action :name` and names embedded in strings (e.g. node-pattern
|
|
173
|
+
# `#helper` references).
|
|
174
|
+
def literal_names
|
|
175
|
+
@literal_names ||=
|
|
176
|
+
processed_source.ast.each_descendant(:sym, :str)
|
|
177
|
+
.with_object(Set.new) do |literal, names|
|
|
178
|
+
if literal.sym_type?
|
|
179
|
+
names << literal.value.to_s
|
|
180
|
+
else
|
|
181
|
+
# `scan` raises on binary string literals with invalid byte sequences.
|
|
182
|
+
value = literal.value
|
|
183
|
+
value = value.scrub unless value.valid_encoding?
|
|
184
|
+
value.scan(IDENTIFIER_PATTERN) { |token| names << token }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# The leading literal fragment of an interpolated symbol or string acts
|
|
190
|
+
# as a method-name prefix: `send(:"format_#{type}")` may invoke any
|
|
191
|
+
# method whose name starts with `format_`. Only the leading fragment
|
|
192
|
+
# qualifies (a mid-string fragment is not a name prefix), and a fully
|
|
193
|
+
# dynamic name contributes nothing.
|
|
194
|
+
def literal_name_prefixes
|
|
195
|
+
@literal_name_prefixes ||=
|
|
196
|
+
processed_source.ast.each_descendant(:dsym, :dstr)
|
|
197
|
+
.with_object(Set.new) do |literal, prefixes|
|
|
198
|
+
prefix = interpolation_prefix(literal)
|
|
199
|
+
prefixes << prefix if prefix
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def interpolation_prefix(node)
|
|
204
|
+
return nil if node.children.all?(&:str_type?)
|
|
205
|
+
|
|
206
|
+
leading_fragment = node.children.first
|
|
207
|
+
return nil unless leading_fragment&.str_type?
|
|
208
|
+
|
|
209
|
+
leading_fragment.value[NAME_PREFIX_PATTERN]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# A method defined in a class or module with descendants may be invoked
|
|
213
|
+
# through `super` or inherited dispatch, which the index does not track.
|
|
214
|
+
def owner_with_descendants?(declaration)
|
|
215
|
+
owner = declaration.owner
|
|
216
|
+
return false unless owner.is_a?(Rubydex::Namespace)
|
|
217
|
+
|
|
218
|
+
owner.descendants.any? { |descendant| descendant.name != owner.name }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# An override of an inherited method may be invoked polymorphically
|
|
222
|
+
# (e.g. a framework hook), even when no direct reference exists.
|
|
223
|
+
def override?(declaration, method_name)
|
|
224
|
+
owner = declaration.owner
|
|
225
|
+
return false unless owner.is_a?(Rubydex::Namespace)
|
|
226
|
+
|
|
227
|
+
owner.ancestors.any? do |ancestor|
|
|
228
|
+
ancestor.name != owner.name && ancestor.member("#{method_name}()")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -210,7 +210,7 @@ module RuboCop
|
|
|
210
210
|
end
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
213
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
214
214
|
def check_child_nodes(node, unused, cur_vis)
|
|
215
215
|
node.child_nodes.each do |child|
|
|
216
216
|
if child.send_type? && access_modifier?(child)
|
|
@@ -228,7 +228,6 @@ module RuboCop
|
|
|
228
228
|
|
|
229
229
|
[cur_vis, unused]
|
|
230
230
|
end
|
|
231
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
232
231
|
|
|
233
232
|
def check_send_node(node, cur_vis, unused)
|
|
234
233
|
if node.bare_access_modifier?
|
|
@@ -173,7 +173,7 @@ module RuboCop
|
|
|
173
173
|
node.receiver.nil? && !node.arguments?
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
-
# rubocop:disable Metrics/AbcSize
|
|
176
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
177
177
|
def autocorrect(corrector, assignment)
|
|
178
178
|
if assignment.exception_assignment?
|
|
179
179
|
remove_exception_assignment_part(corrector, assignment.node)
|
|
@@ -189,7 +189,6 @@ module RuboCop
|
|
|
189
189
|
remove_local_variable_assignment_part(corrector, assignment.node)
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
|
-
# rubocop:enable Metrics/AbcSize
|
|
193
192
|
|
|
194
193
|
def remove_exception_assignment_part(corrector, node)
|
|
195
194
|
range = node.parent.children.first&.source_range || node.parent.location.keyword
|
|
@@ -10,6 +10,11 @@ module RuboCop
|
|
|
10
10
|
# It does not support autocorrection due to behavior change and multiple ways to fix it.
|
|
11
11
|
# Or a public constant may be intended.
|
|
12
12
|
#
|
|
13
|
+
# Constant assignments that define classes or modules via `Class.new`, `Module.new`,
|
|
14
|
+
# `Struct.new`, or `Data.define` are allowed. Those forms are class and module definitions
|
|
15
|
+
# written with assignment syntax, and match the common practice of placing nested
|
|
16
|
+
# `class` / `module` bodies after `private` without intending private constant visibility.
|
|
17
|
+
#
|
|
13
18
|
# @example
|
|
14
19
|
#
|
|
15
20
|
# # bad
|
|
@@ -29,6 +34,19 @@ module RuboCop
|
|
|
29
34
|
# PUBLIC_CONST = 42 # If private scope is not intended.
|
|
30
35
|
# end
|
|
31
36
|
#
|
|
37
|
+
# # good - class/module definitions via assignment, same as nested `class`/`module`
|
|
38
|
+
# class Foo
|
|
39
|
+
# private
|
|
40
|
+
#
|
|
41
|
+
# def some_private_method
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# MyClass = Class.new
|
|
45
|
+
# MyModule = Module.new
|
|
46
|
+
# MyStruct = Struct.new(:name)
|
|
47
|
+
# MyData = Data.define(:name)
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
32
50
|
class UselessConstantScoping < Base
|
|
33
51
|
MSG = 'Useless `private` access modifier for constant scope.'
|
|
34
52
|
|
|
@@ -37,9 +55,27 @@ module RuboCop
|
|
|
37
55
|
(send nil? :private_constant $...)
|
|
38
56
|
PATTERN
|
|
39
57
|
|
|
58
|
+
# Matches class/module-like constant assignments. Nested `class` / `module`
|
|
59
|
+
# keyword definitions are not visited by this cop; these assignment forms are
|
|
60
|
+
# the equivalent syntax and should be treated the same way.
|
|
61
|
+
# @!method class_or_module_definition_assignment?(node)
|
|
62
|
+
def_node_matcher :class_or_module_definition_assignment?, <<~PATTERN
|
|
63
|
+
{
|
|
64
|
+
(send (const {nil? cbase} {:Class :Module :Struct}) :new ...)
|
|
65
|
+
(send (const {nil? cbase} :Data) :define ...)
|
|
66
|
+
(any_block
|
|
67
|
+
{
|
|
68
|
+
(send (const {nil? cbase} {:Class :Module :Struct}) :new ...)
|
|
69
|
+
(send (const {nil? cbase} :Data) :define ...)
|
|
70
|
+
}
|
|
71
|
+
...)
|
|
72
|
+
}
|
|
73
|
+
PATTERN
|
|
74
|
+
|
|
40
75
|
def on_casgn(node)
|
|
41
76
|
return unless after_private_modifier?(node.left_siblings)
|
|
42
77
|
return if private_constantize?(node.right_siblings, node.name)
|
|
78
|
+
return if class_or_module_definition_assignment?(node.expression)
|
|
43
79
|
|
|
44
80
|
add_offense(node)
|
|
45
81
|
end
|
|
@@ -59,7 +59,7 @@ module RuboCop
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def use_rest_or_optional_args?(node)
|
|
62
|
-
node.arguments.any? { |arg| arg.type?(:restarg, :optarg, :kwoptarg) }
|
|
62
|
+
node.arguments.any? { |arg| arg.type?(:restarg, :optarg, :kwoptarg, :kwrestarg) }
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def delegating?(node, def_node)
|
|
@@ -56,7 +56,7 @@ module RuboCop
|
|
|
56
56
|
|
|
57
57
|
private
|
|
58
58
|
|
|
59
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
59
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
60
60
|
def only_reraising?(resbody_node)
|
|
61
61
|
return false if use_exception_variable_in_ensure?(resbody_node)
|
|
62
62
|
|
|
@@ -70,7 +70,6 @@ module RuboCop
|
|
|
70
70
|
|
|
71
71
|
exception_objects(resbody_node).include?(exception_name)
|
|
72
72
|
end
|
|
73
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
74
73
|
|
|
75
74
|
def use_exception_variable_in_ensure?(resbody_node)
|
|
76
75
|
return false unless (exception_variable = resbody_node.exception_variable)
|
|
@@ -116,6 +116,8 @@ module RuboCop
|
|
|
116
116
|
# so stop searching once a class/module boundary is crossed without a match.
|
|
117
117
|
return nil if ancestor.type?(:class, :module, :sclass)
|
|
118
118
|
end
|
|
119
|
+
|
|
120
|
+
nil
|
|
119
121
|
end
|
|
120
122
|
|
|
121
123
|
# `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
|
|
@@ -12,6 +12,8 @@ module RuboCop
|
|
|
12
12
|
@receiver = receiver
|
|
13
13
|
@additional_nil_methods = additional_nil_methods
|
|
14
14
|
@checked_nodes = {}.compare_by_identity
|
|
15
|
+
@receiver_binding_name = binding_name(receiver)
|
|
16
|
+
@receiver_binding_scope = binding_scope(receiver) if @receiver_binding_name
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def cant_be_nil?
|
|
@@ -20,7 +22,7 @@ module RuboCop
|
|
|
20
22
|
|
|
21
23
|
private
|
|
22
24
|
|
|
23
|
-
# rubocop:disable Metrics
|
|
25
|
+
# rubocop:disable-next Metrics
|
|
24
26
|
def _cant_be_nil?(node, receiver)
|
|
25
27
|
return false unless node
|
|
26
28
|
|
|
@@ -34,7 +36,9 @@ module RuboCop
|
|
|
34
36
|
when :def, :defs, :class, :module, :sclass
|
|
35
37
|
return false
|
|
36
38
|
when :send
|
|
37
|
-
|
|
39
|
+
if node.receiver == receiver && same_binding_as_receiver?(node.receiver)
|
|
40
|
+
return non_nil_method?(node.method_name)
|
|
41
|
+
end
|
|
38
42
|
|
|
39
43
|
node.arguments.each do |argument|
|
|
40
44
|
return true if _cant_be_nil?(argument, receiver)
|
|
@@ -74,13 +78,12 @@ module RuboCop
|
|
|
74
78
|
false
|
|
75
79
|
end
|
|
76
80
|
end
|
|
77
|
-
# rubocop:enable Metrics
|
|
78
81
|
|
|
79
82
|
def non_nil_method?(method_name)
|
|
80
83
|
!NIL_METHODS.include?(method_name) && !@additional_nil_methods.include?(method_name)
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
86
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
84
87
|
def sole_condition_of_parent_if?(node)
|
|
85
88
|
child = node
|
|
86
89
|
parent = node.parent
|
|
@@ -104,12 +107,63 @@ module RuboCop
|
|
|
104
107
|
|
|
105
108
|
false
|
|
106
109
|
end
|
|
107
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
108
110
|
|
|
109
111
|
def non_nil_condition?(condition, node)
|
|
110
|
-
return true if condition == node
|
|
112
|
+
return true if condition == node && same_binding_as_receiver?(condition)
|
|
113
|
+
return false unless condition.csend_type?
|
|
114
|
+
|
|
115
|
+
root_receiver = csend_root_receiver(condition)
|
|
116
|
+
|
|
117
|
+
root_receiver == node && same_binding_as_receiver?(root_receiver)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Structurally equal occurrences can be different variables: `it` binds to
|
|
121
|
+
# its innermost block, and a block parameter can shadow an outer variable of
|
|
122
|
+
# the same name. Evidence about such a receiver only holds when both occurrences
|
|
123
|
+
# resolve to the same binding.
|
|
124
|
+
def same_binding_as_receiver?(occurrence)
|
|
125
|
+
return true unless @receiver_binding_name
|
|
126
|
+
|
|
127
|
+
binding_scope(occurrence).equal?(@receiver_binding_scope)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The name whose binding can differ between structurally equal occurrences:
|
|
131
|
+
# a local variable (block parameters included), or a bare `it` call, which is
|
|
132
|
+
# how `it` parses when the target Ruby version is below 3.4 even though the code
|
|
133
|
+
# may run on 3.4+ where `it` is the implicit block parameter.
|
|
134
|
+
def binding_name(node)
|
|
135
|
+
if node.lvar_type?
|
|
136
|
+
node.children.first
|
|
137
|
+
elsif node.send_type? && !node.receiver && node.method?(:it) && node.arguments.empty?
|
|
138
|
+
:it
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def binding_scope(occurrence)
|
|
143
|
+
node = occurrence
|
|
144
|
+
|
|
145
|
+
while (parent = node.parent)
|
|
146
|
+
case parent.type
|
|
147
|
+
when :block, :numblock, :itblock
|
|
148
|
+
# Only the body is inside the block's scope; the send node and the arguments of
|
|
149
|
+
# the block node evaluate in the outer scope.
|
|
150
|
+
return parent if parent.body.equal?(node) && block_binds_name?(parent)
|
|
151
|
+
when :def, :defs, :class, :module, :sclass
|
|
152
|
+
return parent
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
node = parent
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def block_binds_name?(block_node)
|
|
162
|
+
name = @receiver_binding_name
|
|
163
|
+
|
|
164
|
+
return true if block_node.argument_list.any? { |argument| argument.name == name }
|
|
111
165
|
|
|
112
|
-
|
|
166
|
+
name == :it && block_node.arguments.empty?
|
|
113
167
|
end
|
|
114
168
|
|
|
115
169
|
# Whether control reaches `node` by falling through its left siblings rather than by
|
|
@@ -16,11 +16,14 @@ module RuboCop
|
|
|
16
16
|
# enumerator.each { |item| item >= 2 } #=> [2, 3]
|
|
17
17
|
# ----
|
|
18
18
|
#
|
|
19
|
-
# NOTE:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
19
|
+
# NOTE: The last expression in an assignment method definition such as `def foo=(arg)`
|
|
20
|
+
# is not flagged. Ruby discards it (the method returns its argument), but the method can
|
|
21
|
+
# still be called directly and its return value relied upon, so flagging it would be a
|
|
22
|
+
# false positive for this lint.
|
|
23
|
+
#
|
|
24
|
+
# NOTE: A constant used in a void context is flagged but not autocorrected, since
|
|
25
|
+
# referencing a constant can trigger autoloading side effects (e.g. forcing a file to
|
|
26
|
+
# load before a monkey-patch), so removing it may change behavior.
|
|
24
27
|
#
|
|
25
28
|
# @example CheckForMethodsWithNoSideEffects: false (default)
|
|
26
29
|
# # bad
|
|
@@ -109,7 +112,7 @@ module RuboCop
|
|
|
109
112
|
def check_begin(node)
|
|
110
113
|
expressions = *node
|
|
111
114
|
inside_each_block = node.each_ancestor(:any_block).first&.method?(:each)
|
|
112
|
-
expressions.pop if !in_void_context?(node) || inside_each_block
|
|
115
|
+
expressions.pop if !in_void_context?(node) || inside_each_block || setter_method?(node)
|
|
113
116
|
expressions.each do |expr|
|
|
114
117
|
check_void_op(expr) { inside_each_block }
|
|
115
118
|
check_expression(expr)
|
|
@@ -150,7 +153,7 @@ module RuboCop
|
|
|
150
153
|
check_expression(case_node.else_branch) if case_node.else_branch
|
|
151
154
|
end
|
|
152
155
|
|
|
153
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
156
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
154
157
|
def check_void_op(node, &block)
|
|
155
158
|
node = node.children.first while node&.begin_type?
|
|
156
159
|
return unless node&.call_type? && OPERATORS.include?(node.method_name)
|
|
@@ -165,7 +168,6 @@ module RuboCop
|
|
|
165
168
|
autocorrect_void_op(corrector, node)
|
|
166
169
|
end
|
|
167
170
|
end
|
|
168
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
169
171
|
|
|
170
172
|
def check_var(node)
|
|
171
173
|
return unless node.variable? || node.const_type?
|
|
@@ -244,6 +246,10 @@ module RuboCop
|
|
|
244
246
|
parent.respond_to?(:void_context?) && parent.void_context?
|
|
245
247
|
end
|
|
246
248
|
|
|
249
|
+
def setter_method?(node)
|
|
250
|
+
node.parent&.any_def_type? && node.parent.assignment_method?
|
|
251
|
+
end
|
|
252
|
+
|
|
247
253
|
def autocorrect_void_op(corrector, node)
|
|
248
254
|
if node.arguments.empty?
|
|
249
255
|
corrector.replace(node, node.receiver.source)
|
|
@@ -258,6 +264,10 @@ module RuboCop
|
|
|
258
264
|
end
|
|
259
265
|
|
|
260
266
|
def autocorrect_void_expression(corrector, node)
|
|
267
|
+
# Referencing a constant can trigger autoloading side effects (e.g. forcing a file
|
|
268
|
+
# to load before a monkey-patch), so removing it may change behavior. Flag but don't
|
|
269
|
+
# autocorrect, leaving it to the user.
|
|
270
|
+
return if node.const_type? && !node.special_keyword?
|
|
261
271
|
return if node.parent.type?(:if, :case, :when, :case_match, :in_pattern)
|
|
262
272
|
return if (def_node = node.each_ancestor(:any_def).first) && def_node.assignment_method?
|
|
263
273
|
|