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.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +78 -0
  4. data/config/default.yml +590 -0
  5. data/docs/cops.md +111 -0
  6. data/lib/rubocop/callbacksystems/autocorrection/batch.rb +135 -0
  7. data/lib/rubocop/callbacksystems/autocorrection/comment_rescue.rb +112 -0
  8. data/lib/rubocop/callbacksystems/autocorrection/rehearsal.rb +65 -0
  9. data/lib/rubocop/callbacksystems/autocorrection/rewrite_permit.rb +14 -0
  10. data/lib/rubocop/callbacksystems/class_structure/builder_assignment.rb +48 -0
  11. data/lib/rubocop/callbacksystems/class_structure/class_body.rb +50 -0
  12. data/lib/rubocop/callbacksystems/class_structure/divergence.rb +31 -0
  13. data/lib/rubocop/callbacksystems/class_structure/node_visibility.rb +82 -0
  14. data/lib/rubocop/callbacksystems/class_structure/reference_order.rb +36 -0
  15. data/lib/rubocop/callbacksystems/class_structure/statement_rank.rb +84 -0
  16. data/lib/rubocop/callbacksystems/class_structure/statement_run.rb +42 -0
  17. data/lib/rubocop/callbacksystems/class_structure/visibility.rb +84 -0
  18. data/lib/rubocop/callbacksystems/cops_document.rb +78 -0
  19. data/lib/rubocop/callbacksystems/execution/deferred_local_variables.rb +138 -0
  20. data/lib/rubocop/callbacksystems/execution/discarded_expression.rb +45 -0
  21. data/lib/rubocop/callbacksystems/execution/eager_evaluation_path.rb +73 -0
  22. data/lib/rubocop/callbacksystems/execution/immediate.rb +71 -0
  23. data/lib/rubocop/callbacksystems/execution/local_variable_occurrences.rb +112 -0
  24. data/lib/rubocop/callbacksystems/hashes/key_transform_detection.rb +108 -0
  25. data/lib/rubocop/callbacksystems/hashes/keyword_options.rb +37 -0
  26. data/lib/rubocop/callbacksystems/helpers/bodies.rb +150 -0
  27. data/lib/rubocop/callbacksystems/helpers/builders.rb +103 -0
  28. data/lib/rubocop/callbacksystems/helpers/comments.rb +56 -0
  29. data/lib/rubocop/callbacksystems/helpers/corrections.rb +15 -0
  30. data/lib/rubocop/callbacksystems/helpers/methods.rb +46 -0
  31. data/lib/rubocop/callbacksystems/helpers/names.rb +65 -0
  32. data/lib/rubocop/callbacksystems/helpers/node_types.rb +154 -0
  33. data/lib/rubocop/callbacksystems/helpers/rails.rb +52 -0
  34. data/lib/rubocop/callbacksystems/helpers/ranges.rb +107 -0
  35. data/lib/rubocop/callbacksystems/helpers/recursion.rb +29 -0
  36. data/lib/rubocop/callbacksystems/helpers/variables.rb +44 -0
  37. data/lib/rubocop/callbacksystems/helpers/visibility.rb +121 -0
  38. data/lib/rubocop/callbacksystems/helpers.rb +43 -0
  39. data/lib/rubocop/callbacksystems/methods/calls.rb +38 -0
  40. data/lib/rubocop/callbacksystems/methods/definitions.rb +218 -0
  41. data/lib/rubocop/callbacksystems/methods/delegate_macro.rb +144 -0
  42. data/lib/rubocop/callbacksystems/methods/delegation_target.rb +55 -0
  43. data/lib/rubocop/callbacksystems/methods/domain.rb +77 -0
  44. data/lib/rubocop/callbacksystems/methods/implicit_invocations.rb +17 -0
  45. data/lib/rubocop/callbacksystems/methods/macro_references.rb +238 -0
  46. data/lib/rubocop/callbacksystems/methods/reference_evidence.rb +74 -0
  47. data/lib/rubocop/callbacksystems/methods/siblings.rb +24 -0
  48. data/lib/rubocop/callbacksystems/methods/statement_definitions.rb +68 -0
  49. data/lib/rubocop/callbacksystems/offense.rb +13 -0
  50. data/lib/rubocop/callbacksystems/plugin.rb +27 -0
  51. data/lib/rubocop/callbacksystems/project_index/cache.rb +16 -0
  52. data/lib/rubocop/callbacksystems/project_index/confinement_cache.rb +40 -0
  53. data/lib/rubocop/callbacksystems/project_index/constant_references.rb +74 -0
  54. data/lib/rubocop/callbacksystems/project_index/corrections.rb +35 -0
  55. data/lib/rubocop/callbacksystems/project_index/coverage.rb +56 -0
  56. data/lib/rubocop/callbacksystems/project_index/diagnostics.rb +60 -0
  57. data/lib/rubocop/callbacksystems/project_index/freshness.rb +104 -0
  58. data/lib/rubocop/callbacksystems/project_index/loader.rb +9 -0
  59. data/lib/rubocop/callbacksystems/project_index/location.rb +29 -0
  60. data/lib/rubocop/callbacksystems/project_index/nested_class_confinement.rb +297 -0
  61. data/lib/rubocop/callbacksystems/project_index/nested_class_method_dispatch.rb +50 -0
  62. data/lib/rubocop/callbacksystems/project_index/reading.rb +129 -0
  63. data/lib/rubocop/callbacksystems/project_index/source_file.rb +59 -0
  64. data/lib/rubocop/callbacksystems/project_index/source_file_match.rb +57 -0
  65. data/lib/rubocop/callbacksystems/project_index/support.rb +46 -0
  66. data/lib/rubocop/callbacksystems/source/comment_blocks.rb +43 -0
  67. data/lib/rubocop/callbacksystems/source/comments.rb +141 -0
  68. data/lib/rubocop/callbacksystems/source/file_ast.rb +15 -0
  69. data/lib/rubocop/callbacksystems/source/files_checksum.rb +43 -0
  70. data/lib/rubocop/callbacksystems/source/single_line_call.rb +181 -0
  71. data/lib/rubocop/callbacksystems/source/statement_with_comments.rb +131 -0
  72. data/lib/rubocop/callbacksystems/testing/cop_helpers.rb +42 -0
  73. data/lib/rubocop/callbacksystems/testing/fixture_call.rb +30 -0
  74. data/lib/rubocop/callbacksystems/testing/fixture_names.rb +62 -0
  75. data/lib/rubocop/callbacksystems/testing/path_mapping.rb +104 -0
  76. data/lib/rubocop/cop/callbacksystems/application_controller_method_definition.rb +44 -0
  77. data/lib/rubocop/cop/callbacksystems/assert_with_expected_argument.rb +60 -0
  78. data/lib/rubocop/cop/callbacksystems/base.rb +134 -0
  79. data/lib/rubocop/cop/callbacksystems/body_order.rb +478 -0
  80. data/lib/rubocop/cop/callbacksystems/builder_method_parentheses.rb +74 -0
  81. data/lib/rubocop/cop/callbacksystems/collapse_multiline_expression.rb +140 -0
  82. data/lib/rubocop/cop/callbacksystems/comment_block_length.rb +137 -0
  83. data/lib/rubocop/cop/callbacksystems/complex_conditional.rb +75 -0
  84. data/lib/rubocop/cop/callbacksystems/controller_test_response_assertion.rb +105 -0
  85. data/lib/rubocop/cop/callbacksystems/controller_test_response_first_assertion.rb +123 -0
  86. data/lib/rubocop/cop/callbacksystems/data_clump.rb +339 -0
  87. data/lib/rubocop/cop/callbacksystems/declarative_method_naming.rb +350 -0
  88. data/lib/rubocop/cop/callbacksystems/delegate_requires_private_option.rb +81 -0
  89. data/lib/rubocop/cop/callbacksystems/eager_loading_scope_naming.rb +386 -0
  90. data/lib/rubocop/cop/callbacksystems/early_return.rb +215 -0
  91. data/lib/rubocop/cop/callbacksystems/empty_line_before_method.rb +168 -0
  92. data/lib/rubocop/cop/callbacksystems/empty_line_between_sections.rb +169 -0
  93. data/lib/rubocop/cop/callbacksystems/empty_private_section.rb +107 -0
  94. data/lib/rubocop/cop/callbacksystems/expanded_empty_class_or_module.rb +71 -0
  95. data/lib/rubocop/cop/callbacksystems/inline_memoized_computation.rb +110 -0
  96. data/lib/rubocop/cop/callbacksystems/job_enqueue_methods.rb +93 -0
  97. data/lib/rubocop/cop/callbacksystems/max_safe_navigation_depth.rb +118 -0
  98. data/lib/rubocop/cop/callbacksystems/method_invocation_order.rb +261 -0
  99. data/lib/rubocop/cop/callbacksystems/nested_class_order.rb +255 -0
  100. data/lib/rubocop/cop/callbacksystems/no_abbreviations.rb +197 -0
  101. data/lib/rubocop/cop/callbacksystems/no_anemic_delegation.rb +101 -0
  102. data/lib/rubocop/cop/callbacksystems/no_anemic_record.rb +309 -0
  103. data/lib/rubocop/cop/callbacksystems/no_assert_select.rb +31 -0
  104. data/lib/rubocop/cop/callbacksystems/no_bang_method_without_counterpart.rb +451 -0
  105. data/lib/rubocop/cop/callbacksystems/no_call_method.rb +73 -0
  106. data/lib/rubocop/cop/callbacksystems/no_comments_in_test_class_body.rb +159 -0
  107. data/lib/rubocop/cop/callbacksystems/no_consecutive_appends.rb +261 -0
  108. data/lib/rubocop/cop/callbacksystems/no_custom_route_actions.rb +124 -0
  109. data/lib/rubocop/cop/callbacksystems/no_direct_eager_loading.rb +90 -0
  110. data/lib/rubocop/cop/callbacksystems/no_each_with_early_return.rb +58 -0
  111. data/lib/rubocop/cop/callbacksystems/no_explicit_public_modifier.rb +81 -0
  112. data/lib/rubocop/cop/callbacksystems/no_hash_with_default_block.rb +68 -0
  113. data/lib/rubocop/cop/callbacksystems/no_local_variable_return.rb +173 -0
  114. data/lib/rubocop/cop/callbacksystems/no_manual_accumulation.rb +188 -0
  115. data/lib/rubocop/cop/callbacksystems/no_mixed_memoization.rb +145 -0
  116. data/lib/rubocop/cop/callbacksystems/no_nested_modules_in_classes.rb +78 -0
  117. data/lib/rubocop/cop/callbacksystems/no_nil_at_end_of_method.rb +74 -0
  118. data/lib/rubocop/cop/callbacksystems/no_redundant_validation_name.rb +37 -0
  119. data/lib/rubocop/cop/callbacksystems/no_redundant_wrapper_method.rb +161 -0
  120. data/lib/rubocop/cop/callbacksystems/no_section_divider_comments.rb +105 -0
  121. data/lib/rubocop/cop/callbacksystems/no_send_in_tests.rb +75 -0
  122. data/lib/rubocop/cop/callbacksystems/no_service_objects.rb +64 -0
  123. data/lib/rubocop/cop/callbacksystems/no_single_letter_names.rb +47 -0
  124. data/lib/rubocop/cop/callbacksystems/no_singleton_class_in_modules.rb +43 -0
  125. data/lib/rubocop/cop/callbacksystems/no_tap_collection_building.rb +173 -0
  126. data/lib/rubocop/cop/callbacksystems/no_turbo_stream_in_controllers.rb +52 -0
  127. data/lib/rubocop/cop/callbacksystems/no_typographic_clutter.rb +159 -0
  128. data/lib/rubocop/cop/callbacksystems/no_writes_in_get_actions.rb +149 -0
  129. data/lib/rubocop/cop/callbacksystems/ordered_mixin_arguments.rb +78 -0
  130. data/lib/rubocop/cop/callbacksystems/plural_controller_names.rb +103 -0
  131. data/lib/rubocop/cop/callbacksystems/prefer_attr_reader_over_instance_variable.rb +86 -0
  132. data/lib/rubocop/cop/callbacksystems/prefer_backslash_continuation.rb +478 -0
  133. data/lib/rubocop/cop/callbacksystems/prefer_class_for_state.rb +532 -0
  134. data/lib/rubocop/cop/callbacksystems/prefer_class_new_for_empty_nested_class.rb +247 -0
  135. data/lib/rubocop/cop/callbacksystems/prefer_class_over_builder_block.rb +124 -0
  136. data/lib/rubocop/cop/callbacksystems/prefer_default_argument.rb +225 -0
  137. data/lib/rubocop/cop/callbacksystems/prefer_delegate.rb +242 -0
  138. data/lib/rubocop/cop/callbacksystems/prefer_delegate_over_instance_variable_assignment.rb +175 -0
  139. data/lib/rubocop/cop/callbacksystems/prefer_excluding.rb +204 -0
  140. data/lib/rubocop/cop/callbacksystems/prefer_extract_associated.rb +54 -0
  141. data/lib/rubocop/cop/callbacksystems/prefer_it_block_parameter.rb +156 -0
  142. data/lib/rubocop/cop/callbacksystems/prefer_many.rb +57 -0
  143. data/lib/rubocop/cop/callbacksystems/prefer_method_over_lambda.rb +211 -0
  144. data/lib/rubocop/cop/callbacksystems/prefer_method_over_variable.rb +172 -0
  145. data/lib/rubocop/cop/callbacksystems/prefer_module_for_static_class.rb +286 -0
  146. data/lib/rubocop/cop/callbacksystems/prefer_ordinal_array_access.rb +136 -0
  147. data/lib/rubocop/cop/callbacksystems/prefer_positive_wrap.rb +265 -0
  148. data/lib/rubocop/cop/callbacksystems/prefer_squish.rb +47 -0
  149. data/lib/rubocop/cop/callbacksystems/prefer_stringify_keys.rb +24 -0
  150. data/lib/rubocop/cop/callbacksystems/prefer_symbolize_keys.rb +24 -0
  151. data/lib/rubocop/cop/callbacksystems/prefer_turbo_page_refresh_broadcasts.rb +40 -0
  152. data/lib/rubocop/cop/callbacksystems/private_constants_in_private_section.rb +160 -0
  153. data/lib/rubocop/cop/callbacksystems/private_delegate_placement.rb +342 -0
  154. data/lib/rubocop/cop/callbacksystems/private_methods_in_private_section.rb +70 -0
  155. data/lib/rubocop/cop/callbacksystems/private_nested_class_method_visibility.rb +269 -0
  156. data/lib/rubocop/cop/callbacksystems/private_nested_classes.rb +94 -0
  157. data/lib/rubocop/cop/callbacksystems/public_methods_must_have_tests.rb +190 -0
  158. data/lib/rubocop/cop/callbacksystems/redundant_concern_extend.rb +157 -0
  159. data/lib/rubocop/cop/callbacksystems/redundant_constant_freeze.rb +57 -0
  160. data/lib/rubocop/cop/callbacksystems/redundant_constant_namespace.rb +134 -0
  161. data/lib/rubocop/cop/callbacksystems/reflow_comments.rb +133 -0
  162. data/lib/rubocop/cop/callbacksystems/repeated_fixture_in_tests.rb +106 -0
  163. data/lib/rubocop/cop/callbacksystems/restricted_controller_actions.rb +85 -0
  164. data/lib/rubocop/cop/callbacksystems/routes_module_scope.rb +336 -0
  165. data/lib/rubocop/cop/callbacksystems/single_line_setup_block.rb +107 -0
  166. data/lib/rubocop/cop/callbacksystems/single_singleton_class.rb +49 -0
  167. data/lib/rubocop/cop/callbacksystems/single_top_level_class_or_module.rb +160 -0
  168. data/lib/rubocop/cop/callbacksystems/single_use_fixture_variable.rb +211 -0
  169. data/lib/rubocop/cop/callbacksystems/single_use_setup_variable.rb +684 -0
  170. data/lib/rubocop/cop/callbacksystems/singular_scope_names.rb +47 -0
  171. data/lib/rubocop/cop/callbacksystems/space_inside_percent_literal.rb +73 -0
  172. data/lib/rubocop/cop/callbacksystems/system_test_browser_driver.rb +30 -0
  173. data/lib/rubocop/cop/callbacksystems/test_method_definition.rb +86 -0
  174. data/lib/rubocop/cop/callbacksystems/test_method_order.rb +319 -0
  175. data/lib/rubocop/cop/callbacksystems/too_many_instance_variables.rb +78 -0
  176. data/lib/rubocop/cop/callbacksystems/too_many_local_variables.rb +77 -0
  177. data/lib/rubocop/cop/callbacksystems/too_many_validation_guards.rb +84 -0
  178. data/lib/rubocop/cop/callbacksystems/unnecessary_local_variable.rb +176 -0
  179. data/lib/rubocop/cop/callbacksystems/unused_private_method_in_nested_class.rb +203 -0
  180. data/lib/rubocop-callbacksystems.rb +21 -0
  181. data/rubocop.yml +685 -0
  182. 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