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,261 @@
|
|
|
1
|
+
# Detects consecutive appends to one collection. `list << a` followed by
|
|
2
|
+
# `list << b` makes the reader walk the same receiver twice, where a single
|
|
3
|
+
# `list.push(a, b)` states the whole addition at once.
|
|
4
|
+
#
|
|
5
|
+
# The run has to be adjacent statements on the same local variable, instance
|
|
6
|
+
# variable or bare method call, and none of the values may read or rebind the
|
|
7
|
+
# receiver, since `list << list.size` after an append counts what was pushed
|
|
8
|
+
# before it, while `list << (list = other)` changes what the next append reaches.
|
|
9
|
+
# `<<` also writes to a String, a Set or a stream, none of which takes several
|
|
10
|
+
# values through `push`, so the rule needs positive evidence that every visible
|
|
11
|
+
# assignment builds an Array and, for a local, an unconditional assignment above
|
|
12
|
+
# the run. An unknown receiver is left alone. A run carrying a comment or a
|
|
13
|
+
# heredoc is reported without a correction, since collapsing it would drop the
|
|
14
|
+
# comment or strand the heredoc body.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # bad
|
|
18
|
+
# list = []
|
|
19
|
+
# list << header
|
|
20
|
+
# list << body
|
|
21
|
+
#
|
|
22
|
+
# # bad
|
|
23
|
+
# list = []
|
|
24
|
+
# list.push(header)
|
|
25
|
+
# list << body
|
|
26
|
+
#
|
|
27
|
+
# # good
|
|
28
|
+
# list = []
|
|
29
|
+
# list.push(header, body)
|
|
30
|
+
#
|
|
31
|
+
# # good - the second value reads the receiver
|
|
32
|
+
# list = []
|
|
33
|
+
# list << header
|
|
34
|
+
# list << list.size
|
|
35
|
+
#
|
|
36
|
+
class RuboCop::Cop::Callbacksystems::NoConsecutiveAppends < RuboCop::Cop::Callbacksystems::Base
|
|
37
|
+
extend RuboCop::Cop::AutoCorrector
|
|
38
|
+
|
|
39
|
+
def on_new_investigation
|
|
40
|
+
@assigned_values = AssignedValues.new(processed_source.ast)
|
|
41
|
+
@source_comments = RuboCop::Callbacksystems::Source::Comments.for(processed_source)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def on_begin(node)
|
|
45
|
+
report_each AppendRuns.new(node, assigned_values:, source_comments:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
alias on_kwbegin on_begin
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
attr_reader :assigned_values, :source_comments
|
|
52
|
+
|
|
53
|
+
# The runs of adjacent appends to one receiver inside a statement list.
|
|
54
|
+
class AppendRuns
|
|
55
|
+
def initialize(statement_list, assigned_values:, source_comments:)
|
|
56
|
+
@statement_list = statement_list
|
|
57
|
+
@assignment_index = assigned_values
|
|
58
|
+
@source_comments = source_comments
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def each_offense
|
|
62
|
+
runs.select(&:array_receiver?).each { yield it.offense }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
attr_reader :statement_list, :assignment_index, :source_comments
|
|
67
|
+
|
|
68
|
+
def runs
|
|
69
|
+
appends.chunk_while { |left, right| left.continued_by?(right) }.select(&:many?).map { run_of(it) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def appends
|
|
73
|
+
statement_list.children.map { Append.new(it) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run_of(members)
|
|
77
|
+
Run.new(members, assigned_values: assignment_index, source_comments:)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# One statement read as an append, or as the statement that ends a run.
|
|
82
|
+
class Append
|
|
83
|
+
include RuboCop::Callbacksystems::Helpers
|
|
84
|
+
extend RuboCop::AST::NodePattern::Macros
|
|
85
|
+
|
|
86
|
+
attr_reader :node
|
|
87
|
+
|
|
88
|
+
# @!method appended(node)
|
|
89
|
+
def_node_matcher :appended, <<~PATTERN
|
|
90
|
+
(send ${lvar ivar (send nil? _)} {:<< :push} $!block_pass+)
|
|
91
|
+
PATTERN
|
|
92
|
+
|
|
93
|
+
def initialize(node)
|
|
94
|
+
@node = node
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def continued_by?(other)
|
|
98
|
+
append? && other.append? && receiver.source == other.receiver.source
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def append?
|
|
102
|
+
captures.present? && !touches_receiver?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def receiver
|
|
106
|
+
captures.first
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def values
|
|
110
|
+
captures.second
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
def captures
|
|
115
|
+
@captures ||= appended(node)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def touches_receiver?
|
|
119
|
+
values.any? { |value| nodes_in(value, *receiver_node_types).any? { same_receiver?(it) } }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def receiver_node_types
|
|
123
|
+
if receiver.lvar_type? then %i[ lvar lvasgn match_var ]
|
|
124
|
+
elsif receiver.ivar_type? then %i[ ivar ivasgn ]
|
|
125
|
+
else [ receiver.type ]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def same_receiver?(occurrence)
|
|
130
|
+
if receiver.lvar_type?
|
|
131
|
+
variable_name_of(occurrence) == receiver.name &&
|
|
132
|
+
!name_rebound_between?(receiver.name, node: occurrence, boundary: scope)
|
|
133
|
+
elsif receiver.ivar_type?
|
|
134
|
+
occurrence.name == receiver.name
|
|
135
|
+
else
|
|
136
|
+
occurrence.source == receiver.source
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def scope
|
|
141
|
+
@scope ||= enclosing_scope_of(node)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class Run
|
|
146
|
+
include RuboCop::Callbacksystems::Helpers
|
|
147
|
+
|
|
148
|
+
MESSAGE = "Append `%<values>s` to `%<receiver>s` in one `push` instead of consecutive appends."
|
|
149
|
+
|
|
150
|
+
def initialize(appends, assigned_values:, source_comments:)
|
|
151
|
+
@appends = appends
|
|
152
|
+
@assignment_index = assigned_values
|
|
153
|
+
@source_comments = source_comments
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def array_receiver?
|
|
157
|
+
assigned_values.any? && assigned_values.all? { array_value?(it) } && initialized_before_run?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def offense
|
|
161
|
+
RuboCop::Callbacksystems::Offense.new(range, message, correcting: rewritable?) { correct(it) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
attr_reader :appends, :assignment_index, :source_comments
|
|
166
|
+
delegate :receiver, to: "appends.first", private: true
|
|
167
|
+
|
|
168
|
+
def assigned_values
|
|
169
|
+
@assigned_values ||= assignments.map { assigned_value_of(it) unless it.match_var_type? }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def assignments
|
|
173
|
+
assignment_index.for(receiver)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def assigned_value_of(assignment)
|
|
177
|
+
assignment.parent.or_asgn_type? ? assignment.parent.expression : assignment.expression
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def array_value?(value)
|
|
181
|
+
reads_as_array?(value)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def initialized_before_run?
|
|
185
|
+
!receiver.lvar_type? || assignments.any? { direct_assignment_before_run?(it) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def direct_assignment_before_run?(assignment)
|
|
189
|
+
assignment.parent.equal?(appends.first.node.parent) &&
|
|
190
|
+
assignment.source_range.begin_pos < appends.first.node.source_range.begin_pos
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def range
|
|
194
|
+
range_spanning(appends.map(&:node))
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def message
|
|
198
|
+
format(MESSAGE, values: values.map(&:source).join(", "), receiver: receiver.source)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def values
|
|
202
|
+
appends.flat_map(&:values)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def rewritable?
|
|
206
|
+
!receiver.send_type? && !commented? && appends.none? { carries_heredoc?(it.node) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def commented?
|
|
210
|
+
source_comments.any_on_lines?(range.first_line..range.last_line)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def correct(corrector)
|
|
214
|
+
corrector.replace(range, "#{receiver.source}.push(#{values.map(&:source).join(", ")})")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Assignments grouped once by local lexical scope or by instance-variable name for the whole file.
|
|
219
|
+
class AssignedValues
|
|
220
|
+
include RuboCop::Callbacksystems::Helpers
|
|
221
|
+
|
|
222
|
+
def initialize(ast)
|
|
223
|
+
@ast = ast
|
|
224
|
+
@local_variables = RuboCop::Callbacksystems::Execution::LocalVariableOccurrences.new(ast)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def for(receiver)
|
|
228
|
+
if receiver.lvar_type?
|
|
229
|
+
local_variables.named(receiver.name, around: receiver).select { it.type?(:lvasgn, :match_var) }
|
|
230
|
+
else
|
|
231
|
+
instance_assignments.fetch(instance_variable_key_of(receiver)) { [] }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
private
|
|
236
|
+
attr_reader :ast, :local_variables
|
|
237
|
+
|
|
238
|
+
def instance_assignments
|
|
239
|
+
@instance_assignments ||= nodes_in(ast, :ivasgn).select { state_belongs_to_domain?(it) }
|
|
240
|
+
.group_by { instance_variable_key_of(it) }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def state_belongs_to_domain?(assignment)
|
|
244
|
+
self_preserved_between?(assignment, boundary: state_boundary_of(assignment))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def state_boundary_of(assignment)
|
|
248
|
+
enclosing_method_of(assignment) || RuboCop::Callbacksystems::Methods::Domain.new(assignment).container
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def instance_variable_key_of(node)
|
|
252
|
+
domain = RuboCop::Callbacksystems::Methods::Domain.new(node)
|
|
253
|
+
|
|
254
|
+
[ instance_variable_name_of(node), domain.container&.object_id, domain.identity ]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def instance_variable_name_of(receiver)
|
|
258
|
+
receiver.send_type? ? :"@#{receiver.method_name}" : receiver.name
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Keeps routes on the standard resource actions. A custom action is a verb
|
|
2
|
+
# bolted onto a controller, and the noun it acts on is a resource of its own
|
|
3
|
+
# waiting to be named, so `confirm` on messages becomes a `confirmation`
|
|
4
|
+
# resource with a `create` action and the controller stays one noun with the
|
|
5
|
+
# seven actions a reader expects.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - custom action through an option
|
|
9
|
+
# resources :messages do
|
|
10
|
+
# post :confirm, on: :member
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# # bad - custom actions through a block
|
|
14
|
+
# resources :messages do
|
|
15
|
+
# member do
|
|
16
|
+
# post :confirm
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good - the action becomes a resource
|
|
21
|
+
# resources :messages do
|
|
22
|
+
# resource :confirmation, only: :create
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class RuboCop::Cop::Callbacksystems::NoCustomRouteActions < RuboCop::Cop::Callbacksystems::Base
|
|
26
|
+
def on_new_investigation
|
|
27
|
+
@immediate_nodes = {}.compare_by_identity
|
|
28
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(processed_source.ast).each { @immediate_nodes[it] = true }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def on_block(node)
|
|
32
|
+
report ScopeBlock.new(node) if in_routes_file? && immediately_executed?(node)
|
|
33
|
+
end
|
|
34
|
+
alias on_numblock on_block
|
|
35
|
+
alias on_itblock on_block
|
|
36
|
+
|
|
37
|
+
def on_send(node)
|
|
38
|
+
report VerbRoute.new(node) if in_routes_file? && immediately_executed?(node)
|
|
39
|
+
end
|
|
40
|
+
alias on_csend on_send
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
CUSTOM_SCOPES = %i[ member collection new ]
|
|
44
|
+
|
|
45
|
+
def in_routes_file?
|
|
46
|
+
routes_file?(processed_source.file_path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def immediately_executed?(node)
|
|
50
|
+
@immediate_nodes.key?(node)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class ScopeBlock
|
|
54
|
+
include RuboCop::Callbacksystems::Helpers
|
|
55
|
+
|
|
56
|
+
MESSAGE = "Custom actions belong to their own resource. Replace the `%<scope>s` block with nested resources."
|
|
57
|
+
|
|
58
|
+
def initialize(node)
|
|
59
|
+
@node = node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
if scoped_block?
|
|
64
|
+
RuboCop::Callbacksystems::Offense.new(node.send_node, format(MESSAGE, scope: node.method_name))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
attr_reader :node
|
|
70
|
+
|
|
71
|
+
def scoped_block?
|
|
72
|
+
CUSTOM_SCOPES.include?(node.method_name) && bare_send?(node.send_node) &&
|
|
73
|
+
node.send_node.arguments.empty? && inside_resources?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def inside_resources?
|
|
77
|
+
node.each_ancestor(*BLOCK_NODE_TYPES).any? do |block|
|
|
78
|
+
bare_send?(block.send_node) && %i[ resources resource ].include?(block.method_name)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class VerbRoute
|
|
84
|
+
include RuboCop::Callbacksystems::Helpers
|
|
85
|
+
|
|
86
|
+
MESSAGE = "Custom actions belong to their own resource. Route `%<action>s` as a resource instead of `on: " \
|
|
87
|
+
":%<scope>s`."
|
|
88
|
+
ROUTE_VERBS = %i[ get post patch put delete match ]
|
|
89
|
+
|
|
90
|
+
def initialize(node)
|
|
91
|
+
@node = node
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def offense
|
|
95
|
+
if scoped_route?
|
|
96
|
+
RuboCop::Callbacksystems::Offense.new(node, format(MESSAGE, action: action_name, scope: custom_scope))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
attr_reader :node
|
|
102
|
+
delegate :first_argument, to: :node, private: true
|
|
103
|
+
|
|
104
|
+
def scoped_route?
|
|
105
|
+
ROUTE_VERBS.include?(node.method_name) && bare_send?(node) && custom_scope.present?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def custom_scope
|
|
109
|
+
@custom_scope ||= on_option_value&.then { it.value if it.sym_type? && CUSTOM_SCOPES.include?(it.value) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def on_option_value
|
|
113
|
+
keyword_options.option_for(:on)&.value
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def keyword_options
|
|
117
|
+
@keyword_options ||= RuboCop::Callbacksystems::Hashes::KeywordOptions.new(*node.arguments.select(&:hash_type?))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def action_name
|
|
121
|
+
literal_name_of(first_argument)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Keeps preload, eager_load, and includes inside app/models. Which associations
|
|
2
|
+
# a query loads is knowledge about the model, so every caller asks for a named
|
|
3
|
+
# scope instead of repeating that knowledge at each call site.
|
|
4
|
+
# Methods defined on a known receiver in the same source are left alone; calls
|
|
5
|
+
# on unknown receivers are assumed to use the Active Record query API.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - direct eager loading in controller
|
|
9
|
+
# class UsersController < ApplicationController
|
|
10
|
+
# def index
|
|
11
|
+
# @users = User.includes(:posts).all
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# # good - use a scope
|
|
16
|
+
# class UsersController < ApplicationController
|
|
17
|
+
# def index
|
|
18
|
+
# @users = User.with_posts.all
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # In the model:
|
|
23
|
+
# class User < ApplicationRecord
|
|
24
|
+
# scope :with_posts, -> { includes(:posts) }
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
class RuboCop::Cop::Callbacksystems::NoDirectEagerLoading < RuboCop::Cop::Callbacksystems::Base
|
|
28
|
+
MESSAGE = "Don't use `%<method>s` outside `app/models`. Define a scope in the model instead."
|
|
29
|
+
|
|
30
|
+
def on_new_investigation
|
|
31
|
+
@custom_methods = CustomMethods.new(processed_source.ast)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_send(node)
|
|
35
|
+
add_offense(node, message: format(MESSAGE, method: node.method_name)) if direct_eager_loading?(node)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias on_csend on_send
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
attr_reader :custom_methods
|
|
42
|
+
|
|
43
|
+
def direct_eager_loading?(node)
|
|
44
|
+
!model_file?(processed_source.file_path) && EAGER_LOADING_METHODS.include?(node.method_name) &&
|
|
45
|
+
node.arguments.any? && !custom_methods.defines?(node)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Explicit definitions distinguish an application method from the homonymous query API without guessing types.
|
|
49
|
+
class CustomMethods
|
|
50
|
+
include RuboCop::Callbacksystems::Helpers
|
|
51
|
+
|
|
52
|
+
def initialize(root)
|
|
53
|
+
@root = root
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def defines?(call)
|
|
57
|
+
if call_on_self?(call)
|
|
58
|
+
siblings.named(call.method_name, beside: call).any?
|
|
59
|
+
elsif call.receiver.const_type?
|
|
60
|
+
singleton_definition_for?(call)
|
|
61
|
+
else
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :root
|
|
68
|
+
|
|
69
|
+
def siblings
|
|
70
|
+
@siblings ||= RuboCop::Callbacksystems::Methods::Siblings.new
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def singleton_definition_for?(call)
|
|
74
|
+
singleton_definitions.any? do |definition|
|
|
75
|
+
definition.method?(call.method_name) && receiver_names_match?(call.receiver, definition:)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def singleton_definitions
|
|
80
|
+
@singleton_definitions ||= nodes_in(root, :def, :defs).select do |definition|
|
|
81
|
+
RuboCop::Callbacksystems::Methods::Domain.new(definition).identity == [ :self ] &&
|
|
82
|
+
direct_method_definition?(definition)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def receiver_names_match?(receiver, definition:)
|
|
87
|
+
enclosing_class_or_module_of(definition).identifier.short_name == receiver.short_name
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Detects `each` blocks that `return` an element from inside a condition. The
|
|
2
|
+
# loop is a search written by hand, and `find` says so in one line, while the
|
|
3
|
+
# `each` form makes a reader trace the `return` to learn that the method hands
|
|
4
|
+
# back the first match and `nil` otherwise.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - each with early return
|
|
8
|
+
# items.each do |item|
|
|
9
|
+
# return item if item.valid?
|
|
10
|
+
# end
|
|
11
|
+
# nil
|
|
12
|
+
#
|
|
13
|
+
# # good - use find
|
|
14
|
+
# items.find { it.valid? }
|
|
15
|
+
#
|
|
16
|
+
# # bad - each with early return (implicit nil)
|
|
17
|
+
# def find_valid
|
|
18
|
+
# items.each do |item|
|
|
19
|
+
# return item if item.valid?
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# def find_valid
|
|
25
|
+
# items.find { it.valid? }
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
class RuboCop::Cop::Callbacksystems::NoEachWithEarlyReturn < RuboCop::Cop::Callbacksystems::Base
|
|
29
|
+
def on_block(node)
|
|
30
|
+
report EachBlock.new(node)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias on_numblock on_block
|
|
34
|
+
alias on_itblock on_block
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
class EachBlock
|
|
38
|
+
include RuboCop::Callbacksystems::Helpers
|
|
39
|
+
|
|
40
|
+
MESSAGE = "Don't use `each` with early `return`. Use `find` instead."
|
|
41
|
+
|
|
42
|
+
def initialize(node)
|
|
43
|
+
@node = node
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def offense
|
|
47
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE) if node.method?(:each) && returns_conditionally?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
attr_reader :node
|
|
52
|
+
|
|
53
|
+
def returns_conditionally?
|
|
54
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(node.body).nodes_of_type(:return)
|
|
55
|
+
.any? { conditional_return_of_block_argument?(it, block: node) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Do not use an explicit `public` modifier. A class reads top-down, public
|
|
2
|
+
# methods first and a single `private` after them, so a `public` that reopens
|
|
3
|
+
# visibility later makes a reader track which section each method sits in
|
|
4
|
+
# instead of trusting the order.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - using explicit public modifier
|
|
8
|
+
# class Foo
|
|
9
|
+
# private
|
|
10
|
+
# def private_method; end
|
|
11
|
+
#
|
|
12
|
+
# public
|
|
13
|
+
# def public_method; end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# # bad - using public with method name
|
|
17
|
+
# class Foo
|
|
18
|
+
# def some_method; end
|
|
19
|
+
# public :some_method
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # good - public methods at the beginning
|
|
23
|
+
# class Foo
|
|
24
|
+
# def public_method; end
|
|
25
|
+
#
|
|
26
|
+
# private
|
|
27
|
+
# def private_method; end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
class RuboCop::Cop::Callbacksystems::NoExplicitPublicModifier < RuboCop::Cop::Callbacksystems::Base
|
|
31
|
+
extend RuboCop::Cop::AutoCorrector
|
|
32
|
+
|
|
33
|
+
def on_send(node)
|
|
34
|
+
report PublicModifier.new(node, source_comments:)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
alias on_csend on_send
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
class PublicModifier
|
|
41
|
+
include RuboCop::Callbacksystems::Helpers
|
|
42
|
+
|
|
43
|
+
MESSAGE = "Do not use `public` modifier. Public methods should be defined at the beginning of the class."
|
|
44
|
+
|
|
45
|
+
def initialize(node, source_comments:)
|
|
46
|
+
@node = node
|
|
47
|
+
@source_comments = source_comments
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def offense
|
|
51
|
+
if explicit?
|
|
52
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE, correcting: redundant? && uncommented?) { correct(it) }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
attr_reader :node, :source_comments
|
|
58
|
+
|
|
59
|
+
def explicit?
|
|
60
|
+
bare_send?(node) && node.method?(:public) && !inside_nested_class?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def inside_nested_class?
|
|
64
|
+
node.each_ancestor(:class, :module).drop(1).any?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A bare `public` after `private` is what keeps the methods below it public, so a human reorders that one.
|
|
68
|
+
def redundant?
|
|
69
|
+
node.arguments.empty? && enclosing_definition_of(node) &&
|
|
70
|
+
statements_before(node).none? { leaves_public_section?(it) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def uncommented?
|
|
74
|
+
!source_comments.any_on_lines?(node.first_line..node.last_line)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def correct(corrector)
|
|
78
|
+
corrector.remove(statement_removal_range_for(node))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Detects `Hash.new` with a default block that initializes missing keys to an
|
|
2
|
+
# empty collection. The pattern reaches for an imperative accumulator when the
|
|
3
|
+
# declarative equivalent (`group_by`, `group_by + transform_values`) is almost
|
|
4
|
+
# always clearer.
|
|
5
|
+
#
|
|
6
|
+
# `NoTapCollectionBuilding` only catches the `.tap { ... }` flavor of this
|
|
7
|
+
# pattern; this cop also catches it when used standalone, passed to
|
|
8
|
+
# `each_with_object`, assigned to a variable, etc.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad - building a hash of groups imperatively
|
|
12
|
+
# groups = Hash.new { |h, k| h[k] = [] }
|
|
13
|
+
# items.each { |i| groups[i.type] << i }
|
|
14
|
+
#
|
|
15
|
+
# # bad - same with each_with_object
|
|
16
|
+
# items.each_with_object(Hash.new { |h, k| h[k] = [] }) do |item, groups|
|
|
17
|
+
# groups[item.type] << item
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # bad - hash of sets
|
|
21
|
+
# tags = Hash.new { |h, k| h[k] = Set.new }
|
|
22
|
+
#
|
|
23
|
+
# # good - use group_by
|
|
24
|
+
# items.group_by(&:type)
|
|
25
|
+
#
|
|
26
|
+
# # good - group_by + transform_values when the values need more shaping
|
|
27
|
+
# items.group_by(&:type).transform_values { Set.new(it.map(&:tag)) }
|
|
28
|
+
#
|
|
29
|
+
class RuboCop::Cop::Callbacksystems::NoHashWithDefaultBlock < RuboCop::Cop::Callbacksystems::Base
|
|
30
|
+
MESSAGE = "Avoid `Hash.new { |h, k| h[k] = ... }`. Use `group_by` (or `group_by + transform_values`) instead."
|
|
31
|
+
|
|
32
|
+
# @!method value_assigned_to_key(body, hash_name, key_name)
|
|
33
|
+
def_node_matcher :value_assigned_to_key, <<~PATTERN
|
|
34
|
+
(send (lvar %1) :[]= (lvar %2) $_)
|
|
35
|
+
PATTERN
|
|
36
|
+
|
|
37
|
+
def on_block(node)
|
|
38
|
+
if hash_new_block?(node) && core_collection_constructor?(node.send_node, :Hash)
|
|
39
|
+
hash_name, key_name = node.argument_list.map(&:name)
|
|
40
|
+
if empty_collection?(value_assigned_to_key(node.body, hash_name, key_name))
|
|
41
|
+
add_offense(node, message: MESSAGE)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
alias on_numblock on_block
|
|
47
|
+
alias on_itblock on_block
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
def hash_new_block?(node)
|
|
51
|
+
call = node.send_node
|
|
52
|
+
call.send_type? && call.method?(:new) && node.argument_list.size == 2 &&
|
|
53
|
+
node.argument_list.all?(&:arg_type?)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def core_collection_constructor?(node, *receivers)
|
|
57
|
+
node&.send_type? && node.method?(:new) && core_constant?(node.receiver) &&
|
|
58
|
+
receivers.include?(node.receiver.short_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def empty_collection?(node)
|
|
62
|
+
empty_collection_literal?(node) || collection_constructor?(node)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def collection_constructor?(node)
|
|
66
|
+
node&.send_type? && node.arguments.empty? && core_collection_constructor?(node, :Array, :Hash, :Set)
|
|
67
|
+
end
|
|
68
|
+
end
|