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,156 @@
|
|
|
1
|
+
# Detects single-line brace blocks with a single parameter that should use `it`
|
|
2
|
+
# instead. Since Ruby 3.4 a block can read its one parameter as `it`, and on a
|
|
3
|
+
# single line a name for it only repeats what the receiver already said.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# # bad
|
|
7
|
+
# items.map { |item| item.name }
|
|
8
|
+
# users.select { |user| user.active? }
|
|
9
|
+
# items.each { |item| process(item) }
|
|
10
|
+
# numbers.map { |n| n.to_s }
|
|
11
|
+
#
|
|
12
|
+
# # good
|
|
13
|
+
# items.map { it.name }
|
|
14
|
+
# users.select { it.active? }
|
|
15
|
+
# items.each { process(it) }
|
|
16
|
+
# numbers.map { it.to_s }
|
|
17
|
+
#
|
|
18
|
+
# # good - multi-line block
|
|
19
|
+
# items.map do |item|
|
|
20
|
+
# item.name
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good - multiple parameters
|
|
24
|
+
# hash.each { |key, value| puts key }
|
|
25
|
+
#
|
|
26
|
+
# # good - lambda or proc, where the parameter name documents the signature
|
|
27
|
+
# double = ->(number) { number * 2 }
|
|
28
|
+
# greet = proc { |name| puts name }
|
|
29
|
+
#
|
|
30
|
+
# # good - define_method, where the block sets the defined method's signature
|
|
31
|
+
# define_method(:double) { |number| number * 2 }
|
|
32
|
+
#
|
|
33
|
+
class RuboCop::Cop::Callbacksystems::PreferItBlockParameter < RuboCop::Cop::Callbacksystems::Base
|
|
34
|
+
extend RuboCop::Cop::AutoCorrector
|
|
35
|
+
|
|
36
|
+
def on_block(node)
|
|
37
|
+
report NamedParameterBlock.new(node)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias on_numblock on_block
|
|
41
|
+
alias on_itblock on_block
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
class NamedParameterBlock
|
|
45
|
+
include RuboCop::Callbacksystems::Helpers
|
|
46
|
+
|
|
47
|
+
MESSAGE = "Use `it` instead of explicit block parameter `|%<parameter>s|`."
|
|
48
|
+
METHOD_DEFINITIONS = %i[ define_method define_singleton_method ]
|
|
49
|
+
|
|
50
|
+
def initialize(node)
|
|
51
|
+
@node = node
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def offense
|
|
55
|
+
offense_for_parameter if convertible?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
attr_reader :node
|
|
60
|
+
|
|
61
|
+
def convertible?
|
|
62
|
+
single_line_braced? && single_replaceable_parameter? &&
|
|
63
|
+
!node.lambda_or_proc? && !method_definition? && !nested_in_convertible_block? &&
|
|
64
|
+
!read_inside_an_inner_block? && !it_has_an_existing_meaning?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def single_line_braced?
|
|
68
|
+
node.body && node.braces? && node.single_line?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def single_replaceable_parameter?
|
|
72
|
+
node.arguments.size == 1 && node.first_argument.arg_type? && !parameter_reassigned?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parameter_reassigned?
|
|
76
|
+
nodes_in(node.body, :lvasgn, :match_var).any? { it.children.first == parameter_name }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parameter_name
|
|
80
|
+
node.first_argument.name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def method_definition?
|
|
84
|
+
METHOD_DEFINITIONS.include?(node.method_name)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def nested_in_convertible_block?
|
|
88
|
+
node.each_ancestor(:any_block).any? { it.arguments.size == 1 && it.braces? && it.single_line? }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def read_inside_an_inner_block?
|
|
92
|
+
parameter_reads.any? { |read| read.each_ancestor(*BLOCK_NODE_TYPES).take_while { !it.equal?(node) }.any? }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parameter_reads
|
|
96
|
+
@parameter_reads ||= reads_of(parameter_name, within: node.body)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def it_has_an_existing_meaning?
|
|
100
|
+
parameter_name != :it && (it_variable_nodes.any? || bare_it_calls.any?)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def it_variable_nodes
|
|
104
|
+
nodes_in(
|
|
105
|
+
node.body,
|
|
106
|
+
:lvar, :lvasgn, :match_var, :shadowarg, :arg, :optarg, :restarg, :kwarg, :kwoptarg, :kwrestarg, :blockarg
|
|
107
|
+
).select { it.children.first == :it }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def bare_it_calls
|
|
111
|
+
nodes_in(node.body, :send).select { bare_send?(it) && it.method?(:it) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def offense_for_parameter
|
|
115
|
+
if parameter_reads.empty?
|
|
116
|
+
RuboCop::Callbacksystems::Offense.new(node, message)
|
|
117
|
+
else
|
|
118
|
+
RuboCop::Callbacksystems::Offense.new(node, message) { correct(it) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def message
|
|
123
|
+
format(MESSAGE, parameter: node.first_argument.source)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def correct(corrector)
|
|
127
|
+
Correction.new(corrector, node, reads: parameter_reads).apply
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class Correction
|
|
132
|
+
include RuboCop::Callbacksystems::Helpers
|
|
133
|
+
|
|
134
|
+
def initialize(corrector, node, reads:)
|
|
135
|
+
@corrector = corrector
|
|
136
|
+
@node = node
|
|
137
|
+
@reads = reads
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def apply
|
|
141
|
+
replace_parameter_reads
|
|
142
|
+
remove_parameters
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
attr_reader :corrector, :node, :reads
|
|
147
|
+
|
|
148
|
+
def replace_parameter_reads
|
|
149
|
+
reads.each { replace_expression(corrector, it, with: "it") }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def remove_parameters
|
|
153
|
+
corrector.remove(node.arguments.source_range.join(node.arguments.source_range.end.adjust(end_pos: 1)))
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Detects `collection.size > 1` patterns that should use `many?`. The predicate
|
|
2
|
+
# asks the question in one word, where the comparison makes the reader count.
|
|
3
|
+
#
|
|
4
|
+
# `size`, `length` and `count` answer for a String too, while `many?` comes from
|
|
5
|
+
# `Enumerable` and a String is not one, so a receiver we can read as a String is
|
|
6
|
+
# left alone. A receiver whose type the file does not show stays flagged: getting
|
|
7
|
+
# it wrong raises on the first call rather than passing quietly.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # bad
|
|
11
|
+
# users.size > 1
|
|
12
|
+
# users.length > 1
|
|
13
|
+
# users.count > 1
|
|
14
|
+
#
|
|
15
|
+
# # good
|
|
16
|
+
# users.many?
|
|
17
|
+
#
|
|
18
|
+
# # good - a String has no `many?`
|
|
19
|
+
# name.strip.length > 1
|
|
20
|
+
#
|
|
21
|
+
class RuboCop::Cop::Callbacksystems::PreferMany < RuboCop::Cop::Callbacksystems::Base
|
|
22
|
+
extend RuboCop::Cop::AutoCorrector
|
|
23
|
+
|
|
24
|
+
MESSAGE = "Use `many?` instead of `%<method>s > 1`."
|
|
25
|
+
|
|
26
|
+
# @!method size_greater_than_one?(node)
|
|
27
|
+
def_node_matcher :size_greater_than_one?, <<~PATTERN
|
|
28
|
+
(call $(call _ ${:size :length :count}) :> (int 1))
|
|
29
|
+
PATTERN
|
|
30
|
+
|
|
31
|
+
def on_send(node)
|
|
32
|
+
size_greater_than_one?(node) do |size_call, method|
|
|
33
|
+
next if string_receiver?(size_call.receiver)
|
|
34
|
+
|
|
35
|
+
add_many_offense(node, call: size_call, method:)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
alias on_csend on_send
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def string_receiver?(receiver)
|
|
43
|
+
reads_as_string?(receiver)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_many_offense(node, call:, method:)
|
|
47
|
+
if source_comments.any_within?(node)
|
|
48
|
+
add_offense(node, message: format(MESSAGE, method:))
|
|
49
|
+
else
|
|
50
|
+
add_offense(node, message: format(MESSAGE, method:)) { it.replace(node, replacement_for(call)) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def replacement_for(call)
|
|
55
|
+
"#{call_prefix_of(call)}many?"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Detects a lambda or proc assigned to a local variable inside a method when
|
|
2
|
+
# its body reads nothing from that method. A closure that closes over no
|
|
3
|
+
# local and no parameter is a method in disguise: giving it a name of its own
|
|
4
|
+
# lets it be read and tested alone, and keeps the enclosing method to its
|
|
5
|
+
# job. Instance variables, methods on `self` and constants do not count as
|
|
6
|
+
# captures, since a method reads those too, and neither do the closure's own
|
|
7
|
+
# parameters and locals.
|
|
8
|
+
#
|
|
9
|
+
# A closure that reads or reassigns a local or a parameter of the method is
|
|
10
|
+
# left alone, since that capture is the point of the closure. So is one that
|
|
11
|
+
# yields to the method's block or calls `super`, one defined outside a method,
|
|
12
|
+
# such as a scope or a callback, and one not held in a local, whose shape
|
|
13
|
+
# other cops judge.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad - the lambda reads nothing from `total`
|
|
17
|
+
# def total(orders)
|
|
18
|
+
# price_of = ->(order) { order.quantity * order.unit_price }
|
|
19
|
+
# orders.sum { price_of.call(it) }
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # good - a method of its own
|
|
23
|
+
# def total(orders)
|
|
24
|
+
# orders.sum { price_of(it) }
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def price_of(order)
|
|
28
|
+
# order.quantity * order.unit_price
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # good - the lambda closes over `rate`
|
|
32
|
+
# def total(orders, rate)
|
|
33
|
+
# price_of = ->(order) { order.quantity * rate }
|
|
34
|
+
# orders.sum { price_of.call(it) }
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
class RuboCop::Cop::Callbacksystems::PreferMethodOverLambda < RuboCop::Cop::Callbacksystems::Base
|
|
38
|
+
def on_lvasgn(node)
|
|
39
|
+
node.each_ancestor(:any_def, :class, :module, :sclass).first&.then do |definition|
|
|
40
|
+
report HeldClosure.new(node, method_node: definition) if definition.any_def_type?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
# A local assignment inside a method, read as a closure against the names that method binds around it.
|
|
46
|
+
class HeldClosure
|
|
47
|
+
extend RuboCop::AST::NodePattern::Macros
|
|
48
|
+
include RuboCop::Callbacksystems::Helpers
|
|
49
|
+
|
|
50
|
+
MESSAGE = "`%<name>s` captures nothing from `%<method>s`, so it can be a method of its own."
|
|
51
|
+
|
|
52
|
+
# @!method closure?(node)
|
|
53
|
+
def_node_matcher :closure?, <<~PATTERN
|
|
54
|
+
(any_block {(lambda) (send nil? {:lambda :proc}) (send (const {nil? cbase} :Proc) :new)} ...)
|
|
55
|
+
PATTERN
|
|
56
|
+
|
|
57
|
+
def initialize(node, method_node:)
|
|
58
|
+
@node = node
|
|
59
|
+
@method_node = method_node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if closure?(closure) && captures_nothing?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :node, :method_node
|
|
68
|
+
|
|
69
|
+
def closure
|
|
70
|
+
node.expression
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def captures_nothing?
|
|
74
|
+
captured_uses.none? && !depends_on_method_context?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def captured_uses
|
|
78
|
+
LocalVariables.used_in(closure).select do |use|
|
|
79
|
+
method_names.include?(use.name) && !bound_within_closure?(use)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The closure's own name is left out so that a recursive call counts as no capture.
|
|
84
|
+
def method_names
|
|
85
|
+
@method_names ||= bindings_around_closure.filter_map(&:name).excluding(node.name)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def bindings_around_closure
|
|
89
|
+
LocalVariables.bound_in(method_node).select { binding_visible_around_closure?(it) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def binding_visible_around_closure?(binding)
|
|
93
|
+
binding.node.source_range.begin_pos < closure.source_range.begin_pos &&
|
|
94
|
+
binding_scopes.include?(lexical_scope_of(binding.node))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def binding_scopes
|
|
98
|
+
@binding_scopes ||= closure.each_ancestor(:any_block, :any_def)
|
|
99
|
+
.take_while { !it.equal?(method_node) }
|
|
100
|
+
.push(method_node)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def lexical_scope_of(binding)
|
|
104
|
+
binding.each_ancestor(:any_def, :any_block, :class, :module, :sclass).first
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def bound_within_closure?(use)
|
|
108
|
+
blocks_from(use).any? { block_binds?(it, use.name) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def blocks_from(use)
|
|
112
|
+
use.node.each_ancestor(:any_block).take_while { !it.equal?(closure) }.push(closure)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def block_binds?(block, name)
|
|
116
|
+
block.argument_list.any? { it.name == name }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def depends_on_method_context?
|
|
120
|
+
nodes_in(closure, :yield, :super, :zsuper, :return, :break, :next, :redo, :retry).any? ||
|
|
121
|
+
nodes_in(closure, :send, :csend).any? { ContextualCall.new(it).contextual? }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def message
|
|
125
|
+
format(MESSAGE, name: node.name, method: method_node.method_name)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class ContextualCall
|
|
129
|
+
include RuboCop::Callbacksystems::Helpers
|
|
130
|
+
|
|
131
|
+
CONTEXTUAL_METHODS = %i[ binding block_given? __method__ __callee__ eval local_variables ]
|
|
132
|
+
REFLECTIVE_METHODS = %i[ send __send__ method ]
|
|
133
|
+
STRING_EVAL_METHODS = %i[ instance_eval class_eval module_eval ]
|
|
134
|
+
|
|
135
|
+
def initialize(node)
|
|
136
|
+
@node = node
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def contextual?
|
|
140
|
+
direct? || reflective? || string_eval?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
attr_reader :node
|
|
145
|
+
|
|
146
|
+
def direct?
|
|
147
|
+
contextual_receiver? && CONTEXTUAL_METHODS.include?(node.method_name)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def contextual_receiver?
|
|
151
|
+
call_on_self?(node) || (core_constant?(node.receiver) && node.receiver.short_name == :Kernel)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def reflective?
|
|
155
|
+
node.first_argument&.then do |argument|
|
|
156
|
+
REFLECTIVE_METHODS.include?(node.method_name) &&
|
|
157
|
+
(!argument.type?(:sym, :str) || contextual_reflection?(argument.value.to_sym))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def contextual_reflection?(method_name)
|
|
162
|
+
STRING_EVAL_METHODS.include?(method_name) ||
|
|
163
|
+
(contextual_receiver? && CONTEXTUAL_METHODS.include?(method_name))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def string_eval?
|
|
167
|
+
STRING_EVAL_METHODS.include?(node.method_name) && node.arguments.any?
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class LocalVariables
|
|
172
|
+
include RuboCop::Callbacksystems::Helpers
|
|
173
|
+
|
|
174
|
+
BINDING_TYPES = %i[ arg optarg restarg kwarg kwoptarg kwrestarg blockarg shadowarg lvasgn match_var ]
|
|
175
|
+
USE_TYPES = %i[ lvar lvasgn match_var ]
|
|
176
|
+
Occurrence = Data.define(:node, :name)
|
|
177
|
+
|
|
178
|
+
class << self
|
|
179
|
+
def bound_in(tree)
|
|
180
|
+
new(tree, types: BINDING_TYPES).to_a
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def used_in(tree)
|
|
184
|
+
new(tree, types: USE_TYPES).to_a
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def initialize(tree, types:)
|
|
189
|
+
@tree = tree
|
|
190
|
+
@types = types
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def to_a
|
|
194
|
+
node_occurrences + regexp_occurrences
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
attr_reader :tree, :types
|
|
199
|
+
|
|
200
|
+
def node_occurrences
|
|
201
|
+
nodes_in(tree, *types).map { Occurrence.new(it, variable_name_of(it)) }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def regexp_occurrences
|
|
205
|
+
nodes_in(tree, :match_with_lvasgn).flat_map do |match|
|
|
206
|
+
match.children.first.to_regexp.named_captures.each_key.map { Occurrence.new(match, it.to_sym) }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Detects local variable assignments that could be methods instead.
|
|
2
|
+
# Assigning to a variable with the same name as the method being called
|
|
3
|
+
# suggests the code would be more declarative with a method or delegate.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# # bad - variable name matches method call (simple receiver)
|
|
7
|
+
# account = user.account
|
|
8
|
+
# plan = @account.plan
|
|
9
|
+
#
|
|
10
|
+
# # good - use delegate when method names match
|
|
11
|
+
# delegate :account, to: :user
|
|
12
|
+
# delegate :plan, to: :account
|
|
13
|
+
#
|
|
14
|
+
# # good - or extract method
|
|
15
|
+
# def account
|
|
16
|
+
# user.account
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # bad - finder pattern
|
|
20
|
+
# user = find_user
|
|
21
|
+
# user = User.find(params[:id])
|
|
22
|
+
# order = Order.find_by(number: number)
|
|
23
|
+
#
|
|
24
|
+
# # good - extract to method
|
|
25
|
+
# def user
|
|
26
|
+
# User.find(params[:id])
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # ok - assignment in conditional
|
|
30
|
+
# if user = find_user
|
|
31
|
+
# user.activate
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# # ok - chain receiver (delegate doesn't apply)
|
|
35
|
+
# length = answer.value.to_s.length
|
|
36
|
+
#
|
|
37
|
+
# # ok - receiver is a method parameter (delegate doesn't apply)
|
|
38
|
+
# def process(user)
|
|
39
|
+
# account = user.account # user is a parameter, not a method
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # ok - different name indicates transformation
|
|
43
|
+
# formatted_date = date.strftime("%Y-%m-%d")
|
|
44
|
+
# active_users = users.select(&:active?)
|
|
45
|
+
#
|
|
46
|
+
class RuboCop::Cop::Callbacksystems::PreferMethodOverVariable < RuboCop::Cop::Callbacksystems::Base
|
|
47
|
+
def on_lvasgn(node)
|
|
48
|
+
report Assignment.new(node)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
class Assignment
|
|
53
|
+
DELEGATE_MESSAGE = "Variable `%<variable>s` mirrors method call. Use `delegate :%<variable>s, " \
|
|
54
|
+
"to: :%<receiver>s` or extract a method."
|
|
55
|
+
FINDER_MESSAGE = "Variable `%<variable>s` assigned from finder. Consider extracting a `%<variable>s` method."
|
|
56
|
+
CONDITIONAL_TYPES = %i[ if while until while_post until_post case case_match ]
|
|
57
|
+
|
|
58
|
+
def initialize(node)
|
|
59
|
+
@node = node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if message
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :node
|
|
68
|
+
|
|
69
|
+
def message
|
|
70
|
+
@message ||= delegation_message || finder_message if assigns_a_call? && !inside_conditional?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def assigns_a_call?
|
|
74
|
+
node.expression&.send_type?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inside_conditional?
|
|
78
|
+
node.each_ancestor(*CONDITIONAL_TYPES).any? { condition_holds_assignment?(it.condition) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def condition_holds_assignment?(condition)
|
|
82
|
+
condition&.then { it.equal?(node) || it.source_range.contains?(node.source_range) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def delegation_message
|
|
86
|
+
format(DELEGATE_MESSAGE, variable: variable_name, receiver: receiver.name) if same_name_with_simple_receiver?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def same_name_with_simple_receiver?
|
|
90
|
+
value_node.method?(variable_name) && delegatable_receiver?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def value_node
|
|
94
|
+
node.expression
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def variable_name
|
|
98
|
+
node.name
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# `delegate` needs a method or a reader to send to, which also keeps `now = Time.now` holding its one instant.
|
|
102
|
+
def delegatable_receiver?
|
|
103
|
+
value_node.receiver && !value_node.receiver.lvar_type? && !receiver.chained? && !receiver.name.nil?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def receiver
|
|
107
|
+
@receiver ||= Receiver.new(value_node)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def finder_message
|
|
111
|
+
format(FINDER_MESSAGE, variable: variable_name) if finder_call.finds_the_variable?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def finder_call
|
|
115
|
+
FinderCall.new(variable_name, value_node)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class Receiver
|
|
120
|
+
include RuboCop::Callbacksystems::Helpers
|
|
121
|
+
|
|
122
|
+
def initialize(value_node)
|
|
123
|
+
@value_node = value_node
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def chained?
|
|
127
|
+
value_node.receiver.send_type? && value_node.receiver.receiver
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def name
|
|
131
|
+
case value_node.receiver.type
|
|
132
|
+
when :send then value_node.receiver.method_name
|
|
133
|
+
when :ivar then name_without_sigil(value_node.receiver.name)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
attr_reader :value_node
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class FinderCall
|
|
142
|
+
FINDER_PREFIXES = %w[ find_ get_ fetch_ load_ ]
|
|
143
|
+
CLASS_FINDERS = %w[ find find_by find_by! ]
|
|
144
|
+
|
|
145
|
+
def initialize(variable_name, value_node)
|
|
146
|
+
@variable_name = variable_name
|
|
147
|
+
@value_node = value_node
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def finds_the_variable?
|
|
151
|
+
(finder_method? || class_finder?) && !reads_a_local?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
attr_reader :variable_name, :value_node
|
|
156
|
+
|
|
157
|
+
def finder_method?
|
|
158
|
+
FINDER_PREFIXES.any? { value_node.method_name.to_s == "#{it}#{variable_name}" }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def class_finder?
|
|
162
|
+
value_node.receiver&.const_type? &&
|
|
163
|
+
CLASS_FINDERS.include?(value_node.method_name.to_s) &&
|
|
164
|
+
variable_name.to_s == value_node.receiver.short_name.to_s.underscore
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# A method extracted out of here could not read a local of the caller, so a finder given one stays a variable.
|
|
168
|
+
def reads_a_local?
|
|
169
|
+
value_node.each_node(:lvar).any?
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|