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,60 @@
1
+ class RuboCop::Callbacksystems::ProjectIndex::Diagnostics
2
+ extend RuboCop::Callbacksystems::ProjectIndex::Cache
3
+
4
+ def initialize(project_index)
5
+ @by_rule_name = {}
6
+ @by_rule_name_and_uri = {}
7
+ project_index.diagnostics.each { index(it) }
8
+ end
9
+
10
+ def any_named?(*rule_names)
11
+ diagnostics_named(rule_names).any?
12
+ end
13
+
14
+ def within_definitions?(definitions, named:)
15
+ definitions.any? do |definition|
16
+ diagnostics_named_in(definition.location.uri, named).any? do |diagnostic|
17
+ LocationContainment.new(definition.location, diagnostic.location).contained?
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+ attr_reader :by_rule_name, :by_rule_name_and_uri
24
+
25
+ def index(diagnostic)
26
+ rule_name = diagnostic.rule.rule_name
27
+ (by_rule_name[rule_name] ||= []) << diagnostic
28
+ (by_rule_name_and_uri[[ rule_name, diagnostic.location.uri ]] ||= []) << diagnostic
29
+ end
30
+
31
+ def diagnostics_named(rule_names)
32
+ rule_names.flat_map { by_rule_name.fetch(it) { [] } }
33
+ end
34
+
35
+ def diagnostics_named_in(uri, rule_names)
36
+ rule_names.flat_map { by_rule_name_and_uri.fetch([ it, uri ]) { [] } }
37
+ end
38
+
39
+ class LocationContainment
40
+ def initialize(outer, inner)
41
+ @outer = outer
42
+ @inner = inner
43
+ end
44
+
45
+ def contained?
46
+ outer.uri == inner.uri && starts_before? && ends_after?
47
+ end
48
+
49
+ private
50
+ attr_reader :outer, :inner
51
+
52
+ def starts_before?
53
+ ([ outer.start_line, outer.start_column ] <=> [ inner.start_line, inner.start_column ]) <= 0
54
+ end
55
+
56
+ def ends_after?
57
+ ([ outer.end_line, outer.end_column ] <=> [ inner.end_line, inner.end_column ]) >= 0
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ require "digest"
2
+
3
+ # The source revisions held by a project index while RuboCop autocorrects in memory. RuboCop builds one graph before
4
+ # inspecting the project, then may run several corrected versions of a file before writing it. Each accepted revision
5
+ # replaces that document in Rubydex; only revisions descended from the indexed disk source are trusted.
6
+ class RuboCop::Callbacksystems::ProjectIndex::Freshness
7
+ class << self
8
+ def update(project_index, processed_source, new_source)
9
+ mutex.synchronize do
10
+ RevisionChain.new(
11
+ processed_source, state_for(project_index), project_index:, new_source:
12
+ ).update
13
+ end
14
+ rescue SystemCallError
15
+ false
16
+ end
17
+
18
+ def current_source?(project_index, processed_source)
19
+ mutex.synchronize do
20
+ RevisionChain.new(processed_source, state_for(project_index)).current?
21
+ end || false
22
+ rescue SystemCallError
23
+ false
24
+ end
25
+
26
+ def reliable?(project_index)
27
+ mutex.synchronize { state_for(project_index).reliable }
28
+ end
29
+
30
+ def generation(project_index)
31
+ mutex.synchronize { state_for(project_index).generation }
32
+ end
33
+
34
+ private
35
+ def mutex
36
+ @mutex ||= Mutex.new
37
+ end
38
+
39
+ def state_for(project_index)
40
+ states[project_index] ||= State.new(0, true, {})
41
+ end
42
+
43
+ def states
44
+ @states ||= ObjectSpace::WeakKeyMap.new
45
+ end
46
+ end
47
+
48
+ private
49
+ State = Struct.new(:generation, :reliable, :revisions)
50
+ Revision = Data.define(:source, :backing_checksum)
51
+
52
+ class RevisionChain
53
+ def initialize(processed_source, state, project_index: nil, new_source: nil)
54
+ @file = RuboCop::Callbacksystems::ProjectIndex::SourceFile.new(processed_source.file_path)
55
+ @source = processed_source.raw_source.b
56
+ @state = state
57
+ @project_index = project_index
58
+ @new_source = new_source
59
+ end
60
+
61
+ def update
62
+ backing_checksum ? update_index : false
63
+ end
64
+
65
+ def current?
66
+ current_revision? && revision.backing_checksum == disk_checksum
67
+ end
68
+
69
+ private
70
+ attr_reader :file, :source, :state, :project_index, :new_source
71
+
72
+ def backing_checksum
73
+ @backing_checksum ||= disk_checksum if source_on_disk? || current?
74
+ end
75
+
76
+ def source_on_disk?
77
+ disk_checksum == Digest::SHA256.hexdigest(source)
78
+ end
79
+
80
+ def disk_checksum
81
+ @disk_checksum ||= Digest::SHA256.file(file.path).hexdigest
82
+ end
83
+
84
+ def update_index
85
+ project_index.index_source(file.uri, new_source, "ruby")
86
+ project_index.resolve
87
+ state.revisions[file.path] = Revision.new(new_source.b.dup.freeze, backing_checksum)
88
+ state.generation += 1
89
+ true
90
+ rescue
91
+ state.reliable = false
92
+ state.generation += 1
93
+ false
94
+ end
95
+
96
+ def current_revision?
97
+ revision&.source == source
98
+ end
99
+
100
+ def revision
101
+ state.revisions[file.path]
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,9 @@
1
+ module RuboCop::Callbacksystems::ProjectIndex::Loader
2
+ def build_index(paths)
3
+ super.tap do |project_index|
4
+ if project_index
5
+ RuboCop::Callbacksystems::ProjectIndex::Coverage.register(project_index, paths:)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ # One source location expressed in Rubydex's zero-based lines and byte columns, so parser ranges and graph locations can
2
+ # be compared without confusing a character column for a byte column.
3
+ class RuboCop::Callbacksystems::ProjectIndex::Location < Data.define \
4
+ :uri, :start_line, :start_column, :end_line, :end_column
5
+ class << self
6
+ def for_ast(range, uri:)
7
+ new \
8
+ uri:,
9
+ start_line: range.line.pred,
10
+ start_column: byte_column_of(range, line: range.line, at: range.column),
11
+ end_line: range.last_line.pred,
12
+ end_column: byte_column_of(range, line: range.last_line, at: range.last_column)
13
+ end
14
+
15
+ def for_rubydex(location)
16
+ new \
17
+ uri: location.uri,
18
+ start_line: location.start_line,
19
+ start_column: location.start_column,
20
+ end_line: location.end_line,
21
+ end_column: location.end_column
22
+ end
23
+
24
+ private
25
+ def byte_column_of(range, line:, at:)
26
+ range.source_buffer.source_line(line).slice(0, at).bytesize
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,297 @@
1
+ # Whether every visible use of a nested class and every instance it constructs stays within the file.
2
+ class RuboCop::Callbacksystems::ProjectIndex::NestedClassConfinement
3
+ include RuboCop::Callbacksystems::Helpers
4
+
5
+ CONSTRUCTION_METHODS = %i[ allocate new ]
6
+ MIXIN_METHODS = %i[ extend include prepend ]
7
+ CLASS_BODY_PRIMITIVES = %i[
8
+ alias_method attr attr_accessor attr_reader attr_writer deprecate_constant module_function
9
+ private private_class_method private_constant protected public public_class_method public_constant
10
+ remove_method ruby2_keywords undef_method
11
+ ]
12
+ VALUE_EXPOSURE_METHODS = %i[
13
+ __send__ binding clone dup enum_for freeze instance_eval instance_exec itself method public_method public_send
14
+ send singleton_class singleton_method tap then to_enum yield_self
15
+ ]
16
+ HANDOFF_TYPES = %i[
17
+ array hash pair splat kwsplat block_pass yield return super break next def defs
18
+ and or case when rescue resbody ensure for match_pattern match_pattern_p irange erange
19
+ lvasgn ivasgn cvasgn gvasgn casgn masgn or_asgn and_asgn op_asgn
20
+ ].to_set.merge(RuboCop::Callbacksystems::Helpers::NodeTypes::BLOCK_NODE_TYPES)
21
+
22
+ def initialize(nested_class, declaration:, project_index:)
23
+ @nested_class = nested_class
24
+ @declaration = declaration
25
+ @project_index = project_index
26
+ end
27
+
28
+ def confined?
29
+ return @confined if defined?(@confined)
30
+
31
+ @confined = definition_confined? && values_confined?
32
+ end
33
+
34
+ private
35
+ attr_reader :nested_class, :declaration, :project_index
36
+
37
+ def definition_confined?
38
+ nested_class.parent_class.nil? && !mixin_used? && !singleton_callback_defined? &&
39
+ references_in_definition_file? && all_references_located? && reference_nodes.all? { safe_reference?(it) }
40
+ end
41
+
42
+ def mixin_used?
43
+ sends.any? do |send_node|
44
+ call_on_self?(send_node) && MIXIN_METHODS.include?(send_node.method_name)
45
+ end
46
+ end
47
+
48
+ def sends
49
+ @sends ||= nodes_in(nested_class.body, :send, :csend)
50
+ end
51
+
52
+ def singleton_callback_defined?
53
+ direct_method_nodes.any? do |definition|
54
+ RuboCop::Callbacksystems::Methods::ImplicitInvocations::RUNTIME_METHODS.include?(definition.method_name) &&
55
+ singleton_definition?(definition)
56
+ end
57
+ end
58
+
59
+ def direct_method_nodes
60
+ @direct_method_nodes ||= direct_method_nodes_in(nested_class.body)
61
+ end
62
+
63
+ def singleton_definition?(definition)
64
+ RuboCop::Callbacksystems::Methods::Domain.new(definition).identity.then do |identity|
65
+ identity.present? && identity.all? { it == :self }
66
+ end
67
+ end
68
+
69
+ def references_in_definition_file?
70
+ definition_uris.one? && references.all? { definition_uris.include?(it.location.uri) }
71
+ end
72
+
73
+ def definition_uris
74
+ @definition_uris ||= declaration.definitions.to_set { it.location.uri }
75
+ end
76
+
77
+ def references
78
+ @references ||= declaration.references.to_a
79
+ end
80
+
81
+ def all_references_located?
82
+ reference_nodes.size == references.size
83
+ end
84
+
85
+ def reference_nodes
86
+ @reference_nodes ||= references.filter_map { const_node_at(it.location) }
87
+ end
88
+
89
+ def const_node_at(location)
90
+ SourceConstants.for(source_root, uri: location.uri).at(location)
91
+ end
92
+
93
+ def source_root
94
+ @source_root ||= nested_class.then do |node|
95
+ node = node.parent while node.parent
96
+ node
97
+ end
98
+ end
99
+
100
+ def safe_reference?(node)
101
+ namespace_prefix?(node) || locally_consumed_construction?(node)
102
+ end
103
+
104
+ def namespace_prefix?(node)
105
+ node.parent.const_type? && node.parent.namespace.equal?(node)
106
+ end
107
+
108
+ def locally_consumed_construction?(node)
109
+ node.parent.call_type? && node.parent.receiver.equal?(node) && construction?(node.parent) &&
110
+ !handed_off?(node.parent)
111
+ end
112
+
113
+ def construction?(send_node)
114
+ CONSTRUCTION_METHODS.include?(send_node.method_name)
115
+ end
116
+
117
+ def handed_off?(candidate)
118
+ CarriedValue.new(candidate).outermost.then do |value|
119
+ value.parent&.type?(*HANDOFF_TYPES) || passed_as_argument?(value)
120
+ end
121
+ end
122
+
123
+ def passed_as_argument?(value)
124
+ value.parent&.call_type? && !value.parent.receiver.equal?(value)
125
+ end
126
+
127
+ def values_confined?
128
+ !class_escapes? && !instance_escapes? && method_dispatch_confined? &&
129
+ implicit_constructions.none? { implicit_construction_escapes?(it) }
130
+ end
131
+
132
+ def class_escapes?
133
+ class_values.any? { handed_off?(it) } || opaque_class_body_call?
134
+ end
135
+
136
+ def class_values
137
+ explicit_class_values + implicit_class_values
138
+ end
139
+
140
+ def explicit_class_values
141
+ self_nodes.select do |self_node|
142
+ !structural_self?(self_node) && possible_class_side?(self_node)
143
+ end
144
+ end
145
+
146
+ def self_nodes
147
+ @self_nodes ||= nodes_in(nested_class.body, :self)
148
+ end
149
+
150
+ def possible_class_side?(node)
151
+ RuboCop::Callbacksystems::Methods::Domain.new(node).then do |domain|
152
+ if domain.container.equal?(nested_class)
153
+ domain.singleton?
154
+ else
155
+ opaque_block_in_nested_class?(domain.container)
156
+ end
157
+ end
158
+ end
159
+
160
+ def opaque_block_in_nested_class?(container)
161
+ any_block_type?(container) && enclosing_class_or_module_of(container).equal?(nested_class)
162
+ end
163
+
164
+ def implicit_class_values
165
+ sends.select do |send_node|
166
+ VALUE_EXPOSURE_METHODS.include?(send_node.method_name) && call_on_self?(send_node) &&
167
+ possible_class_side?(send_node)
168
+ end
169
+ end
170
+
171
+ def opaque_class_body_call?
172
+ sends.any? do |send_node|
173
+ direct_class_body_call?(send_node) && call_on_self?(send_node) &&
174
+ !trusted_class_body_call?(send_node)
175
+ end
176
+ end
177
+
178
+ def direct_class_body_call?(send_node)
179
+ enclosing_method_of(send_node).nil? && enclosing_class_or_module_of(send_node).equal?(nested_class)
180
+ end
181
+
182
+ def trusted_class_body_call?(send_node)
183
+ CLASS_BODY_PRIMITIVES.include?(send_node.method_name) && !class_method_redefined?(send_node.method_name)
184
+ end
185
+
186
+ def class_method_redefined?(method_name)
187
+ project_index["Class"].find_member("#{method_name}()")&.definitions&.any? || false
188
+ rescue
189
+ true
190
+ end
191
+
192
+ def instance_escapes?
193
+ instance_values.any? { handed_off?(it) }
194
+ end
195
+
196
+ def instance_values
197
+ explicit_instance_values + implicit_instance_values
198
+ end
199
+
200
+ def explicit_instance_values
201
+ self_nodes.select { possible_instance_side?(it) }
202
+ end
203
+
204
+ def possible_instance_side?(node)
205
+ RuboCop::Callbacksystems::Methods::Domain.new(node).then do |domain|
206
+ (domain.container.equal?(nested_class) && !domain.singleton?) ||
207
+ opaque_block_in_nested_class?(domain.container)
208
+ end
209
+ end
210
+
211
+ def implicit_instance_values
212
+ sends.select do |send_node|
213
+ VALUE_EXPOSURE_METHODS.include?(send_node.method_name) && call_on_self?(send_node) &&
214
+ possible_instance_side?(send_node)
215
+ end
216
+ end
217
+
218
+ def method_dispatch_confined?
219
+ RuboCop::Callbacksystems::ProjectIndex::NestedClassMethodDispatch.new(nested_class, declaration:).confined?
220
+ end
221
+
222
+ def implicit_constructions
223
+ sends.select do |send_node|
224
+ construction?(send_node) && call_on_self?(send_node) && possible_class_side?(send_node)
225
+ end
226
+ end
227
+
228
+ def implicit_construction_escapes?(construction)
229
+ opaque_block_in_nested_class?(RuboCop::Callbacksystems::Methods::Domain.new(construction).container) ||
230
+ handed_off?(construction)
231
+ end
232
+
233
+ class SourceConstants
234
+ class << self
235
+ def for(source_root, uri:)
236
+ context = Context.new(source_root, uri)
237
+
238
+ cached.then do |cached_context, constants|
239
+ if cached_context&.same_source?(context)
240
+ constants
241
+ else
242
+ new(context).tap { self.cached = [ context, it ] }
243
+ end
244
+ end
245
+ end
246
+
247
+ private
248
+ attr_accessor :cached
249
+ end
250
+
251
+ def initialize(context)
252
+ @by_location = context.source_root.each_node(:const).group_by do |node|
253
+ RuboCop::Callbacksystems::ProjectIndex::Location.for_ast(node.loc.name, uri: context.uri)
254
+ end
255
+ end
256
+
257
+ def at(location)
258
+ key = RuboCop::Callbacksystems::ProjectIndex::Location.for_rubydex(location)
259
+ by_location.fetch(key) { [] }.then { it.first if it.one? }
260
+ end
261
+
262
+ private
263
+ attr_reader :by_location
264
+
265
+ class Context < Data.define(:source_root, :uri)
266
+ def same_source?(other)
267
+ source_root.equal?(other.source_root) && uri == other.uri
268
+ end
269
+ end
270
+ end
271
+
272
+ class CarriedValue
273
+ def initialize(node)
274
+ @node = node
275
+ end
276
+
277
+ def outermost
278
+ node.then do |current|
279
+ current = current.parent while carries_value?(current.parent, child: current)
280
+ current
281
+ end
282
+ end
283
+
284
+ private
285
+ attr_reader :node
286
+
287
+ def carries_value?(parent, child:)
288
+ case parent&.type
289
+ when :begin, :kwbegin then parent.children.last.equal?(child)
290
+ when :if then parent.children.drop(1).any?(child)
291
+ when :send, :csend then parent.receiver.equal?(child)
292
+ when :class, :module, :sclass then parent.body.equal?(child)
293
+ else false
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,50 @@
1
+ # Whether self-dispatch inside a nested class stays on methods the class itself owns.
2
+ class RuboCop::Callbacksystems::ProjectIndex::NestedClassMethodDispatch
3
+ include RuboCop::Callbacksystems::Helpers
4
+
5
+ def initialize(nested_class, declaration:)
6
+ @nested_class = nested_class
7
+ @declaration = declaration
8
+ end
9
+
10
+ def confined?
11
+ self_calls.all? { owned_method_call?(it) } && !super_dispatch?
12
+ end
13
+
14
+ private
15
+ attr_reader :nested_class, :declaration
16
+
17
+ def self_calls
18
+ nodes_in(nested_class.body, :send, :csend).select do |send_node|
19
+ call_on_self?(send_node) && enclosing_method_of(send_node) && method_owner_of(send_node)
20
+ end
21
+ end
22
+
23
+ def method_owner_of(send_node)
24
+ RuboCop::Callbacksystems::Methods::Domain.new(send_node).then do |domain|
25
+ owner_for(domain.identity) if domain.container.equal?(nested_class)
26
+ end
27
+ end
28
+
29
+ def owner_for(identity)
30
+ if identity.empty?
31
+ declaration
32
+ elsif identity.all? { it == :self }
33
+ declaration.singleton_class
34
+ end
35
+ end
36
+
37
+ def owned_method_call?(send_node)
38
+ method_owner_of(send_node).then do |owner|
39
+ owner.member("#{send_node.method_name}()").present?
40
+ end
41
+ rescue
42
+ false
43
+ end
44
+
45
+ def super_dispatch?
46
+ nodes_in(nested_class.body, :super, :zsuper).any? do |super_node|
47
+ enclosing_class_or_module_of(super_node).equal?(nested_class)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,129 @@
1
+ require "digest"
2
+
3
+ class RuboCop::Callbacksystems::ProjectIndex::Reading
4
+ extend RuboCop::Callbacksystems::ProjectIndex::Cache
5
+
6
+ INCOMPLETE_DIAGNOSTICS = %w[
7
+ ParseError DynamicConstantReference DynamicAncestor DynamicSingletonDefinition
8
+ ]
9
+ OPAQUE_CONSTANT_REFLECTIONS = %w[
10
+ const_get const_set remove_const autoload constantize safe_constantize
11
+ eval class_eval class_exec module_eval module_exec instance_eval instance_exec
12
+ each_object constants descendants subclasses unsafe_load unsafe_load_file
13
+ ].to_set
14
+ OPAQUE_DISPATCH_METHODS = %w[ send __send__ public_send method public_method ].to_set
15
+ OPAQUE_RECEIVER_METHODS = { "Marshal" => %w[ load restore ].to_set }
16
+ RECEIVER_SEPARATOR_WIDTHS = [ 1, 2 ]
17
+
18
+ def initialize(project_index)
19
+ @project_index = project_index
20
+ end
21
+
22
+ def checksum
23
+ @checksum ||= DocumentsChecksum.new(project_index).to_s
24
+ end
25
+
26
+ def reliable?
27
+ return @reliable if defined?(@reliable)
28
+
29
+ @reliable = RuboCop::Callbacksystems::ProjectIndex::Coverage.complete?(project_index) &&
30
+ !diagnostics.any_named?(*INCOMPLETE_DIAGNOSTICS) &&
31
+ !opaque_reflection?
32
+ end
33
+
34
+ private
35
+ attr_reader :project_index
36
+
37
+ def diagnostics
38
+ @diagnostics ||= RuboCop::Callbacksystems::ProjectIndex::Diagnostics.for(project_index)
39
+ end
40
+
41
+ def opaque_reflection?
42
+ return @opaque_reflection if defined?(@opaque_reflection)
43
+
44
+ @opaque_reflection = project_index.method_references.any? do |reference|
45
+ method_name = reference.name.delete_suffix("()")
46
+ OPAQUE_CONSTANT_REFLECTIONS.include?(method_name) ||
47
+ (OPAQUE_DISPATCH_METHODS.include?(method_name) && opaque_dispatch_receiver?(reference.receiver)) ||
48
+ opaque_receiver_method?(reference, method_name:)
49
+ end
50
+ end
51
+
52
+ def opaque_dispatch_receiver?(receiver)
53
+ receiver.nil? || receiver.is_a?(Rubydex::SingletonClass)
54
+ end
55
+
56
+ def opaque_receiver_method?(reference, method_name:)
57
+ receiver_names_of(reference).any? do |receiver_name|
58
+ OPAQUE_RECEIVER_METHODS.fetch(receiver_name) { [] }.include?(method_name)
59
+ end
60
+ end
61
+
62
+ def receiver_names_of(reference)
63
+ direct_receiver_name_of(reference).then do |name|
64
+ name ? Set[name] : constant_receiver_names.fetch(reference_location_key(reference)) { Set.new }
65
+ end
66
+ end
67
+
68
+ def direct_receiver_name_of(reference)
69
+ receiver = reference.receiver
70
+ receiver.attached_class.name if receiver.is_a?(Rubydex::SingletonClass)
71
+ end
72
+
73
+ def constant_receiver_names
74
+ @constant_receiver_names ||= project_index.constant_references.each_with_object({}) do |reference, names|
75
+ RECEIVER_SEPARATOR_WIDTHS.each do |separator_width|
76
+ key = location_key(reference.location, column: reference.location.end_column + separator_width)
77
+ (names[key] ||= Set.new) << constant_name_of(reference)
78
+ end
79
+ end
80
+ end
81
+
82
+ def location_key(location, column:)
83
+ [ location.uri, location.start_line, column ]
84
+ end
85
+
86
+ def constant_name_of(reference)
87
+ if reference.is_a?(Rubydex::ResolvedConstantReference)
88
+ reference.declaration.name
89
+ else
90
+ reference.name.delete_prefix("<").delete_suffix(">")
91
+ end
92
+ end
93
+
94
+ def reference_location_key(reference)
95
+ location_key(reference.location, column: reference.location.start_column)
96
+ end
97
+
98
+ class DocumentsChecksum
99
+ def initialize(project_index)
100
+ @project_index = project_index
101
+ end
102
+
103
+ def to_s
104
+ Digest::SHA256.new.tap do |digest|
105
+ documents.each do |document|
106
+ digest << document.uri << "\0" << checksum_for(document) << "\0"
107
+ end
108
+ end.hexdigest
109
+ end
110
+
111
+ private
112
+ attr_reader :project_index
113
+
114
+ def documents
115
+ project_index.documents.select { it.uri.start_with?(RuboCop::Cop::ProjectIndexHelp::FILE_URI_PREFIX) }
116
+ .sort_by(&:uri)
117
+ end
118
+
119
+ def checksum_for(document)
120
+ Digest::SHA256.file(path_for(document)).hexdigest
121
+ rescue SystemCallError => error
122
+ error.class.name
123
+ end
124
+
125
+ def path_for(document)
126
+ RuboCop::Callbacksystems::ProjectIndex::SourceFile.for_document(document).path
127
+ end
128
+ end
129
+ end