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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +78 -0
- data/config/default.yml +590 -0
- data/docs/cops.md +111 -0
- data/lib/rubocop/callbacksystems/autocorrection/batch.rb +135 -0
- data/lib/rubocop/callbacksystems/autocorrection/comment_rescue.rb +112 -0
- data/lib/rubocop/callbacksystems/autocorrection/rehearsal.rb +65 -0
- data/lib/rubocop/callbacksystems/autocorrection/rewrite_permit.rb +14 -0
- data/lib/rubocop/callbacksystems/class_structure/builder_assignment.rb +48 -0
- data/lib/rubocop/callbacksystems/class_structure/class_body.rb +50 -0
- data/lib/rubocop/callbacksystems/class_structure/divergence.rb +31 -0
- data/lib/rubocop/callbacksystems/class_structure/node_visibility.rb +82 -0
- data/lib/rubocop/callbacksystems/class_structure/reference_order.rb +36 -0
- data/lib/rubocop/callbacksystems/class_structure/statement_rank.rb +84 -0
- data/lib/rubocop/callbacksystems/class_structure/statement_run.rb +42 -0
- data/lib/rubocop/callbacksystems/class_structure/visibility.rb +84 -0
- data/lib/rubocop/callbacksystems/cops_document.rb +78 -0
- data/lib/rubocop/callbacksystems/execution/deferred_local_variables.rb +138 -0
- data/lib/rubocop/callbacksystems/execution/discarded_expression.rb +45 -0
- data/lib/rubocop/callbacksystems/execution/eager_evaluation_path.rb +73 -0
- data/lib/rubocop/callbacksystems/execution/immediate.rb +71 -0
- data/lib/rubocop/callbacksystems/execution/local_variable_occurrences.rb +112 -0
- data/lib/rubocop/callbacksystems/hashes/key_transform_detection.rb +108 -0
- data/lib/rubocop/callbacksystems/hashes/keyword_options.rb +37 -0
- data/lib/rubocop/callbacksystems/helpers/bodies.rb +150 -0
- data/lib/rubocop/callbacksystems/helpers/builders.rb +103 -0
- data/lib/rubocop/callbacksystems/helpers/comments.rb +56 -0
- data/lib/rubocop/callbacksystems/helpers/corrections.rb +15 -0
- data/lib/rubocop/callbacksystems/helpers/methods.rb +46 -0
- data/lib/rubocop/callbacksystems/helpers/names.rb +65 -0
- data/lib/rubocop/callbacksystems/helpers/node_types.rb +154 -0
- data/lib/rubocop/callbacksystems/helpers/rails.rb +52 -0
- data/lib/rubocop/callbacksystems/helpers/ranges.rb +107 -0
- data/lib/rubocop/callbacksystems/helpers/recursion.rb +29 -0
- data/lib/rubocop/callbacksystems/helpers/variables.rb +44 -0
- data/lib/rubocop/callbacksystems/helpers/visibility.rb +121 -0
- data/lib/rubocop/callbacksystems/helpers.rb +43 -0
- data/lib/rubocop/callbacksystems/methods/calls.rb +38 -0
- data/lib/rubocop/callbacksystems/methods/definitions.rb +218 -0
- data/lib/rubocop/callbacksystems/methods/delegate_macro.rb +144 -0
- data/lib/rubocop/callbacksystems/methods/delegation_target.rb +55 -0
- data/lib/rubocop/callbacksystems/methods/domain.rb +77 -0
- data/lib/rubocop/callbacksystems/methods/implicit_invocations.rb +17 -0
- data/lib/rubocop/callbacksystems/methods/macro_references.rb +238 -0
- data/lib/rubocop/callbacksystems/methods/reference_evidence.rb +74 -0
- data/lib/rubocop/callbacksystems/methods/siblings.rb +24 -0
- data/lib/rubocop/callbacksystems/methods/statement_definitions.rb +68 -0
- data/lib/rubocop/callbacksystems/offense.rb +13 -0
- data/lib/rubocop/callbacksystems/plugin.rb +27 -0
- data/lib/rubocop/callbacksystems/project_index/cache.rb +16 -0
- data/lib/rubocop/callbacksystems/project_index/confinement_cache.rb +40 -0
- data/lib/rubocop/callbacksystems/project_index/constant_references.rb +74 -0
- data/lib/rubocop/callbacksystems/project_index/corrections.rb +35 -0
- data/lib/rubocop/callbacksystems/project_index/coverage.rb +56 -0
- data/lib/rubocop/callbacksystems/project_index/diagnostics.rb +60 -0
- data/lib/rubocop/callbacksystems/project_index/freshness.rb +104 -0
- data/lib/rubocop/callbacksystems/project_index/loader.rb +9 -0
- data/lib/rubocop/callbacksystems/project_index/location.rb +29 -0
- data/lib/rubocop/callbacksystems/project_index/nested_class_confinement.rb +297 -0
- data/lib/rubocop/callbacksystems/project_index/nested_class_method_dispatch.rb +50 -0
- data/lib/rubocop/callbacksystems/project_index/reading.rb +129 -0
- data/lib/rubocop/callbacksystems/project_index/source_file.rb +59 -0
- data/lib/rubocop/callbacksystems/project_index/source_file_match.rb +57 -0
- data/lib/rubocop/callbacksystems/project_index/support.rb +46 -0
- data/lib/rubocop/callbacksystems/source/comment_blocks.rb +43 -0
- data/lib/rubocop/callbacksystems/source/comments.rb +141 -0
- data/lib/rubocop/callbacksystems/source/file_ast.rb +15 -0
- data/lib/rubocop/callbacksystems/source/files_checksum.rb +43 -0
- data/lib/rubocop/callbacksystems/source/single_line_call.rb +181 -0
- data/lib/rubocop/callbacksystems/source/statement_with_comments.rb +131 -0
- data/lib/rubocop/callbacksystems/testing/cop_helpers.rb +42 -0
- data/lib/rubocop/callbacksystems/testing/fixture_call.rb +30 -0
- data/lib/rubocop/callbacksystems/testing/fixture_names.rb +62 -0
- data/lib/rubocop/callbacksystems/testing/path_mapping.rb +104 -0
- data/lib/rubocop/cop/callbacksystems/application_controller_method_definition.rb +44 -0
- data/lib/rubocop/cop/callbacksystems/assert_with_expected_argument.rb +60 -0
- data/lib/rubocop/cop/callbacksystems/base.rb +134 -0
- data/lib/rubocop/cop/callbacksystems/body_order.rb +478 -0
- data/lib/rubocop/cop/callbacksystems/builder_method_parentheses.rb +74 -0
- data/lib/rubocop/cop/callbacksystems/collapse_multiline_expression.rb +140 -0
- data/lib/rubocop/cop/callbacksystems/comment_block_length.rb +137 -0
- data/lib/rubocop/cop/callbacksystems/complex_conditional.rb +75 -0
- data/lib/rubocop/cop/callbacksystems/controller_test_response_assertion.rb +105 -0
- data/lib/rubocop/cop/callbacksystems/controller_test_response_first_assertion.rb +123 -0
- data/lib/rubocop/cop/callbacksystems/data_clump.rb +339 -0
- data/lib/rubocop/cop/callbacksystems/declarative_method_naming.rb +350 -0
- data/lib/rubocop/cop/callbacksystems/delegate_requires_private_option.rb +81 -0
- data/lib/rubocop/cop/callbacksystems/eager_loading_scope_naming.rb +386 -0
- data/lib/rubocop/cop/callbacksystems/early_return.rb +215 -0
- data/lib/rubocop/cop/callbacksystems/empty_line_before_method.rb +168 -0
- data/lib/rubocop/cop/callbacksystems/empty_line_between_sections.rb +169 -0
- data/lib/rubocop/cop/callbacksystems/empty_private_section.rb +107 -0
- data/lib/rubocop/cop/callbacksystems/expanded_empty_class_or_module.rb +71 -0
- data/lib/rubocop/cop/callbacksystems/inline_memoized_computation.rb +110 -0
- data/lib/rubocop/cop/callbacksystems/job_enqueue_methods.rb +93 -0
- data/lib/rubocop/cop/callbacksystems/max_safe_navigation_depth.rb +118 -0
- data/lib/rubocop/cop/callbacksystems/method_invocation_order.rb +261 -0
- data/lib/rubocop/cop/callbacksystems/nested_class_order.rb +255 -0
- data/lib/rubocop/cop/callbacksystems/no_abbreviations.rb +197 -0
- data/lib/rubocop/cop/callbacksystems/no_anemic_delegation.rb +101 -0
- data/lib/rubocop/cop/callbacksystems/no_anemic_record.rb +309 -0
- data/lib/rubocop/cop/callbacksystems/no_assert_select.rb +31 -0
- data/lib/rubocop/cop/callbacksystems/no_bang_method_without_counterpart.rb +451 -0
- data/lib/rubocop/cop/callbacksystems/no_call_method.rb +73 -0
- data/lib/rubocop/cop/callbacksystems/no_comments_in_test_class_body.rb +159 -0
- data/lib/rubocop/cop/callbacksystems/no_consecutive_appends.rb +261 -0
- data/lib/rubocop/cop/callbacksystems/no_custom_route_actions.rb +124 -0
- data/lib/rubocop/cop/callbacksystems/no_direct_eager_loading.rb +90 -0
- data/lib/rubocop/cop/callbacksystems/no_each_with_early_return.rb +58 -0
- data/lib/rubocop/cop/callbacksystems/no_explicit_public_modifier.rb +81 -0
- data/lib/rubocop/cop/callbacksystems/no_hash_with_default_block.rb +68 -0
- data/lib/rubocop/cop/callbacksystems/no_local_variable_return.rb +173 -0
- data/lib/rubocop/cop/callbacksystems/no_manual_accumulation.rb +188 -0
- data/lib/rubocop/cop/callbacksystems/no_mixed_memoization.rb +145 -0
- data/lib/rubocop/cop/callbacksystems/no_nested_modules_in_classes.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/no_nil_at_end_of_method.rb +74 -0
- data/lib/rubocop/cop/callbacksystems/no_redundant_validation_name.rb +37 -0
- data/lib/rubocop/cop/callbacksystems/no_redundant_wrapper_method.rb +161 -0
- data/lib/rubocop/cop/callbacksystems/no_section_divider_comments.rb +105 -0
- data/lib/rubocop/cop/callbacksystems/no_send_in_tests.rb +75 -0
- data/lib/rubocop/cop/callbacksystems/no_service_objects.rb +64 -0
- data/lib/rubocop/cop/callbacksystems/no_single_letter_names.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/no_singleton_class_in_modules.rb +43 -0
- data/lib/rubocop/cop/callbacksystems/no_tap_collection_building.rb +173 -0
- data/lib/rubocop/cop/callbacksystems/no_turbo_stream_in_controllers.rb +52 -0
- data/lib/rubocop/cop/callbacksystems/no_typographic_clutter.rb +159 -0
- data/lib/rubocop/cop/callbacksystems/no_writes_in_get_actions.rb +149 -0
- data/lib/rubocop/cop/callbacksystems/ordered_mixin_arguments.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/plural_controller_names.rb +103 -0
- data/lib/rubocop/cop/callbacksystems/prefer_attr_reader_over_instance_variable.rb +86 -0
- data/lib/rubocop/cop/callbacksystems/prefer_backslash_continuation.rb +478 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_for_state.rb +532 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_new_for_empty_nested_class.rb +247 -0
- data/lib/rubocop/cop/callbacksystems/prefer_class_over_builder_block.rb +124 -0
- data/lib/rubocop/cop/callbacksystems/prefer_default_argument.rb +225 -0
- data/lib/rubocop/cop/callbacksystems/prefer_delegate.rb +242 -0
- data/lib/rubocop/cop/callbacksystems/prefer_delegate_over_instance_variable_assignment.rb +175 -0
- data/lib/rubocop/cop/callbacksystems/prefer_excluding.rb +204 -0
- data/lib/rubocop/cop/callbacksystems/prefer_extract_associated.rb +54 -0
- data/lib/rubocop/cop/callbacksystems/prefer_it_block_parameter.rb +156 -0
- data/lib/rubocop/cop/callbacksystems/prefer_many.rb +57 -0
- data/lib/rubocop/cop/callbacksystems/prefer_method_over_lambda.rb +211 -0
- data/lib/rubocop/cop/callbacksystems/prefer_method_over_variable.rb +172 -0
- data/lib/rubocop/cop/callbacksystems/prefer_module_for_static_class.rb +286 -0
- data/lib/rubocop/cop/callbacksystems/prefer_ordinal_array_access.rb +136 -0
- data/lib/rubocop/cop/callbacksystems/prefer_positive_wrap.rb +265 -0
- data/lib/rubocop/cop/callbacksystems/prefer_squish.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/prefer_stringify_keys.rb +24 -0
- data/lib/rubocop/cop/callbacksystems/prefer_symbolize_keys.rb +24 -0
- data/lib/rubocop/cop/callbacksystems/prefer_turbo_page_refresh_broadcasts.rb +40 -0
- data/lib/rubocop/cop/callbacksystems/private_constants_in_private_section.rb +160 -0
- data/lib/rubocop/cop/callbacksystems/private_delegate_placement.rb +342 -0
- data/lib/rubocop/cop/callbacksystems/private_methods_in_private_section.rb +70 -0
- data/lib/rubocop/cop/callbacksystems/private_nested_class_method_visibility.rb +269 -0
- data/lib/rubocop/cop/callbacksystems/private_nested_classes.rb +94 -0
- data/lib/rubocop/cop/callbacksystems/public_methods_must_have_tests.rb +190 -0
- data/lib/rubocop/cop/callbacksystems/redundant_concern_extend.rb +157 -0
- data/lib/rubocop/cop/callbacksystems/redundant_constant_freeze.rb +57 -0
- data/lib/rubocop/cop/callbacksystems/redundant_constant_namespace.rb +134 -0
- data/lib/rubocop/cop/callbacksystems/reflow_comments.rb +133 -0
- data/lib/rubocop/cop/callbacksystems/repeated_fixture_in_tests.rb +106 -0
- data/lib/rubocop/cop/callbacksystems/restricted_controller_actions.rb +85 -0
- data/lib/rubocop/cop/callbacksystems/routes_module_scope.rb +336 -0
- data/lib/rubocop/cop/callbacksystems/single_line_setup_block.rb +107 -0
- data/lib/rubocop/cop/callbacksystems/single_singleton_class.rb +49 -0
- data/lib/rubocop/cop/callbacksystems/single_top_level_class_or_module.rb +160 -0
- data/lib/rubocop/cop/callbacksystems/single_use_fixture_variable.rb +211 -0
- data/lib/rubocop/cop/callbacksystems/single_use_setup_variable.rb +684 -0
- data/lib/rubocop/cop/callbacksystems/singular_scope_names.rb +47 -0
- data/lib/rubocop/cop/callbacksystems/space_inside_percent_literal.rb +73 -0
- data/lib/rubocop/cop/callbacksystems/system_test_browser_driver.rb +30 -0
- data/lib/rubocop/cop/callbacksystems/test_method_definition.rb +86 -0
- data/lib/rubocop/cop/callbacksystems/test_method_order.rb +319 -0
- data/lib/rubocop/cop/callbacksystems/too_many_instance_variables.rb +78 -0
- data/lib/rubocop/cop/callbacksystems/too_many_local_variables.rb +77 -0
- data/lib/rubocop/cop/callbacksystems/too_many_validation_guards.rb +84 -0
- data/lib/rubocop/cop/callbacksystems/unnecessary_local_variable.rb +176 -0
- data/lib/rubocop/cop/callbacksystems/unused_private_method_in_nested_class.rb +203 -0
- data/lib/rubocop-callbacksystems.rb +21 -0
- data/rubocop.yml +685 -0
- metadata +337 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# Prohibits bang methods (ending with `!`) unless a non-bang counterpart exists.
|
|
2
|
+
# The `!` suffix promises a version without it that is safer, so a lone bang is a
|
|
3
|
+
# name that raises a question it never answers.
|
|
4
|
+
#
|
|
5
|
+
# Instance and singleton methods answer for themselves. A `def self.reload` is no
|
|
6
|
+
# counterpart to the `reload!` of an instance, so each side is read against the
|
|
7
|
+
# methods that land where it does: the body's own defs, what a `class << self` or a
|
|
8
|
+
# `class_methods do` adds to the class, and what an `included do` adds to
|
|
9
|
+
# instances.
|
|
10
|
+
#
|
|
11
|
+
# Counterparts defined in reopenings, ancestors and mixins count wherever the
|
|
12
|
+
# project index can resolve the whole ancestry. When any link is unknown, the
|
|
13
|
+
# cop abstains rather than calling a method orphaned on incomplete evidence.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad - no non-bang counterpart
|
|
17
|
+
# def process!
|
|
18
|
+
# # ...
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # good - has non-bang counterpart
|
|
22
|
+
# def save
|
|
23
|
+
# # safe version
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# def save!
|
|
27
|
+
# save || raise(RecordNotSaved)
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # good - no bang needed
|
|
31
|
+
# def process
|
|
32
|
+
# # ...
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
class RuboCop::Cop::Callbacksystems::NoBangMethodWithoutCounterpart < RuboCop::Cop::Callbacksystems::Base
|
|
36
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
37
|
+
|
|
38
|
+
def on_new_investigation
|
|
39
|
+
@project_index_available = project_index_reliable? && runtime_definitions_absent?
|
|
40
|
+
@local_definitions = LocalDefinitions.new(local_definition_entries)
|
|
41
|
+
@counterparts_by_namespace = {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def on_class(node)
|
|
45
|
+
namespace_for(node)&.then do |namespace|
|
|
46
|
+
report_each OrphanBangMethods.new \
|
|
47
|
+
local_definitions.definitions_for(node), counterparts: counterparts_for(namespace)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
alias on_module on_class
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
attr_reader :local_definitions, :counterparts_by_namespace, :project_index_available
|
|
55
|
+
|
|
56
|
+
def runtime_definitions_absent?
|
|
57
|
+
RuntimeDefinitions.for(project_index).absent?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def local_definition_entries
|
|
61
|
+
nodes_in(processed_source.ast, :class, :module).filter_map do |container|
|
|
62
|
+
namespace_for(container)&.then { [ container, it.name ] }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def namespace_for(container)
|
|
67
|
+
return unless project_index_available && container.identifier.const_type?
|
|
68
|
+
|
|
69
|
+
resolve_constant_in_index(container.identifier).then { it if it.is_a?(Rubydex::Namespace) }
|
|
70
|
+
rescue
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def counterparts_for(namespace)
|
|
75
|
+
counterparts_by_namespace[namespace.name] ||= Counterparts.new \
|
|
76
|
+
namespace, local_definitions:, project_index:, processed_source:
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A project-wide runtime definition leaves the set of possible counterparts open.
|
|
80
|
+
class RuntimeDefinitions
|
|
81
|
+
extend RuboCop::Callbacksystems::ProjectIndex::Cache
|
|
82
|
+
|
|
83
|
+
METHOD_NAMES = %w[ define_method define_singleton_method ].to_set
|
|
84
|
+
|
|
85
|
+
def initialize(project_index)
|
|
86
|
+
@absent = project_index.method_references.none? do |reference|
|
|
87
|
+
METHOD_NAMES.include?(reference.name.delete_suffix("()"))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def absent?
|
|
92
|
+
absent
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
attr_reader :absent
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Method definitions classified by the side of the container where Ruby installs them.
|
|
100
|
+
class LocalDefinitions
|
|
101
|
+
def initialize(entries)
|
|
102
|
+
definitions = entries.map { |container, name| [ container, name, ContainerDefinitions.new(container) ] }
|
|
103
|
+
@definitions_by_container = definitions.to_h { |container, _, value| [ container, value ] }.compare_by_identity
|
|
104
|
+
@containers_by_namespace = definitions.group_by { |_, name, _| name }
|
|
105
|
+
.transform_values { it.map(&:last) }
|
|
106
|
+
@possibilities = {}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def definitions_for(container)
|
|
110
|
+
definitions_by_container.fetch(container)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def possibly_defines?(namespace_name, name, domain:)
|
|
114
|
+
query = Query.new(namespace_name, name, domain)
|
|
115
|
+
|
|
116
|
+
possibilities.fetch(query) do
|
|
117
|
+
possibilities[query] = containers_by_namespace.fetch(namespace_name, []).any? do |container|
|
|
118
|
+
container.possibly_defines?(name, domain:)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
Query = Data.define(:namespace_name, :name, :domain)
|
|
125
|
+
|
|
126
|
+
attr_reader :definitions_by_container, :containers_by_namespace, :possibilities
|
|
127
|
+
|
|
128
|
+
# Definitions that belong to one lexical class or module body.
|
|
129
|
+
class ContainerDefinitions
|
|
130
|
+
include RuboCop::Callbacksystems::Helpers
|
|
131
|
+
|
|
132
|
+
INSTANCE_MACROS = %i[ included prepended ]
|
|
133
|
+
SINGLETON_MACRO = :class_methods
|
|
134
|
+
CONCERN_MACROS = (INSTANCE_MACROS + [ SINGLETON_MACRO ]).to_set
|
|
135
|
+
ALWAYS_UNCERTAIN_CALLS = %i[ delegate_missing_to has_secure_password has_secure_token ].to_set
|
|
136
|
+
NON_DECLARATIVE_CALLS = %i[ extend include prepend private protected public using ].to_set
|
|
137
|
+
UNKNOWN_NAME = Object.new
|
|
138
|
+
Definition = Data.define(:node, :domain)
|
|
139
|
+
|
|
140
|
+
def initialize(container)
|
|
141
|
+
@container = container
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def possibly_defines?(name, domain:)
|
|
145
|
+
by_domain.fetch(domain, []).any? { it.method?(name) } ||
|
|
146
|
+
runtime_names.include?(name.to_s) || runtime_names.include?(UNKNOWN_NAME)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def by_domain
|
|
150
|
+
@by_domain ||= definitions.group_by(&:domain).transform_values { it.map(&:node) }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
attr_reader :container
|
|
155
|
+
|
|
156
|
+
def definitions
|
|
157
|
+
@definitions ||= method_definitions + macro_definitions
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def method_definitions
|
|
161
|
+
direct_method_nodes_in(container.body).filter_map do |definition|
|
|
162
|
+
if definition.def_type? || definition.receiver.self_type?
|
|
163
|
+
Definition.new(definition, RuboCop::Callbacksystems::Methods::Domain.new(definition).identity)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def macro_definitions
|
|
169
|
+
INSTANCE_MACROS.flat_map { definitions_in_macro(it, domain: []) } +
|
|
170
|
+
definitions_in_macro(SINGLETON_MACRO, domain: [ :self ])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def definitions_in_macro(macro, domain:)
|
|
174
|
+
statements_in(container.body).flat_map do |statement|
|
|
175
|
+
if macro_block?(statement, macro:)
|
|
176
|
+
direct_definitions_in(statement.body, :def).map { Definition.new(it, domain) }
|
|
177
|
+
else
|
|
178
|
+
[]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def macro_block?(statement, macro:)
|
|
184
|
+
if any_block_type?(statement)
|
|
185
|
+
call_on_self?(call_of(statement)) && statement.method?(macro)
|
|
186
|
+
else
|
|
187
|
+
false
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def runtime_names
|
|
192
|
+
@runtime_names ||= runtime_names_from_calls.to_set |
|
|
193
|
+
(ambiguous_definition? ? Set[UNKNOWN_NAME] : Set.new)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def runtime_names_from_calls
|
|
197
|
+
scoped_calls.flat_map { runtime_names_from(it) }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def scoped_calls
|
|
201
|
+
@scoped_calls ||= nodes_in(container.body, :send, :csend).select { belongs_to_container?(it) }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def belongs_to_container?(node)
|
|
205
|
+
enclosing_class_or_module_of(node).equal?(container) && enclosing_method_of(node).nil?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def runtime_names_from(call)
|
|
209
|
+
indexed_names = RuboCop::Callbacksystems::Methods::StatementDefinitions.new(call).names
|
|
210
|
+
|
|
211
|
+
if always_uncertain?(call) || indexed_names.nil?
|
|
212
|
+
[ UNKNOWN_NAME ]
|
|
213
|
+
elsif indexed_names.any?
|
|
214
|
+
indexed_names.map(&:to_s)
|
|
215
|
+
elsif declarative_call?(call)
|
|
216
|
+
names_in_arguments(call.arguments)
|
|
217
|
+
else
|
|
218
|
+
[]
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def always_uncertain?(call)
|
|
223
|
+
ALWAYS_UNCERTAIN_CALLS.include?(call.method_name) ||
|
|
224
|
+
(call.method?(:module_function) && call.arguments.empty?)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def declarative_call?(call)
|
|
228
|
+
[ call.receiver.nil? || call.receiver.self_type?, NON_DECLARATIVE_CALLS.exclude?(call.method_name) ].all?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def names_in_arguments(arguments)
|
|
232
|
+
arguments.map { it.type?(:sym, :str) ? it.value.to_s : UNKNOWN_NAME }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def ambiguous_definition?
|
|
236
|
+
scoped_definitions.any? do |definition|
|
|
237
|
+
owner = RuboCop::Callbacksystems::Methods::Domain.new(definition).container
|
|
238
|
+
|
|
239
|
+
!owner.equal?(container) && !direct_concern_definition?(definition, owner:)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def scoped_definitions
|
|
244
|
+
@scoped_definitions ||= nodes_in(container.body, :def, :defs).select { belongs_to_container?(it) }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def direct_concern_definition?(definition, owner:)
|
|
248
|
+
any_block_type?(owner) && call_on_self?(call_of(owner)) && CONCERN_MACROS.include?(owner.method_name) &&
|
|
249
|
+
direct_definitions_in(owner.body, :def).include?(definition)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Names that might answer a bang method, with absence trusted only across complete ancestry.
|
|
255
|
+
class Counterparts
|
|
256
|
+
include RuboCop::Cop::ProjectIndexHelp
|
|
257
|
+
|
|
258
|
+
SINGLETON_DOMAIN = [ :self ]
|
|
259
|
+
DYNAMIC_DISPATCH_METHODS = %i[ method_missing respond_to_missing? ]
|
|
260
|
+
Query = Data.define(:name, :domain)
|
|
261
|
+
|
|
262
|
+
attr_reader :namespace, :local_definitions, :project_index, :processed_source
|
|
263
|
+
|
|
264
|
+
def initialize(namespace, local_definitions:, project_index:, processed_source:)
|
|
265
|
+
@namespace = namespace
|
|
266
|
+
@local_definitions = local_definitions
|
|
267
|
+
@project_index = project_index
|
|
268
|
+
@processed_source = processed_source
|
|
269
|
+
@absences = {}
|
|
270
|
+
@complete_domains = {}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def absent?(name, domain:)
|
|
274
|
+
query = Query.new(name, domain)
|
|
275
|
+
|
|
276
|
+
absences.fetch(query) { absences[it] = CounterpartSearch.new(it, context: self).absent? }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def complete?(indexed_domain, domain:)
|
|
280
|
+
complete_domains.fetch(domain) do
|
|
281
|
+
ancestors = (namespace.ancestors.to_a + indexed_domain.ancestors.to_a).uniq(&:name)
|
|
282
|
+
|
|
283
|
+
complete_domains[domain] = resolved_ancestry?(indexed_domain, ignore_extend: domain.empty?) &&
|
|
284
|
+
definitions_in_other_files(ancestors.flat_map { it.definitions.to_a }).empty?
|
|
285
|
+
end
|
|
286
|
+
rescue
|
|
287
|
+
false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
private
|
|
291
|
+
attr_reader :absences, :complete_domains
|
|
292
|
+
|
|
293
|
+
def resolved_ancestry?(indexed_domain, ignore_extend:)
|
|
294
|
+
fully_resolved_index_ancestry?(namespace, ignore_extend:) &&
|
|
295
|
+
fully_resolved_index_ancestry?(indexed_domain)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
class CounterpartSearch
|
|
299
|
+
def initialize(query, context:)
|
|
300
|
+
@query = query
|
|
301
|
+
@context = context
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def absent?
|
|
305
|
+
if query.domain.all? { it == :self } && indexed_domain
|
|
306
|
+
!counterpart_possible? && !dynamic_dispatch_possible? &&
|
|
307
|
+
context.complete?(indexed_domain, domain: query.domain)
|
|
308
|
+
else
|
|
309
|
+
false
|
|
310
|
+
end
|
|
311
|
+
rescue
|
|
312
|
+
false
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
attr_reader :query, :context
|
|
317
|
+
delegate :namespace, to: :context, private: true
|
|
318
|
+
|
|
319
|
+
def indexed_domain
|
|
320
|
+
@indexed_domain ||= query.domain.inject(namespace) { |owner, _| owner&.singleton_class }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def counterpart_possible?
|
|
324
|
+
indexed_name_possible?(query.name, namespaces: indexed_domain.ancestors) ||
|
|
325
|
+
concern_name_possible?(query.name)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def indexed_name_possible?(name, namespaces:, local_domain: nil, project_only: false)
|
|
329
|
+
namespaces.any? do |indexed_namespace|
|
|
330
|
+
NamespaceMember.new(indexed_namespace, name:, context:, local_domain:, project_only:).possible?
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def concern_name_possible?(name, project_only: false)
|
|
335
|
+
query.domain == SINGLETON_DOMAIN && indexed_name_possible?(
|
|
336
|
+
name, namespaces: namespace.ancestors, local_domain: SINGLETON_DOMAIN, project_only:
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def dynamic_dispatch_possible?
|
|
341
|
+
DYNAMIC_DISPATCH_METHODS.any? do |method_name|
|
|
342
|
+
indexed_name_possible?(method_name, namespaces: indexed_domain.ancestors, project_only: true) ||
|
|
343
|
+
concern_name_possible?(method_name, project_only: true)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
class NamespaceMember
|
|
349
|
+
def initialize(indexed_namespace, name:, context:, local_domain:, project_only:)
|
|
350
|
+
@indexed_namespace = indexed_namespace
|
|
351
|
+
@name = name
|
|
352
|
+
@context = context
|
|
353
|
+
@local_domain = local_domain
|
|
354
|
+
@project_only = project_only
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def possible?
|
|
358
|
+
locally_possible? || indexed_declaration_possible?
|
|
359
|
+
rescue
|
|
360
|
+
true
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
attr_reader :indexed_namespace, :name, :context, :local_domain, :project_only
|
|
365
|
+
|
|
366
|
+
def locally_possible?
|
|
367
|
+
namespace = indexed_namespace
|
|
368
|
+
domain = []
|
|
369
|
+
|
|
370
|
+
while namespace.is_a?(Rubydex::SingletonClass)
|
|
371
|
+
domain << :self
|
|
372
|
+
namespace = namespace.owner
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
context.local_definitions.possibly_defines?(namespace.name, name, domain: local_domain || domain)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def indexed_declaration_possible?
|
|
379
|
+
if declaration.is_a?(Rubydex::Method)
|
|
380
|
+
project_scope_allows? && readable? && usable?
|
|
381
|
+
else
|
|
382
|
+
false
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def declaration
|
|
387
|
+
@declaration ||= indexed_namespace.member("#{name}()")
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def project_scope_allows?
|
|
391
|
+
!project_only || definitions.any? do |definition|
|
|
392
|
+
definition.location.uri.start_with?(RuboCop::Cop::ProjectIndexHelp::FILE_URI_PREFIX)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def definitions
|
|
397
|
+
@definitions ||= declaration.definitions.to_a
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Rubydex 0.4 indexes an `attr_writer :name` member as `name()`, although Ruby only defines `name=`.
|
|
401
|
+
def readable?
|
|
402
|
+
definitions.empty? || definitions.any? { it.class.name != "Rubydex::AttrWriterDefinition" }
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def usable?
|
|
406
|
+
definitions.empty? || indexed_namespace.is_a?(Rubydex::SingletonClass) || definitions.any? do |definition|
|
|
407
|
+
!definition.location.uri.start_with?(RuboCop::Cop::ProjectIndexHelp::FILE_URI_PREFIX) ||
|
|
408
|
+
!definition.is_a?(Rubydex::MethodDefinition)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# The bang methods of one body with no plain counterpart within reach of where they land.
|
|
415
|
+
class OrphanBangMethods
|
|
416
|
+
include RuboCop::Callbacksystems::Helpers
|
|
417
|
+
|
|
418
|
+
MESSAGE = "Method `%<method>s` has no non-bang counterpart. Only use `!` when a version without `!` exists."
|
|
419
|
+
|
|
420
|
+
def initialize(container_definitions, counterparts:)
|
|
421
|
+
@container_definitions = container_definitions
|
|
422
|
+
@counterparts = counterparts
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def each_offense
|
|
426
|
+
container_definitions.by_domain.flat_map { |domain, definitions| orphans_among(definitions, domain:) }
|
|
427
|
+
.each { yield offense_for(it) }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
private
|
|
431
|
+
attr_reader :container_definitions, :counterparts
|
|
432
|
+
|
|
433
|
+
def orphans_among(definitions, domain:)
|
|
434
|
+
definitions.select do |definition|
|
|
435
|
+
conventional_bang_method?(definition) && counterparts.absent?(plain_name_of(definition), domain:)
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def conventional_bang_method?(definition)
|
|
440
|
+
definition.bang_method? && !definition.method?(:!)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def plain_name_of(definition)
|
|
444
|
+
definition.method_name.to_s.chomp("!").to_sym
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def offense_for(definition)
|
|
448
|
+
RuboCop::Callbacksystems::Offense.new(definition, format(MESSAGE, method: definition.method_name))
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Prohibits defining methods named `call`, except for Rack middleware endpoints.
|
|
2
|
+
# This pattern is associated with service objects which we avoid.
|
|
3
|
+
#
|
|
4
|
+
# @example
|
|
5
|
+
# # bad
|
|
6
|
+
# class OrderProcessor
|
|
7
|
+
# def call
|
|
8
|
+
# # ...
|
|
9
|
+
# end
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# # bad
|
|
13
|
+
# class OrderProcessor
|
|
14
|
+
# def self.call
|
|
15
|
+
# # ...
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # good - use descriptive method names
|
|
20
|
+
# class Order
|
|
21
|
+
# def process
|
|
22
|
+
# # ...
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
class RuboCop::Cop::Callbacksystems::NoCallMethod < RuboCop::Cop::Callbacksystems::Base
|
|
27
|
+
MESSAGE = "Avoid defining `call` methods. Use descriptive method names instead."
|
|
28
|
+
|
|
29
|
+
def on_def(node)
|
|
30
|
+
report MethodName.new(node)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias on_defs on_def
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
class MethodName
|
|
37
|
+
include RuboCop::Callbacksystems::Helpers
|
|
38
|
+
|
|
39
|
+
def initialize(node)
|
|
40
|
+
@node = node
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def offense
|
|
44
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE) if node.method?(:call) && !rack_protocol?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
attr_reader :node
|
|
49
|
+
|
|
50
|
+
def rack_protocol?
|
|
51
|
+
direct_method_definition?(node) && instance_method? && parameter_names_of(node) == [ "env" ] &&
|
|
52
|
+
rack_middleware_class?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def instance_method?
|
|
56
|
+
RuboCop::Callbacksystems::Methods::Domain.new(node).scope == :instance
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def rack_middleware_class?
|
|
60
|
+
class_node.class_type? && (class_name_of(class_node).end_with?("Middleware") || accepts_rack_application?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def class_node
|
|
64
|
+
@class_node ||= enclosing_class_or_module_of(node)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def accepts_rack_application?
|
|
68
|
+
direct_method_nodes_in(class_node.body).any? do |method_node|
|
|
69
|
+
method_node.method?(:initialize) && parameter_names_of(method_node).include?("app")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Detects comments in the direct body of test classes. A test explains itself
|
|
2
|
+
# through its description, so a comment at the class level is a sentence the
|
|
3
|
+
# test names should have carried, and a reader skims past it while looking for
|
|
4
|
+
# the test that matters. Comments inside a test block stay allowed for the
|
|
5
|
+
# non-obvious why.
|
|
6
|
+
#
|
|
7
|
+
# There is no fix. Which sentence a comment was carrying is only visible to
|
|
8
|
+
# whoever wrote it, so deleting one is the author's call and never ours.
|
|
9
|
+
#
|
|
10
|
+
# This cop should only be enabled for test files.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad - comment at class body level
|
|
14
|
+
# class UserTest < ActiveSupport::TestCase
|
|
15
|
+
# # Setup user fixtures
|
|
16
|
+
# setup do
|
|
17
|
+
# @user = users(:john)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# test "validates name" do
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - no comments at class body level
|
|
26
|
+
# class UserTest < ActiveSupport::TestCase
|
|
27
|
+
# setup do
|
|
28
|
+
# @user = users(:john)
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# test "validates name" do
|
|
32
|
+
# # This comment inside test is ok if highly relevant
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
class RuboCop::Cop::Callbacksystems::NoCommentsInTestClassBody < RuboCop::Cop::Callbacksystems::Base
|
|
37
|
+
def on_new_investigation
|
|
38
|
+
report_each ClassBodyComments.new(comments_in_test_file, processed_source.ast)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def comments_in_test_file
|
|
43
|
+
if test_file?(processed_source.file_path)
|
|
44
|
+
prose_comments_in(processed_source)
|
|
45
|
+
else
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class ClassBodyComments
|
|
51
|
+
include RuboCop::Callbacksystems::Helpers
|
|
52
|
+
|
|
53
|
+
MESSAGE = "Avoid comments in test class body. Tests should be self-documenting through descriptive names."
|
|
54
|
+
|
|
55
|
+
def initialize(comments, ast)
|
|
56
|
+
@comments = ScopeComments.new(comments, ast)
|
|
57
|
+
@content_scopes = ContentScopes.new(ast)
|
|
58
|
+
@ast = ast
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def each_offense
|
|
62
|
+
test_classes.each do |test_class|
|
|
63
|
+
comments_in_body_of(test_class).each { yield RuboCop::Callbacksystems::Offense.new(it, MESSAGE) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
attr_reader :comments, :content_scopes, :ast
|
|
69
|
+
|
|
70
|
+
def test_classes
|
|
71
|
+
nodes_in(ast, :class).select { test_class?(it) && class_has_content?(it) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_class?(class_node)
|
|
75
|
+
class_name_of(class_node).end_with?("Test") || rails_test_base_class?(class_node.parent_class)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def class_has_content?(class_node)
|
|
79
|
+
content_scopes.any_within?(class_node)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def comments_in_body_of(class_node)
|
|
83
|
+
comments.directly_within(class_node)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Comments assigned to the innermost lexical scope containing them with one sweep over scopes and source positions.
|
|
88
|
+
class ScopeComments
|
|
89
|
+
include RuboCop::Callbacksystems::Helpers
|
|
90
|
+
|
|
91
|
+
SCOPE_TYPES = [ :class, :module, :sclass, *BLOCK_NODE_TYPES, :def, :defs ]
|
|
92
|
+
|
|
93
|
+
def initialize(comments, ast)
|
|
94
|
+
@scopes = nodes_in(ast, *SCOPE_TYPES).sort_by { [ it.source_range.begin_pos, -it.source_range.end_pos ] }
|
|
95
|
+
@scope_index = 0
|
|
96
|
+
@active_scopes = []
|
|
97
|
+
@by_scope = {}.compare_by_identity
|
|
98
|
+
comments.sort_by { it.source_range.begin_pos }.each { assign(it) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def directly_within(scope)
|
|
102
|
+
by_scope.fetch(scope) { [] }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
attr_reader :scopes, :active_scopes, :by_scope, :scope_index
|
|
107
|
+
|
|
108
|
+
def assign(comment)
|
|
109
|
+
activate_scopes_through(comment.source_range.begin_pos)
|
|
110
|
+
retire_scopes_before(comment.source_range.end_pos)
|
|
111
|
+
(by_scope[active_scopes.last] ||= []) << comment if contained_by_active_scope?(comment)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def activate_scopes_through(position)
|
|
115
|
+
while next_scope && next_scope.source_range.begin_pos <= position
|
|
116
|
+
retire_scopes_before(next_scope.source_range.begin_pos)
|
|
117
|
+
active_scopes << next_scope
|
|
118
|
+
@scope_index += 1
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def next_scope
|
|
123
|
+
scopes[scope_index]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def retire_scopes_before(position)
|
|
127
|
+
active_scopes.pop while active_scopes.last&.then { it.source_range.end_pos < position }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def contained_by_active_scope?(comment)
|
|
131
|
+
active_scopes.last&.source_range&.contains?(comment.source_range)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Definitions and blocks indexed by their start, so a class can ask whether its subtree carries test content.
|
|
136
|
+
class ContentScopes
|
|
137
|
+
include RuboCop::Callbacksystems::Helpers
|
|
138
|
+
|
|
139
|
+
def initialize(ast)
|
|
140
|
+
@scopes = nodes_in(ast, :any_def, :any_block).sort_by { it.source_range.begin_pos }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def any_within?(node)
|
|
144
|
+
scope = scopes[first_index_at(node.source_range.begin_pos)]
|
|
145
|
+
scope && node.source_range.contains?(scope.source_range)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
attr_reader :scopes
|
|
150
|
+
|
|
151
|
+
def first_index_at(position)
|
|
152
|
+
begin_positions.bsearch_index { it >= position } || scopes.size
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def begin_positions
|
|
156
|
+
@begin_positions ||= scopes.map { it.source_range.begin_pos }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|