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,93 @@
|
|
|
1
|
+
# Ensures methods that enqueue jobs follow the `_later` naming convention.
|
|
2
|
+
# This cop only flags when there's a corresponding method that does the actual work.
|
|
3
|
+
#
|
|
4
|
+
# The convention is to have paired methods:
|
|
5
|
+
# - `process` - does the actual work
|
|
6
|
+
# - `process_later` - enqueues a job that calls `process`
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad - work method exists, job method should be named process_later
|
|
10
|
+
# def enqueue_process
|
|
11
|
+
# ProcessJob.perform_later(self)
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# def process
|
|
15
|
+
# # actual work
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # good - follows naming convention
|
|
19
|
+
# def process_later
|
|
20
|
+
# ProcessJob.perform_later(self)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# def process
|
|
24
|
+
# # actual work
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # good - no corresponding work method, so no flag
|
|
28
|
+
# def notify
|
|
29
|
+
# NotificationJob.perform_later(self)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
class RuboCop::Cop::Callbacksystems::JobEnqueueMethods < RuboCop::Cop::Callbacksystems::Base
|
|
33
|
+
def on_new_investigation
|
|
34
|
+
@sibling_methods = RuboCop::Callbacksystems::Methods::Siblings.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def on_def(node)
|
|
38
|
+
report EnqueueMethod.new(node, sibling_methods:) unless node.method_name.to_s.end_with?("_later")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias on_defs on_def
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
attr_reader :sibling_methods
|
|
45
|
+
|
|
46
|
+
# A method enqueuing a job, read for the work method its job is named after.
|
|
47
|
+
class EnqueueMethod
|
|
48
|
+
include RuboCop::Callbacksystems::Helpers
|
|
49
|
+
|
|
50
|
+
MESSAGE = "Rename `%<name>s` to `%<expected>s` to follow the _later convention."
|
|
51
|
+
|
|
52
|
+
def initialize(node, sibling_methods:)
|
|
53
|
+
@node = node
|
|
54
|
+
@sibling_methods = sibling_methods
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def offense
|
|
58
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if pairs_with_work_method?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
attr_reader :node, :sibling_methods
|
|
63
|
+
|
|
64
|
+
def pairs_with_work_method?
|
|
65
|
+
base_name && sibling_methods.named(base_name, beside: node).any?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def base_name
|
|
69
|
+
@base_name ||= job_class_name.delete_suffix("Job").underscore.to_sym if job_class_name.end_with?("Job")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def job_class_name
|
|
73
|
+
@job_class_name ||= class_name_of(perform_later_call&.receiver).to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def class_name_of(receiver)
|
|
77
|
+
case receiver&.type
|
|
78
|
+
when :const then receiver.short_name
|
|
79
|
+
when :send then receiver.method_name
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def perform_later_call
|
|
84
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(node.body).find do |candidate|
|
|
85
|
+
candidate.call_type? && candidate.method?(:perform_later)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def message
|
|
90
|
+
format(MESSAGE, name: node.method_name, expected: :"#{base_name}_later")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Deeply chained safe navigation (`a&.b&.c&.d&.e`) signals deep coupling or
|
|
2
|
+
# poor data modeling. Refactor: pre-validate, restructure the object, or
|
|
3
|
+
# extract an intermediate.
|
|
4
|
+
#
|
|
5
|
+
# @example MaxDepth: 3 (default)
|
|
6
|
+
# # bad - four safe-navigation links
|
|
7
|
+
# value = account&.owner&.address&.city
|
|
8
|
+
#
|
|
9
|
+
# # good - extract an intermediate
|
|
10
|
+
# owner = account&.owner
|
|
11
|
+
# value = owner&.address&.city
|
|
12
|
+
#
|
|
13
|
+
class RuboCop::Cop::Callbacksystems::MaxSafeNavigationDepth < RuboCop::Cop::Callbacksystems::Base
|
|
14
|
+
def on_new_investigation
|
|
15
|
+
@navigation_context = NavigationContext.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_csend(node)
|
|
19
|
+
report NavigationChain.new(node, navigation_context, max_depth: cop_config["MaxDepth"])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
attr_reader :navigation_context
|
|
24
|
+
|
|
25
|
+
# The chain a safe-navigation call sits in, measured once from its outermost link so every chain is reported once.
|
|
26
|
+
class NavigationChain
|
|
27
|
+
include RuboCop::Callbacksystems::Helpers
|
|
28
|
+
|
|
29
|
+
MESSAGE = "Safe-navigation depth %<depth>d exceeds maximum %<maximum>d. Restructure or extract intermediates."
|
|
30
|
+
|
|
31
|
+
def initialize(node, navigation_context, max_depth:)
|
|
32
|
+
@node = node
|
|
33
|
+
@navigation_context = navigation_context
|
|
34
|
+
@max_depth = max_depth
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def offense
|
|
38
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if outermost?(node) && depth > max_depth
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
attr_reader :node, :navigation_context, :max_depth
|
|
43
|
+
delegate :outermost?, :top_of, to: :navigation_context, private: true
|
|
44
|
+
|
|
45
|
+
# Every link in the chain counts, not just a consecutive run (`a&.b.c&.d` is depth 2), matching the JS rule.
|
|
46
|
+
def depth
|
|
47
|
+
@depth ||= links_down_from(top_of(node)).count(&:csend_type?)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def links_down_from(link)
|
|
51
|
+
if block_given?
|
|
52
|
+
while link&.call_type?
|
|
53
|
+
yield link
|
|
54
|
+
link = link.receiver
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
to_enum(__method__, link)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def message
|
|
62
|
+
format(MESSAGE, depth:, maximum: max_depth)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Receiver-chain ancestry shared by every safe link in one investigation, with iterative path compression.
|
|
67
|
+
class NavigationContext
|
|
68
|
+
Facts = Data.define(:top, :safe_link)
|
|
69
|
+
NO_FACTS = Facts.new(nil, nil)
|
|
70
|
+
|
|
71
|
+
def initialize
|
|
72
|
+
@facts_by_link = {}.compare_by_identity
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def outermost?(node)
|
|
76
|
+
safe_link_at_or_above(chained_parent_of(node)).nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def top_of(node)
|
|
80
|
+
facts_for(node).top
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
attr_reader :facts_by_link
|
|
85
|
+
|
|
86
|
+
def safe_link_at_or_above(node)
|
|
87
|
+
node&.then { facts_for(it).safe_link }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def facts_for(node)
|
|
91
|
+
path = uncached_path_from(node)
|
|
92
|
+
facts = inherited_facts_of(path)
|
|
93
|
+
path.reverse_each do |link|
|
|
94
|
+
facts = Facts.new(facts.top || link, link.csend_type? ? link : facts.safe_link)
|
|
95
|
+
facts_by_link[link] = facts
|
|
96
|
+
end
|
|
97
|
+
facts_by_link.fetch(node)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def uncached_path_from(node)
|
|
101
|
+
[].then do |path|
|
|
102
|
+
while node && !facts_by_link.key?(node)
|
|
103
|
+
path << node
|
|
104
|
+
node = chained_parent_of(node)
|
|
105
|
+
end
|
|
106
|
+
path
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def chained_parent_of(link)
|
|
111
|
+
link.parent&.then { it if it.call_type? && it.receiver.equal?(link) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def inherited_facts_of(path)
|
|
115
|
+
path.last&.then { facts_by_link.fetch(chained_parent_of(it), NO_FACTS) } || NO_FACTS
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Ensures methods are ordered by invocation: callers before callees, and a
|
|
2
|
+
# caller's callees in the order it first invokes them (depth-first). Public
|
|
3
|
+
# methods are ordered before private ones. A macro, block, or lambda that names a
|
|
4
|
+
# method (a callback, a delegate target, a validation) calls it too, and it reads
|
|
5
|
+
# at the top of the class, so a method reached only that way leads its visibility
|
|
6
|
+
# group in the order the macros mention it (a guard before its action, since the
|
|
7
|
+
# guard is evaluated first). A method a real method body calls follows that caller
|
|
8
|
+
# instead.
|
|
9
|
+
#
|
|
10
|
+
# The actions of a controller are the exception: `Rails/ActionOrder` owns their
|
|
11
|
+
# order, so they lead their group in the order that cop asks for and the walk
|
|
12
|
+
# places everything else. A macro naming an action (a `rate_limit :create`) would
|
|
13
|
+
# otherwise pull it to the top and the two cops would undo each other forever.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad - called method defined before caller
|
|
17
|
+
# def helper
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def process
|
|
21
|
+
# helper
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # bad - sibling callees defined out of invocation order
|
|
25
|
+
# def process
|
|
26
|
+
# helper_b
|
|
27
|
+
# helper_a
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# def helper_a
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# def helper_b
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # good - caller before callee, callees in first-invocation order
|
|
37
|
+
# def process
|
|
38
|
+
# helper_a
|
|
39
|
+
# helper_b
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# def helper_a
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# def helper_b
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# # good - with private section
|
|
49
|
+
# def process
|
|
50
|
+
# helper
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# private
|
|
54
|
+
# def helper
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# # good - a callback method leads: its macro calls it and reads at the top
|
|
58
|
+
# after_commit :notify_later
|
|
59
|
+
#
|
|
60
|
+
# def notify_later
|
|
61
|
+
# NotifyJob.perform_later(self)
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# def process
|
|
65
|
+
# compute
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# def compute
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# # good - `Rails/ActionOrder` places the actions, whatever a macro names
|
|
72
|
+
# rate_limit :create, to: 10, within: 30.minutes
|
|
73
|
+
#
|
|
74
|
+
# def new
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# def create
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
class RuboCop::Cop::Callbacksystems::MethodInvocationOrder < RuboCop::Cop::Callbacksystems::Base
|
|
81
|
+
extend RuboCop::Cop::AutoCorrector
|
|
82
|
+
|
|
83
|
+
def on_class(node)
|
|
84
|
+
analyze(node, direct_definitions_in(node.body, :def), macro_references: true, actions: actions_of(node))
|
|
85
|
+
analyze(node, direct_definitions_in(node.body, :defs), macro_references: false, actions: [])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
alias on_module on_class
|
|
89
|
+
|
|
90
|
+
def on_sclass(node)
|
|
91
|
+
analyze(node, direct_definitions_in(node.body, :def), macro_references: true, actions: [])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
def analyze(node, methods, macro_references:, actions:)
|
|
96
|
+
report_each InvocationOrder.new(processed_source, node, methods, macro_references:, actions:)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Read from `Rails/ActionOrder`, since two cops ordering the same methods by different rules undo each other.
|
|
100
|
+
def actions_of(node)
|
|
101
|
+
controller_class?(node) ? expected_action_order : []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def expected_action_order
|
|
105
|
+
if config.cop_enabled?("Rails/ActionOrder")
|
|
106
|
+
Array(config.for_cop("Rails/ActionOrder")["ExpectedOrder"]).map(&:to_sym)
|
|
107
|
+
else
|
|
108
|
+
[]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class InvocationOrder
|
|
113
|
+
include RuboCop::Callbacksystems::Helpers
|
|
114
|
+
|
|
115
|
+
MESSAGE = "Method `%<expected>s` should be defined before `%<actual>s` to keep callers before callees."
|
|
116
|
+
|
|
117
|
+
def initialize(processed_source, node, methods, macro_references:, actions:)
|
|
118
|
+
@processed_source = processed_source
|
|
119
|
+
@node = node
|
|
120
|
+
@methods = methods
|
|
121
|
+
@macro_references = macro_references
|
|
122
|
+
@actions = actions
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def each_offense
|
|
126
|
+
yield offense if analyzable? && divergence.found?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
attr_reader :processed_source, :node, :methods, :macro_references, :actions
|
|
131
|
+
|
|
132
|
+
def analyzable?
|
|
133
|
+
node.body && methods.size >= 2 && method_names.uniq.size == method_names.size
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def method_names
|
|
137
|
+
@method_names ||= methods.map(&:method_name)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def divergence
|
|
141
|
+
@divergence ||= RuboCop::Callbacksystems::ClassStructure::Divergence.new(method_names, from: canonical_order)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def canonical_order
|
|
145
|
+
@canonical_order ||= LEVELS.flat_map { ordered_by_visibility(it) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def ordered_by_visibility(visibility)
|
|
149
|
+
leading_first(full_order.select { visibilities[it] == visibility })
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def leading_first(names)
|
|
153
|
+
leading = leading_order.select { names.include?(it) }
|
|
154
|
+
|
|
155
|
+
leading + (names - leading)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def leading_order
|
|
159
|
+
[ :initialize, *actions ]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def full_order
|
|
163
|
+
@full_order ||= RuboCop::Callbacksystems::ClassStructure::ReferenceOrder.new(ordering_seeds, call_graph).to_a
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def ordering_seeds
|
|
167
|
+
macro_led + real_roots + method_names
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def macro_led
|
|
171
|
+
if macro_references
|
|
172
|
+
reference_order.reject { called_methods.include?(it) }
|
|
173
|
+
else
|
|
174
|
+
[]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def reference_order
|
|
179
|
+
@reference_order ||= macro_methods.select { known_method_names.include?(it) }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def macro_methods
|
|
183
|
+
@macro_methods ||= RuboCop::Callbacksystems::Methods::MacroReferences.new(node.body)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def known_method_names
|
|
187
|
+
@known_method_names ||= Set.new(method_names)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def called_methods
|
|
191
|
+
@called_methods ||= Set.new(call_graph.values.flatten)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def call_graph
|
|
195
|
+
@call_graph ||= methods.to_h { [ it.method_name, calls_from(it) ] }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def calls_from(method)
|
|
199
|
+
if method.body
|
|
200
|
+
receiverless_method_names_in(method.body).uniq.select { places?(method.method_name, it) }
|
|
201
|
+
else
|
|
202
|
+
[]
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# A private caller places no public callee, since the public methods lead whatever reads them from below.
|
|
207
|
+
def places?(caller_name, callee_name)
|
|
208
|
+
known_method_names.include?(callee_name) && level_of(callee_name) >= level_of(caller_name)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def level_of(name)
|
|
212
|
+
LEVELS.index(visibilities[name])
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def visibilities
|
|
216
|
+
@visibilities ||= methods.to_h { [ it.method_name, visibility_of(it) ] }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def real_roots
|
|
220
|
+
method_names.reject { called_methods.include?(it) }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def offense
|
|
224
|
+
RuboCop::Callbacksystems::Offense.new(methods_by_name[divergence.expected], message,
|
|
225
|
+
correcting: method_runs.any?(&:reorderable?)) { reorder(it) }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def methods_by_name
|
|
229
|
+
@methods_by_name ||= methods.index_by(&:method_name)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def message
|
|
233
|
+
format(MESSAGE, expected: divergence.expected, actual: divergence.actual)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def method_runs
|
|
237
|
+
@method_runs ||= runs_in(node.body) { methods.include?(it) }.filter_map { run_of(it) }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def run_of(members)
|
|
241
|
+
statements = statements_of(members)
|
|
242
|
+
|
|
243
|
+
if uninterrupted?(statements)
|
|
244
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRun.new(statements, order: canonical_order) { it.node.method_name }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def statements_of(members)
|
|
249
|
+
members.map { RuboCop::Callbacksystems::Source::StatementWithComments.new(it, processed_source) }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# A comment unattached to either method is an intentional boundary, not source for a sortable run.
|
|
253
|
+
def uninterrupted?(statements)
|
|
254
|
+
statements.each_cons(2).none? { |above, below| above.range.end.join(below.range.begin).source.strip.present? }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def reorder(corrector)
|
|
258
|
+
method_runs.each { it.rewrite(corrector) }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|