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,112 @@
1
+ # Local-variable nodes indexed by lexical scope, so several readings of one scope walk its tree only once.
2
+ class RuboCop::Callbacksystems::Execution::LocalVariableOccurrences
3
+ include RuboCop::Callbacksystems::Helpers
4
+
5
+ def initialize(tree)
6
+ @tree = tree
7
+ @by_scope = {}.compare_by_identity
8
+ end
9
+
10
+ def named(name, around:)
11
+ occurrences_around(around).fetch(name) { [] }
12
+ end
13
+
14
+ def named_in_method(name, around:)
15
+ occurrences_in(enclosing_method_of(around)).fetch(name) { [] }
16
+ end
17
+
18
+ private
19
+ attr_reader :tree, :by_scope
20
+
21
+ def occurrences_around(node)
22
+ occurrences_in(scope_of(node))
23
+ end
24
+
25
+ def occurrences_in(scope)
26
+ if scope
27
+ by_scope[scope] ||= OccurrencesWithin.new(scope).group_by { variable_name_of(it) }
28
+ else
29
+ {}
30
+ end
31
+ end
32
+
33
+ def scope_of(node)
34
+ enclosing_scope_of(node) || tree
35
+ end
36
+
37
+ class OccurrencesWithin
38
+ include Enumerable
39
+ include RuboCop::Callbacksystems::Helpers
40
+
41
+ def initialize(scope)
42
+ @scope = scope
43
+ end
44
+
45
+ def each
46
+ if block_given?
47
+ each_occurrence { yield it }
48
+ else
49
+ to_enum(__method__)
50
+ end
51
+ end
52
+
53
+ private
54
+ attr_reader :scope
55
+
56
+ def each_occurrence
57
+ pending = [ Candidate.new(scope, scope:, nested: false) ]
58
+ until pending.empty?
59
+ candidate = pending.pop
60
+ yield candidate.node if candidate.occurrence?
61
+ candidate.children.reverse_each { pending << it }
62
+ end
63
+ end
64
+
65
+ class Candidate
66
+ include RuboCop::Callbacksystems::Helpers
67
+
68
+ attr_reader :node
69
+
70
+ def initialize(node, scope:, nested:)
71
+ @node = node
72
+ @scope = scope
73
+ @nested = nested
74
+ end
75
+
76
+ def occurrence?
77
+ local_node? && !rebound?
78
+ end
79
+
80
+ def children
81
+ child_nodes.map { self.class.new(it, scope:, nested: true) }
82
+ end
83
+
84
+ private
85
+ attr_reader :scope, :nested
86
+
87
+ def local_node?
88
+ node.type?(:lvar, :lvasgn, :match_var)
89
+ end
90
+
91
+ def rebound?
92
+ name_rebound_between?(variable_name_of(node), node:, boundary: scope)
93
+ end
94
+
95
+ def child_nodes
96
+ if nested && execution_boundary?
97
+ immediate_inputs_of(node)
98
+ else
99
+ node.each_child_node
100
+ end
101
+ end
102
+
103
+ def execution_boundary?
104
+ lexical_definition?(node) || deferred_boundary?
105
+ end
106
+
107
+ def deferred_boundary?
108
+ deferred_callable_block?(node) || method_definition_block?(node)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,108 @@
1
+ # The reading PreferStringifyKeys and PreferSymbolizeKeys share: a `transform_keys` that only sends one method to each
2
+ # key, given as a block pass or as a block. An including cop names that method as `source_method` and the Active Support
3
+ # method saying the same thing as `preferred_method`.
4
+ module RuboCop::Callbacksystems::Hashes::KeyTransformDetection
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ extend RuboCop::Cop::AutoCorrector
9
+ end
10
+
11
+ def on_send(node)
12
+ report key_transform_of(node)
13
+ end
14
+
15
+ alias on_csend on_send
16
+
17
+ def on_block(node)
18
+ report key_transform_of(node)
19
+ end
20
+
21
+ alias on_numblock on_block
22
+ alias on_itblock on_block
23
+
24
+ private
25
+ def key_transform_of(node)
26
+ KeyTransform.new(node, source_method:, preferred_method:, source_comments:)
27
+ end
28
+
29
+ class KeyTransform
30
+ include RuboCop::Callbacksystems::Helpers
31
+
32
+ MESSAGE = "Use `%<preferred_method>s` instead of `transform_keys(&:%<source_method>s)`."
33
+
34
+ def initialize(node, source_method:, preferred_method:, source_comments:)
35
+ @node = node
36
+ @source_method = source_method
37
+ @preferred_method = preferred_method
38
+ @source_comments = source_comments
39
+ end
40
+
41
+ def offense
42
+ if sends_source_method_to_keys?
43
+ RuboCop::Callbacksystems::Offense.new(node, message, correcting: uncommented?) { correct(it) }
44
+ end
45
+ end
46
+
47
+ private
48
+ attr_reader :node, :source_method, :preferred_method, :source_comments
49
+
50
+ def sends_source_method_to_keys?
51
+ transform_keys? && sends_source_method?
52
+ end
53
+
54
+ def transform_keys?
55
+ keys_call.method?(:transform_keys) && keys_call.receiver
56
+ end
57
+
58
+ def keys_call
59
+ node.call_type? ? node : node.send_node
60
+ end
61
+
62
+ def sends_source_method?
63
+ node.call_type? ? passes_source_method? : sends_source_method_to_parameter?
64
+ end
65
+
66
+ def passes_source_method?
67
+ keys_call.arguments.one? && block_pass_value&.sym_type? &&
68
+ block_pass_value.value == source_method
69
+ end
70
+
71
+ def block_pass_value
72
+ node.first_argument.children.first if node.first_argument.block_pass_type?
73
+ end
74
+
75
+ def sends_source_method_to_parameter?
76
+ keys_call.arguments.empty? && single_parameter_block? && exact_source_method_send?
77
+ end
78
+
79
+ def single_parameter_block?
80
+ any_block_type?(node) && node.argument_list.one? && block_parameter.arg_type?
81
+ end
82
+
83
+ def block_parameter
84
+ node.argument_list.first
85
+ end
86
+
87
+ def exact_source_method_send?
88
+ node.body&.send_type? && node.body.method?(source_method) && node.body.arguments.empty? &&
89
+ reads_variable?(node.body.receiver, block_parameter.name)
90
+ end
91
+
92
+ def message
93
+ format(MESSAGE, preferred_method: preferred_method, source_method: source_method)
94
+ end
95
+
96
+ def uncommented?
97
+ !source_comments.any_within?(node)
98
+ end
99
+
100
+ def correct(corrector)
101
+ corrector.replace(node, "#{keys_call.receiver.source}#{call_operator}#{preferred_method}")
102
+ end
103
+
104
+ def call_operator
105
+ keys_call.csend_type? ? "&." : "."
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,37 @@
1
+ # Keyword options resolved in Ruby's evaluation order: the last explicit pair wins unless a later splat can replace it.
2
+ class RuboCop::Callbacksystems::Hashes::KeywordOptions
3
+ attr_reader :elements
4
+
5
+ def initialize(*hashes)
6
+ @elements = hashes.compact.flat_map(&:children)
7
+ end
8
+
9
+ def known?(key)
10
+ option_for(key).present? || elements.none? { unknown_option?(it) }
11
+ end
12
+
13
+ def option_for(key)
14
+ explicit = explicit_options_for(key).last
15
+
16
+ explicit unless unknown_option_follows?(explicit)
17
+ end
18
+
19
+ def explicit_options_for(key)
20
+ elements.select { it.pair_type? && it.key.sym_type? && it.key.value == key }
21
+ end
22
+
23
+ private
24
+ def unknown_option_follows?(option)
25
+ option && elements.drop(index_of(option) + 1).any? { unknown_option?(it) }
26
+ end
27
+
28
+ def index_of(option)
29
+ elements.index { it.equal?(option) }
30
+ end
31
+
32
+ # A non-literal key can evaluate to the option being queried just as a keyword splat can. Literal non-symbol keys
33
+ # cannot replace it, but abstaining for those uncommon calls keeps this reader sound without evaluating Ruby.
34
+ def unknown_option?(element)
35
+ element.kwsplat_type? || (element.pair_type? && !element.key.sym_type?)
36
+ end
37
+ end
@@ -0,0 +1,150 @@
1
+ module RuboCop::Callbacksystems::Helpers::Bodies
2
+ def assignment_count(body, *types)
3
+ ScopedAssignments.new(body, types).to_set { variable_name_of(it) }.size
4
+ end
5
+
6
+ def immediate_inputs_of(node)
7
+ if any_block_type?(node)
8
+ call_of(node).then { [ it.receiver, *it.arguments ].compact }
9
+ else
10
+ case node.type
11
+ when :defs then [ node.receiver ]
12
+ when :class then [ node.identifier, node.parent_class ].compact
13
+ when :sclass then [ node.identifier ]
14
+ else []
15
+ end
16
+ end
17
+ end
18
+
19
+ def lexical_definition?(node)
20
+ node.type?(:def, :defs, :class, :module, :sclass)
21
+ end
22
+
23
+ def statements_before(node)
24
+ node.parent&.type?(:begin, :kwbegin) ? node.parent.children.take_while { !it.equal?(node) } : []
25
+ end
26
+
27
+ def direct_definitions_in(body, type)
28
+ statements_in(body).select { it.type?(type) }
29
+ end
30
+
31
+ def statements_in(body)
32
+ if body
33
+ body.type?(:begin, :kwbegin) ? body.children.to_a : [ body ]
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ def runs_in(body)
40
+ statements_in(body).chunk_while { |left, right| yield(left) && yield(right) }.select { yield it.first }
41
+ end
42
+
43
+ def receiverless_method_names_in(body)
44
+ nodes_in(body, :send).filter_map { it.method_name if bare_send?(it) }
45
+ end
46
+
47
+ def first_statement_in(body)
48
+ edge_statement(body, side: :first)
49
+ end
50
+
51
+ def last_statement_in(body)
52
+ edge_statement(body, side: :last)
53
+ end
54
+
55
+ def direct_method_nodes_in(body)
56
+ DirectMethodNodes.new(body).to_a
57
+ end
58
+
59
+ private
60
+ def edge_statement(body, side:)
61
+ current = body
62
+ loop do
63
+ current = case current&.type
64
+ when :begin, :kwbegin then current.children.public_send(side)
65
+ when :rescue then current.body
66
+ when :ensure then current.children.first
67
+ else return current
68
+ end
69
+ end
70
+ end
71
+
72
+ # Method definitions belonging directly to a body, with singleton sections transparent and other owners pruned.
73
+ class DirectMethodNodes
74
+ include Enumerable
75
+
76
+ def initialize(body)
77
+ @body = body
78
+ end
79
+
80
+ def each
81
+ if block_given?
82
+ pending = [ body ].compact
83
+ until pending.empty?
84
+ node = pending.pop
85
+ case node.type
86
+ when :def, :defs then yield node
87
+ when :begin, :kwbegin then pending.concat(node.each_child_node.to_a.reverse)
88
+ when :sclass then pending << node.body if node.body
89
+ end
90
+ end
91
+ else
92
+ to_enum(__method__)
93
+ end
94
+ end
95
+
96
+ private
97
+ attr_reader :body
98
+ end
99
+
100
+ # Assignments executed in one lexical scope, with callable and definition bodies left for their own scopes.
101
+ class ScopedAssignments
102
+ include Enumerable
103
+ include RuboCop::Callbacksystems::Helpers
104
+
105
+ def initialize(body, types)
106
+ @body = body
107
+ @types = types
108
+ end
109
+
110
+ def each
111
+ if block_given?
112
+ @pending = [ body ].compact
113
+ until pending.empty?
114
+ advance
115
+ visit { yield it }
116
+ end
117
+ else
118
+ to_enum(__method__)
119
+ end
120
+ end
121
+
122
+ private
123
+ attr_reader :body, :types, :pending, :current
124
+
125
+ def advance
126
+ @current = pending.pop
127
+ end
128
+
129
+ def visit
130
+ if scope_boundary?
131
+ queue immediate_inputs_of(current)
132
+ else
133
+ yield current if current.type?(*types)
134
+ queue current.each_child_node
135
+ end
136
+ end
137
+
138
+ def scope_boundary?
139
+ (!current.equal?(body) && lexical_definition?(current)) || deferred_boundary?
140
+ end
141
+
142
+ def deferred_boundary?
143
+ deferred_callable_block?(current) || method_definition_block?(current)
144
+ end
145
+
146
+ def queue(nodes)
147
+ pending.concat(nodes.to_a.reverse)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,103 @@
1
+ module RuboCop::Callbacksystems::Helpers::Builders
2
+ SELF_REBINDING_METHODS = %i[ instance_eval instance_exec class_eval class_exec module_eval module_exec ]
3
+
4
+ def self_preserved_between?(node, boundary:)
5
+ SelfPreservation.new(node, boundary:).preserved?
6
+ end
7
+
8
+ def self_rebinding_boundary?(node)
9
+ SelfBoundary.new(node).rebinds?
10
+ end
11
+
12
+ def nested_body?(node)
13
+ class_with_body?(node) || module_with_body?(node)
14
+ end
15
+
16
+ def class_with_body?(node)
17
+ node.class_type? ? node.body.present? : builder_assignment_for(node).builds_class_with_body?
18
+ end
19
+
20
+ def module_with_body?(node)
21
+ node.module_type? ? node.body.present? : builder_assignment_for(node).builds_module_with_body?
22
+ end
23
+
24
+ private
25
+ def builder_assignment_for(node)
26
+ RuboCop::Callbacksystems::ClassStructure::BuilderAssignment.new(node)
27
+ end
28
+
29
+ # Whether one node keeps its lexical self before reaching a caller-selected boundary.
30
+ class SelfPreservation
31
+ def initialize(node, boundary:)
32
+ @node = node
33
+ @boundary = boundary
34
+ end
35
+
36
+ def preserved?
37
+ ancestors.none? { SelfBoundary.new(it, descendant: node).rebinds_around_descendant? }
38
+ end
39
+
40
+ private
41
+ attr_reader :node, :boundary
42
+
43
+ def ancestors
44
+ node.each_ancestor.take_while { !it.equal?(boundary) }
45
+ end
46
+ end
47
+
48
+ # A lexical construct or block whose body observes a self other than the surrounding expression.
49
+ class SelfBoundary
50
+ include RuboCop::Callbacksystems::Helpers
51
+
52
+ def initialize(node, descendant: nil)
53
+ @node = node
54
+ @descendant = descendant
55
+ end
56
+
57
+ def rebinds_around_descendant?
58
+ rebinds? && rebound_parts.any? { contains_descendant?(it) }
59
+ end
60
+
61
+ def rebinds?
62
+ lexical_definition?(node) || (any_block_type?(node) && self_rebinding_block?)
63
+ end
64
+
65
+ private
66
+ attr_reader :node, :descendant
67
+
68
+ def self_rebinding_block?
69
+ method_definition_block?(node) || self_rebinding_builder? || self_rebinding_evaluator?
70
+ end
71
+
72
+ def self_rebinding_builder?
73
+ core_constant?(block_call.receiver) &&
74
+ builder_method_for(block_call.receiver.short_name) == block_call.method_name
75
+ end
76
+
77
+ def block_call
78
+ @block_call ||= call_of(node)
79
+ end
80
+
81
+ def builder_method_for(name)
82
+ RuboCop::Callbacksystems::ClassStructure::BuilderAssignment::CLASS_BUILDERS[name] ||
83
+ RuboCop::Callbacksystems::ClassStructure::BuilderAssignment::MODULE_BUILDERS[name]
84
+ end
85
+
86
+ def self_rebinding_evaluator?
87
+ block_call.receiver && !block_call.receiver.self_type? &&
88
+ SELF_REBINDING_METHODS.include?(block_call.method_name)
89
+ end
90
+
91
+ def rebound_parts
92
+ if any_block_type?(node) || node.any_def_type?
93
+ [ node.arguments, node.body ].compact
94
+ else
95
+ [ node.body ].compact
96
+ end
97
+ end
98
+
99
+ def contains_descendant?(part)
100
+ part.equal?(descendant) || part.source_range&.contains?(descendant.source_range)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,56 @@
1
+ module RuboCop::Callbacksystems::Helpers::Comments
2
+ TOOLING_MARKER = %r{
3
+ \A\#\s*
4
+ (?:
5
+ :?[a-z][\w-]*(?::[a-z][\w-]*)+:?
6
+ |
7
+ :[a-z][\w-]*:
8
+ |
9
+ (?:typed|rbs_inline):\s+(?:disabled|enabled|false|ignore|strict|strong|true|weak)
10
+ |
11
+ @(?:dynamic|implements|rbs|type)
12
+ |
13
+ :?(?:nocov|nodoc):?
14
+ |
15
+ [a-z][\w-]*-ignore
16
+ )
17
+ (?:\s|\z)
18
+ }ix
19
+
20
+ def comment_removal_range_for(comment)
21
+ own_line_comment?(comment) ? line_removal_range_for(comment) : trailing_comment_range_for(comment)
22
+ end
23
+
24
+ def own_line_comment?(comment)
25
+ range = comment.source_range
26
+ range.source_line[0...range.column].strip.empty?
27
+ end
28
+
29
+ def comment_blocks_in(processed_source, reject_tooling_runs: false)
30
+ RuboCop::Callbacksystems::Source::CommentBlocks.new(processed_source, reject_tooling_runs:)
31
+ end
32
+
33
+ def prose_comments_in(processed_source)
34
+ processed_source.comments.reject { tooling_comment?(it) }
35
+ end
36
+
37
+ def tooling_comment?(comment)
38
+ comment.text.start_with?("#!") || RuboCop::MagicComment.parse(comment.text).any? ||
39
+ RuboCop::DirectiveComment.new(comment).start_with_marker? || rdoc_directive?(comment) ||
40
+ comment.text.match?(TOOLING_MARKER)
41
+ end
42
+
43
+ private
44
+ def trailing_comment_range_for(comment)
45
+ range = comment.source_range
46
+ range.with(begin_pos: range.begin_pos - range.column + code_before(range).rstrip.length)
47
+ end
48
+
49
+ def code_before(range)
50
+ range.source_line[0...range.column]
51
+ end
52
+
53
+ def rdoc_directive?(comment)
54
+ comment.text.match?(/\A#(?:--|\+\+)\s*\z/)
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module RuboCop::Callbacksystems::Helpers::Corrections
2
+ # A shorthand keyword's synthetic value shares the key's source range, so replacing that node would rename the key.
3
+ def replace_expression(corrector, node, with:)
4
+ if shorthand_keyword_value?(node)
5
+ corrector.insert_after(node.parent, " #{with}")
6
+ else
7
+ corrector.replace(node, with)
8
+ end
9
+ end
10
+
11
+ private
12
+ def shorthand_keyword_value?(node)
13
+ node.parent&.pair_type? && node.equal?(node.parent.value) && node.parent.value_omission?
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module RuboCop::Callbacksystems::Helpers::Methods
2
+ PARAMETER_TYPES = %i[ arg optarg kwarg kwoptarg ]
3
+
4
+ BUILDER_METHODS = %i[
5
+ new
6
+ create build update
7
+ assign_attributes update_attribute update_column update_columns
8
+ find find_by find_sole_by
9
+ find_or_create_by find_or_initialize_by create_or_find_by
10
+ destroy_by delete_by
11
+ permit expect
12
+ ].to_set
13
+
14
+ def builder_method?(node)
15
+ BUILDER_METHODS.include?(node.method_name)
16
+ end
17
+
18
+ def parameter_names_of(method_node)
19
+ parameter_nodes_of(method_node).map { it.children.first.to_s }
20
+ end
21
+
22
+ def parameter_nodes_of(method_node)
23
+ method_node.arguments.children.select { parameter_node?(it) }
24
+ end
25
+
26
+ def parameter_node?(node)
27
+ node&.type?(*PARAMETER_TYPES)
28
+ end
29
+
30
+ def inside_initialize?(node)
31
+ node.each_ancestor(:any_def).any? { it.method?(:initialize) }
32
+ end
33
+
34
+ # A definition below another method or a block may belong to a receiver established only at runtime. Attribute only
35
+ # definitions whose path to the nearest named class or module crosses neither boundary.
36
+ def direct_method_definition?(node)
37
+ enclosing_class_or_module_of(node)&.then do |owner|
38
+ node.each_ancestor.take_while { !it.equal?(owner) }
39
+ .none? { it.any_def_type? || any_block_type?(it) }
40
+ end || false
41
+ end
42
+
43
+ def single_send_private_non_predicate?(method_node)
44
+ method_node.body&.send_type? && private_method?(method_node) && !method_node.predicate_method?
45
+ end
46
+ end
@@ -0,0 +1,65 @@
1
+ module RuboCop::Callbacksystems::Helpers::Names
2
+ def literal_name_of(node)
3
+ node.type?(:sym, :str) ? node.value.to_s : node.source
4
+ end
5
+
6
+ def declared_name_of(statement)
7
+ case statement.type
8
+ when :casgn, :lvasgn then statement.name
9
+ when :class, :module then statement.identifier.short_name
10
+ end
11
+ end
12
+
13
+ def name_arguments_of(node)
14
+ node.arguments.select { it.type?(:sym, :str) }
15
+ end
16
+
17
+ def class_name_of(node)
18
+ node.class_type? ? node.identifier.source : node.name.to_s
19
+ end
20
+
21
+ def name_without_sigil(name)
22
+ name.to_s.sub(/\A(?:@@|@|\$)/, "")
23
+ end
24
+
25
+ def constant_name_of(node)
26
+ if node
27
+ ConstantName.new(node).to_s
28
+ end
29
+ end
30
+
31
+ private
32
+ class ConstantName
33
+ def initialize(node)
34
+ @current = node
35
+ @names = []
36
+ end
37
+
38
+ def to_s
39
+ traverse
40
+ name if complete?
41
+ end
42
+
43
+ private
44
+ attr_reader :current, :names
45
+
46
+ def traverse
47
+ while current&.const_type?
48
+ names << current.short_name
49
+ @current = current.namespace
50
+ end
51
+ end
52
+
53
+ def complete?
54
+ current.nil? || current.cbase_type?
55
+ end
56
+
57
+ def name
58
+ "#{prefix}#{names.reverse.join("::")}"
59
+ end
60
+
61
+ def prefix
62
+ "::" if current && names.any?
63
+ end
64
+ end
65
+ end