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,238 @@
|
|
|
1
|
+
class RuboCop::Callbacksystems::Methods::MacroReferences
|
|
2
|
+
include Enumerable
|
|
3
|
+
include RuboCop::Callbacksystems::Helpers
|
|
4
|
+
|
|
5
|
+
delegate :each, to: :names_in_source_order
|
|
6
|
+
|
|
7
|
+
def initialize(body)
|
|
8
|
+
@body = body
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
attr_reader :body
|
|
13
|
+
|
|
14
|
+
def names_in_source_order
|
|
15
|
+
@names_in_source_order ||= References.new(body).to_a.uniq
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# References in one lexical owner, with method bodies skipped and nested class domains pruned.
|
|
19
|
+
class References
|
|
20
|
+
include Enumerable
|
|
21
|
+
|
|
22
|
+
def initialize(body)
|
|
23
|
+
@body = body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def each
|
|
27
|
+
if block_given?
|
|
28
|
+
pending = [ Visit.new(body, block_calls_collected: false) ]
|
|
29
|
+
until pending.empty?
|
|
30
|
+
visit = pending.pop
|
|
31
|
+
unless visit.boundary?
|
|
32
|
+
visit.method_names.each { yield it }
|
|
33
|
+
pending.concat(visit.children.reverse)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
to_enum(__method__)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
attr_reader :body
|
|
43
|
+
|
|
44
|
+
class Visit
|
|
45
|
+
include RuboCop::Callbacksystems::Helpers
|
|
46
|
+
|
|
47
|
+
def initialize(node, block_calls_collected:)
|
|
48
|
+
@node = node
|
|
49
|
+
@block_calls_collected = block_calls_collected
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def boundary?
|
|
53
|
+
node.nil? || node.type?(:def, :defs, :class, :module, :sclass)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def method_names
|
|
57
|
+
if any_block_type?(node)
|
|
58
|
+
block_calls_collected ? [] : BlockCalls.new(node.body).to_a
|
|
59
|
+
else
|
|
60
|
+
Statement.new(node).references
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def children
|
|
65
|
+
node.each_child_node.map do |child|
|
|
66
|
+
self.class.new(child, block_calls_collected: block_calls_collected || any_block_type?(node))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
attr_reader :node, :block_calls_collected
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Receiverless calls in the first block of a nested block tree, including its method definitions as before.
|
|
76
|
+
class BlockCalls
|
|
77
|
+
include Enumerable
|
|
78
|
+
include RuboCop::Callbacksystems::Helpers
|
|
79
|
+
|
|
80
|
+
def initialize(body)
|
|
81
|
+
@body = body
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def each
|
|
85
|
+
if block_given?
|
|
86
|
+
pending = [ body ].compact
|
|
87
|
+
until pending.empty?
|
|
88
|
+
node = pending.pop
|
|
89
|
+
unless node.type?(:class, :module, :sclass)
|
|
90
|
+
yield node.method_name if bare_send?(node)
|
|
91
|
+
pending.concat(node.each_child_node.to_a.reverse)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
to_enum(__method__)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
attr_reader :body
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class Statement
|
|
104
|
+
include RuboCop::Callbacksystems::Helpers
|
|
105
|
+
|
|
106
|
+
CALLBACK_OPTIONS = %i[ if unless ]
|
|
107
|
+
|
|
108
|
+
# Unknown class macros can take symbols as data (`validates :name`), so only these known callbacks let a
|
|
109
|
+
# positional symbol make a method look referenced.
|
|
110
|
+
METHOD_REFERENCE_MACROS = %i[
|
|
111
|
+
after_action after_commit after_create after_create_commit after_destroy after_destroy_commit
|
|
112
|
+
after_deliver after_discard after_enqueue after_find after_initialize after_perform after_rollback
|
|
113
|
+
after_save after_save_commit after_touch after_update after_update_commit after_validation before_action
|
|
114
|
+
before_commit before_create before_deliver before_destroy before_enqueue before_perform before_save
|
|
115
|
+
before_update before_validation around_action around_create around_deliver around_destroy around_enqueue
|
|
116
|
+
around_perform around_save around_update append_after_action append_around_action append_before_action
|
|
117
|
+
helper_method prepend_after_action prepend_around_action prepend_before_action rescue_from setup
|
|
118
|
+
skip_after_action skip_around_action skip_before_action teardown validate
|
|
119
|
+
]
|
|
120
|
+
CALLBACK_CONFIGURATION_MACROS = %i[ set_callback skip_callback ]
|
|
121
|
+
|
|
122
|
+
# `delegate` defines its leading names as the accessors do, and its `to:` target still counts through the options.
|
|
123
|
+
DEFINING_MACROS = %i[
|
|
124
|
+
attr_reader attr_writer attr_accessor
|
|
125
|
+
mattr_reader mattr_writer mattr_accessor
|
|
126
|
+
cattr_reader cattr_writer cattr_accessor
|
|
127
|
+
thread_mattr_accessor thread_cattr_accessor
|
|
128
|
+
class_attribute store_accessor attribute delegate
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def initialize(node)
|
|
132
|
+
@node = node
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def references
|
|
136
|
+
node.send_type? ? from_send : []
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
attr_reader :node
|
|
141
|
+
|
|
142
|
+
# A callback's `if:`/`unless:` runs before the action it guards, so the hash options lead.
|
|
143
|
+
def from_send
|
|
144
|
+
hash_option_references + symbol_arguments + lambda_argument_calls
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def hash_option_references
|
|
148
|
+
hash_pairs_of(node).flat_map { OptionReference.new(node, it).to_a }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def symbol_arguments
|
|
152
|
+
if CALLBACK_CONFIGURATION_MACROS.include?(node.method_name)
|
|
153
|
+
configured_callback_arguments
|
|
154
|
+
elsif METHOD_REFERENCE_MACROS.include?(node.method_name) && DEFINING_MACROS.exclude?(node.method_name)
|
|
155
|
+
node.arguments.select(&:sym_type?).map(&:value)
|
|
156
|
+
else
|
|
157
|
+
[]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The event and timing in `set_callback :save, :before, :normalize` are data; only the remaining filters name
|
|
162
|
+
# methods.
|
|
163
|
+
def configured_callback_arguments
|
|
164
|
+
node.arguments.drop(2).select(&:sym_type?).map(&:value)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def lambda_argument_calls
|
|
168
|
+
node.arguments.select { any_block_type?(it) }.flat_map { receiverless_method_names_in(it.body) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# One callback option read as the method names its key and value can reference.
|
|
172
|
+
class OptionReference
|
|
173
|
+
include RuboCop::Callbacksystems::Helpers
|
|
174
|
+
|
|
175
|
+
def initialize(macro, pair)
|
|
176
|
+
@macro = macro
|
|
177
|
+
@key = pair.key
|
|
178
|
+
@value = pair.value
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def to_a
|
|
182
|
+
if key.sym_type?
|
|
183
|
+
case key.value
|
|
184
|
+
when :to then delegate_target
|
|
185
|
+
when *CALLBACK_OPTIONS then callback_condition
|
|
186
|
+
when :with then rescue_handler
|
|
187
|
+
else []
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
[]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
attr_reader :macro, :key, :value
|
|
196
|
+
|
|
197
|
+
def delegate_target
|
|
198
|
+
case value.type
|
|
199
|
+
when :sym then [ value.value ]
|
|
200
|
+
when :str then Array(value.value.split(".").first&.to_sym)
|
|
201
|
+
else []
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def callback_condition
|
|
206
|
+
if value.sym_type?
|
|
207
|
+
[ value.value ]
|
|
208
|
+
elsif any_block_type?(value)
|
|
209
|
+
receiverless_method_names_in(value.body)
|
|
210
|
+
else
|
|
211
|
+
[]
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def rescue_handler
|
|
216
|
+
macro.method?(:rescue_from) && value.sym_type? ? [ value.value ] : []
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
class ContainerIndex
|
|
222
|
+
def initialize
|
|
223
|
+
@by_container = {}.compare_by_identity
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def include?(name, beside:)
|
|
227
|
+
container = RuboCop::Callbacksystems::Methods::Domain.new(beside).container
|
|
228
|
+
container && references_in(container).include?(name)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
attr_reader :by_container
|
|
233
|
+
|
|
234
|
+
def references_in(container)
|
|
235
|
+
by_container[container] ||= RuboCop::Callbacksystems::Methods::MacroReferences.new(container.body).to_set
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Source nodes that can name or expose a method, indexed in one walk and shared by every cop reading the same AST.
|
|
2
|
+
class RuboCop::Callbacksystems::Methods::ReferenceEvidence
|
|
3
|
+
IDENTIFIER_PATTERN = /[a-zA-Z_]\w*[?!=]?/
|
|
4
|
+
|
|
5
|
+
delegate :calls, :block_passes, :alias_nodes, :literals, :interpolated_literals, to: :nodes
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def for(root)
|
|
9
|
+
mutex.synchronize { cache[root] ||= new(root) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def mutex
|
|
14
|
+
@mutex ||= Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cache
|
|
18
|
+
@cache ||= ObjectSpace::WeakMap.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(root)
|
|
23
|
+
@root = root
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def macro_referenced_methods
|
|
27
|
+
@macro_referenced_methods ||= RuboCop::Callbacksystems::Methods::MacroReferences.new(root).to_set
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def literal_method_names
|
|
31
|
+
@literal_method_names ||= literals.flat_map do |literal|
|
|
32
|
+
if literal.sym_type?
|
|
33
|
+
literal.value.to_s
|
|
34
|
+
else
|
|
35
|
+
literal.value.to_s.scrub.scan(IDENTIFIER_PATTERN)
|
|
36
|
+
end
|
|
37
|
+
end.to_set
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
attr_reader :root
|
|
42
|
+
|
|
43
|
+
def nodes
|
|
44
|
+
@nodes ||= Nodes.new(root)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The relevant node kinds partitioned during one traversal without losing source order between send and csend.
|
|
48
|
+
class Nodes
|
|
49
|
+
TYPES = %i[ alias block_pass csend dstr dsym send str sym ]
|
|
50
|
+
|
|
51
|
+
attr_reader :calls, :block_passes, :alias_nodes, :literals, :interpolated_literals
|
|
52
|
+
|
|
53
|
+
def initialize(root)
|
|
54
|
+
@calls = []
|
|
55
|
+
@block_passes = []
|
|
56
|
+
@alias_nodes = []
|
|
57
|
+
@literals = []
|
|
58
|
+
@interpolated_literals = []
|
|
59
|
+
|
|
60
|
+
root.each_node(*TYPES).each { partition(it) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
def partition(node)
|
|
65
|
+
case node.type
|
|
66
|
+
when :send, :csend then calls << node
|
|
67
|
+
when :block_pass then block_passes << node
|
|
68
|
+
when :alias then alias_nodes << node
|
|
69
|
+
when :str, :sym then literals << node
|
|
70
|
+
when :dstr, :dsym then interpolated_literals << node
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Direct method definitions indexed within a class or module and its instance/singleton sides.
|
|
2
|
+
class RuboCop::Callbacksystems::Methods::Siblings
|
|
3
|
+
include RuboCop::Callbacksystems::Helpers
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@by_container = {}.compare_by_identity
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def named(name, beside:)
|
|
10
|
+
domain = RuboCop::Callbacksystems::Methods::Domain.new(beside)
|
|
11
|
+
definitions_in(domain.container).fetch([ domain.identity, name ]) { [] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
attr_reader :by_container
|
|
16
|
+
|
|
17
|
+
def definitions_in(container)
|
|
18
|
+
return {} if container.nil?
|
|
19
|
+
|
|
20
|
+
by_container[container] ||= direct_method_nodes_in(container.body).group_by do |method_node|
|
|
21
|
+
[ RuboCop::Callbacksystems::Methods::Domain.new(method_node).identity, method_node.method_name ]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# The method names one class-body statement defines. A missing result means the statement does define methods, but their
|
|
2
|
+
# complete names cannot be proven without executing the file.
|
|
3
|
+
class RuboCop::Callbacksystems::Methods::StatementDefinitions
|
|
4
|
+
include RuboCop::Callbacksystems::Helpers
|
|
5
|
+
|
|
6
|
+
RUBY_ACCESSOR_MACROS = %i[ attr_reader attr_writer attr_accessor ]
|
|
7
|
+
|
|
8
|
+
def initialize(statement)
|
|
9
|
+
@statement = statement
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def names
|
|
13
|
+
@names ||= explicit_definition_names || macro_definition_names
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
attr_reader :statement
|
|
18
|
+
|
|
19
|
+
def explicit_definition_names
|
|
20
|
+
Set[statement.method_name] if statement.type?(:def, :defs)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def macro_definition_names
|
|
24
|
+
if delegate_macro?(macro)
|
|
25
|
+
RuboCop::Callbacksystems::Methods::DelegateMacro.new(macro).defined_method_names&.to_set
|
|
26
|
+
elsif scope_macro?
|
|
27
|
+
scope_names
|
|
28
|
+
elsif ruby_accessor_macro?
|
|
29
|
+
accessor_names
|
|
30
|
+
elsif attribute_macro?(macro) || association_macro?(macro)
|
|
31
|
+
nil
|
|
32
|
+
else
|
|
33
|
+
Set.new
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def macro
|
|
38
|
+
@macro ||= call_of(statement)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def scope_macro?
|
|
42
|
+
bare_send?(macro) && macro.method?(:scope)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def scope_names
|
|
46
|
+
name = macro.first_argument
|
|
47
|
+
|
|
48
|
+
name&.type?(:sym, :str) ? Set[name.value.to_sym] : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ruby_accessor_macro?
|
|
52
|
+
bare_send?(macro) && RUBY_ACCESSOR_MACROS.include?(macro.method_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def accessor_names
|
|
56
|
+
return if macro.arguments.any? { !it.type?(:sym, :str) }
|
|
57
|
+
|
|
58
|
+
names = name_arguments_of(macro).to_set { it.value.to_sym }
|
|
59
|
+
|
|
60
|
+
if macro.method?(:attr_reader)
|
|
61
|
+
names
|
|
62
|
+
elsif macro.method?(:attr_writer)
|
|
63
|
+
names.to_set { :"#{it}=" }
|
|
64
|
+
else
|
|
65
|
+
names | names.to_set { :"#{it}=" }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class RuboCop::Callbacksystems::Offense
|
|
2
|
+
attr_reader :range, :message, :correction
|
|
3
|
+
|
|
4
|
+
def initialize(range, message, correcting: true, &correction)
|
|
5
|
+
@range = range
|
|
6
|
+
@message = message
|
|
7
|
+
@correction = correcting ? correction : nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def correct(corrector)
|
|
11
|
+
correction&.call(corrector)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "lint_roller"
|
|
2
|
+
|
|
3
|
+
class RuboCop::Callbacksystems::Plugin < LintRoller::Plugin
|
|
4
|
+
GEM_NAME = "rubocop-callbacksystems"
|
|
5
|
+
CONFIGURATION_PATH = File.expand_path("../../../config/default.yml", __dir__)
|
|
6
|
+
|
|
7
|
+
def about
|
|
8
|
+
LintRoller::About.new \
|
|
9
|
+
name: specification.name,
|
|
10
|
+
version: specification.version.to_s,
|
|
11
|
+
homepage: specification.homepage,
|
|
12
|
+
description: specification.summary
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def supported?(context)
|
|
16
|
+
context.engine == :rubocop
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def rules(context)
|
|
20
|
+
LintRoller::Rules.new(type: :path, config_format: :rubocop, value: CONFIGURATION_PATH)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def specification
|
|
25
|
+
@specification ||= Gem.loaded_specs.fetch(GEM_NAME)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# One derived reading per consumer, replaced whenever its project graph or in-memory revision changes.
|
|
2
|
+
module RuboCop::Callbacksystems::ProjectIndex::Cache
|
|
3
|
+
def for(project_index)
|
|
4
|
+
generation = RuboCop::Callbacksystems::ProjectIndex::Freshness.generation(project_index)
|
|
5
|
+
cached_index_reading.then do |index, cached_generation, reading|
|
|
6
|
+
if index.equal?(project_index) && cached_generation == generation
|
|
7
|
+
reading
|
|
8
|
+
else
|
|
9
|
+
new(project_index).tap { self.cached_index_reading = [ project_index, generation, it ] }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
attr_accessor :cached_index_reading
|
|
16
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Nested-class confinement analyses shared by every cop investigating the same AST. The weak key lets a completed source
|
|
2
|
+
# leave the cache, while graph identity and generation keep an analysis from crossing project revisions.
|
|
3
|
+
class RuboCop::Callbacksystems::ProjectIndex::ConfinementCache
|
|
4
|
+
class << self
|
|
5
|
+
def for(nested_class, declaration:, project_index:)
|
|
6
|
+
generation = RuboCop::Callbacksystems::ProjectIndex::Freshness.generation(project_index)
|
|
7
|
+
mutex.synchronize do
|
|
8
|
+
cached = cache[nested_class]
|
|
9
|
+
|
|
10
|
+
if cached&.matches?(declaration, project_index, generation:)
|
|
11
|
+
cached.confinement
|
|
12
|
+
else
|
|
13
|
+
confinement = RuboCop::Callbacksystems::ProjectIndex::NestedClassConfinement.new \
|
|
14
|
+
nested_class,
|
|
15
|
+
declaration: declaration,
|
|
16
|
+
project_index: project_index
|
|
17
|
+
cache[nested_class] = Entry.new(declaration.name, project_index, generation, confinement)
|
|
18
|
+
confinement
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def mutex
|
|
25
|
+
@mutex ||= Mutex.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cache
|
|
29
|
+
@cache ||= ObjectSpace::WeakMap.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
class Entry < Data.define(:declaration_name, :project_index, :generation, :confinement)
|
|
35
|
+
def matches?(other_declaration, other_project_index, generation:)
|
|
36
|
+
declaration_name == other_declaration.name && project_index.equal?(other_project_index) &&
|
|
37
|
+
self.generation == generation
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Resolved constant references from Rubydex, indexed once by their exact source location so AST readers can ask which
|
|
2
|
+
# declaration a constant names without rebuilding or walking the project graph.
|
|
3
|
+
class RuboCop::Callbacksystems::ProjectIndex::ConstantReferences
|
|
4
|
+
extend RuboCop::Callbacksystems::ProjectIndex::Cache
|
|
5
|
+
|
|
6
|
+
def within(processed_source)
|
|
7
|
+
Source.new \
|
|
8
|
+
project_index,
|
|
9
|
+
references_by_location,
|
|
10
|
+
RuboCop::Callbacksystems::ProjectIndex::SourceFile.new(processed_source.file_path).uri
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
attr_reader :project_index
|
|
15
|
+
|
|
16
|
+
def initialize(project_index)
|
|
17
|
+
@project_index = project_index
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def references_by_location
|
|
21
|
+
@references_by_location ||= project_index.constant_references.grep(Rubydex::ResolvedConstantReference)
|
|
22
|
+
.group_by { RuboCop::Callbacksystems::ProjectIndex::Location.for_rubydex(it.location) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The references and resolver belonging to one source file.
|
|
26
|
+
class Source
|
|
27
|
+
include RuboCop::Cop::ProjectIndexHelp
|
|
28
|
+
|
|
29
|
+
def initialize(project_index, references_by_location, uri)
|
|
30
|
+
@project_index = project_index
|
|
31
|
+
@references_by_location = references_by_location
|
|
32
|
+
@uri = uri
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def referenced_declaration_for(constant_node)
|
|
36
|
+
declarations_at(constant_node.loc.name).grep_v(Rubydex::SingletonClass)
|
|
37
|
+
.uniq(&:name).then { it.first if it.one? }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def declaration_named(name, beside:)
|
|
41
|
+
nesting = name.to_s.start_with?("::") ? [] : lexical_nesting_of(beside)
|
|
42
|
+
query = Resolution.new(name.to_s, nesting)
|
|
43
|
+
|
|
44
|
+
resolutions.fetch(query) { resolutions[query] = query.declaration_in(project_index) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
attr_reader :project_index, :references_by_location, :uri
|
|
49
|
+
|
|
50
|
+
def declarations_at(range)
|
|
51
|
+
location = RuboCop::Callbacksystems::ProjectIndex::Location.for_ast(range, uri:)
|
|
52
|
+
references_by_location.fetch(location) { [] }.map(&:declaration)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def resolutions
|
|
56
|
+
@resolutions ||= {}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Resolution < Data.define(:name, :nesting)
|
|
60
|
+
def declaration_in(project_index)
|
|
61
|
+
segments.drop(1).reduce(project_index.resolve_constant(segments.first, nesting)) do |declaration, segment|
|
|
62
|
+
declaration.find_member(segment) if declaration.is_a?(Rubydex::Namespace)
|
|
63
|
+
end
|
|
64
|
+
rescue
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
def segments
|
|
70
|
+
name.delete_prefix("::").split("::")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Keeps the project index synchronized with RuboCop's in-memory autocorrection iterations. RuboCop has no public
|
|
2
|
+
# correction lifecycle hook, so this adapter stays at the narrow write boundary and delegates the update itself to an
|
|
3
|
+
# object that can fail closed.
|
|
4
|
+
module RuboCop::Callbacksystems::ProjectIndex::Corrections
|
|
5
|
+
private
|
|
6
|
+
def apply_correction(processed_source, new_source)
|
|
7
|
+
Correction.new(cops, processed_source, new_source).update_project_index
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Correction
|
|
12
|
+
def initialize(cops, processed_source, new_source)
|
|
13
|
+
@cops = cops
|
|
14
|
+
@processed_source = processed_source
|
|
15
|
+
@new_source = new_source
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_project_index
|
|
19
|
+
if project_index
|
|
20
|
+
RuboCop::Callbacksystems::ProjectIndex::Freshness.update(project_index, processed_source, new_source)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
attr_reader :cops, :processed_source, :new_source
|
|
26
|
+
|
|
27
|
+
def project_index
|
|
28
|
+
@project_index ||= indexes.first if indexes.all? { it.equal?(indexes.first) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def indexes
|
|
32
|
+
@indexes ||= cops.filter_map(&:project_index)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class RuboCop::Callbacksystems::ProjectIndex::Coverage
|
|
2
|
+
class << self
|
|
3
|
+
def register(project_index, paths:)
|
|
4
|
+
self.cached = [ project_index, new(project_index, paths:) ]
|
|
5
|
+
rescue
|
|
6
|
+
self.cached = [ project_index, nil ]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def complete?(project_index)
|
|
10
|
+
cached.then do |index, coverage|
|
|
11
|
+
coverage ? index.equal?(project_index) && coverage.complete? : false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
attr_accessor :cached
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(project_index, paths:)
|
|
20
|
+
@project_index = project_index
|
|
21
|
+
@expected_files = paths.to_set { RuboCop::Callbacksystems::ProjectIndex::SourceFile.new(it) }
|
|
22
|
+
@complete = missing_files.empty? || (supplemented? && indexed_files == expected_files)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def complete?
|
|
26
|
+
complete
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
attr_reader :project_index, :expected_files, :complete
|
|
31
|
+
|
|
32
|
+
def missing_files
|
|
33
|
+
@missing_files ||= expected_files - indexed_files
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def indexed_files
|
|
37
|
+
project_index.documents.filter_map do |document|
|
|
38
|
+
if document.uri.start_with?(RuboCop::Cop::ProjectIndexHelp::FILE_URI_PREFIX)
|
|
39
|
+
RuboCop::Callbacksystems::ProjectIndex::SourceFile.for_document(document)
|
|
40
|
+
end
|
|
41
|
+
end.to_set
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def supplemented?
|
|
45
|
+
missing_files.map { index_file(it) }.all?.tap { project_index.resolve }
|
|
46
|
+
rescue
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def index_file(file)
|
|
51
|
+
project_index.index_source(file.uri, file.source, "ruby")
|
|
52
|
+
true
|
|
53
|
+
rescue
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|