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,59 @@
1
+ require "uri"
2
+
3
+ class RuboCop::Callbacksystems::ProjectIndex::SourceFile
4
+ attr_reader :path
5
+
6
+ delegate :hash, to: :identity
7
+
8
+ class << self
9
+ def for_document(document)
10
+ Rubydex::Location.new(uri: document.uri, start_line: 0, start_column: 0, end_line: 0, end_column: 0)
11
+ .then { new(it.to_file_path) }
12
+ end
13
+ end
14
+
15
+ def initialize(path)
16
+ @path = canonical_path_of(path.to_s)
17
+ @windows = Gem.win_platform?
18
+ end
19
+
20
+ def eql?(other)
21
+ other.is_a?(self.class) && identity == other.identity
22
+ end
23
+
24
+ def source
25
+ Parser::Source::Buffer.new(path).read.source.encode(Encoding::UTF_8).tap do |source|
26
+ raise EncodingError, "Cannot index invalid UTF-8 source: #{path}" unless source.valid_encoding?
27
+ end
28
+ end
29
+
30
+ def uri
31
+ "file://#{URI::DEFAULT_PARSER.escape(uri_path)}"
32
+ end
33
+
34
+ protected
35
+ def identity
36
+ @identity ||= normalized_path.then { windows ? it.downcase : it }
37
+ end
38
+
39
+ private
40
+ attr_reader :windows
41
+
42
+ def canonical_path_of(path)
43
+ File.realpath(path)
44
+ rescue SystemCallError
45
+ File.expand_path(path)
46
+ end
47
+
48
+ def normalized_path
49
+ @normalized_path ||= path.tr("\\", "/")
50
+ end
51
+
52
+ def uri_path
53
+ if windows && !normalized_path.start_with?("/")
54
+ "/#{normalized_path}"
55
+ else
56
+ normalized_path
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
1
+ # The source-to-file comparison shared by every cop investigating one ProcessedSource.
2
+ class RuboCop::Callbacksystems::ProjectIndex::SourceFileMatch
3
+ class << self
4
+ def for(processed_source)
5
+ return false unless valid_source?(processed_source.raw_source)
6
+
7
+ snapshot = Snapshot.new(processed_source)
8
+ MUTEX.synchronize do
9
+ entry = CACHE[processed_source]
10
+
11
+ if entry&.fingerprint == snapshot.fingerprint
12
+ entry.matches
13
+ else
14
+ snapshot.matches?.tap { CACHE[processed_source] = Entry.new(snapshot.fingerprint, it) }
15
+ end
16
+ end
17
+ rescue
18
+ false
19
+ end
20
+
21
+ private
22
+ CACHE = ObjectSpace::WeakMap.new
23
+ MUTEX = Mutex.new
24
+ Entry = Data.define(:fingerprint, :matches)
25
+
26
+ def valid_source?(source)
27
+ source.encoding == Encoding::UTF_8 && source.valid_encoding?
28
+ end
29
+ end
30
+
31
+ private
32
+ class Snapshot
33
+ attr_reader :fingerprint
34
+
35
+ def initialize(processed_source)
36
+ @source = processed_source.raw_source
37
+ @path = File.expand_path(processed_source.file_path)
38
+ @file_fingerprint = fingerprint_of(File.stat(path))
39
+ @fingerprint = [ path, file_fingerprint ].freeze
40
+ end
41
+
42
+ def matches?
43
+ File.binread(path) == source.b && file_fingerprint == fingerprint_of(File.stat(path))
44
+ end
45
+
46
+ private
47
+ attr_reader :source, :path, :file_fingerprint
48
+
49
+ def fingerprint_of(stat)
50
+ [ stat.dev, stat.ino, stat.size, *timestamps_of(stat) ].freeze
51
+ end
52
+
53
+ def timestamps_of(stat)
54
+ [ stat.mtime.to_i, stat.mtime.nsec, stat.ctime.to_i, stat.ctime.nsec ]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ module RuboCop::Callbacksystems::ProjectIndex::Support
2
+ include RuboCop::Cop::ProjectIndexHelp
3
+
4
+ def external_dependency_checksum
5
+ RuboCop::Callbacksystems::ProjectIndex::Reading.for(project_index).checksum if project_index
6
+ end
7
+
8
+ private
9
+ def project_index_reliable?
10
+ project_index && RuboCop::Callbacksystems::ProjectIndex::Freshness.reliable?(project_index) &&
11
+ source_matches_index? && RuboCop::Callbacksystems::ProjectIndex::Reading.for(project_index).reliable?
12
+ end
13
+
14
+ def source_matches_index?
15
+ source_matches_disk? ||
16
+ RuboCop::Callbacksystems::ProjectIndex::Freshness.current_source?(project_index, processed_source)
17
+ end
18
+
19
+ def source_matches_disk?
20
+ RuboCop::Callbacksystems::ProjectIndex::SourceFileMatch.for(processed_source)
21
+ end
22
+
23
+ def confined_declaration_of(nested_class)
24
+ resolve_constant_in_index(nested_class.identifier).then do |declaration|
25
+ declaration if confined_to_current_file?(declaration, nested_class:)
26
+ end
27
+ end
28
+
29
+ def confined_to_current_file?(declaration, nested_class:)
30
+ declaration.is_a?(Rubydex::Class) && project_declaration_reliable?(declaration) &&
31
+ definitions_in_other_files(declaration.definitions).empty? && declaration.definitions.one? &&
32
+ RuboCop::Callbacksystems::ProjectIndex::ConfinementCache
33
+ .for(nested_class, declaration:, project_index:).confined?
34
+ end
35
+
36
+ def project_declaration_reliable?(declaration)
37
+ !project_index_diagnostics.within_definitions?(
38
+ declaration.definitions,
39
+ named: [ "InvalidMethodVisibility" ]
40
+ )
41
+ end
42
+
43
+ def project_index_diagnostics
44
+ RuboCop::Callbacksystems::ProjectIndex::Diagnostics.for(project_index)
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ # The unit the comment cops reason about: consecutive own-line `#` lines starting at the same column, two or more of
2
+ # them, leaving out what addresses the tooling rather than the reader.
3
+ class RuboCop::Callbacksystems::Source::CommentBlocks
4
+ include Enumerable
5
+ include RuboCop::Callbacksystems::Helpers
6
+
7
+ delegate :each, to: :blocks
8
+
9
+ def initialize(processed_source, reject_tooling_runs: false)
10
+ @processed_source = processed_source
11
+ @reject_tooling_runs = reject_tooling_runs
12
+ end
13
+
14
+ private
15
+ attr_reader :processed_source, :reject_tooling_runs
16
+
17
+ def blocks
18
+ comment_runs.flat_map { blocks_in(it) }
19
+ end
20
+
21
+ def comment_runs
22
+ own_line_comments.chunk_while { |previous, comment| continues?(previous, comment) }
23
+ end
24
+
25
+ def own_line_comments
26
+ processed_source.comments.select { it.inline? && own_line_comment?(it) }
27
+ end
28
+
29
+ def continues?(previous, comment)
30
+ comment.source_range.line == previous.source_range.line + 1 &&
31
+ comment.source_range.column == previous.source_range.column
32
+ end
33
+
34
+ def blocks_in(run)
35
+ if reject_tooling_runs && run.any? { tooling_comment?(it) }
36
+ []
37
+ else
38
+ run.reject { tooling_comment?(it) }
39
+ .chunk_while { |previous, comment| continues?(previous, comment) }
40
+ .select(&:many?)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,141 @@
1
+ # Comments indexed by their position in one source, so asking whether a node contains comments does not walk every
2
+ # comment in the file for every node the cops inspect.
3
+ class RuboCop::Callbacksystems::Source::Comments
4
+ include RuboCop::Callbacksystems::Helpers
5
+
6
+ class << self
7
+ def for(processed_source)
8
+ CACHE_LOCK.synchronize { CACHE[processed_source.comments] ||= new(processed_source.comments) }
9
+ end
10
+
11
+ private
12
+ CACHE = ObjectSpace::WeakMap.new
13
+ CACHE_LOCK = Mutex.new
14
+ end
15
+
16
+ def initialize(comments)
17
+ @comments = comments.sort_by { it.source_range.begin_pos }
18
+ end
19
+
20
+ def any_within?(node_or_range)
21
+ first_within(range_of(node_or_range)).present?
22
+ end
23
+
24
+ def tooling_within?(node_or_range)
25
+ within(node_or_range).any? { tooling_comment?(it) }
26
+ end
27
+
28
+ def within(node_or_range)
29
+ range = range_of(node_or_range)
30
+ CommentsWithin.new(comments, range, from: first_index_at(range.begin_pos)).to_a
31
+ end
32
+
33
+ def own_line_comment_on(line)
34
+ own_line_comments_by_line[line]
35
+ end
36
+
37
+ def trailing_comment_on(line)
38
+ trailing_comments_by_line[line]
39
+ end
40
+
41
+ def any_on_lines?(lines)
42
+ if lines.begin <= lines.end
43
+ CommentsOnLines.new(comments, lines, from: first_index_on(lines.begin)).any?
44
+ else
45
+ false
46
+ end
47
+ end
48
+
49
+ def on_lines(lines)
50
+ if lines.begin <= lines.end
51
+ CommentsOnLines.new(comments, lines, from: first_index_on(lines.begin)).to_a
52
+ else
53
+ []
54
+ end
55
+ end
56
+
57
+ private
58
+ attr_reader :comments
59
+
60
+ def first_within(range)
61
+ comments[first_index_at(range.begin_pos)]&.then { it if range.contains?(it.source_range) }
62
+ end
63
+
64
+ def first_index_at(position)
65
+ begin_positions.bsearch_index { it >= position } || comments.size
66
+ end
67
+
68
+ def begin_positions
69
+ @begin_positions ||= comments.map { it.source_range.begin_pos }
70
+ end
71
+
72
+ def range_of(node_or_range)
73
+ node_or_range.respond_to?(:source_range) ? node_or_range.source_range : node_or_range
74
+ end
75
+
76
+ def own_line_comments_by_line
77
+ @own_line_comments_by_line ||= comments.select { own_line_comment?(it) }.index_by { it.loc.line }
78
+ end
79
+
80
+ def trailing_comments_by_line
81
+ @trailing_comments_by_line ||= comments.reject { own_line_comment?(it) }.index_by { it.loc.line }
82
+ end
83
+
84
+ def first_index_on(line)
85
+ line_numbers.bsearch_index { it >= line } || comments.size
86
+ end
87
+
88
+ def line_numbers
89
+ @line_numbers ||= comments.map { it.loc.line }
90
+ end
91
+
92
+ class CommentsWithin
93
+ include Enumerable
94
+
95
+ def initialize(comments, range, from:)
96
+ @comments = comments
97
+ @range = range
98
+ @from = from
99
+ end
100
+
101
+ def each
102
+ if block_given?
103
+ index = from
104
+ while comments[index] && comments[index].source_range.begin_pos <= range.end_pos
105
+ yield comments[index] if range.contains?(comments[index].source_range)
106
+ index += 1
107
+ end
108
+ else
109
+ to_enum(__method__)
110
+ end
111
+ end
112
+
113
+ private
114
+ attr_reader :comments, :range, :from
115
+ end
116
+
117
+ class CommentsOnLines
118
+ include Enumerable
119
+
120
+ def initialize(comments, lines, from:)
121
+ @comments = comments
122
+ @lines = lines
123
+ @from = from
124
+ end
125
+
126
+ def each
127
+ if block_given?
128
+ index = from
129
+ while comments[index] && comments[index].loc.line <= lines.end
130
+ yield comments[index]
131
+ index += 1
132
+ end
133
+ else
134
+ to_enum(__method__)
135
+ end
136
+ end
137
+
138
+ private
139
+ attr_reader :comments, :lines, :from
140
+ end
141
+ end
@@ -0,0 +1,15 @@
1
+ module RuboCop::Callbacksystems::Source::FileAst
2
+ extend self
3
+
4
+ def ast(path, ruby_version:)
5
+ processed_source(path, ruby_version:)&.ast
6
+ end
7
+
8
+ def processed_source(path, ruby_version:)
9
+ return unless path && File.exist?(path)
10
+
11
+ RuboCop::AST::ProcessedSource.new(File.read(path), ruby_version, path)
12
+ rescue SystemCallError, EncodingError
13
+ nil
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require "digest"
2
+
3
+ module RuboCop::Callbacksystems::Source::FilesChecksum
4
+ extend self
5
+
6
+ def for(patterns, root: Dir.pwd, include_contents: true)
7
+ Contents.new(Array(patterns), File.expand_path(root), include_contents:).checksum
8
+ end
9
+
10
+ private
11
+ class Contents
12
+ def initialize(patterns, root, include_contents:)
13
+ @patterns = patterns
14
+ @root = root
15
+ @include_contents = include_contents
16
+ end
17
+
18
+ def checksum
19
+ Digest::SHA256.new.tap { |digest| matching.each { append(it, to: digest) } }.hexdigest
20
+ end
21
+
22
+ private
23
+ attr_reader :patterns, :root, :include_contents
24
+
25
+ def matching
26
+ patterns.flat_map { Dir.glob(File.join(root, it)) }.select { File.file?(it) }.uniq.sort
27
+ end
28
+
29
+ def append(path, to:)
30
+ to << relative_path_for(path) << "\0"
31
+ to.file(path) if include_contents?
32
+ to << "\0"
33
+ end
34
+
35
+ def relative_path_for(path)
36
+ path.delete_prefix("#{root}/")
37
+ end
38
+
39
+ def include_contents?
40
+ include_contents
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,181 @@
1
+ # The one-line form of a call and whether the call belongs on it. Cops measuring this apart end up giving the same call
2
+ # contradictory advice, so they read it here.
3
+ class RuboCop::Callbacksystems::Source::SingleLineCall
4
+ include RuboCop::Callbacksystems::Helpers
5
+
6
+ attr_reader :node
7
+
8
+ def initialize(node, max_line_length)
9
+ @node = node
10
+ @max_line_length = max_line_length
11
+ end
12
+
13
+ def fits?
14
+ multiline? && !stays_multiline? && chain_collapsible? && fits_on_one_line?
15
+ end
16
+
17
+ def source
18
+ @source ||= CollapsedSource.new(node).value
19
+ end
20
+
21
+ private
22
+ attr_reader :max_line_length
23
+ delegate :operator_method?, to: :node, private: true
24
+
25
+ def multiline?
26
+ !node.single_line?
27
+ end
28
+
29
+ def stays_multiline?
30
+ block_call? || operator_method? || link_in_chain? || chain_contains_blocks? || carries_heredoc?(node)
31
+ end
32
+
33
+ def block_call?
34
+ any_block_type?(node.parent)
35
+ end
36
+
37
+ def link_in_chain?
38
+ node.parent&.call_type? && node.equal?(node.parent.receiver)
39
+ end
40
+
41
+ def chain_contains_blocks?(current = node.receiver)
42
+ ReceiverPath.new(current).any? { any_block_type?(it) }
43
+ end
44
+
45
+ def chain_collapsible?
46
+ chain_sends.all? { send_args_single_line?(it) }
47
+ end
48
+
49
+ def chain_sends
50
+ [ node, *ReceiverPath.new(node.receiver).take_while(&:call_type?) ]
51
+ end
52
+
53
+ def send_args_single_line?(send_node)
54
+ send_node.arguments.all? { argument_single_line?(it) }
55
+ end
56
+
57
+ def argument_single_line?(arg)
58
+ return arg.children.all?(&:single_line?) if implicit_hash?(arg)
59
+
60
+ arg.single_line?
61
+ end
62
+
63
+ def implicit_hash?(arg)
64
+ arg.hash_type? && !arg.loc.begin
65
+ end
66
+
67
+ def fits_on_one_line?
68
+ source.exclude?("\n") && fits_on_line?(node, source, max_line_length)
69
+ end
70
+
71
+ # The receiver nodes leading inward from a call, walked without depending on Ruby's call stack.
72
+ class ReceiverPath
73
+ include Enumerable
74
+
75
+ def initialize(node)
76
+ @node = node
77
+ end
78
+
79
+ def each
80
+ if block_given?
81
+ current = node
82
+ while current
83
+ yield current
84
+ current = current.call_type? ? current.receiver : nil
85
+ end
86
+ else
87
+ to_enum(__method__)
88
+ end
89
+ end
90
+
91
+ private
92
+ attr_reader :node
93
+ end
94
+
95
+ # The collapsed source of a receiver call chain, assembled from its innermost call outward in one pass.
96
+ class CollapsedSource
97
+ include RuboCop::Callbacksystems::Helpers
98
+
99
+ def initialize(node)
100
+ @node = node
101
+ end
102
+
103
+ def value
104
+ fragments.join
105
+ end
106
+
107
+ private
108
+ attr_reader :node
109
+
110
+ def fragments
111
+ calls.reverse_each.with_index.map do |call, index|
112
+ CallFragment.new(call, leading_receiver: index.zero?).value
113
+ end
114
+ end
115
+
116
+ def calls
117
+ current = node
118
+ [ current ].then do |chain|
119
+ while current.receiver&.call_type? && !current.receiver.operator_method?
120
+ current = current.receiver
121
+ chain << current
122
+ end
123
+ chain
124
+ end
125
+ end
126
+ end
127
+
128
+ class CallFragment
129
+ include RuboCop::Callbacksystems::Helpers
130
+
131
+ def initialize(call, leading_receiver:)
132
+ @call = call
133
+ @leading_receiver = leading_receiver
134
+ end
135
+
136
+ def value
137
+ [ receiver_source, dot_source, call.method_name, arguments_suffix ].compact.join
138
+ end
139
+
140
+ private
141
+ attr_reader :call, :leading_receiver
142
+
143
+ # An index or operator call has no selector to rebuild from, so it stays as written.
144
+ def receiver_source
145
+ call.receiver.source.strip if leading_receiver && call.receiver
146
+ end
147
+
148
+ def dot_source
149
+ call.loc.dot.source if call.receiver
150
+ end
151
+
152
+ def arguments_suffix
153
+ if parenthesized_once_collapsed?
154
+ "(#{arguments_joined})"
155
+ elsif call.arguments?
156
+ " #{arguments_joined}"
157
+ else
158
+ ""
159
+ end
160
+ end
161
+
162
+ def parenthesized_once_collapsed?
163
+ # A backslash continuation is what lets a builder call go without parentheses, so they come back on collapse.
164
+ call.parenthesized? || (call.arguments? && builder_method?(call))
165
+ end
166
+
167
+ def arguments_joined
168
+ call.arguments.flat_map { sources_of(it) }.join(", ")
169
+ end
170
+
171
+ def sources_of(argument)
172
+ return argument.children.map { it.source.strip } if implicit_hash?(argument)
173
+
174
+ [ argument.source.strip ]
175
+ end
176
+
177
+ def implicit_hash?(argument)
178
+ argument.hash_type? && !argument.loc.begin
179
+ end
180
+ end
181
+ end