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,269 @@
|
|
|
1
|
+
# Detects public methods in visually private nested classes that are never
|
|
2
|
+
# called from outside. When the project index confirms that no other file can
|
|
3
|
+
# reach or subclass the class, a method none of its local callers names is an
|
|
4
|
+
# implementation detail and belongs in the private section.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - unused_method is public but never called from outside
|
|
8
|
+
# class Foo
|
|
9
|
+
# def process
|
|
10
|
+
# Bar.new(node).used_method
|
|
11
|
+
# nil
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# private
|
|
15
|
+
# class Bar
|
|
16
|
+
# def used_method; end
|
|
17
|
+
# def unused_method; end # should be private
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # good - internal methods are private
|
|
22
|
+
# class Foo
|
|
23
|
+
# def process
|
|
24
|
+
# Bar.new(node).used_method
|
|
25
|
+
# nil
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# private
|
|
29
|
+
# class Bar
|
|
30
|
+
# def used_method; end
|
|
31
|
+
#
|
|
32
|
+
# private
|
|
33
|
+
# def internal_method; end
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
class RuboCop::Cop::Callbacksystems::PrivateNestedClassMethodVisibility < RuboCop::Cop::Callbacksystems::Base
|
|
38
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
39
|
+
|
|
40
|
+
def on_new_investigation
|
|
41
|
+
@file_references = FileReferences.new(processed_source.ast) if project_index
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def on_class(node)
|
|
45
|
+
if project_index_reliable?
|
|
46
|
+
private_nested_classes_in(node).select { confined_without_descendants?(it) }.each do |nested_class|
|
|
47
|
+
report_each UnusedPublicMethods.new(nested_class, references: file_references.for(nested_class))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias on_module on_class
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
attr_reader :file_references
|
|
56
|
+
|
|
57
|
+
def confined_without_descendants?(nested_class)
|
|
58
|
+
confined_declaration_of(nested_class)&.then do |declaration|
|
|
59
|
+
declaration.descendants.none? { it.name != declaration.name }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# References in the whole file compared with those inside one nested class, indexed once for every reader.
|
|
64
|
+
class FileReferences
|
|
65
|
+
def initialize(root)
|
|
66
|
+
@root = root
|
|
67
|
+
@by_class = {}.compare_by_identity
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def for(nested_class)
|
|
71
|
+
by_class[nested_class] ||= ClassReferences.new(all, References.new(nested_class))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
attr_reader :root, :by_class
|
|
76
|
+
|
|
77
|
+
def all
|
|
78
|
+
@all ||= References.new(root)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class ClassReferences
|
|
82
|
+
delegate :public_visibility_uncertain?, to: :all
|
|
83
|
+
|
|
84
|
+
def initialize(all, inside)
|
|
85
|
+
@all = all
|
|
86
|
+
@inside = inside
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def mentions_outside?(method_name)
|
|
90
|
+
all.call_count(method_name) > inside.call_count(method_name) ||
|
|
91
|
+
all.block_pass_count(method_name) > inside.block_pass_count(method_name) ||
|
|
92
|
+
all.delegation_count(method_name) > inside.delegation_count(method_name) ||
|
|
93
|
+
all.dynamic_delegation_count > inside.dynamic_delegation_count
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def public_visibility_required?(method_name)
|
|
97
|
+
all.public_visibility_count(method_name).positive?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
attr_reader :all, :inside
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class References
|
|
106
|
+
include RuboCop::Callbacksystems::Helpers
|
|
107
|
+
|
|
108
|
+
PUBLIC_LOOKUPS = %i[ protected public public_method public_send respond_to? try try! ]
|
|
109
|
+
VISIBILITY_ENUMERATIONS = %i[
|
|
110
|
+
instance_methods methods private_instance_methods private_methods protected_instance_methods
|
|
111
|
+
protected_methods public_instance_methods public_methods singleton_methods
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def initialize(root)
|
|
115
|
+
@evidence = RuboCop::Callbacksystems::Methods::ReferenceEvidence.for(root)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def call_count(name)
|
|
119
|
+
call_counts.fetch(name, 0)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def public_visibility_count(name)
|
|
123
|
+
explicit_call_counts.fetch(name, 0) + block_pass_count(name) + delegation_count(name) +
|
|
124
|
+
public_lookup_counts.fetch(name, 0) + (evidence.literal_method_names.include?(name.to_s) ? 1 : 0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def block_pass_count(name)
|
|
128
|
+
block_pass_counts.fetch(name, 0)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def delegation_count(name)
|
|
132
|
+
delegation_counts.fetch(name, 0)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def public_visibility_uncertain?
|
|
136
|
+
dynamic_public_lookup? || dynamic_block_pass? || dynamic_delegation_count.positive? ||
|
|
137
|
+
interpolated_name? || visibility_enumerated?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def dynamic_delegation_count
|
|
141
|
+
@dynamic_delegation_count ||= evidence.calls.count { dynamic_delegation?(it) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
attr_reader :evidence
|
|
146
|
+
|
|
147
|
+
def call_counts
|
|
148
|
+
@call_counts ||= evidence.calls.map(&:method_name).tally
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def explicit_call_counts
|
|
152
|
+
@explicit_call_counts ||= evidence.calls.filter_map do |send_node|
|
|
153
|
+
send_node.method_name if send_node.receiver && !send_node.receiver.self_type?
|
|
154
|
+
end.tally
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def block_pass_counts
|
|
158
|
+
@block_pass_counts ||= evidence.block_passes.filter_map { block_pass_name_of(it) }.tally
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def block_pass_name_of(node)
|
|
162
|
+
node.children.first.value if node.children.first&.sym_type?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def delegation_counts
|
|
166
|
+
@delegation_counts ||= evidence.calls.flat_map { delegated_names_of(it) }.tally
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def delegated_names_of(send_node)
|
|
170
|
+
if delegate_macro?(send_node)
|
|
171
|
+
RuboCop::Callbacksystems::Methods::DelegateMacro.new(send_node).target_method_names || []
|
|
172
|
+
else
|
|
173
|
+
[]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def public_lookup_counts
|
|
178
|
+
@public_lookup_counts ||= public_lookups.filter_map { public_lookup_name_of(it) }.tally
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def public_lookups
|
|
182
|
+
@public_lookups ||= evidence.calls.select { PUBLIC_LOOKUPS.include?(it.method_name) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def public_lookup_name_of(send_node)
|
|
186
|
+
send_node.first_argument&.then do |argument|
|
|
187
|
+
if argument.type?(:sym, :str)
|
|
188
|
+
argument.value.to_sym if argument.value.to_s.valid_encoding?
|
|
189
|
+
elsif argument.any_def_type?
|
|
190
|
+
argument.method_name
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def dynamic_public_lookup?
|
|
196
|
+
public_lookups.any? { public_lookup_name_of(it).nil? }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def dynamic_block_pass?
|
|
200
|
+
evidence.block_passes.any? do |block_pass|
|
|
201
|
+
block_pass.children.first && !block_pass.children.first.sym_type?
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def dynamic_delegation?(send_node)
|
|
206
|
+
delegate_macro?(send_node) && RuboCop::Callbacksystems::Methods::DelegateMacro.new(send_node).target_method_names.nil?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def interpolated_name?
|
|
210
|
+
evidence.interpolated_literals.any?
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def visibility_enumerated?
|
|
214
|
+
evidence.calls.any? { VISIBILITY_ENUMERATIONS.include?(it.method_name) }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
class UnusedPublicMethods
|
|
219
|
+
include RuboCop::Callbacksystems::Helpers
|
|
220
|
+
|
|
221
|
+
MESSAGE = "Method `%<method>s` in visually private nested class `%<class>s` is never called from outside. " \
|
|
222
|
+
"Make it private."
|
|
223
|
+
|
|
224
|
+
# Ruby calls these structurally (a double splat calls `to_hash`, `case` calls `===`), so no source names them.
|
|
225
|
+
PROTOCOL_METHODS = RuboCop::Callbacksystems::Methods::ImplicitInvocations::METHOD_NAMES
|
|
226
|
+
|
|
227
|
+
def initialize(nested_class, references:)
|
|
228
|
+
@nested_class = nested_class
|
|
229
|
+
@references = references
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def each_offense
|
|
233
|
+
unused_public_methods.each { yield RuboCop::Callbacksystems::Offense.new(it, message_for(it)) }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
attr_reader :nested_class, :references
|
|
238
|
+
|
|
239
|
+
def unused_public_methods
|
|
240
|
+
if references && !references.public_visibility_uncertain?
|
|
241
|
+
uncalled_public_methods
|
|
242
|
+
else
|
|
243
|
+
[]
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def uncalled_public_methods
|
|
248
|
+
public_methods_in(nested_class).reject { mentioned_outside?(it.method_name) }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# An instance travels beyond one file's reading, so any mention of the name outside the class counts as a use.
|
|
252
|
+
def mentioned_outside?(method_name)
|
|
253
|
+
PROTOCOL_METHODS.include?(method_name) || macro_referenced_methods.include?(method_name) ||
|
|
254
|
+
references.mentions_outside?(method_name) || references.public_visibility_required?(method_name)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def macro_referenced_methods
|
|
258
|
+
@macro_referenced_methods ||= RuboCop::Callbacksystems::Methods::MacroReferences.new(nested_class.body).to_a
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def message_for(method_node)
|
|
262
|
+
format(MESSAGE, method: method_node.method_name, class: class_name)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def class_name
|
|
266
|
+
nested_class.identifier.short_name
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Requires nested classes to be declared in the private section. A nested class
|
|
2
|
+
# is an implementation detail, so the class holding one says as much by where it
|
|
3
|
+
# puts it. One that other files build has no business being nested at all: it
|
|
4
|
+
# goes in its own file, where Zeitwerk expects it.
|
|
5
|
+
#
|
|
6
|
+
# A constant assigned from `Data.define`, `Struct.new` or `Class.new` defines a
|
|
7
|
+
# class in everything but syntax, so it reads under the same rule.
|
|
8
|
+
#
|
|
9
|
+
# A definition with no body is left alone. An error class or a bare list of
|
|
10
|
+
# members declares what something is rather than how it works, and a file of its
|
|
11
|
+
# own would cost more than it says.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad - public nested class
|
|
15
|
+
# class Order
|
|
16
|
+
# class LineItem
|
|
17
|
+
# def total
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # bad - a constant that builds a class with behavior
|
|
23
|
+
# class Order
|
|
24
|
+
# LineItem = Data.define(:sku, :quantity) do
|
|
25
|
+
# def total
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# private
|
|
30
|
+
# def process
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# # good - nested class in private section
|
|
35
|
+
# class Order
|
|
36
|
+
# def process
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# private
|
|
40
|
+
# class LineItem
|
|
41
|
+
# def total
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # good - declarations with no body, wherever they read best
|
|
47
|
+
# class Order
|
|
48
|
+
# class Rejected < StandardError; end
|
|
49
|
+
#
|
|
50
|
+
# LineItem = Data.define(:sku, :quantity)
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
class RuboCop::Cop::Callbacksystems::PrivateNestedClasses < RuboCop::Cop::Callbacksystems::Base
|
|
54
|
+
def on_class(node)
|
|
55
|
+
report NestedClass.new(node)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias on_casgn on_class
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
class NestedClass
|
|
62
|
+
include RuboCop::Callbacksystems::Helpers
|
|
63
|
+
|
|
64
|
+
MESSAGE = "Nested class `%<name>s` should be in the private section, or live in its own file if it is part of " \
|
|
65
|
+
"the API."
|
|
66
|
+
|
|
67
|
+
def initialize(node)
|
|
68
|
+
@node = node
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def offense
|
|
72
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if outside_private_section?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
attr_reader :node
|
|
77
|
+
|
|
78
|
+
def outside_private_section?
|
|
79
|
+
class_with_body?(node) && nested? && !in_private_section?(node, enclosing.body)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def nested?
|
|
83
|
+
enclosing.present?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def enclosing
|
|
87
|
+
@enclosing ||= enclosing_class_or_module_of(node)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def message
|
|
91
|
+
format(MESSAGE, name: class_name_of(node))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Ensures every public method of a class or module has a test, found by the
|
|
2
|
+
# convention that a test description opens with the name of the method it
|
|
3
|
+
# covers. A public method is the API of the class, so one without a test is a
|
|
4
|
+
# promise nothing checks, and the naming convention is what lets a reader go
|
|
5
|
+
# from the method to its tests and back.
|
|
6
|
+
#
|
|
7
|
+
# Methods of private nested classes are implementation details and stay out,
|
|
8
|
+
# and so do methods a macro references (callbacks, delegates), since the macro
|
|
9
|
+
# is what exercises them. A method returning a plain literal needs no test that
|
|
10
|
+
# would only restate the literal. Scopes and methods in `class_methods` blocks
|
|
11
|
+
# count.
|
|
12
|
+
#
|
|
13
|
+
# Test files are located based on the source file path:
|
|
14
|
+
# - lib/foo/bar.rb -> test/lib/foo/bar_test.rb or test/foo/bar_test.rb
|
|
15
|
+
# - app/models/user.rb -> test/models/user_test.rb
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# # bad - public method without test
|
|
19
|
+
# class User
|
|
20
|
+
# def full_name
|
|
21
|
+
# "#{first_name} #{last_name}"
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# # And test file has no test "full_name ..." block
|
|
25
|
+
#
|
|
26
|
+
# # good - public method has test
|
|
27
|
+
# class User
|
|
28
|
+
# def full_name
|
|
29
|
+
# "#{first_name} #{last_name}"
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
# # test "full_name returns concatenated names" do ... end
|
|
33
|
+
#
|
|
34
|
+
# # good - method returning a plain literal needs no test
|
|
35
|
+
# class User
|
|
36
|
+
# def role
|
|
37
|
+
# "member"
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
class RuboCop::Cop::Callbacksystems::PublicMethodsMustHaveTests < RuboCop::Cop::Callbacksystems::Base
|
|
42
|
+
def external_dependency_checksum
|
|
43
|
+
[
|
|
44
|
+
RuboCop::Callbacksystems::Source::FilesChecksum.for("test/**/*_test.rb", root: project_root),
|
|
45
|
+
RuboCop::Callbacksystems::Source::FilesChecksum.for("*.gemspec", root: project_root, include_contents: false)
|
|
46
|
+
].join
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def on_new_investigation
|
|
50
|
+
@path_mapping = RuboCop::Callbacksystems::Testing::PathMapping.new(processed_source.file_path)
|
|
51
|
+
@tested_methods = path_mapping.test_path ? TestedMethods.new(path_mapping.test_path, ruby_version:) : Set.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def on_class(node)
|
|
55
|
+
unless private_nested_class?(node)
|
|
56
|
+
report_each TestCoverage.new(node, processed_source, path_mapping:, tested_methods:)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
alias on_module on_class
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
attr_reader :path_mapping, :tested_methods
|
|
64
|
+
delegate :ruby_version, to: :processed_source, private: true
|
|
65
|
+
|
|
66
|
+
class TestCoverage
|
|
67
|
+
MESSAGE = "Public method `%<method>s` has no test. " \
|
|
68
|
+
"Convention: tests begin with the method name followed by what it does, " \
|
|
69
|
+
"e.g. `test \"%<method>s returns the expected value\" do`"
|
|
70
|
+
EXCLUDED_METHODS = %i[ initialize ].to_set
|
|
71
|
+
|
|
72
|
+
def initialize(node, processed_source, path_mapping:, tested_methods:)
|
|
73
|
+
@node = node
|
|
74
|
+
@processed_source = processed_source
|
|
75
|
+
@path_mapping = path_mapping
|
|
76
|
+
@tested_methods = tested_methods
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def each_offense
|
|
80
|
+
untested_methods.each { yield RuboCop::Callbacksystems::Offense.new(it.node, message_for(it)) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
attr_reader :node, :processed_source, :path_mapping, :tested_methods
|
|
85
|
+
|
|
86
|
+
delegate :file_path, to: :processed_source, private: true
|
|
87
|
+
|
|
88
|
+
def untested_methods
|
|
89
|
+
checked? ? testable_public_methods.reject { covered?(it) } : []
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def checked?
|
|
93
|
+
file_path.exclude?("/test/") && !lib_file_in_non_gem_project? && test_source_valid?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def lib_file_in_non_gem_project?
|
|
97
|
+
file_path.match?(%r{(^|/)lib/}) && !path_mapping.gem_project?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_source_valid?
|
|
101
|
+
path_mapping.test_path.nil? || tested_methods.valid?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def testable_public_methods
|
|
105
|
+
RuboCop::Callbacksystems::Methods::Definitions.new(node).reject { exempt?(it) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def exempt?(definition)
|
|
109
|
+
EXCLUDED_METHODS.include?(definition.name) || returns_literal?(definition.node)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def returns_literal?(method_node)
|
|
113
|
+
method_node.type?(:any_def) && method_node.body&.recursive_literal?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def covered?(definition)
|
|
117
|
+
MethodCoverage.new(definition, tested: tested_methods, macro_referenced: macro_referenced_methods).covered?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def macro_referenced_methods
|
|
121
|
+
@macro_referenced_methods ||= RuboCop::Callbacksystems::Methods::MacroReferences.new(node.body).to_set
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def message_for(definition)
|
|
125
|
+
format(MESSAGE, method: definition.name)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# A scope a macro names is still API a test reads, so the macro exempts every method but a scope.
|
|
129
|
+
class MethodCoverage
|
|
130
|
+
def initialize(definition, tested:, macro_referenced:)
|
|
131
|
+
@definition = definition
|
|
132
|
+
@tested = tested
|
|
133
|
+
@macro_referenced = macro_referenced
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def covered?
|
|
137
|
+
tested? || macro_referenced_non_scope?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
attr_reader :definition, :tested, :macro_referenced
|
|
142
|
+
|
|
143
|
+
def tested?
|
|
144
|
+
tested.include?(definition.name.to_s)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def macro_referenced_non_scope?
|
|
148
|
+
macro_referenced.include?(definition.name) && !scope?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def scope?
|
|
152
|
+
definition.node.send_type? && definition.node.method?(:scope)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class TestedMethods
|
|
158
|
+
extend RuboCop::AST::NodePattern::Macros
|
|
159
|
+
include Enumerable
|
|
160
|
+
include RuboCop::Callbacksystems::Testing::CopHelpers
|
|
161
|
+
include RuboCop::Callbacksystems::Helpers
|
|
162
|
+
|
|
163
|
+
delegate :each, to: :names
|
|
164
|
+
|
|
165
|
+
def initialize(test_file_path, ruby_version:)
|
|
166
|
+
@test_file_path = test_file_path
|
|
167
|
+
@ruby_version = ruby_version
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def valid?
|
|
171
|
+
processed_test_source&.valid_syntax? || false
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
attr_reader :test_file_path, :ruby_version
|
|
176
|
+
|
|
177
|
+
def names
|
|
178
|
+
@names ||= descriptions.filter_map { it.split(/\s+/).first }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def descriptions
|
|
182
|
+
nodes_in(processed_test_source&.ast, :any_block).filter_map { test_block?(it) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def processed_test_source
|
|
186
|
+
@processed_test_source ||= RuboCop::Callbacksystems::Source::FileAst.processed_source \
|
|
187
|
+
test_file_path, ruby_version:
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Detects an `extend ActiveSupport::Concern` the module never uses. The extend
|
|
2
|
+
# promises `included`, `class_methods` or `prepended` hooks, or concern
|
|
3
|
+
# dependencies declared with `include` or `prepend`, so a reader goes looking
|
|
4
|
+
# for them. A module with none is a plain mixin carrying a promise it never
|
|
5
|
+
# keeps.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - extend without using concern features
|
|
9
|
+
# module Searchable
|
|
10
|
+
# extend ActiveSupport::Concern
|
|
11
|
+
#
|
|
12
|
+
# def search
|
|
13
|
+
# # ...
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # good - using included block
|
|
18
|
+
# module Searchable
|
|
19
|
+
# extend ActiveSupport::Concern
|
|
20
|
+
#
|
|
21
|
+
# included do
|
|
22
|
+
# scope :search, -> { ... }
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# # good - using class_methods
|
|
27
|
+
# module Searchable
|
|
28
|
+
# extend ActiveSupport::Concern
|
|
29
|
+
#
|
|
30
|
+
# class_methods do
|
|
31
|
+
# def search(query)
|
|
32
|
+
# # ...
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# # good - no extend needed for simple module
|
|
38
|
+
# module Searchable
|
|
39
|
+
# def search
|
|
40
|
+
# # ...
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
class RuboCop::Cop::Callbacksystems::RedundantConcernExtend < RuboCop::Cop::Callbacksystems::Base
|
|
45
|
+
extend RuboCop::Cop::AutoCorrector
|
|
46
|
+
|
|
47
|
+
def on_module(node)
|
|
48
|
+
report ConcernModule.new(node, source_comments:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
class ConcernModule
|
|
53
|
+
include RuboCop::Callbacksystems::Helpers
|
|
54
|
+
|
|
55
|
+
MESSAGE = "Unnecessary `extend ActiveSupport::Concern`. " \
|
|
56
|
+
"Remove it or use concern hooks or dependencies."
|
|
57
|
+
CONCERN_CONSTANTS = %w[ ActiveSupport::Concern Concern ]
|
|
58
|
+
CONCERN_METHODS = %i[ included class_methods prepended ]
|
|
59
|
+
DEPENDENCY_METHODS = %i[ include prepend ]
|
|
60
|
+
|
|
61
|
+
def initialize(node, source_comments:)
|
|
62
|
+
@node = node
|
|
63
|
+
@source_comments = source_comments
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def offense
|
|
67
|
+
if redundant?
|
|
68
|
+
RuboCop::Callbacksystems::Offense.new \
|
|
69
|
+
concern_extend, MESSAGE, correcting: correction_uncommented? do |corrector|
|
|
70
|
+
correct(corrector)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
attr_reader :node, :source_comments
|
|
77
|
+
|
|
78
|
+
def redundant?
|
|
79
|
+
concern_extend && !uses_concern_features?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def concern_extend
|
|
83
|
+
@concern_extend ||= statements_in(node.body).find { concern_extend?(it) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def concern_extend?(statement)
|
|
87
|
+
statement.send_type? && statement.method?(:extend) && statement.arguments.any? { concern_constant?(it) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def concern_constant?(argument)
|
|
91
|
+
CONCERN_CONSTANTS.include?(constant_name_of(argument)&.delete_prefix("::"))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def uses_concern_features?
|
|
95
|
+
concern_hooks.any? || concern_dependencies.any? || class_methods_module?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def concern_hooks
|
|
99
|
+
nodes_in(node.body, :any_block).select do |block|
|
|
100
|
+
bare_send?(block.send_node) && CONCERN_METHODS.include?(block.method_name)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def concern_dependencies
|
|
105
|
+
nodes_in(node.body, :send).select do |call|
|
|
106
|
+
bare_send?(call) && DEPENDENCY_METHODS.include?(call.method_name) &&
|
|
107
|
+
call.each_ancestor(:def, :defs, :class, :module).first.equal?(node)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def class_methods_module?
|
|
112
|
+
statements_in(node.body).any? do |statement|
|
|
113
|
+
statement.module_type? && constant_name_of(statement.identifier) == "ClassMethods"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def correction_uncommented?
|
|
118
|
+
!source_comments.any_within?(correction_range)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def correction_range
|
|
122
|
+
concern_extend.arguments.one? ? statement_removal_range_for(concern_extend) : concern_argument_removal_range
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def concern_argument_removal_range
|
|
126
|
+
if argument_after_concern
|
|
127
|
+
concern_argument.source_range.with(end_pos: argument_after_concern.source_range.begin_pos)
|
|
128
|
+
else
|
|
129
|
+
argument_before_concern.source_range.end.join(concern_argument.source_range.end)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def argument_after_concern
|
|
134
|
+
concern_extend.arguments[concern_argument_index + 1]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def concern_argument_index
|
|
138
|
+
concern_extend.arguments.index(concern_argument)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def concern_argument
|
|
142
|
+
@concern_argument ||= concern_extend.arguments.find { concern_constant?(it) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def argument_before_concern
|
|
146
|
+
concern_extend.arguments[concern_argument_index - 1]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def correct(corrector)
|
|
150
|
+
if concern_extend.arguments.one?
|
|
151
|
+
corrector.remove(statement_removal_range_for(concern_extend))
|
|
152
|
+
else
|
|
153
|
+
corrector.remove(concern_argument_removal_range)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|