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,255 @@
|
|
|
1
|
+
# Orders the nested classes of a body by reference: one that names another reads
|
|
2
|
+
# before the one it names, the way `MethodInvocationOrder` reads a caller before
|
|
3
|
+
# its callees.
|
|
4
|
+
#
|
|
5
|
+
# Only a class or module carrying a body counts. A constant built in a line
|
|
6
|
+
# (`Row = Data.define(:name)`, `Error = Class.new(StandardError)`) is a
|
|
7
|
+
# declaration, and `BodyOrder` reads those with the values at the top.
|
|
8
|
+
#
|
|
9
|
+
# Where the nested classes sit is `BodyOrder`'s business, which keeps them last.
|
|
10
|
+
# This one only orders them among themselves, inside the run they already form.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad - Row names Cell, so Row reads first
|
|
14
|
+
# class Report
|
|
15
|
+
# class Cell
|
|
16
|
+
# def value; end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# class Row
|
|
20
|
+
# def cells
|
|
21
|
+
# [ Cell.new ]
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# # good
|
|
27
|
+
# class Report
|
|
28
|
+
# class Row
|
|
29
|
+
# def cells
|
|
30
|
+
# [ Cell.new ]
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# class Cell
|
|
35
|
+
# def value; end
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
class RuboCop::Cop::Callbacksystems::NestedClassOrder < RuboCop::Cop::Callbacksystems::Base
|
|
40
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
41
|
+
extend RuboCop::Cop::AutoCorrector
|
|
42
|
+
|
|
43
|
+
def on_new_investigation
|
|
44
|
+
report_each Bodies.new(processed_source, constant_references:) if project_index_reliable?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
def constant_references
|
|
49
|
+
RuboCop::Callbacksystems::ProjectIndex::ConstantReferences.for(project_index).within(processed_source)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Bodies
|
|
53
|
+
include RuboCop::Callbacksystems::Helpers
|
|
54
|
+
|
|
55
|
+
def initialize(processed_source, constant_references:)
|
|
56
|
+
@processed_source = processed_source
|
|
57
|
+
@constant_references = constant_references
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def each_offense(&block)
|
|
61
|
+
runs.each do |run|
|
|
62
|
+
NestedClasses.new(run, processed_source, constant_references:, permit:).each_offense(&block)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :processed_source, :constant_references
|
|
68
|
+
|
|
69
|
+
def runs
|
|
70
|
+
@runs ||= definition_nodes_in(processed_source.ast).flat_map { runs_of(it) }.select(&:many?)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def runs_of(node)
|
|
74
|
+
runs_in(node.body) { declared_class?(it) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A constant built from a builder stays out, since `BodyOrder` already places it among the values or the nested
|
|
78
|
+
# classes.
|
|
79
|
+
def declared_class?(node)
|
|
80
|
+
node.type?(:class, :module) && node.body.present?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def permit
|
|
84
|
+
@permit ||= RuboCop::Callbacksystems::Autocorrection::RewritePermit.new
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# One run of nested classes written together, read for the first that sits before a class naming it.
|
|
89
|
+
class NestedClasses
|
|
90
|
+
MESSAGE = "Class `%<expected>s` should be defined before `%<actual>s`, since it names it."
|
|
91
|
+
|
|
92
|
+
def initialize(nodes, processed_source, constant_references:, permit:)
|
|
93
|
+
@nodes = nodes
|
|
94
|
+
@processed_source = processed_source
|
|
95
|
+
@constant_references = constant_references
|
|
96
|
+
@permit = permit
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def each_offense
|
|
100
|
+
yield offense if reportable?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
attr_reader :nodes, :processed_source, :constant_references, :permit
|
|
105
|
+
delegate :rewrite, to: :statement_run, private: true
|
|
106
|
+
|
|
107
|
+
def reportable?
|
|
108
|
+
analyzable? && ordered_safely?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Reopening gives one declaration several load positions, so no single reference order can place it truthfully.
|
|
112
|
+
def analyzable?
|
|
113
|
+
classes.all?(&:uniquely_declared?) && identities.uniq.size == identities.size
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def classes
|
|
117
|
+
@classes ||= nodes.map { NestedClass.new(it, processed_source, constant_references:) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def identities
|
|
121
|
+
@identities ||= classes.map(&:identity)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def ordered_safely?
|
|
125
|
+
divergence.found? && load_order_kept?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def divergence
|
|
129
|
+
@divergence ||= RuboCop::Callbacksystems::ClassStructure::Divergence.new(identities, from: canonical_order)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def canonical_order
|
|
133
|
+
@canonical_order ||= RuboCop::Callbacksystems::ClassStructure::ReferenceOrder.new(seeds, reference_graph).to_a
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def seeds
|
|
137
|
+
identities.reject { referenced.include?(it) } + identities
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def referenced
|
|
141
|
+
@referenced ||= reference_graph.values.flatten.to_set
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def reference_graph
|
|
145
|
+
@reference_graph ||= graph_of(:reads)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def graph_of(reading)
|
|
149
|
+
classes.to_h { [ it.identity, it.public_send(reading).intersection(identities) ] }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# A superclass or a name read outside a method runs as the body loads, so an order breaking one is refused.
|
|
153
|
+
def load_order_kept?
|
|
154
|
+
dependency_graph.all? { |name, required| required.all? { loaded_before?(dependency: it, dependent: name) } }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def dependency_graph
|
|
158
|
+
@dependency_graph ||= graph_of(:dependencies)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def loaded_before?(dependency:, dependent:)
|
|
162
|
+
canonical_positions.fetch(dependency) < canonical_positions.fetch(dependent)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def canonical_positions
|
|
166
|
+
@canonical_positions ||= canonical_order.each_with_index.to_h
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def offense
|
|
170
|
+
RuboCop::Callbacksystems::Offense.new(nodes[divergence.index], message,
|
|
171
|
+
correcting: correctable?) { rewrite(it) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def message
|
|
175
|
+
format(MESSAGE, expected: name_of(divergence.expected), actual: name_of(divergence.actual))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def name_of(identity)
|
|
179
|
+
classes_by_identity.fetch(identity).name
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def classes_by_identity
|
|
183
|
+
@classes_by_identity ||= classes.index_by(&:identity)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def correctable?
|
|
187
|
+
uninterrupted? && statement_run.reorderable? && permit.claim
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# A comment unattached to either class is an intentional boundary, not source for a sortable run.
|
|
191
|
+
def uninterrupted?
|
|
192
|
+
classes.each_cons(2).none? { |above, below| above.range.end.join(below.range.begin).source.strip.present? }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def statement_run
|
|
196
|
+
@statement_run ||= RuboCop::Callbacksystems::ClassStructure::StatementRun.new \
|
|
197
|
+
classes, order: canonical_order, &:identity
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class NestedClass
|
|
202
|
+
include RuboCop::Callbacksystems::Helpers
|
|
203
|
+
|
|
204
|
+
delegate :range, :source, :contains_tooling_comment?, to: :block
|
|
205
|
+
|
|
206
|
+
def initialize(node, processed_source, constant_references:)
|
|
207
|
+
@node = node
|
|
208
|
+
@processed_source = processed_source
|
|
209
|
+
@constant_references = constant_references
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def reads
|
|
213
|
+
referenced_identities_of(constants.select { enclosing_method_of(it).present? })
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def name
|
|
217
|
+
node.identifier.short_name
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def identity
|
|
221
|
+
declaration&.name
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def uniquely_declared?
|
|
225
|
+
declaration.is_a?(Rubydex::Namespace) && declaration.definitions.one?
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def dependencies
|
|
229
|
+
referenced_identities_of(superclass_constants + constants.reject { enclosing_method_of(it).present? })
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private
|
|
233
|
+
attr_reader :node, :processed_source, :constant_references
|
|
234
|
+
|
|
235
|
+
def block
|
|
236
|
+
@block ||= RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def referenced_identities_of(found)
|
|
240
|
+
found.filter_map { constant_references.referenced_declaration_for(it)&.name }.uniq.excluding(identity)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def constants
|
|
244
|
+
@constants ||= node.each_descendant(:const).to_a
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def declaration
|
|
248
|
+
@declaration ||= constant_references.declaration_named(node.identifier.const_name, beside: node.identifier)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def superclass_constants
|
|
252
|
+
node.class_type? && node.parent_class ? node.parent_class.each_node(:const).to_a : []
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Prohibits common abbreviations in declared names, including CamelCase. A
|
|
2
|
+
# full word costs a few keystrokes once and reads at a glance every time after,
|
|
3
|
+
# while an abbreviation makes every reader expand it.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# # bad
|
|
7
|
+
# def calc_total
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# # bad
|
|
11
|
+
# attrs = {}
|
|
12
|
+
#
|
|
13
|
+
# # bad
|
|
14
|
+
# USER_ATTRS = %i[name email]
|
|
15
|
+
#
|
|
16
|
+
# # bad
|
|
17
|
+
# class UserAttrs
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# def calculate_total
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# attributes = {}
|
|
26
|
+
#
|
|
27
|
+
# # good
|
|
28
|
+
# USER_ATTRIBUTES = %i[name email]
|
|
29
|
+
#
|
|
30
|
+
# # good
|
|
31
|
+
# class UserAttributes
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
class RuboCop::Cop::Callbacksystems::NoAbbreviations < RuboCop::Cop::Callbacksystems::Base
|
|
35
|
+
def on_class(node)
|
|
36
|
+
on_def(node.identifier)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
alias on_module on_class
|
|
40
|
+
|
|
41
|
+
def on_def(node)
|
|
42
|
+
report_each AbbreviatedName.new(node.loc.name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias on_defs on_def
|
|
46
|
+
alias on_lvasgn on_def
|
|
47
|
+
alias on_match_var on_def
|
|
48
|
+
alias on_ivasgn on_def
|
|
49
|
+
alias on_cvasgn on_def
|
|
50
|
+
alias on_gvasgn on_def
|
|
51
|
+
alias on_casgn on_def
|
|
52
|
+
alias on_arg on_def
|
|
53
|
+
alias on_optarg on_def
|
|
54
|
+
alias on_restarg on_def
|
|
55
|
+
alias on_kwarg on_def
|
|
56
|
+
alias on_kwoptarg on_def
|
|
57
|
+
alias on_kwrestarg on_def
|
|
58
|
+
alias on_blockarg on_def
|
|
59
|
+
alias on_shadowarg on_def
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
# Ruby and Rails conventions stay readable: params, args, id, ids, config, env, info, lib, max, min,
|
|
63
|
+
# proc(s), temp, sync, and the attr_reader, attr_writer, and attr_accessor APIs.
|
|
64
|
+
ABBREVIATIONS = {
|
|
65
|
+
"abbrev" => "abbreviation",
|
|
66
|
+
"amt" => "amount",
|
|
67
|
+
"arr" => "array",
|
|
68
|
+
"attr" => "attribute",
|
|
69
|
+
"attrs" => "attributes",
|
|
70
|
+
"btn" => "button",
|
|
71
|
+
"buf" => "buffer",
|
|
72
|
+
"calc" => "calculate",
|
|
73
|
+
"cb" => "callback",
|
|
74
|
+
"cfg" => "configuration",
|
|
75
|
+
"cmd" => "command",
|
|
76
|
+
"conf" => "configuration",
|
|
77
|
+
"ctx" => "context",
|
|
78
|
+
"curr" => "current",
|
|
79
|
+
"decl" => "declaration",
|
|
80
|
+
"decls" => "declarations",
|
|
81
|
+
"dep" => "dependency",
|
|
82
|
+
"deps" => "dependencies",
|
|
83
|
+
"dest" => "destination",
|
|
84
|
+
"doc" => "document",
|
|
85
|
+
"docs" => %w[ documentation documents ],
|
|
86
|
+
"elem" => "element",
|
|
87
|
+
"elems" => "elements",
|
|
88
|
+
"err" => "error",
|
|
89
|
+
"errs" => "errors",
|
|
90
|
+
"evt" => "event",
|
|
91
|
+
"expr" => "expression",
|
|
92
|
+
"exprs" => "expressions",
|
|
93
|
+
"func" => "function",
|
|
94
|
+
"funcs" => "functions",
|
|
95
|
+
"ident" => "identifier",
|
|
96
|
+
"idents" => "identifiers",
|
|
97
|
+
"idx" => "index",
|
|
98
|
+
"impl" => "implementation",
|
|
99
|
+
"init" => "initialize",
|
|
100
|
+
"len" => "length",
|
|
101
|
+
"libs" => "libraries",
|
|
102
|
+
"msg" => "message",
|
|
103
|
+
"msgs" => "messages",
|
|
104
|
+
"num" => "number",
|
|
105
|
+
"nums" => "numbers",
|
|
106
|
+
"obj" => "object",
|
|
107
|
+
"objs" => "objects",
|
|
108
|
+
"opt" => "option",
|
|
109
|
+
"opts" => "options",
|
|
110
|
+
"org" => "organization",
|
|
111
|
+
"orgs" => "organizations",
|
|
112
|
+
"perf" => "performance",
|
|
113
|
+
"pkg" => "package",
|
|
114
|
+
"pkgs" => "packages",
|
|
115
|
+
"pos" => "position",
|
|
116
|
+
"prev" => "previous",
|
|
117
|
+
"prop" => "property",
|
|
118
|
+
"props" => "properties",
|
|
119
|
+
"ref" => "reference",
|
|
120
|
+
"refs" => "references",
|
|
121
|
+
"repo" => "repository",
|
|
122
|
+
"repos" => "repositories",
|
|
123
|
+
"req" => "request",
|
|
124
|
+
"reqs" => "requests",
|
|
125
|
+
"res" => %w[ resource response result ],
|
|
126
|
+
"resp" => "response",
|
|
127
|
+
"sep" => "separator",
|
|
128
|
+
"src" => "source",
|
|
129
|
+
"stmt" => "statement",
|
|
130
|
+
"stmts" => "statements",
|
|
131
|
+
"str" => "string",
|
|
132
|
+
"strs" => "strings",
|
|
133
|
+
"tbl" => "table",
|
|
134
|
+
"tmp" => "temporary",
|
|
135
|
+
"val" => "value",
|
|
136
|
+
"vals" => "values",
|
|
137
|
+
"var" => "variable",
|
|
138
|
+
"vars" => "variables",
|
|
139
|
+
"ver" => "version"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Word boundaries preserve acronyms and source offsets while recognizing both snake_case and CamelCase names.
|
|
143
|
+
class AbbreviatedName
|
|
144
|
+
MESSAGE = "Avoid abbreviation `%<abbreviation>s`. Use %<full>s instead."
|
|
145
|
+
WORD_PATTERN = /[[:upper:]]+(?=[[:upper:]][[:lower:]]|[^[:alpha:]]|\z)|[[:upper:]]?[[:lower:]]+/
|
|
146
|
+
NEXT_WORD_PATTERN = /\A_?(#{WORD_PATTERN})/
|
|
147
|
+
ATTRIBUTE_ACCESSORS = %w[ accessor reader writer ]
|
|
148
|
+
|
|
149
|
+
def initialize(range)
|
|
150
|
+
@range = range
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def each_offense
|
|
154
|
+
range&.source&.scan(WORD_PATTERN) do
|
|
155
|
+
match = Regexp.last_match
|
|
156
|
+
if abbreviation_at?(match)
|
|
157
|
+
yield RuboCop::Callbacksystems::Offense.new(location_of(match), message_for(match.to_s))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
attr_reader :range
|
|
164
|
+
|
|
165
|
+
def abbreviation_at?(match)
|
|
166
|
+
ABBREVIATIONS.key?(match.to_s.downcase) && !attribute_accessor_at?(match)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def attribute_accessor_at?(match)
|
|
170
|
+
match.to_s.casecmp?("attr") && range.source[match.end(0)..].match(NEXT_WORD_PATTERN)&.then do |following|
|
|
171
|
+
ATTRIBUTE_ACCESSORS.include?(following.captures.first.downcase)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def location_of(match)
|
|
176
|
+
range.with(begin_pos: range.begin_pos + match.begin(0), end_pos: range.begin_pos + match.end(0))
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def message_for(word)
|
|
180
|
+
format(MESSAGE, abbreviation: word, full: replacements_for(word))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def replacements_for(word)
|
|
184
|
+
Array(ABBREVIATIONS[word.downcase]).map { "`#{replacement_for(word, with: it)}`" }.join(" or ")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def replacement_for(word, with:)
|
|
188
|
+
if word == word.upcase
|
|
189
|
+
with.upcase
|
|
190
|
+
elsif word.start_with?(/[[:upper:]]/)
|
|
191
|
+
with.capitalize
|
|
192
|
+
else
|
|
193
|
+
with
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Detects private methods that only delegate to a newly created object.
|
|
2
|
+
# Such methods add indirection without value and should be inlined or reconsidered.
|
|
3
|
+
# Public methods and predicates are excluded as they often provide semantic value.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# # bad - private method only creates object and calls single method
|
|
7
|
+
# private
|
|
8
|
+
# def eager_loading_association(body)
|
|
9
|
+
# ScopeBody.new(body).eager_loading_association
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# # good - inline the call
|
|
13
|
+
# ScopeBody.new(body).eager_loading_association
|
|
14
|
+
#
|
|
15
|
+
# # good - public method (may be API)
|
|
16
|
+
# def eager_loading_association(body)
|
|
17
|
+
# ScopeBody.new(body).eager_loading_association
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good - predicate adds semantic meaning
|
|
21
|
+
# def valid?
|
|
22
|
+
# Validator.new(self).valid?
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class RuboCop::Cop::Callbacksystems::NoAnemicDelegation < RuboCop::Cop::Callbacksystems::Base
|
|
26
|
+
def on_def(node)
|
|
27
|
+
report MethodDelegation.new(node)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias on_defs on_def
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
class MethodDelegation
|
|
34
|
+
include RuboCop::Callbacksystems::Helpers
|
|
35
|
+
|
|
36
|
+
MESSAGE = "Method `%<method>s` only delegates to `%<class>s.new(...).%<target>s`. Consider inlining or " \
|
|
37
|
+
"removing this method."
|
|
38
|
+
|
|
39
|
+
def initialize(node)
|
|
40
|
+
@node = node
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def offense
|
|
44
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if anemic_delegation?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
attr_reader :node
|
|
49
|
+
delegate :body, to: :node, private: true
|
|
50
|
+
|
|
51
|
+
def anemic_delegation?
|
|
52
|
+
eligible_method? && delegates_only_parameters?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def eligible_method?
|
|
56
|
+
single_send_private_non_predicate?(node) && direct_method_declaration?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def direct_method_declaration?
|
|
60
|
+
owner && transparent_parents?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def owner
|
|
64
|
+
@owner ||= enclosing_class_or_module_of(node)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def transparent_parents?
|
|
68
|
+
node.each_ancestor.take_while { !it.equal?(owner) }.all? { transparent_method_parent?(it) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def transparent_method_parent?(parent)
|
|
72
|
+
parent.type?(:begin, :kwbegin, :sclass) || visibility_applied_to(parent, node)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def delegates_only_parameters?
|
|
76
|
+
receiver_is_new_instance? && passes_only_parameters?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def receiver_is_new_instance?
|
|
80
|
+
body.receiver&.send_type? &&
|
|
81
|
+
body.receiver.method?(:new) &&
|
|
82
|
+
body.receiver.receiver&.const_type?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def passes_only_parameters?
|
|
86
|
+
invocation_arguments.all? { it.lvar_type? && parameter_names.include?(it.name) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def invocation_arguments
|
|
90
|
+
body.receiver.arguments + body.arguments
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def parameter_names
|
|
94
|
+
@parameter_names ||= node.arguments.children.filter_map { it.name if it.respond_to?(:name) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def message
|
|
98
|
+
format(MESSAGE, method: node.method_name, class: body.receiver.receiver.short_name, target: body.method_name)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|