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,36 @@
1
+ # The order that reads each name before the names it refers to, walked depth first from the seeds it is given. The graph
2
+ # maps a name to the names it refers to, and a name the graph does not hold is skipped.
3
+ class RuboCop::Callbacksystems::ClassStructure::ReferenceOrder
4
+ include Enumerable
5
+
6
+ delegate :each, to: :ordered
7
+
8
+ def initialize(seeds, graph)
9
+ @seeds = seeds
10
+ @graph = graph
11
+ end
12
+
13
+ private
14
+ attr_reader :seeds, :graph
15
+
16
+ def ordered
17
+ @ordered ||= seeds.each_with_object([]) { |name, order| gather(name, order) }
18
+ end
19
+
20
+ def gather(name, order)
21
+ stack = [ name ]
22
+
23
+ until stack.empty?
24
+ current = stack.pop
25
+ next if visited.include?(current) || graph.exclude?(current)
26
+
27
+ visited.add(current)
28
+ order << current
29
+ graph.fetch(current).reverse_each { stack << it if visited.exclude?(it) }
30
+ end
31
+ end
32
+
33
+ def visited
34
+ @visited ||= Set.new
35
+ end
36
+ end
@@ -0,0 +1,84 @@
1
+ # The place a statement takes in the order a class body reads: the mixins and the class attribute assignments first,
2
+ # then the values, the classes declared in a line, the state, the associations, the delegates, the other macros, the
3
+ # methods, and last the nested classes with a body of their own. The siblings are the statements of the same body, which
4
+ # a `private_constant` needs to find the constant it hides.
5
+ class RuboCop::Callbacksystems::ClassStructure::StatementRank
6
+ include RuboCop::Callbacksystems::Helpers
7
+
8
+ MIXIN = 0
9
+ VALUE = 1
10
+ CLASS_DECLARATION = 2
11
+ ATTRIBUTE = 3
12
+ ASSOCIATION = 4
13
+ DELEGATE = 5
14
+ MACRO = 6
15
+ METHOD = 7
16
+ NESTED_CLASS = 8
17
+
18
+ def initialize(node, siblings)
19
+ @node = node
20
+ @siblings = siblings
21
+ end
22
+
23
+ def to_i
24
+ shape_rank || marker_rank || macro_rank || METHOD
25
+ end
26
+
27
+ private
28
+ attr_reader :node, :siblings
29
+
30
+ def shape_rank
31
+ if nested_body?(statement)
32
+ NESTED_CLASS
33
+ elsif statement.casgn_type?
34
+ value_name? ? VALUE : CLASS_DECLARATION
35
+ end
36
+ end
37
+
38
+ # `include Detection if Rails.env.local?` is the include, whatever the condition around it.
39
+ def statement
40
+ node.if_type? && node.modifier_form? ? node.if_branch : node
41
+ end
42
+
43
+ # `Naming/ConstantName` wants SCREAMING_SNAKE_CASE on a value and not on a class, so the case tells them apart.
44
+ def value_name?
45
+ statement.name.match?(/\A[A-Z][A-Z0-9_]*\z/)
46
+ end
47
+
48
+ # `private_constant :X` belongs with the `X` it hides, so it takes that declaration's rank.
49
+ def marker_rank
50
+ marked_ranks.max || VALUE if private_constant_marker?
51
+ end
52
+
53
+ def private_constant_marker?
54
+ bare_send?(macro) && macro.method?(:private_constant)
55
+ end
56
+
57
+ # `has_many :blocks do ... end` is the `has_many`, whatever the block adds to it.
58
+ def macro
59
+ call_of(statement)
60
+ end
61
+
62
+ def marked_ranks
63
+ siblings.select { marked_names.include?(declared_name_of(it)) }
64
+ .map { self.class.new(it, siblings).to_i }
65
+ end
66
+
67
+ def marked_names
68
+ @marked_names ||= private_constant_names_of(node)
69
+ end
70
+
71
+ def macro_rank
72
+ case
73
+ when mixin_macro?(macro) || class_attribute_assignment? then MIXIN
74
+ when attribute_macro?(macro) then ATTRIBUTE
75
+ when association_macro?(macro) then ASSOCIATION
76
+ when delegate_macro?(macro) then DELEGATE
77
+ when bare_send?(macro) then MACRO
78
+ end
79
+ end
80
+
81
+ def class_attribute_assignment?
82
+ statement.send_type? && statement.receiver&.self_type? && statement.assignment_method?
83
+ end
84
+ end
@@ -0,0 +1,42 @@
1
+ # Statements written together with nothing between them, rewritten into the order given when every one has a place in
2
+ # it. The block names each statement the way the order does, and a statement answers `range` and `source`.
3
+ class RuboCop::Callbacksystems::ClassStructure::StatementRun
4
+ include RuboCop::Callbacksystems::Helpers
5
+
6
+ def initialize(statements, order:, &name_of)
7
+ @statements = statements
8
+ @order = order
9
+ @name_of = name_of
10
+ end
11
+
12
+ def rewrite(corrector)
13
+ corrector.replace(range, ordered_source) if reorderable?
14
+ end
15
+
16
+ def reorderable?
17
+ ordered != statements && statements.none?(&:contains_tooling_comment?)
18
+ end
19
+
20
+ private
21
+ attr_reader :statements, :order, :name_of
22
+
23
+ def ordered
24
+ @ordered ||= placed? ? statements.sort_by { positions.fetch(name_of.call(it)) } : statements
25
+ end
26
+
27
+ def placed?
28
+ statements.all? { positions.key?(name_of.call(it)) }
29
+ end
30
+
31
+ def positions
32
+ @positions ||= order.each_with_index.reverse_each.to_h
33
+ end
34
+
35
+ def range
36
+ span_of(statements.map(&:range))
37
+ end
38
+
39
+ def ordered_source
40
+ ordered.map(&:source).join("\n\n")
41
+ end
42
+ end
@@ -0,0 +1,84 @@
1
+ # The visibility established by one class body, indexed once for every reader of that body. Section modifiers apply to
2
+ # the statements below them, while named modifiers are kept separately because they reach definitions above them.
3
+ class RuboCop::Callbacksystems::ClassStructure::Visibility
4
+ include RuboCop::Callbacksystems::Helpers
5
+
6
+ Assignment = Data.define(:kind, :name, :position, :level)
7
+
8
+ class << self
9
+ def for(body)
10
+ body ? CACHE_LOCK.synchronize { CACHE[body] ||= new(body) } : new(nil)
11
+ end
12
+
13
+ private
14
+ CACHE = ObjectSpace::WeakMap.new
15
+ CACHE_LOCK = Mutex.new
16
+ end
17
+
18
+ def initialize(body)
19
+ @body = body
20
+ end
21
+
22
+ def each_statement
23
+ if block_given?
24
+ statements.each_with_index { |statement, index| yield statement, transitions[index] }
25
+ else
26
+ to_enum(__method__)
27
+ end
28
+ end
29
+
30
+ def level_at(node)
31
+ statement_index_at(node)&.then { transitions[it] } || transitions.last
32
+ end
33
+
34
+ def assignment_after(node, singleton:)
35
+ assignments.fetch([ singleton ? :singleton : :instance, node.method_name ], []).last&.then do |assignment|
36
+ assignment if assignment.position >= node.source_range.end_pos
37
+ end
38
+ end
39
+
40
+ private
41
+ NAMED_LEVELS = {
42
+ public: [ :instance, :public ],
43
+ protected: [ :instance, :protected ],
44
+ private: [ :instance, :private ],
45
+ public_class_method: [ :singleton, :public ],
46
+ private_class_method: [ :singleton, :private ]
47
+ }
48
+
49
+ attr_reader :body
50
+
51
+ def statements
52
+ @statements ||= statements_in(body)
53
+ end
54
+
55
+ def transitions
56
+ @transitions ||= statements.each_with_object([ :public ]) do |statement, levels|
57
+ levels << (visibility_modifier_of(statement) || levels.last)
58
+ end
59
+ end
60
+
61
+ def statement_index_at(node)
62
+ statement_end_positions.bsearch_index { it > node.source_range.begin_pos }
63
+ end
64
+
65
+ def statement_end_positions
66
+ @statement_end_positions ||= statements.map { it.source_range.end_pos }
67
+ end
68
+
69
+ def assignments
70
+ @assignments ||= statements.flat_map { assignments_from(it) }.group_by { [ it.kind, it.name ] }
71
+ end
72
+
73
+ def assignments_from(statement)
74
+ named_level_of(statement)&.then do |kind, level|
75
+ name_arguments_of(statement).map do |name|
76
+ Assignment.new(kind:, name: name.value.to_sym, position: statement.source_range.begin_pos, level:)
77
+ end
78
+ end || []
79
+ end
80
+
81
+ def named_level_of(statement)
82
+ NAMED_LEVELS[statement.method_name] if bare_send?(statement)
83
+ end
84
+ end
@@ -0,0 +1,78 @@
1
+ require "yaml"
2
+
3
+ class RuboCop::Callbacksystems::CopsDocument
4
+ DEFAULT_PATH = File.expand_path("../../../docs/cops.md", __dir__)
5
+ HEADING = <<~MARKDOWN
6
+ # Cops
7
+
8
+ <!-- Generated by `bin/docs`. Do not edit by hand. -->
9
+
10
+ Every cop lives under the `Callbacksystems/` namespace and ships enabled. Each one links to its source, where an `@example` block contrasts the code it rejects with the code it asks for, and `config/default.yml` holds its options and default scope.
11
+ MARKDOWN
12
+ TABLE_HEADER = [ "| Cop | Autocorrect | Description |", "|---|---|---|" ]
13
+
14
+ def initialize(path = DEFAULT_PATH)
15
+ @path = path
16
+ end
17
+
18
+ def save
19
+ File.write path, text
20
+ end
21
+
22
+ def text
23
+ [ HEADING, *TABLE_HEADER, *rows, "" ].join("\n")
24
+ end
25
+
26
+ def committed
27
+ File.read(path)
28
+ end
29
+
30
+ def undescribed
31
+ rows.select { it.description.blank? }.map(&:name)
32
+ end
33
+
34
+ private
35
+ SOURCE_DIRECTORY = "../lib/rubocop/cop/callbacksystems"
36
+
37
+ attr_reader :path
38
+
39
+ def rows
40
+ @rows ||= cops.map { Row.new(it, descriptions[it.cop_name]) }
41
+ end
42
+
43
+ def cops
44
+ RuboCop::Cop::Registry.global.cops.select { it.department == :Callbacksystems }.sort_by(&:cop_name)
45
+ end
46
+
47
+ def descriptions
48
+ YAML.load_file(RuboCop::Callbacksystems::Plugin::CONFIGURATION_PATH).transform_values { it["Description"] }
49
+ end
50
+
51
+ class Row
52
+ attr_reader :description
53
+
54
+ def initialize(cop, description)
55
+ @cop = cop
56
+ @description = description
57
+ end
58
+
59
+ def to_s
60
+ "| [`#{name}`](#{source_path}) | #{autocorrection} | #{description} |"
61
+ end
62
+
63
+ def name
64
+ cop.cop_name
65
+ end
66
+
67
+ private
68
+ attr_reader :cop
69
+
70
+ def source_path
71
+ "#{SOURCE_DIRECTORY}/#{cop.badge.cop_name.underscore}.rb"
72
+ end
73
+
74
+ def autocorrection
75
+ "yes" if cop.support_autocorrect?
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,138 @@
1
+ # Local variables captured or reassigned by callable bodies that execute after their surrounding expression.
2
+ class RuboCop::Callbacksystems::Execution::DeferredLocalVariables
3
+ include RuboCop::Callbacksystems::Helpers
4
+
5
+ delegate :include?, :exclude?, to: :names
6
+
7
+ def initialize(body)
8
+ @body = body
9
+ end
10
+
11
+ private
12
+ attr_reader :body
13
+
14
+ def names
15
+ @names ||= Traversal.new(body).to_set.freeze
16
+ end
17
+
18
+ # An iterative lexical walk keeps bindings paired with entry and exit while deferred bodies are visited.
19
+ class Traversal
20
+ include Enumerable
21
+ include RuboCop::Callbacksystems::Helpers
22
+
23
+ def initialize(body)
24
+ @context = Context.new(Visit.new(body, false))
25
+ end
26
+
27
+ def each
28
+ if block_given?
29
+ advance { yield it }
30
+ else
31
+ to_enum(__method__)
32
+ end
33
+ end
34
+
35
+ private
36
+ attr_reader :context
37
+
38
+ def advance
39
+ context.next_event.perform(context) { yield it } while context.pending?
40
+ end
41
+ end
42
+
43
+ class Context
44
+ def initialize(first_event)
45
+ @pending = [ first_event ]
46
+ @binding_counts = Hash.new(0)
47
+ end
48
+
49
+ def pending?
50
+ pending.any?
51
+ end
52
+
53
+ def next_event
54
+ pending.pop
55
+ end
56
+
57
+ def queue(*events)
58
+ pending.concat(events.reverse)
59
+ end
60
+
61
+ def enter(names)
62
+ names.each { binding_counts[it] += 1 }
63
+ end
64
+
65
+ def leave(names)
66
+ names.each { binding_counts[it] -= 1 }
67
+ end
68
+
69
+ def bound?(name)
70
+ binding_counts[name].positive?
71
+ end
72
+
73
+ private
74
+ attr_reader :pending, :binding_counts
75
+ end
76
+
77
+ class Visit < Data.define(:node, :deferred)
78
+ include RuboCop::Callbacksystems::Helpers
79
+
80
+ def perform(context)
81
+ if node
82
+ if any_block_type?(node)
83
+ queue_block(context)
84
+ elsif lexical_definition?(node)
85
+ context.queue(*input_visits)
86
+ else
87
+ yield variable_name_of(node) if captured_variable?(context)
88
+ queue_children(context)
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+ def queue_block(context)
95
+ bindings = binding_names
96
+ body_deferred = deferred || deferred_callable_block?(node) || method_definition_block?(node)
97
+
98
+ context.queue \
99
+ *input_visits,
100
+ EnterBindings.new(bindings),
101
+ self.class.new(argument_node, body_deferred),
102
+ self.class.new(node.body, body_deferred),
103
+ LeaveBindings.new(bindings)
104
+ end
105
+
106
+ def binding_names
107
+ node.argument_list.filter_map(&:name)
108
+ end
109
+
110
+ def input_visits
111
+ immediate_inputs_of(node).map { self.class.new(it, deferred) }
112
+ end
113
+
114
+ def argument_node
115
+ node.arguments if node.arguments.respond_to?(:type)
116
+ end
117
+
118
+ def captured_variable?(context)
119
+ deferred && node.type?(:lvar, :lvasgn, :match_var) && !context.bound?(variable_name_of(node))
120
+ end
121
+
122
+ def queue_children(context)
123
+ context.queue(*node.each_child_node.map { self.class.new(it, deferred) })
124
+ end
125
+ end
126
+
127
+ class EnterBindings < Data.define(:names)
128
+ def perform(context)
129
+ context.enter(names)
130
+ end
131
+ end
132
+
133
+ class LeaveBindings < Data.define(:names)
134
+ def perform(context)
135
+ context.leave(names)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,45 @@
1
+ # Whether an expression's value is ignored, following final values through sequences and definition bodies.
2
+ class RuboCop::Callbacksystems::Execution::DiscardedExpression
3
+ def initialize(expression)
4
+ @current = expression
5
+ end
6
+
7
+ def discarded?
8
+ disposition == :discarded
9
+ end
10
+
11
+ private
12
+ attr_reader :current
13
+ delegate :parent, to: :current, private: true
14
+
15
+ def disposition
16
+ advance until @disposition
17
+ @disposition
18
+ end
19
+
20
+ def advance
21
+ if parent.nil? || discarded_sequence?
22
+ @disposition = :discarded
23
+ elsif transparent?
24
+ @current = parent
25
+ else
26
+ @disposition = :observed
27
+ end
28
+ end
29
+
30
+ def discarded_sequence?
31
+ sequence? && current.right_sibling
32
+ end
33
+
34
+ def sequence?
35
+ parent.type?(:begin, :kwbegin)
36
+ end
37
+
38
+ def transparent?
39
+ sequence? || definition_body?
40
+ end
41
+
42
+ def definition_body?
43
+ parent.type?(:class, :module, :sclass) && parent.body.equal?(current)
44
+ end
45
+ end
@@ -0,0 +1,73 @@
1
+ # The path to a read where an already evaluated value can move without becoming conditional, running repeatedly, or
2
+ # crossing earlier work whose result the moved evaluation could change.
3
+ class RuboCop::Callbacksystems::Execution::EagerEvaluationPath
4
+ SEQUENTIAL_PARENT_TYPES = %i[
5
+ array begin block_pass break casgn csend cvasgn dstr dsym erange gvasgn hash irange ivasgn kwbegin lvasgn next pair
6
+ kwsplat regexp return send splat super yield
7
+ ]
8
+ EAGER_FIRST_CHILD_PARENT_TYPES = %i[ and case case_match if or ]
9
+ BLOCK_PARENT_TYPES = %i[ block numblock itblock ]
10
+ INERT_NODE_TYPES = %i[ self lvar ]
11
+
12
+ def initialize(read, within:)
13
+ @read = read
14
+ @boundary = within
15
+ end
16
+
17
+ def preserves_order?
18
+ read_within_boundary? && steps_preserve_order?
19
+ end
20
+
21
+ private
22
+ attr_reader :read, :boundary
23
+
24
+ def read_within_boundary?
25
+ read.equal?(boundary) || read.each_ancestor.any? { it.equal?(boundary) }
26
+ end
27
+
28
+ def steps_preserve_order?
29
+ cursor = read
30
+ cursor = cursor.parent while !cursor.equal?(boundary) && Step.new(cursor).preserves_order?
31
+ cursor.equal?(boundary)
32
+ end
33
+
34
+ class Step
35
+ def initialize(node)
36
+ @node = node
37
+ end
38
+
39
+ def preserves_order?
40
+ eager_once? && preceding_nodes.all? { inert?(it) }
41
+ end
42
+
43
+ private
44
+ attr_reader :node
45
+
46
+ delegate :parent, to: :node, private: true
47
+
48
+ def eager_once?
49
+ sequential? || eager_first_child? || immediate_block_call?
50
+ end
51
+
52
+ def sequential?
53
+ SEQUENTIAL_PARENT_TYPES.include?(parent.type) &&
54
+ (!parent.csend_type? || parent.receiver.equal?(node))
55
+ end
56
+
57
+ def eager_first_child?
58
+ EAGER_FIRST_CHILD_PARENT_TYPES.include?(parent.type) && parent.children.first.equal?(node)
59
+ end
60
+
61
+ def immediate_block_call?
62
+ BLOCK_PARENT_TYPES.include?(parent.type) && parent.send_node.equal?(node)
63
+ end
64
+
65
+ def preceding_nodes
66
+ parent.each_child_node.take_while { !it.equal?(node) }
67
+ end
68
+
69
+ def inert?(candidate)
70
+ candidate.recursive_basic_literal? || INERT_NODE_TYPES.include?(candidate.type)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,71 @@
1
+ # The nodes an expression can execute now, without entering a definition or a callable whose body runs later.
2
+ class RuboCop::Callbacksystems::Execution::Immediate
3
+ include Enumerable
4
+
5
+ def initialize(node, deferred_blocks: [])
6
+ @node = node
7
+ @deferred_blocks = {}.compare_by_identity
8
+ deferred_blocks.each { @deferred_blocks[it] = true }
9
+ end
10
+
11
+ def nodes_of_type(*types)
12
+ select { it.type?(*types) }
13
+ end
14
+
15
+ def each
16
+ if block_given?
17
+ execution.each { yield it }
18
+ else
19
+ enum_for
20
+ end
21
+ end
22
+
23
+ private
24
+ attr_reader :node, :deferred_blocks
25
+
26
+ def execution
27
+ Traversal.new(node, deferred_blocks)
28
+ end
29
+
30
+ # An iterative walk that keeps its cursor and pending nodes together with the shared boundary index.
31
+ class Traversal
32
+ include RuboCop::Callbacksystems::Helpers
33
+
34
+ def initialize(node, deferred_blocks)
35
+ @pending = [ node ]
36
+ @deferred_blocks = deferred_blocks
37
+ end
38
+
39
+ def each
40
+ until pending.empty?
41
+ advance
42
+ visit { yield it } if current
43
+ end
44
+ end
45
+
46
+ private
47
+ attr_reader :pending, :deferred_blocks, :current
48
+
49
+ def advance
50
+ @current = pending.pop
51
+ end
52
+
53
+ def visit
54
+ if execution_boundary?
55
+ queue immediate_inputs_of(current)
56
+ else
57
+ yield current
58
+ queue current.each_child_node
59
+ end
60
+ end
61
+
62
+ def execution_boundary?
63
+ lexical_definition?(current) || deferred_callable_block?(current) || method_definition_block?(current) ||
64
+ deferred_blocks.key?(current)
65
+ end
66
+
67
+ def queue(nodes)
68
+ pending.concat(nodes.to_a.reverse)
69
+ end
70
+ end
71
+ end