rubocop-callbacksystems 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +78 -0
- data/config/default.yml +590 -0
- data/docs/cops.md +111 -0
- data/lib/rubocop/callbacksystems/autocorrection/batch.rb +135 -0
- data/lib/rubocop/callbacksystems/autocorrection/comment_rescue.rb +112 -0
- data/lib/rubocop/callbacksystems/autocorrection/rehearsal.rb +65 -0
- data/lib/rubocop/callbacksystems/autocorrection/rewrite_permit.rb +14 -0
- data/lib/rubocop/callbacksystems/class_structure/builder_assignment.rb +48 -0
- data/lib/rubocop/callbacksystems/class_structure/class_body.rb +50 -0
- data/lib/rubocop/callbacksystems/class_structure/divergence.rb +31 -0
- data/lib/rubocop/callbacksystems/class_structure/node_visibility.rb +82 -0
- data/lib/rubocop/callbacksystems/class_structure/reference_order.rb +36 -0
- data/lib/rubocop/callbacksystems/class_structure/statement_rank.rb +84 -0
- data/lib/rubocop/callbacksystems/class_structure/statement_run.rb +42 -0
- data/lib/rubocop/callbacksystems/class_structure/visibility.rb +84 -0
- data/lib/rubocop/callbacksystems/cops_document.rb +78 -0
- data/lib/rubocop/callbacksystems/execution/deferred_local_variables.rb +138 -0
- data/lib/rubocop/callbacksystems/execution/discarded_expression.rb +45 -0
- data/lib/rubocop/callbacksystems/execution/eager_evaluation_path.rb +73 -0
- data/lib/rubocop/callbacksystems/execution/immediate.rb +71 -0
- data/lib/rubocop/callbacksystems/execution/local_variable_occurrences.rb +112 -0
- data/lib/rubocop/callbacksystems/hashes/key_transform_detection.rb +108 -0
- data/lib/rubocop/callbacksystems/hashes/keyword_options.rb +37 -0
- data/lib/rubocop/callbacksystems/helpers/bodies.rb +150 -0
- data/lib/rubocop/callbacksystems/helpers/builders.rb +103 -0
- data/lib/rubocop/callbacksystems/helpers/comments.rb +56 -0
- data/lib/rubocop/callbacksystems/helpers/corrections.rb +15 -0
- data/lib/rubocop/callbacksystems/helpers/methods.rb +46 -0
- data/lib/rubocop/callbacksystems/helpers/names.rb +65 -0
- data/lib/rubocop/callbacksystems/helpers/node_types.rb +154 -0
- data/lib/rubocop/callbacksystems/helpers/rails.rb +52 -0
- data/lib/rubocop/callbacksystems/helpers/ranges.rb +107 -0
- data/lib/rubocop/callbacksystems/helpers/recursion.rb +29 -0
- data/lib/rubocop/callbacksystems/helpers/variables.rb +44 -0
- data/lib/rubocop/callbacksystems/helpers/visibility.rb +121 -0
- data/lib/rubocop/callbacksystems/helpers.rb +43 -0
- data/lib/rubocop/callbacksystems/methods/calls.rb +38 -0
- data/lib/rubocop/callbacksystems/methods/definitions.rb +218 -0
- data/lib/rubocop/callbacksystems/methods/delegate_macro.rb +144 -0
- data/lib/rubocop/callbacksystems/methods/delegation_target.rb +55 -0
- data/lib/rubocop/callbacksystems/methods/domain.rb +77 -0
- data/lib/rubocop/callbacksystems/methods/implicit_invocations.rb +17 -0
- data/lib/rubocop/callbacksystems/methods/macro_references.rb +238 -0
- data/lib/rubocop/callbacksystems/methods/reference_evidence.rb +74 -0
- data/lib/rubocop/callbacksystems/methods/siblings.rb +24 -0
- data/lib/rubocop/callbacksystems/methods/statement_definitions.rb +68 -0
- data/lib/rubocop/callbacksystems/offense.rb +13 -0
- data/lib/rubocop/callbacksystems/plugin.rb +27 -0
- data/lib/rubocop/callbacksystems/project_index/cache.rb +16 -0
- data/lib/rubocop/callbacksystems/project_index/confinement_cache.rb +40 -0
- data/lib/rubocop/callbacksystems/project_index/constant_references.rb +74 -0
- data/lib/rubocop/callbacksystems/project_index/corrections.rb +35 -0
- data/lib/rubocop/callbacksystems/project_index/coverage.rb +56 -0
- data/lib/rubocop/callbacksystems/project_index/diagnostics.rb +60 -0
- data/lib/rubocop/callbacksystems/project_index/freshness.rb +104 -0
- data/lib/rubocop/callbacksystems/project_index/loader.rb +9 -0
- data/lib/rubocop/callbacksystems/project_index/location.rb +29 -0
- data/lib/rubocop/callbacksystems/project_index/nested_class_confinement.rb +297 -0
- data/lib/rubocop/callbacksystems/project_index/nested_class_method_dispatch.rb +50 -0
- data/lib/rubocop/callbacksystems/project_index/reading.rb +129 -0
- data/lib/rubocop/callbacksystems/project_index/source_file.rb +59 -0
- data/lib/rubocop/callbacksystems/project_index/source_file_match.rb +57 -0
- data/lib/rubocop/callbacksystems/project_index/support.rb +46 -0
- data/lib/rubocop/callbacksystems/source/comment_blocks.rb +43 -0
- data/lib/rubocop/callbacksystems/source/comments.rb +141 -0
- data/lib/rubocop/callbacksystems/source/file_ast.rb +15 -0
- data/lib/rubocop/callbacksystems/source/files_checksum.rb +43 -0
- data/lib/rubocop/callbacksystems/source/single_line_call.rb +181 -0
- data/lib/rubocop/callbacksystems/source/statement_with_comments.rb +131 -0
- data/lib/rubocop/callbacksystems/testing/cop_helpers.rb +42 -0
- data/lib/rubocop/callbacksystems/testing/fixture_call.rb +30 -0
- data/lib/rubocop/callbacksystems/testing/fixture_names.rb +62 -0
- data/lib/rubocop/callbacksystems/testing/path_mapping.rb +104 -0
- data/lib/rubocop/cop/callbacksystems/application_controller_method_definition.rb +44 -0
- data/lib/rubocop/cop/callbacksystems/assert_with_expected_argument.rb +60 -0
- data/lib/rubocop/cop/callbacksystems/base.rb +134 -0
- data/lib/rubocop/cop/callbacksystems/body_order.rb +478 -0
- data/lib/rubocop/cop/callbacksystems/builder_method_parentheses.rb +74 -0
- data/lib/rubocop/cop/callbacksystems/collapse_multiline_expression.rb +140 -0
- data/lib/rubocop/cop/callbacksystems/comment_block_length.rb +137 -0
- data/lib/rubocop/cop/callbacksystems/complex_conditional.rb +75 -0
- data/lib/rubocop/cop/callbacksystems/controller_test_response_assertion.rb +105 -0
- data/lib/rubocop/cop/callbacksystems/controller_test_response_first_assertion.rb +123 -0
- data/lib/rubocop/cop/callbacksystems/data_clump.rb +339 -0
- data/lib/rubocop/cop/callbacksystems/declarative_method_naming.rb +350 -0
- data/lib/rubocop/cop/callbacksystems/delegate_requires_private_option.rb +81 -0
- data/lib/rubocop/cop/callbacksystems/eager_loading_scope_naming.rb +386 -0
- data/lib/rubocop/cop/callbacksystems/early_return.rb +215 -0
- data/lib/rubocop/cop/callbacksystems/empty_line_before_method.rb +168 -0
- data/lib/rubocop/cop/callbacksystems/empty_line_between_sections.rb +169 -0
- data/lib/rubocop/cop/callbacksystems/empty_private_section.rb +107 -0
- data/lib/rubocop/cop/callbacksystems/expanded_empty_class_or_module.rb +71 -0
- data/lib/rubocop/cop/callbacksystems/inline_memoized_computation.rb +110 -0
- data/lib/rubocop/cop/callbacksystems/job_enqueue_methods.rb +93 -0
- data/lib/rubocop/cop/callbacksystems/max_safe_navigation_depth.rb +118 -0
- data/lib/rubocop/cop/callbacksystems/method_invocation_order.rb +261 -0
- data/lib/rubocop/cop/callbacksystems/nested_class_order.rb +255 -0
- data/lib/rubocop/cop/callbacksystems/no_abbreviations.rb +197 -0
- data/lib/rubocop/cop/callbacksystems/no_anemic_delegation.rb +101 -0
- data/lib/rubocop/cop/callbacksystems/no_anemic_record.rb +309 -0
- data/lib/rubocop/cop/callbacksystems/no_assert_select.rb +31 -0
- data/lib/rubocop/cop/callbacksystems/no_bang_method_without_counterpart.rb +451 -0
- data/lib/rubocop/cop/callbacksystems/no_call_method.rb +73 -0
- data/lib/rubocop/cop/callbacksystems/no_comments_in_test_class_body.rb +159 -0
- data/lib/rubocop/cop/callbacksystems/no_consecutive_appends.rb +261 -0
- data/lib/rubocop/cop/callbacksystems/no_custom_route_actions.rb +124 -0
- data/lib/rubocop/cop/callbacksystems/no_direct_eager_loading.rb +90 -0
- data/lib/rubocop/cop/callbacksystems/no_each_with_early_return.rb +58 -0
- data/lib/rubocop/cop/callbacksystems/no_explicit_public_modifier.rb +81 -0
- data/lib/rubocop/cop/callbacksystems/no_hash_with_default_block.rb +68 -0
- data/lib/rubocop/cop/callbacksystems/no_local_variable_return.rb +173 -0
- data/lib/rubocop/cop/callbacksystems/no_manual_accumulation.rb +188 -0
- data/lib/rubocop/cop/callbacksystems/no_mixed_memoization.rb +145 -0
- data/lib/rubocop/cop/callbacksystems/no_nested_modules_in_classes.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/no_nil_at_end_of_method.rb +74 -0
- data/lib/rubocop/cop/callbacksystems/no_redundant_validation_name.rb +37 -0
- data/lib/rubocop/cop/callbacksystems/no_redundant_wrapper_method.rb +161 -0
- data/lib/rubocop/cop/callbacksystems/no_section_divider_comments.rb +105 -0
- data/lib/rubocop/cop/callbacksystems/no_send_in_tests.rb +75 -0
- data/lib/rubocop/cop/callbacksystems/no_service_objects.rb +64 -0
- data/lib/rubocop/cop/callbacksystems/no_single_letter_names.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/no_singleton_class_in_modules.rb +43 -0
- data/lib/rubocop/cop/callbacksystems/no_tap_collection_building.rb +173 -0
- data/lib/rubocop/cop/callbacksystems/no_turbo_stream_in_controllers.rb +52 -0
- data/lib/rubocop/cop/callbacksystems/no_typographic_clutter.rb +159 -0
- data/lib/rubocop/cop/callbacksystems/no_writes_in_get_actions.rb +149 -0
- data/lib/rubocop/cop/callbacksystems/ordered_mixin_arguments.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/plural_controller_names.rb +103 -0
- data/lib/rubocop/cop/callbacksystems/prefer_attr_reader_over_instance_variable.rb +86 -0
- data/lib/rubocop/cop/callbacksystems/prefer_backslash_continuation.rb +478 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_for_state.rb +532 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_new_for_empty_nested_class.rb +247 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_over_builder_block.rb +124 -0
- data/lib/rubocop/cop/callbacksystems/prefer_default_argument.rb +225 -0
- data/lib/rubocop/cop/callbacksystems/prefer_delegate.rb +242 -0
- data/lib/rubocop/cop/callbacksystems/prefer_delegate_over_instance_variable_assignment.rb +175 -0
- data/lib/rubocop/cop/callbacksystems/prefer_excluding.rb +204 -0
- data/lib/rubocop/cop/callbacksystems/prefer_extract_associated.rb +54 -0
- data/lib/rubocop/cop/callbacksystems/prefer_it_block_parameter.rb +156 -0
- data/lib/rubocop/cop/callbacksystems/prefer_many.rb +57 -0
- data/lib/rubocop/cop/callbacksystems/prefer_method_over_lambda.rb +211 -0
- data/lib/rubocop/cop/callbacksystems/prefer_method_over_variable.rb +172 -0
- data/lib/rubocop/cop/callbacksystems/prefer_module_for_static_class.rb +286 -0
- data/lib/rubocop/cop/callbacksystems/prefer_ordinal_array_access.rb +136 -0
- data/lib/rubocop/cop/callbacksystems/prefer_positive_wrap.rb +265 -0
- data/lib/rubocop/cop/callbacksystems/prefer_squish.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/prefer_stringify_keys.rb +24 -0
- data/lib/rubocop/cop/callbacksystems/prefer_symbolize_keys.rb +24 -0
- data/lib/rubocop/cop/callbacksystems/prefer_turbo_page_refresh_broadcasts.rb +40 -0
- data/lib/rubocop/cop/callbacksystems/private_constants_in_private_section.rb +160 -0
- data/lib/rubocop/cop/callbacksystems/private_delegate_placement.rb +342 -0
- data/lib/rubocop/cop/callbacksystems/private_methods_in_private_section.rb +70 -0
- data/lib/rubocop/cop/callbacksystems/private_nested_class_method_visibility.rb +269 -0
- data/lib/rubocop/cop/callbacksystems/private_nested_classes.rb +94 -0
- data/lib/rubocop/cop/callbacksystems/public_methods_must_have_tests.rb +190 -0
- data/lib/rubocop/cop/callbacksystems/redundant_concern_extend.rb +157 -0
- data/lib/rubocop/cop/callbacksystems/redundant_constant_freeze.rb +57 -0
- data/lib/rubocop/cop/callbacksystems/redundant_constant_namespace.rb +134 -0
- data/lib/rubocop/cop/callbacksystems/reflow_comments.rb +133 -0
- data/lib/rubocop/cop/callbacksystems/repeated_fixture_in_tests.rb +106 -0
- data/lib/rubocop/cop/callbacksystems/restricted_controller_actions.rb +85 -0
- data/lib/rubocop/cop/callbacksystems/routes_module_scope.rb +336 -0
- data/lib/rubocop/cop/callbacksystems/single_line_setup_block.rb +107 -0
- data/lib/rubocop/cop/callbacksystems/single_singleton_class.rb +49 -0
- data/lib/rubocop/cop/callbacksystems/single_top_level_class_or_module.rb +160 -0
- data/lib/rubocop/cop/callbacksystems/single_use_fixture_variable.rb +211 -0
- data/lib/rubocop/cop/callbacksystems/single_use_setup_variable.rb +684 -0
- data/lib/rubocop/cop/callbacksystems/singular_scope_names.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/space_inside_percent_literal.rb +73 -0
- data/lib/rubocop/cop/callbacksystems/system_test_browser_driver.rb +30 -0
- data/lib/rubocop/cop/callbacksystems/test_method_definition.rb +86 -0
- data/lib/rubocop/cop/callbacksystems/test_method_order.rb +319 -0
- data/lib/rubocop/cop/callbacksystems/too_many_instance_variables.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/too_many_local_variables.rb +77 -0
- data/lib/rubocop/cop/callbacksystems/too_many_validation_guards.rb +84 -0
- data/lib/rubocop/cop/callbacksystems/unnecessary_local_variable.rb +176 -0
- data/lib/rubocop/cop/callbacksystems/unused_private_method_in_nested_class.rb +203 -0
- data/lib/rubocop-callbacksystems.rb +21 -0
- data/rubocop.yml +685 -0
- metadata +337 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Counts only validation guards: leading `return`s that reject input by
|
|
2
|
+
# returning nothing, `nil`, or `false`. Dispatch branches like
|
|
3
|
+
# `return start_value if start?` are a lookup table written as control flow,
|
|
4
|
+
# not validation, so they don't count. Many validations at the start of a
|
|
5
|
+
# method usually means the checks belong in an extracted predicate.
|
|
6
|
+
#
|
|
7
|
+
# @example MaxGuards: 2 (default)
|
|
8
|
+
# # bad - three leading validation guards
|
|
9
|
+
# def process(account)
|
|
10
|
+
# return unless account
|
|
11
|
+
# return false if account.closed?
|
|
12
|
+
# return nil unless account.active?
|
|
13
|
+
# charge(account)
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# # good - extract the validation
|
|
17
|
+
# def process(account)
|
|
18
|
+
# return unless chargeable?(account)
|
|
19
|
+
# charge(account)
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
class RuboCop::Cop::Callbacksystems::TooManyValidationGuards < RuboCop::Cop::Callbacksystems::Base
|
|
23
|
+
def on_def(node)
|
|
24
|
+
report LeadingGuards.new(node, maximum: cop_config["MaxGuards"])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias on_defs on_def
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
class LeadingGuards
|
|
31
|
+
include RuboCop::Callbacksystems::Helpers
|
|
32
|
+
|
|
33
|
+
MESSAGE = "Method has %<count>d leading validation guards (max %<maximum>d). Extract validation into a helper."
|
|
34
|
+
|
|
35
|
+
def initialize(node, maximum:)
|
|
36
|
+
@node = node
|
|
37
|
+
@maximum = maximum
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def offense
|
|
41
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if count > maximum
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
attr_reader :node, :maximum
|
|
46
|
+
|
|
47
|
+
def count
|
|
48
|
+
@count ||= leading_zone.count { validation_guard?(it) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def leading_zone
|
|
52
|
+
statements_in(node.body).take_while { in_zone?(it) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def in_zone?(statement)
|
|
56
|
+
statement.lvasgn_type? || validation_guard?(statement)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validation_guard?(statement)
|
|
60
|
+
statement.if_type? && single_branch?(statement) && rejecting_return?(lone_branch_of(statement))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A full `if`/`else` is a two-way choice, not a guard.
|
|
64
|
+
def single_branch?(if_node)
|
|
65
|
+
[ if_node.if_branch, if_node.else_branch ].compact.one?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rejecting_return?(branch)
|
|
69
|
+
branch.return_type? && (branch.children.empty? || single_rejecting_value?(branch))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def single_rejecting_value?(return_node)
|
|
73
|
+
return_node.children.one? && return_node.children.first.falsey_literal?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def lone_branch_of(if_node)
|
|
77
|
+
if_node.if_branch || if_node.else_branch
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def message
|
|
81
|
+
format(MESSAGE, count:, maximum:)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Detects local variables that alias a method call that already reads as a name.
|
|
2
|
+
# Call the method directly or extract a well-named declarative method instead.
|
|
3
|
+
# A variable that names a computed expression (operator, block-pass, literal
|
|
4
|
+
# receiver) or a multiline call is left alone: there the name does real work.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - variable just aliases a method call
|
|
8
|
+
# directory = forbidden_directory
|
|
9
|
+
# add_offense(node) if directory
|
|
10
|
+
#
|
|
11
|
+
# # good - call the method directly
|
|
12
|
+
# add_offense(node) if forbidden_directory
|
|
13
|
+
#
|
|
14
|
+
# # ok - variable names a computed expression
|
|
15
|
+
# valid_values = options.map(&:value)
|
|
16
|
+
# answer.errors.add(:base) if (submitted_values - valid_values).any?
|
|
17
|
+
#
|
|
18
|
+
# # ok - call spans multiple lines
|
|
19
|
+
# record = relation.create! \
|
|
20
|
+
# type: "Report",
|
|
21
|
+
# data: {}
|
|
22
|
+
# record.deliver
|
|
23
|
+
#
|
|
24
|
+
# # ok - variable used multiple times
|
|
25
|
+
# user = find_user
|
|
26
|
+
# user.activate
|
|
27
|
+
# user.notify
|
|
28
|
+
#
|
|
29
|
+
# # ok - variable used inside a block (may be mutated across iterations)
|
|
30
|
+
# result = Set.new
|
|
31
|
+
# items.each { |item| result << item }
|
|
32
|
+
#
|
|
33
|
+
# # ok - assignment in conditional
|
|
34
|
+
# if user = find_user
|
|
35
|
+
# user.activate
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# # ok - variable reassigned
|
|
39
|
+
# current = first
|
|
40
|
+
# current = current.next while current
|
|
41
|
+
#
|
|
42
|
+
class RuboCop::Cop::Callbacksystems::UnnecessaryLocalVariable < RuboCop::Cop::Callbacksystems::Base
|
|
43
|
+
extend RuboCop::Cop::AutoCorrector
|
|
44
|
+
|
|
45
|
+
def on_new_investigation
|
|
46
|
+
@local_variables = RuboCop::Callbacksystems::Execution::LocalVariableOccurrences.new(processed_source.ast)
|
|
47
|
+
@source_comments = RuboCop::Callbacksystems::Source::Comments.for(processed_source)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_lvasgn(node)
|
|
51
|
+
report Assignment.new(node, @local_variables, @source_comments)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
class Assignment
|
|
56
|
+
include RuboCop::Callbacksystems::Helpers
|
|
57
|
+
|
|
58
|
+
MESSAGE = "Variable `%<name>s` is unnecessary. Call the method directly or extract a well-named declarative " \
|
|
59
|
+
"method."
|
|
60
|
+
|
|
61
|
+
def initialize(node, local_variables, source_comments)
|
|
62
|
+
@node = node
|
|
63
|
+
@local_variables = local_variables
|
|
64
|
+
@source_comments = source_comments
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def offense
|
|
68
|
+
RuboCop::Callbacksystems::Offense.new(node, message, correcting: inlineable?) { correct(it) } if unnecessary?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
attr_reader :node, :local_variables, :source_comments
|
|
73
|
+
|
|
74
|
+
def unnecessary?
|
|
75
|
+
aliases_named_call? && !conditional? && single_use? && !used_inside_nested_block? && !used_for_restoration?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def aliases_named_call?
|
|
79
|
+
value&.type?(:call) && !computed_expression? && !value.multiline?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def value
|
|
83
|
+
node.expression
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def computed_expression?
|
|
87
|
+
value.operator_method? || value.arguments.any?(&:block_pass_type?) || literal_receiver?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def literal_receiver?
|
|
91
|
+
value.receiver&.then { it.type?(:array, :hash) || it.recursive_literal? }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def conditional?
|
|
95
|
+
node.each_ancestor(:if, :while, :until, :case, :and, :or).any?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def single_use?
|
|
99
|
+
enclosing_scope && !reassigned? && references.one?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def enclosing_scope
|
|
103
|
+
@enclosing_scope ||= enclosing_scope_of(node)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def reassigned?
|
|
107
|
+
other_occurrences.any? { it.type?(:lvasgn, :match_var) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def other_occurrences
|
|
111
|
+
@other_occurrences ||= local_variables.named(node.name, around: node).reject { it.equal?(node) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def references
|
|
115
|
+
@references ||= other_occurrences.select(&:lvar_type?)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def used_inside_nested_block?
|
|
119
|
+
reference.each_ancestor(:any_block).any? { it != enclosing_scope }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def reference
|
|
123
|
+
references.first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def used_for_restoration?
|
|
127
|
+
reference.each_ancestor(:ensure, :resbody).any?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def message
|
|
131
|
+
format(MESSAGE, name: node.name)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The next statement removes intervening work; its evaluation path must also keep the call eager and ordered.
|
|
135
|
+
def inlineable?
|
|
136
|
+
nodes_in(next_statement, :lvar).any? { it.equal?(reference) } && !read_by_another_assignment? &&
|
|
137
|
+
!carries_heredoc?(node) && relocation_preserves_order? && removal_uncommented?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def next_statement
|
|
141
|
+
node.right_sibling if node.parent.begin_type?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Two corrections on one line would overwrite each other, so this one waits for the pass that follows.
|
|
145
|
+
def read_by_another_assignment?
|
|
146
|
+
reference.each_ancestor(:lvasgn).any?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def relocation_preserves_order?
|
|
150
|
+
RuboCop::Callbacksystems::Execution::EagerEvaluationPath.new(reference, within: next_statement)
|
|
151
|
+
.preserves_order?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def removal_uncommented?
|
|
155
|
+
!source_comments.any_within?(node.source_range.with(end_pos: next_statement.source_range.begin_pos))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def correct(corrector)
|
|
159
|
+
corrector.remove(node.source_range.with(end_pos: next_statement.source_range.begin_pos))
|
|
160
|
+
replace_expression(corrector, reference, with: inlined_source)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Bare arguments at the read would swallow the following `.method` or comma, so they get parentheses.
|
|
164
|
+
def inlined_source
|
|
165
|
+
if value.arguments? && !value.parenthesized?
|
|
166
|
+
"#{call_source_through_selector}(#{value.arguments.map(&:source).join(", ")})"
|
|
167
|
+
else
|
|
168
|
+
value.source
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def call_source_through_selector
|
|
173
|
+
value.source_range.with(end_pos: value.loc.selector.end_pos).source
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Detects private methods of a visually private nested class that nothing
|
|
2
|
+
# calls. When the project index confirms that no other file reaches or reopens
|
|
3
|
+
# the class, a private method no line of the file names is dead code a reader
|
|
4
|
+
# still has to account for.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - unused private method
|
|
8
|
+
# class Foo
|
|
9
|
+
# def process
|
|
10
|
+
# Bar.new.run
|
|
11
|
+
# nil
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# private
|
|
15
|
+
# class Bar
|
|
16
|
+
# def run
|
|
17
|
+
# helper
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# private
|
|
21
|
+
# def helper; end
|
|
22
|
+
# def unused; end # never called
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# # good - all private methods are used
|
|
27
|
+
# class Foo
|
|
28
|
+
# def process
|
|
29
|
+
# Bar.new.run
|
|
30
|
+
# nil
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# private
|
|
34
|
+
# class Bar
|
|
35
|
+
# def run
|
|
36
|
+
# helper
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# private
|
|
40
|
+
# def helper; end
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
class RuboCop::Cop::Callbacksystems::UnusedPrivateMethodInNestedClass < RuboCop::Cop::Callbacksystems::Base
|
|
45
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
46
|
+
|
|
47
|
+
def on_new_investigation
|
|
48
|
+
@file_references = FileReferences.new(processed_source.ast) if project_index
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def on_class(node)
|
|
52
|
+
if project_index_reliable?
|
|
53
|
+
private_nested_classes_in(node).each do |nested_class|
|
|
54
|
+
confined_declaration_of(nested_class)&.then do |declaration|
|
|
55
|
+
report_each UnusedMethods.new(nested_class, declaration:, references: file_references)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
alias on_module on_class
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
attr_reader :file_references
|
|
65
|
+
|
|
66
|
+
# Every source-level way the file can name or discover one of the nested class's methods, indexed once.
|
|
67
|
+
class FileReferences
|
|
68
|
+
include RuboCop::Callbacksystems::Helpers
|
|
69
|
+
|
|
70
|
+
NAME_PREFIX_PATTERN = /[a-zA-Z_]\w*\z/
|
|
71
|
+
DYNAMIC_METHOD_LOOKUPS = %i[
|
|
72
|
+
send __send__ public_send method public_method private_method instance_method public_instance_method
|
|
73
|
+
respond_to? method_defined? public_method_defined? protected_method_defined? private_method_defined?
|
|
74
|
+
alias_method
|
|
75
|
+
]
|
|
76
|
+
METHOD_ENUMERATIONS = %i[
|
|
77
|
+
methods public_methods protected_methods private_methods singleton_methods
|
|
78
|
+
instance_methods public_instance_methods protected_instance_methods private_instance_methods
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
def initialize(root)
|
|
82
|
+
@evidence = RuboCop::Callbacksystems::Methods::ReferenceEvidence.for(root)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def complete?
|
|
86
|
+
dynamic_calls_resolved? && !methods_enumerated?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def include?(method_name)
|
|
90
|
+
called_methods.include?(method_name) || dynamically_called_methods.include?(method_name) ||
|
|
91
|
+
aliased_methods.include?(method_name) || macro_referenced_methods.include?(method_name) ||
|
|
92
|
+
literal_method_names.include?(method_name.to_s) ||
|
|
93
|
+
literal_method_prefixes.any? { method_name.to_s.start_with?(it) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
attr_reader :evidence
|
|
98
|
+
|
|
99
|
+
delegate :macro_referenced_methods, :literal_method_names, to: :evidence, private: true
|
|
100
|
+
|
|
101
|
+
def dynamic_calls_resolved?
|
|
102
|
+
dynamic_calls.all? do |send_node|
|
|
103
|
+
method_name_arguments_of(send_node).all? { it.type?(:sym, :str) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def dynamic_calls
|
|
108
|
+
@dynamic_calls ||= evidence.calls.select { DYNAMIC_METHOD_LOOKUPS.include?(it.method_name) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def method_name_arguments_of(send_node)
|
|
112
|
+
send_node.method?(:alias_method) ? send_node.arguments : [ send_node.first_argument ].compact
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def methods_enumerated?
|
|
116
|
+
evidence.calls.any? { METHOD_ENUMERATIONS.include?(it.method_name) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def called_methods
|
|
120
|
+
@called_methods ||= evidence.calls.to_set(&:method_name)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def dynamically_called_methods
|
|
124
|
+
@dynamically_called_methods ||= dynamic_calls.flat_map do |send_node|
|
|
125
|
+
method_name_arguments_of(send_node).filter_map { literal_method_name_of(it) }
|
|
126
|
+
end.to_set
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def literal_method_name_of(literal)
|
|
130
|
+
literal.value.to_s.then { it.to_sym if it.valid_encoding? }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def aliased_methods
|
|
134
|
+
@aliased_methods ||= evidence.alias_nodes.flat_map do |alias_node|
|
|
135
|
+
alias_node.children.filter_map { literal_method_name_of(it) if it.type?(:sym, :str) }
|
|
136
|
+
end.to_set
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def literal_method_prefixes
|
|
140
|
+
@literal_method_prefixes ||= evidence.interpolated_literals.filter_map do |literal|
|
|
141
|
+
interpolation_prefix(literal)
|
|
142
|
+
end.to_set
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def interpolation_prefix(node)
|
|
146
|
+
if node.children.any? { !it.str_type? }
|
|
147
|
+
node.children.first.then { it.value.to_s.scrub[NAME_PREFIX_PATTERN] if it.str_type? }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class UnusedMethods
|
|
153
|
+
include RuboCop::Callbacksystems::Helpers
|
|
154
|
+
|
|
155
|
+
MESSAGE = "Private method `%<method>s` in nested class `%<class>s` is never called. Remove it."
|
|
156
|
+
STRUCTURAL_METHODS = RuboCop::Callbacksystems::Methods::ImplicitInvocations::METHOD_NAMES
|
|
157
|
+
|
|
158
|
+
def initialize(nested_class, declaration:, references:)
|
|
159
|
+
@nested_class = nested_class
|
|
160
|
+
@declaration = declaration
|
|
161
|
+
@references = references
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def each_offense
|
|
165
|
+
unused_methods.each { yield RuboCop::Callbacksystems::Offense.new(it, message_for(it)) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
attr_reader :nested_class, :declaration, :references
|
|
170
|
+
|
|
171
|
+
def unused_methods
|
|
172
|
+
provable? ? uncalled_methods : []
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# A superclass calls the hooks its subclass overrides, and that call is nowhere in this file.
|
|
176
|
+
def provable?
|
|
177
|
+
nested_class.parent_class.nil? && !subclassed_elsewhere? && references.complete?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def subclassed_elsewhere?
|
|
181
|
+
declaration.descendants.any? { it.name != declaration.name }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def uncalled_methods
|
|
185
|
+
private_methods_in(nested_class).reject do |method_node|
|
|
186
|
+
STRUCTURAL_METHODS.include?(method_node.method_name) || override?(method_node.method_name) ||
|
|
187
|
+
referenced?(method_node.method_name)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def override?(method_name)
|
|
192
|
+
declaration.find_member("#{method_name}()", only_inherited: true).present?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def referenced?(method_name)
|
|
196
|
+
references.include?(method_name)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def message_for(method_node)
|
|
200
|
+
format(MESSAGE, method: method_node.method_name, class: nested_class.identifier.short_name)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "zeitwerk"
|
|
2
|
+
require "rubocop"
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "active_support/core_ext/array/access"
|
|
5
|
+
require "active_support/core_ext/enumerable"
|
|
6
|
+
require "active_support/core_ext/module/delegation"
|
|
7
|
+
require "active_support/core_ext/object/blank"
|
|
8
|
+
require "active_support/core_ext/string/exclude"
|
|
9
|
+
require "active_support/core_ext/string/inflections"
|
|
10
|
+
|
|
11
|
+
module RubocopCallbacksystems
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
loader = Zeitwerk::Loader.new
|
|
15
|
+
loader.tag = "rubocop-callbacksystems"
|
|
16
|
+
loader.push_dir("#{__dir__}/rubocop", namespace: RuboCop)
|
|
17
|
+
loader.ignore("#{__dir__}/rubocop/callbacksystems/cops_document.rb")
|
|
18
|
+
loader.setup
|
|
19
|
+
loader.eager_load
|
|
20
|
+
RuboCop::ProjectIndexLoader.singleton_class.prepend RuboCop::Callbacksystems::ProjectIndex::Loader
|
|
21
|
+
RuboCop::Cop::Team.prepend RuboCop::Callbacksystems::ProjectIndex::Corrections
|