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,131 @@
1
+ # A statement together with the comments that read as its own: the contiguous own-line comments right above it and the
2
+ # comment closing its last line. A rewrite that moves the statement moves them, and a range spanning it spans them.
3
+ class RuboCop::Callbacksystems::Source::StatementWithComments
4
+ include RuboCop::Callbacksystems::Helpers
5
+
6
+ attr_reader :node
7
+
8
+ delegate :source, to: :range
9
+
10
+ class << self
11
+ def sibling_lines_for(processed_source)
12
+ SIBLING_LINES_LOCK.synchronize { SIBLING_LINES[processed_source.comments] ||= SiblingLines.new }
13
+ end
14
+
15
+ private
16
+ SIBLING_LINES = ObjectSpace::WeakMap.new
17
+ SIBLING_LINES_LOCK = Mutex.new
18
+ end
19
+
20
+ def initialize(node, processed_source)
21
+ @node = node
22
+ @processed_source = processed_source
23
+ @source_comments = RuboCop::Callbacksystems::Source::Comments.for(processed_source)
24
+ @sibling_lines = self.class.sibling_lines_for(processed_source)
25
+ end
26
+
27
+ def contains_tooling_comment?
28
+ source_comments.tooling_within?(range)
29
+ end
30
+
31
+ def range
32
+ @range ||= range_between(begin_position, end_position)
33
+ end
34
+
35
+ def begin_position
36
+ @begin_position ||=
37
+ previous_statement_on_line? ? node.source_range.begin_pos : line_start_position_of(leading_comments.first || node)
38
+ end
39
+
40
+ private
41
+ attr_reader :processed_source, :source_comments, :sibling_lines
42
+
43
+ def previous_statement_on_line?
44
+ sibling_lines.previous_on_line?(node)
45
+ end
46
+
47
+ def leading_comments
48
+ @leading_comments ||= comments_above(node.first_line - 1)
49
+ end
50
+
51
+ def comments_above(first_line)
52
+ comments = []
53
+ line = first_line
54
+ while (comment = source_comments.own_line_comment_on(line))
55
+ comments << comment
56
+ line -= 1
57
+ end
58
+ comments.reverse
59
+ end
60
+
61
+ def end_position
62
+ [ range_through_heredocs(node).end_pos, trailing_comment&.source_range&.end_pos ].compact.max
63
+ end
64
+
65
+ def trailing_comment
66
+ source_comments.trailing_comment_on(node.last_line) unless following_statement_on_line?
67
+ end
68
+
69
+ def following_statement_on_line?
70
+ sibling_lines.following_on_line?(node)
71
+ end
72
+
73
+ # Same-line sibling relationships indexed without retaining AST nodes beyond the processed source's lifetime.
74
+ class SiblingLines
75
+ def initialize
76
+ @relationships = {}
77
+ @indexed_parents = Set.new
78
+ end
79
+
80
+ def previous_on_line?(node)
81
+ relationship_for(node).first
82
+ end
83
+
84
+ def following_on_line?(node)
85
+ relationship_for(node).last
86
+ end
87
+
88
+ private
89
+ attr_reader :relationships, :indexed_parents
90
+
91
+ def relationship_for(node)
92
+ index(node.parent)
93
+ relationships.fetch(key_of(node)) { [ false, false ] }
94
+ end
95
+
96
+ def index(parent)
97
+ if parent&.type?(:begin, :kwbegin) && indexed_parents.add?(key_of(parent))
98
+ siblings = parent.each_child_node.to_a
99
+ siblings.each_with_index do |sibling, position|
100
+ relationships[key_of(sibling)] = Relationship.new(siblings, position).to_a
101
+ end
102
+ end
103
+ end
104
+
105
+ def key_of(node)
106
+ [ node.type, node.source_range.begin_pos, node.source_range.end_pos ]
107
+ end
108
+
109
+ class Relationship
110
+ def initialize(siblings, position)
111
+ @siblings = siblings
112
+ @position = position
113
+ end
114
+
115
+ def to_a
116
+ [ previous_on_same_line?, following_on_same_line? ]
117
+ end
118
+
119
+ private
120
+ attr_reader :siblings, :position
121
+
122
+ def previous_on_same_line?
123
+ position.positive? && siblings[position - 1].last_line == siblings[position].first_line
124
+ end
125
+
126
+ def following_on_same_line?
127
+ position < siblings.size - 1 && siblings[position].last_line == siblings[position + 1].first_line
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,42 @@
1
+ module RuboCop::Callbacksystems::Testing::CopHelpers
2
+ extend RuboCop::AST::NodePattern::Macros
3
+
4
+ # @!method test_block?(node)
5
+ def_node_matcher :test_block?, <<~PATTERN
6
+ (any_block (send {nil? (self)} :test (str $_)) ...)
7
+ PATTERN
8
+
9
+ # @!method setup_block?(node)
10
+ def_node_matcher :setup_block?, <<~PATTERN
11
+ (any_block (send {nil? (self)} :setup) ...)
12
+ PATTERN
13
+
14
+ private
15
+ HTTP_METHODS = %i[ get post put patch delete ]
16
+ RESPONSE_ASSERTIONS = %i[ assert_response assert_redirected_to ]
17
+
18
+ def test_blocks(scope = processed_source.ast)
19
+ nodes_in(scope, :any_block).select { test_block?(it) }
20
+ end
21
+
22
+ def setup_blocks(scope = processed_source.ast)
23
+ nodes_in(scope, :any_block).select { setup_block?(it) }
24
+ end
25
+
26
+ def immediate_calls_in(statement)
27
+ immediate_calls_by_statement[statement] ||= RuboCop::Callbacksystems::Execution::Immediate.new(statement)
28
+ .nodes_of_type(:send, :csend)
29
+ end
30
+
31
+ def immediate_calls_by_statement
32
+ @immediate_calls_by_statement ||= {}.compare_by_identity
33
+ end
34
+
35
+ def response_assertion?(send_node)
36
+ call_on_self?(send_node) && RESPONSE_ASSERTIONS.include?(send_node.method_name)
37
+ end
38
+
39
+ def http_request?(send_node)
40
+ call_on_self?(send_node) && HTTP_METHODS.include?(send_node.method_name)
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ class RuboCop::Callbacksystems::Testing::FixtureCall < Data.define(:node)
2
+ include RuboCop::Callbacksystems::Helpers
3
+
4
+ def valid?
5
+ bare_send?(node) &&
6
+ node.arguments.size == 1 &&
7
+ node.first_argument.sym_type? &&
8
+ fixture_set?
9
+ end
10
+
11
+ def signature
12
+ "#{node.method_name}(#{node.first_argument.source})"
13
+ end
14
+
15
+ def identity
16
+ [ node.method_name, node.first_argument.value ]
17
+ end
18
+
19
+ private
20
+ delegate :source_buffer, to: "node.source_range", private: true
21
+
22
+ # A helper reads like a fixture call, so the project's own fixture sets are what tells the two apart.
23
+ def fixture_set?
24
+ RuboCop::Callbacksystems::Testing::FixtureNames.new(file_path, lifetime: source_buffer).include?(node.method_name)
25
+ end
26
+
27
+ def file_path
28
+ source_buffer.name
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ # The fixture sets a project defines, read from the yml files under the `test/fixtures` directory of the tree the test
2
+ # file sits in, since a project can hold more than one of those. Rails names the accessor after the path of the file, so
3
+ # `test/fixtures/webhook/deliveries.yml` answers `webhook_deliveries(:name)`, and what sits under `files` stays out
4
+ # because `file_fixture_path` points there.
5
+ class RuboCop::Callbacksystems::Testing::FixtureNames
6
+ def initialize(test_file, lifetime: nil)
7
+ @test_file = test_file
8
+ @lifetime = lifetime
9
+ end
10
+
11
+ def include?(name)
12
+ accessors.include?(name.to_s)
13
+ end
14
+
15
+ private
16
+ TEST_DIRECTORY = "test"
17
+ FIXTURE_DIRECTORY = "fixtures"
18
+ FILE_FIXTURE_DIRECTORY = "files"
19
+
20
+ # Calls made while one source is being investigated share a walk, while the weak lifetime makes the next
21
+ # investigation read the project again and lets completed ones leave no cache behind.
22
+ ACCESSORS_BY_LIFETIME = ObjectSpace::WeakMap.new
23
+ ACCESSORS_LOCK = Mutex.new
24
+
25
+ attr_reader :test_file, :lifetime
26
+
27
+ def accessors
28
+ @accessors ||= lifetime ? shared_accessors : discovered_accessors
29
+ end
30
+
31
+ def shared_accessors
32
+ ACCESSORS_LOCK.synchronize { ACCESSORS_BY_LIFETIME[lifetime] ||= discovered_accessors }
33
+ end
34
+
35
+ def discovered_accessors
36
+ directory ? accessors_in(directory) : Set[]
37
+ end
38
+
39
+ def directory
40
+ @directory ||= test_directory&.join(FIXTURE_DIRECTORY)
41
+ end
42
+
43
+ def test_directory
44
+ Pathname.new(test_file).ascend.find { it.basename.to_s == TEST_DIRECTORY } if test_file
45
+ end
46
+
47
+ def accessors_in(directory)
48
+ Pathname.glob(directory.join("**/*.yml")).reject { file_fixture?(it) }.to_set { accessor_for(it) }
49
+ end
50
+
51
+ def file_fixture?(file)
52
+ relative_path_of(file).each_filename.first == FILE_FIXTURE_DIRECTORY
53
+ end
54
+
55
+ def relative_path_of(file)
56
+ file.relative_path_from(directory)
57
+ end
58
+
59
+ def accessor_for(file)
60
+ relative_path_of(file).sub_ext("").to_s.tr("/", "_")
61
+ end
62
+ end
@@ -0,0 +1,104 @@
1
+ class RuboCop::Callbacksystems::Testing::PathMapping
2
+ def initialize(path)
3
+ @path = path
4
+ end
5
+
6
+ def source_path
7
+ return @source_path if defined?(@source_path)
8
+
9
+ app_path = source_path_in("app")
10
+ lib_path = source_path_in("lib")
11
+
12
+ @source_path = if app_path != path && File.exist?(app_path)
13
+ app_path
14
+ elsif lib_path != path && File.exist?(lib_path)
15
+ lib_path
16
+ else
17
+ namespaced_gem_source_path
18
+ end
19
+ end
20
+
21
+ def gem_project?
22
+ return @gem_project if defined?(@gem_project)
23
+
24
+ @gem_project = Dir.glob(File.join(project_root_path, "*.gemspec")).any?
25
+ end
26
+
27
+ def test_path
28
+ return @test_path if defined?(@test_path)
29
+
30
+ @test_path = candidate_test_paths.find { File.exist?(it) }
31
+ end
32
+
33
+ private
34
+ attr_reader :path
35
+
36
+ def source_path_in(directory)
37
+ path.sub(%r{(^|/)test/(.+)_test\.rb$}) do
38
+ "#{Regexp.last_match(1)}#{directory}/#{Regexp.last_match(2)}.rb"
39
+ end
40
+ end
41
+
42
+ def namespaced_gem_source_path
43
+ if gem_project? && path.match?(%r{(^|/)test/.+_test\.rb$})
44
+ relative_source = path.match(%r{(^|/)test/(.+)_test\.rb$}).captures.second
45
+ candidates = Dir.glob(File.join(project_root_path, "lib", "*", "#{relative_source}.rb"))
46
+ candidates.one? ? candidates.first : nil
47
+ end
48
+ end
49
+
50
+ def project_root_path
51
+ project_root.empty? ? "." : project_root
52
+ end
53
+
54
+ def project_root
55
+ project_directory_match ? matched_project_root : path
56
+ end
57
+
58
+ def project_directory_match
59
+ @project_directory_match ||= path.match(%r{\A(?:(.*?)/)?(?:app|lib|test)/})
60
+ end
61
+
62
+ def matched_project_root
63
+ project_directory_match.captures.first.to_s.presence || absolute_root
64
+ end
65
+
66
+ def absolute_root
67
+ path.start_with?(File::SEPARATOR) ? File::SEPARATOR : ""
68
+ end
69
+
70
+ def candidate_test_paths
71
+ lib_test_paths + flat_gem_test_paths + app_test_paths
72
+ end
73
+
74
+ def lib_test_paths
75
+ test_paths_for("lib")
76
+ end
77
+
78
+ def test_paths_for(root)
79
+ if path.match?(%r{(^|/)#{root}/})
80
+ [ rewrite(root, "test/#{root}/"), rewrite(root, "test/") ]
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ def rewrite(root, prefix)
87
+ path.sub(%r{(^|/)#{root}/(.+)\.rb$}) { "#{Regexp.last_match(1)}#{prefix}#{Regexp.last_match(2)}_test.rb" }
88
+ end
89
+
90
+ def flat_gem_test_paths
91
+ if gem_project?
92
+ candidate = path.sub(%r{(^|/)lib/[^/]+/(.+)\.rb$}) do
93
+ "#{Regexp.last_match(1)}test/#{Regexp.last_match(2)}_test.rb"
94
+ end
95
+ candidate == path ? [] : [ candidate ]
96
+ else
97
+ []
98
+ end
99
+ end
100
+
101
+ def app_test_paths
102
+ test_paths_for("app").last(1)
103
+ end
104
+ end
@@ -0,0 +1,44 @@
1
+ # Prohibits defining methods directly in ApplicationController.
2
+ #
3
+ # Methods in ApplicationController should be extracted into concerns
4
+ # to keep the base controller clean and maintainable. Include concerns
5
+ # instead of defining methods directly.
6
+ #
7
+ # @example
8
+ # # bad - method defined directly
9
+ # class ApplicationController < ActionController::Base
10
+ # def current_user
11
+ # @current_user ||= User.find(session[:user_id])
12
+ # end
13
+ # end
14
+ #
15
+ # # good - use concerns
16
+ # class ApplicationController < ActionController::Base
17
+ # include Authentication
18
+ # include CurrentUser
19
+ # end
20
+ #
21
+ # # app/controllers/concerns/current_user.rb
22
+ # module CurrentUser
23
+ # extend ActiveSupport::Concern
24
+ #
25
+ # included do
26
+ # helper_method :current_user
27
+ # end
28
+ #
29
+ # def current_user
30
+ # @current_user ||= User.find(session[:user_id])
31
+ # end
32
+ # end
33
+ #
34
+ class RuboCop::Cop::Callbacksystems::ApplicationControllerMethodDefinition < RuboCop::Cop::Callbacksystems::Base
35
+ MESSAGE = "Don't define methods in ApplicationController. Extract to a concern and include it."
36
+
37
+ def on_def(node)
38
+ if direct_method_definition?(node) && application_controller_class?(enclosing_class_or_module_of(node))
39
+ add_offense(node, message: MESSAGE)
40
+ end
41
+ end
42
+
43
+ alias on_defs on_def
44
+ end
@@ -0,0 +1,60 @@
1
+ # `assert expected, actual` passes whenever `expected` is truthy, so a
2
+ # two-argument `assert` usually means `assert_equal`. The second argument is
3
+ # legitimate when it reads as a failure message: a string, or a variable or
4
+ # method whose name says so. This replaces `Minitest/AssertWithExpectedArgument`,
5
+ # which only recognizes string literals and the bare names `message` and `msg`.
6
+ #
7
+ # @example
8
+ # # bad
9
+ # assert(3, my_list.length)
10
+ # assert(expected, actual)
11
+ #
12
+ # # good
13
+ # assert_equal(3, my_list.length)
14
+ # assert foo, "must be present"
15
+ # assert foo, msg
16
+ # assert foo, error_message
17
+ # assert audit.passed?, audit.failure_message
18
+ # assert user.valid?, user.errors
19
+ #
20
+ class RuboCop::Cop::Callbacksystems::AssertWithExpectedArgument < RuboCop::Cop::Callbacksystems::Base
21
+ MESSAGE = "Did you mean to use `assert_equal(%<arguments>s)`?"
22
+ RESTRICT_ON_SEND = %i[ assert ]
23
+ MESSAGE_NAME = /message\z|\Amsg\z/
24
+ BOOLEAN_NODE_TYPES = %i[ and false match_pattern_p or true ]
25
+ BOOLEAN_METHODS = %i[ ! =~ !~ ]
26
+
27
+ # @!method assert_with_two_arguments?(node)
28
+ def_node_matcher :assert_with_two_arguments?, <<~PATTERN
29
+ (call {nil? (self)} :assert $_ $_)
30
+ PATTERN
31
+
32
+ def on_send(node)
33
+ if test_file?(processed_source.file_path)
34
+ assert_with_two_arguments?(node) do |asserted, second_argument|
35
+ unless predicate?(asserted) || message_argument?(second_argument)
36
+ add_offense(node, message: format(MESSAGE, arguments: node.arguments.map(&:source).join(", ")))
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ alias on_csend on_send
43
+
44
+ private
45
+ # With a predicate asserted there is no expected/actual confusion, so the second argument is a message by intention.
46
+ def predicate?(argument)
47
+ BOOLEAN_NODE_TYPES.include?(argument.type) ||
48
+ (argument.call_type? && (argument.predicate_method? || argument.comparison_method? ||
49
+ BOOLEAN_METHODS.include?(argument.method_name)))
50
+ end
51
+
52
+ def message_argument?(argument)
53
+ argument.type?(:str, :dstr) || deferred_callable_block?(argument) || named_like_a_message?(argument)
54
+ end
55
+
56
+ def named_like_a_message?(argument)
57
+ name = argument.send_type? ? argument.method_name : argument.source
58
+ name.to_s.match?(MESSAGE_NAME)
59
+ end
60
+ end
@@ -0,0 +1,134 @@
1
+ class RuboCop::Cop::Callbacksystems::Base < RuboCop::Cop::Base
2
+ include RuboCop::Callbacksystems::Helpers
3
+
4
+ exclude_from_registry
5
+
6
+ def add_offense(node_or_range, message: nil, severity: nil, rewrites_comments: false, &correction)
7
+ if autocorrect? && !flushing_corrections?
8
+ request = CorrectionRequest.new \
9
+ range_from_node_or_range(node_or_range), message:, severity:, correction:, rewrites_comments: rewrites_comments
10
+ if reserve(request.range)
11
+ correction_queue.add(request, enabled: correction_enabled?(request.range))
12
+ end
13
+ else
14
+ super(node_or_range, message:, severity:, &correction)
15
+ end
16
+ end
17
+
18
+ def on_investigation_end
19
+ flush_corrections
20
+ super
21
+ end
22
+
23
+ private
24
+ def flushing_corrections?
25
+ correction_queue.flushing?
26
+ end
27
+
28
+ def correction_queue
29
+ @correction_queue ||= CorrectionQueue.new
30
+ end
31
+
32
+ def reserve(range)
33
+ reserved_locations.add?(range)
34
+ end
35
+
36
+ def reserved_locations
37
+ if @reservation_source != processed_source
38
+ @reservation_source = processed_source
39
+ @reserved_locations = Set.new
40
+ end
41
+ @reserved_locations
42
+ end
43
+
44
+ def correction_enabled?(range)
45
+ if processed_source.comment_config.respond_to?(:cop_enabled_at_lines?)
46
+ range.first_line.upto(range.last_line).all? { enabled_line?(it) }
47
+ else
48
+ enabled_line?(range.first_line)
49
+ end
50
+ end
51
+
52
+ def flush_corrections
53
+ correction_queue.flush(processed_source) do |offense, correction|
54
+ add_offense offense.range, message: offense.message, severity: offense.severity, &correction
55
+ end
56
+ end
57
+
58
+ def source_comments
59
+ RuboCop::Callbacksystems::Source::Comments.for(processed_source)
60
+ end
61
+
62
+ def project_root
63
+ @config.base_dir_for_path_parameters
64
+ end
65
+
66
+ # Measuring against any width but the enforced one leaves two corrections undoing each other.
67
+ def max_line_length
68
+ config.for_cop("Layout/LineLength")["Max"]
69
+ end
70
+
71
+ def report(analysis, rewrites_comments: false)
72
+ offense = analysis.offense
73
+ add_offense(offense.range, message: offense.message, rewrites_comments:, &offense.correction) if offense
74
+ end
75
+
76
+ def report_each(analyses, rewrites_comments: false)
77
+ analyses.each_offense do |offense|
78
+ add_offense(offense.range, message: offense.message, rewrites_comments:, &offense.correction)
79
+ end
80
+ end
81
+
82
+ class CorrectionRequest
83
+ attr_reader :range, :message, :severity, :correction
84
+
85
+ def initialize(range, message:, severity:, correction:, rewrites_comments:)
86
+ @range = range
87
+ @message = message
88
+ @severity = severity
89
+ @correction = correction
90
+ @rewrites_comments = rewrites_comments
91
+ end
92
+
93
+ def batchable?(enabled:)
94
+ correction && enabled
95
+ end
96
+
97
+ def rewrites_comments?
98
+ rewrites_comments
99
+ end
100
+
101
+ private
102
+ attr_reader :rewrites_comments
103
+ end
104
+
105
+ class CorrectionQueue
106
+ def initialize
107
+ @offenses = []
108
+ @corrections = []
109
+ @flushing = false
110
+ end
111
+
112
+ def add(request, enabled:)
113
+ offenses << request
114
+ corrections << request if request.batchable?(enabled:)
115
+ end
116
+
117
+ def flushing?
118
+ flushing
119
+ end
120
+
121
+ def flush(processed_source)
122
+ batch = RuboCop::Callbacksystems::Autocorrection::Batch.new(corrections, processed_source)
123
+ @flushing = true
124
+ offenses.each { yield it, batch.correction_for(it) }
125
+ ensure
126
+ @flushing = false
127
+ offenses.clear
128
+ corrections.clear
129
+ end
130
+
131
+ private
132
+ attr_reader :offenses, :corrections, :flushing
133
+ end
134
+ end