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,255 @@
1
+ # Orders the nested classes of a body by reference: one that names another reads
2
+ # before the one it names, the way `MethodInvocationOrder` reads a caller before
3
+ # its callees.
4
+ #
5
+ # Only a class or module carrying a body counts. A constant built in a line
6
+ # (`Row = Data.define(:name)`, `Error = Class.new(StandardError)`) is a
7
+ # declaration, and `BodyOrder` reads those with the values at the top.
8
+ #
9
+ # Where the nested classes sit is `BodyOrder`'s business, which keeps them last.
10
+ # This one only orders them among themselves, inside the run they already form.
11
+ #
12
+ # @example
13
+ # # bad - Row names Cell, so Row reads first
14
+ # class Report
15
+ # class Cell
16
+ # def value; end
17
+ # end
18
+ #
19
+ # class Row
20
+ # def cells
21
+ # [ Cell.new ]
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ # # good
27
+ # class Report
28
+ # class Row
29
+ # def cells
30
+ # [ Cell.new ]
31
+ # end
32
+ # end
33
+ #
34
+ # class Cell
35
+ # def value; end
36
+ # end
37
+ # end
38
+ #
39
+ class RuboCop::Cop::Callbacksystems::NestedClassOrder < RuboCop::Cop::Callbacksystems::Base
40
+ include RuboCop::Callbacksystems::ProjectIndex::Support
41
+ extend RuboCop::Cop::AutoCorrector
42
+
43
+ def on_new_investigation
44
+ report_each Bodies.new(processed_source, constant_references:) if project_index_reliable?
45
+ end
46
+
47
+ private
48
+ def constant_references
49
+ RuboCop::Callbacksystems::ProjectIndex::ConstantReferences.for(project_index).within(processed_source)
50
+ end
51
+
52
+ class Bodies
53
+ include RuboCop::Callbacksystems::Helpers
54
+
55
+ def initialize(processed_source, constant_references:)
56
+ @processed_source = processed_source
57
+ @constant_references = constant_references
58
+ end
59
+
60
+ def each_offense(&block)
61
+ runs.each do |run|
62
+ NestedClasses.new(run, processed_source, constant_references:, permit:).each_offense(&block)
63
+ end
64
+ end
65
+
66
+ private
67
+ attr_reader :processed_source, :constant_references
68
+
69
+ def runs
70
+ @runs ||= definition_nodes_in(processed_source.ast).flat_map { runs_of(it) }.select(&:many?)
71
+ end
72
+
73
+ def runs_of(node)
74
+ runs_in(node.body) { declared_class?(it) }
75
+ end
76
+
77
+ # A constant built from a builder stays out, since `BodyOrder` already places it among the values or the nested
78
+ # classes.
79
+ def declared_class?(node)
80
+ node.type?(:class, :module) && node.body.present?
81
+ end
82
+
83
+ def permit
84
+ @permit ||= RuboCop::Callbacksystems::Autocorrection::RewritePermit.new
85
+ end
86
+ end
87
+
88
+ # One run of nested classes written together, read for the first that sits before a class naming it.
89
+ class NestedClasses
90
+ MESSAGE = "Class `%<expected>s` should be defined before `%<actual>s`, since it names it."
91
+
92
+ def initialize(nodes, processed_source, constant_references:, permit:)
93
+ @nodes = nodes
94
+ @processed_source = processed_source
95
+ @constant_references = constant_references
96
+ @permit = permit
97
+ end
98
+
99
+ def each_offense
100
+ yield offense if reportable?
101
+ end
102
+
103
+ private
104
+ attr_reader :nodes, :processed_source, :constant_references, :permit
105
+ delegate :rewrite, to: :statement_run, private: true
106
+
107
+ def reportable?
108
+ analyzable? && ordered_safely?
109
+ end
110
+
111
+ # Reopening gives one declaration several load positions, so no single reference order can place it truthfully.
112
+ def analyzable?
113
+ classes.all?(&:uniquely_declared?) && identities.uniq.size == identities.size
114
+ end
115
+
116
+ def classes
117
+ @classes ||= nodes.map { NestedClass.new(it, processed_source, constant_references:) }
118
+ end
119
+
120
+ def identities
121
+ @identities ||= classes.map(&:identity)
122
+ end
123
+
124
+ def ordered_safely?
125
+ divergence.found? && load_order_kept?
126
+ end
127
+
128
+ def divergence
129
+ @divergence ||= RuboCop::Callbacksystems::ClassStructure::Divergence.new(identities, from: canonical_order)
130
+ end
131
+
132
+ def canonical_order
133
+ @canonical_order ||= RuboCop::Callbacksystems::ClassStructure::ReferenceOrder.new(seeds, reference_graph).to_a
134
+ end
135
+
136
+ def seeds
137
+ identities.reject { referenced.include?(it) } + identities
138
+ end
139
+
140
+ def referenced
141
+ @referenced ||= reference_graph.values.flatten.to_set
142
+ end
143
+
144
+ def reference_graph
145
+ @reference_graph ||= graph_of(:reads)
146
+ end
147
+
148
+ def graph_of(reading)
149
+ classes.to_h { [ it.identity, it.public_send(reading).intersection(identities) ] }
150
+ end
151
+
152
+ # A superclass or a name read outside a method runs as the body loads, so an order breaking one is refused.
153
+ def load_order_kept?
154
+ dependency_graph.all? { |name, required| required.all? { loaded_before?(dependency: it, dependent: name) } }
155
+ end
156
+
157
+ def dependency_graph
158
+ @dependency_graph ||= graph_of(:dependencies)
159
+ end
160
+
161
+ def loaded_before?(dependency:, dependent:)
162
+ canonical_positions.fetch(dependency) < canonical_positions.fetch(dependent)
163
+ end
164
+
165
+ def canonical_positions
166
+ @canonical_positions ||= canonical_order.each_with_index.to_h
167
+ end
168
+
169
+ def offense
170
+ RuboCop::Callbacksystems::Offense.new(nodes[divergence.index], message,
171
+ correcting: correctable?) { rewrite(it) }
172
+ end
173
+
174
+ def message
175
+ format(MESSAGE, expected: name_of(divergence.expected), actual: name_of(divergence.actual))
176
+ end
177
+
178
+ def name_of(identity)
179
+ classes_by_identity.fetch(identity).name
180
+ end
181
+
182
+ def classes_by_identity
183
+ @classes_by_identity ||= classes.index_by(&:identity)
184
+ end
185
+
186
+ def correctable?
187
+ uninterrupted? && statement_run.reorderable? && permit.claim
188
+ end
189
+
190
+ # A comment unattached to either class is an intentional boundary, not source for a sortable run.
191
+ def uninterrupted?
192
+ classes.each_cons(2).none? { |above, below| above.range.end.join(below.range.begin).source.strip.present? }
193
+ end
194
+
195
+ def statement_run
196
+ @statement_run ||= RuboCop::Callbacksystems::ClassStructure::StatementRun.new \
197
+ classes, order: canonical_order, &:identity
198
+ end
199
+ end
200
+
201
+ class NestedClass
202
+ include RuboCop::Callbacksystems::Helpers
203
+
204
+ delegate :range, :source, :contains_tooling_comment?, to: :block
205
+
206
+ def initialize(node, processed_source, constant_references:)
207
+ @node = node
208
+ @processed_source = processed_source
209
+ @constant_references = constant_references
210
+ end
211
+
212
+ def reads
213
+ referenced_identities_of(constants.select { enclosing_method_of(it).present? })
214
+ end
215
+
216
+ def name
217
+ node.identifier.short_name
218
+ end
219
+
220
+ def identity
221
+ declaration&.name
222
+ end
223
+
224
+ def uniquely_declared?
225
+ declaration.is_a?(Rubydex::Namespace) && declaration.definitions.one?
226
+ end
227
+
228
+ def dependencies
229
+ referenced_identities_of(superclass_constants + constants.reject { enclosing_method_of(it).present? })
230
+ end
231
+
232
+ private
233
+ attr_reader :node, :processed_source, :constant_references
234
+
235
+ def block
236
+ @block ||= RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source)
237
+ end
238
+
239
+ def referenced_identities_of(found)
240
+ found.filter_map { constant_references.referenced_declaration_for(it)&.name }.uniq.excluding(identity)
241
+ end
242
+
243
+ def constants
244
+ @constants ||= node.each_descendant(:const).to_a
245
+ end
246
+
247
+ def declaration
248
+ @declaration ||= constant_references.declaration_named(node.identifier.const_name, beside: node.identifier)
249
+ end
250
+
251
+ def superclass_constants
252
+ node.class_type? && node.parent_class ? node.parent_class.each_node(:const).to_a : []
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,197 @@
1
+ # Prohibits common abbreviations in declared names, including CamelCase. A
2
+ # full word costs a few keystrokes once and reads at a glance every time after,
3
+ # while an abbreviation makes every reader expand it.
4
+ #
5
+ # @example
6
+ # # bad
7
+ # def calc_total
8
+ # end
9
+ #
10
+ # # bad
11
+ # attrs = {}
12
+ #
13
+ # # bad
14
+ # USER_ATTRS = %i[name email]
15
+ #
16
+ # # bad
17
+ # class UserAttrs
18
+ # end
19
+ #
20
+ # # good
21
+ # def calculate_total
22
+ # end
23
+ #
24
+ # # good
25
+ # attributes = {}
26
+ #
27
+ # # good
28
+ # USER_ATTRIBUTES = %i[name email]
29
+ #
30
+ # # good
31
+ # class UserAttributes
32
+ # end
33
+ #
34
+ class RuboCop::Cop::Callbacksystems::NoAbbreviations < RuboCop::Cop::Callbacksystems::Base
35
+ def on_class(node)
36
+ on_def(node.identifier)
37
+ end
38
+
39
+ alias on_module on_class
40
+
41
+ def on_def(node)
42
+ report_each AbbreviatedName.new(node.loc.name)
43
+ end
44
+
45
+ alias on_defs on_def
46
+ alias on_lvasgn on_def
47
+ alias on_match_var on_def
48
+ alias on_ivasgn on_def
49
+ alias on_cvasgn on_def
50
+ alias on_gvasgn on_def
51
+ alias on_casgn on_def
52
+ alias on_arg on_def
53
+ alias on_optarg on_def
54
+ alias on_restarg on_def
55
+ alias on_kwarg on_def
56
+ alias on_kwoptarg on_def
57
+ alias on_kwrestarg on_def
58
+ alias on_blockarg on_def
59
+ alias on_shadowarg on_def
60
+
61
+ private
62
+ # Ruby and Rails conventions stay readable: params, args, id, ids, config, env, info, lib, max, min,
63
+ # proc(s), temp, sync, and the attr_reader, attr_writer, and attr_accessor APIs.
64
+ ABBREVIATIONS = {
65
+ "abbrev" => "abbreviation",
66
+ "amt" => "amount",
67
+ "arr" => "array",
68
+ "attr" => "attribute",
69
+ "attrs" => "attributes",
70
+ "btn" => "button",
71
+ "buf" => "buffer",
72
+ "calc" => "calculate",
73
+ "cb" => "callback",
74
+ "cfg" => "configuration",
75
+ "cmd" => "command",
76
+ "conf" => "configuration",
77
+ "ctx" => "context",
78
+ "curr" => "current",
79
+ "decl" => "declaration",
80
+ "decls" => "declarations",
81
+ "dep" => "dependency",
82
+ "deps" => "dependencies",
83
+ "dest" => "destination",
84
+ "doc" => "document",
85
+ "docs" => %w[ documentation documents ],
86
+ "elem" => "element",
87
+ "elems" => "elements",
88
+ "err" => "error",
89
+ "errs" => "errors",
90
+ "evt" => "event",
91
+ "expr" => "expression",
92
+ "exprs" => "expressions",
93
+ "func" => "function",
94
+ "funcs" => "functions",
95
+ "ident" => "identifier",
96
+ "idents" => "identifiers",
97
+ "idx" => "index",
98
+ "impl" => "implementation",
99
+ "init" => "initialize",
100
+ "len" => "length",
101
+ "libs" => "libraries",
102
+ "msg" => "message",
103
+ "msgs" => "messages",
104
+ "num" => "number",
105
+ "nums" => "numbers",
106
+ "obj" => "object",
107
+ "objs" => "objects",
108
+ "opt" => "option",
109
+ "opts" => "options",
110
+ "org" => "organization",
111
+ "orgs" => "organizations",
112
+ "perf" => "performance",
113
+ "pkg" => "package",
114
+ "pkgs" => "packages",
115
+ "pos" => "position",
116
+ "prev" => "previous",
117
+ "prop" => "property",
118
+ "props" => "properties",
119
+ "ref" => "reference",
120
+ "refs" => "references",
121
+ "repo" => "repository",
122
+ "repos" => "repositories",
123
+ "req" => "request",
124
+ "reqs" => "requests",
125
+ "res" => %w[ resource response result ],
126
+ "resp" => "response",
127
+ "sep" => "separator",
128
+ "src" => "source",
129
+ "stmt" => "statement",
130
+ "stmts" => "statements",
131
+ "str" => "string",
132
+ "strs" => "strings",
133
+ "tbl" => "table",
134
+ "tmp" => "temporary",
135
+ "val" => "value",
136
+ "vals" => "values",
137
+ "var" => "variable",
138
+ "vars" => "variables",
139
+ "ver" => "version"
140
+ }
141
+
142
+ # Word boundaries preserve acronyms and source offsets while recognizing both snake_case and CamelCase names.
143
+ class AbbreviatedName
144
+ MESSAGE = "Avoid abbreviation `%<abbreviation>s`. Use %<full>s instead."
145
+ WORD_PATTERN = /[[:upper:]]+(?=[[:upper:]][[:lower:]]|[^[:alpha:]]|\z)|[[:upper:]]?[[:lower:]]+/
146
+ NEXT_WORD_PATTERN = /\A_?(#{WORD_PATTERN})/
147
+ ATTRIBUTE_ACCESSORS = %w[ accessor reader writer ]
148
+
149
+ def initialize(range)
150
+ @range = range
151
+ end
152
+
153
+ def each_offense
154
+ range&.source&.scan(WORD_PATTERN) do
155
+ match = Regexp.last_match
156
+ if abbreviation_at?(match)
157
+ yield RuboCop::Callbacksystems::Offense.new(location_of(match), message_for(match.to_s))
158
+ end
159
+ end
160
+ end
161
+
162
+ private
163
+ attr_reader :range
164
+
165
+ def abbreviation_at?(match)
166
+ ABBREVIATIONS.key?(match.to_s.downcase) && !attribute_accessor_at?(match)
167
+ end
168
+
169
+ def attribute_accessor_at?(match)
170
+ match.to_s.casecmp?("attr") && range.source[match.end(0)..].match(NEXT_WORD_PATTERN)&.then do |following|
171
+ ATTRIBUTE_ACCESSORS.include?(following.captures.first.downcase)
172
+ end
173
+ end
174
+
175
+ def location_of(match)
176
+ range.with(begin_pos: range.begin_pos + match.begin(0), end_pos: range.begin_pos + match.end(0))
177
+ end
178
+
179
+ def message_for(word)
180
+ format(MESSAGE, abbreviation: word, full: replacements_for(word))
181
+ end
182
+
183
+ def replacements_for(word)
184
+ Array(ABBREVIATIONS[word.downcase]).map { "`#{replacement_for(word, with: it)}`" }.join(" or ")
185
+ end
186
+
187
+ def replacement_for(word, with:)
188
+ if word == word.upcase
189
+ with.upcase
190
+ elsif word.start_with?(/[[:upper:]]/)
191
+ with.capitalize
192
+ else
193
+ with
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,101 @@
1
+ # Detects private methods that only delegate to a newly created object.
2
+ # Such methods add indirection without value and should be inlined or reconsidered.
3
+ # Public methods and predicates are excluded as they often provide semantic value.
4
+ #
5
+ # @example
6
+ # # bad - private method only creates object and calls single method
7
+ # private
8
+ # def eager_loading_association(body)
9
+ # ScopeBody.new(body).eager_loading_association
10
+ # end
11
+ #
12
+ # # good - inline the call
13
+ # ScopeBody.new(body).eager_loading_association
14
+ #
15
+ # # good - public method (may be API)
16
+ # def eager_loading_association(body)
17
+ # ScopeBody.new(body).eager_loading_association
18
+ # end
19
+ #
20
+ # # good - predicate adds semantic meaning
21
+ # def valid?
22
+ # Validator.new(self).valid?
23
+ # end
24
+ #
25
+ class RuboCop::Cop::Callbacksystems::NoAnemicDelegation < RuboCop::Cop::Callbacksystems::Base
26
+ def on_def(node)
27
+ report MethodDelegation.new(node)
28
+ end
29
+
30
+ alias on_defs on_def
31
+
32
+ private
33
+ class MethodDelegation
34
+ include RuboCop::Callbacksystems::Helpers
35
+
36
+ MESSAGE = "Method `%<method>s` only delegates to `%<class>s.new(...).%<target>s`. Consider inlining or " \
37
+ "removing this method."
38
+
39
+ def initialize(node)
40
+ @node = node
41
+ end
42
+
43
+ def offense
44
+ RuboCop::Callbacksystems::Offense.new(node, message) if anemic_delegation?
45
+ end
46
+
47
+ private
48
+ attr_reader :node
49
+ delegate :body, to: :node, private: true
50
+
51
+ def anemic_delegation?
52
+ eligible_method? && delegates_only_parameters?
53
+ end
54
+
55
+ def eligible_method?
56
+ single_send_private_non_predicate?(node) && direct_method_declaration?
57
+ end
58
+
59
+ def direct_method_declaration?
60
+ owner && transparent_parents?
61
+ end
62
+
63
+ def owner
64
+ @owner ||= enclosing_class_or_module_of(node)
65
+ end
66
+
67
+ def transparent_parents?
68
+ node.each_ancestor.take_while { !it.equal?(owner) }.all? { transparent_method_parent?(it) }
69
+ end
70
+
71
+ def transparent_method_parent?(parent)
72
+ parent.type?(:begin, :kwbegin, :sclass) || visibility_applied_to(parent, node)
73
+ end
74
+
75
+ def delegates_only_parameters?
76
+ receiver_is_new_instance? && passes_only_parameters?
77
+ end
78
+
79
+ def receiver_is_new_instance?
80
+ body.receiver&.send_type? &&
81
+ body.receiver.method?(:new) &&
82
+ body.receiver.receiver&.const_type?
83
+ end
84
+
85
+ def passes_only_parameters?
86
+ invocation_arguments.all? { it.lvar_type? && parameter_names.include?(it.name) }
87
+ end
88
+
89
+ def invocation_arguments
90
+ body.receiver.arguments + body.arguments
91
+ end
92
+
93
+ def parameter_names
94
+ @parameter_names ||= node.arguments.children.filter_map { it.name if it.respond_to?(:name) }
95
+ end
96
+
97
+ def message
98
+ format(MESSAGE, method: node.method_name, class: body.receiver.receiver.short_name, target: body.method_name)
99
+ end
100
+ end
101
+ end