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,173 @@
|
|
|
1
|
+
# Prohibits returning a local variable as the last statement of a method. The
|
|
2
|
+
# variable is a name for the value the method already hands back, so the last
|
|
3
|
+
# line reads as a summary of the ones above it, and the whole body is a
|
|
4
|
+
# `tap`, a `then` or an `each_with_object` spelled out by hand.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - returning a local variable
|
|
8
|
+
# def process
|
|
9
|
+
# result = calculate_something
|
|
10
|
+
# result
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# # bad - assign, mutate, return
|
|
14
|
+
# def process
|
|
15
|
+
# result = []
|
|
16
|
+
# result << item
|
|
17
|
+
# result
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # bad - explicit return of local variable
|
|
21
|
+
# def process
|
|
22
|
+
# result = {}
|
|
23
|
+
# result[:key] = value
|
|
24
|
+
# return result
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # good - return the expression directly
|
|
28
|
+
# def process
|
|
29
|
+
# calculate_something
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # good - use tap
|
|
33
|
+
# def process
|
|
34
|
+
# [].tap do |result|
|
|
35
|
+
# result << item
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# # good - use each_with_object
|
|
40
|
+
# def process
|
|
41
|
+
# items.each_with_object([]) do |item, result|
|
|
42
|
+
# result << transform(item)
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # good - use then
|
|
47
|
+
# def process
|
|
48
|
+
# calculate_something.then do |result|
|
|
49
|
+
# result.merge(extra: value)
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# # good - declarative approach
|
|
54
|
+
# def process
|
|
55
|
+
# [item]
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
class RuboCop::Cop::Callbacksystems::NoLocalVariableReturn < RuboCop::Cop::Callbacksystems::Base
|
|
59
|
+
extend RuboCop::Cop::AutoCorrector
|
|
60
|
+
|
|
61
|
+
def on_def(node)
|
|
62
|
+
last_statement_in(node.body)&.then do |statement|
|
|
63
|
+
report ReturnedLocal.new(statement, source_comments: RuboCop::Callbacksystems::Source::Comments.for(processed_source))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
alias on_defs on_def
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
class ReturnedLocal
|
|
71
|
+
include RuboCop::Callbacksystems::Helpers
|
|
72
|
+
|
|
73
|
+
MESSAGE = "Avoid returning a local variable. " \
|
|
74
|
+
"Return the expression directly, or use `.tap`, `.then`, or `each_with_object`."
|
|
75
|
+
SCOPE_REFLECTION_METHODS = %i[ binding eval local_variables ]
|
|
76
|
+
|
|
77
|
+
def initialize(node, source_comments:)
|
|
78
|
+
@node = node
|
|
79
|
+
@source_comments = source_comments
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def offense
|
|
83
|
+
if variable&.lvar_type? && assigned?
|
|
84
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE, correcting: inlineable?) { correct(it) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
attr_reader :node, :source_comments
|
|
90
|
+
|
|
91
|
+
def variable
|
|
92
|
+
@variable ||= returned_expression_of(node)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A parameter handed back as it came is not a value computed and then returned.
|
|
96
|
+
def assigned?
|
|
97
|
+
occurrences.any? { it.type?(:lvasgn, :match_var) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def occurrences
|
|
101
|
+
@occurrences ||= RuboCop::Callbacksystems::Execution::LocalVariableOccurrences.new(method_node)
|
|
102
|
+
.named_in_method(variable.name, around: node)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def method_node
|
|
106
|
+
@method_node ||= enclosing_method_of(node)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A heredoc keeps its body on the lines past the assignment, so inlining the opener would leave the body behind.
|
|
110
|
+
def inlineable?
|
|
111
|
+
assignment && only_read_once? && scope_preserved? && source_preserved?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def assignment
|
|
115
|
+
@assignment ||= statements_before(node).last(1).find { it.lvasgn_type? && it.name == variable.name }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def only_read_once?
|
|
119
|
+
occurrences.one?(&:lvar_type?) && occurrences.one?(&:lvasgn_type?) && occurrences.none?(&:match_var_type?)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def scope_preserved?
|
|
123
|
+
!captured_by_deferred_callable? && !reflects_on_local_scope?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def captured_by_deferred_callable?
|
|
127
|
+
RuboCop::Callbacksystems::Execution::DeferredLocalVariables.new(method_node.body).include?(variable.name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Removing the assignment removes its name from bindings even when Ruby source never reads that name directly.
|
|
131
|
+
def reflects_on_local_scope?
|
|
132
|
+
nodes_in(method_node.body, :send, :csend).any? do |call|
|
|
133
|
+
SCOPE_REFLECTION_METHODS.include?(call.method_name) && scope_reader?(call)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def scope_reader?(call)
|
|
138
|
+
call_on_self?(call) || (core_constant?(call.receiver) && call.receiver.short_name == :Kernel)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def source_preserved?
|
|
142
|
+
!carries_heredoc?(assignment.expression) && !reads_source_line? && !moves_tooling_comment? &&
|
|
143
|
+
!assignment_trailing_comment?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def reads_source_line?
|
|
147
|
+
assignment.expression.source.include?("__LINE__")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def moves_tooling_comment?
|
|
151
|
+
source_comments.tooling_within?(assignment.source_range.join(node.source_range))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def assignment_trailing_comment?
|
|
155
|
+
source_comments.trailing_comment_on(assignment.last_line).present?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def correct(corrector)
|
|
159
|
+
corrector.remove(assignment_removal_range)
|
|
160
|
+
corrector.replace(node, replacement)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def assignment_removal_range
|
|
164
|
+
inline_statement_removal_range_for(assignment) ||
|
|
165
|
+
blank_line_below(line_removal_range_for(assignment)) ||
|
|
166
|
+
line_removal_range_for(assignment)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def replacement
|
|
170
|
+
node.return_type? ? "return #{assignment.expression.source}" : assignment.expression.source
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Detects an empty collection filled by hand inside an `each`. An empty array
|
|
2
|
+
# or hash assigned to a variable and then filled from a loop is a `map`,
|
|
3
|
+
# `select`, `filter_map`, `index_by` or `to_h` spread over two statements, so
|
|
4
|
+
# a reader has to run the loop in their head to learn what the collection
|
|
5
|
+
# holds, where the declarative call names it. The collection may be a local or
|
|
6
|
+
# an instance variable, and it need not be returned afterwards. The mutation
|
|
7
|
+
# may sit under `if`, `unless` or `case` branches, as long as every statement
|
|
8
|
+
# the body reaches is that mutation. A loop with any other statement, or one
|
|
9
|
+
# leaving through `next`, `break`, `return` or `raise`, is left alone, since
|
|
10
|
+
# the declarative form would drop that behavior.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad - array accumulation returned by the method
|
|
14
|
+
# def names
|
|
15
|
+
# results = []
|
|
16
|
+
# items.each { |x| results << x.name }
|
|
17
|
+
# results
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good - use map
|
|
21
|
+
# def names
|
|
22
|
+
# items.map { it.name }
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # bad - array accumulation with a guard
|
|
26
|
+
# results = []
|
|
27
|
+
# items.each { |x| results << x.name if x.active? }
|
|
28
|
+
#
|
|
29
|
+
# # good - use select or filter_map
|
|
30
|
+
# results = items.filter_map { it.name if it.active? }
|
|
31
|
+
#
|
|
32
|
+
# # bad - array filled by index
|
|
33
|
+
# results = []
|
|
34
|
+
# items.each_with_index { |x, index| results[index] = x.name }
|
|
35
|
+
#
|
|
36
|
+
# # good - use map
|
|
37
|
+
# results = items.map { it.name }
|
|
38
|
+
#
|
|
39
|
+
# # bad - hash accumulation into an instance variable
|
|
40
|
+
# @index = {}
|
|
41
|
+
# items.each { |x| @index[x.id] = x }
|
|
42
|
+
#
|
|
43
|
+
# # good - use index_by or to_h
|
|
44
|
+
# @index = items.index_by { it.id }
|
|
45
|
+
#
|
|
46
|
+
class RuboCop::Cop::Callbacksystems::NoManualAccumulation < RuboCop::Cop::Callbacksystems::Base
|
|
47
|
+
LOOP_METHODS = %i[ each each_with_index ]
|
|
48
|
+
|
|
49
|
+
def on_block(node)
|
|
50
|
+
report Accumulation.new(node) if LOOP_METHODS.include?(node.method_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias on_numblock on_block
|
|
54
|
+
alias on_itblock on_block
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
# A loop filling an empty collection that an earlier statement in the same list assigned.
|
|
58
|
+
class Accumulation
|
|
59
|
+
include RuboCop::Callbacksystems::Helpers
|
|
60
|
+
|
|
61
|
+
MESSAGES = {
|
|
62
|
+
array: "Don't fill an array with `%<loop>s`. Use `map`, `select` or `filter_map` instead.",
|
|
63
|
+
hash: "Don't fill a hash with `%<loop>s`. Use `index_by` or `to_h` instead."
|
|
64
|
+
}
|
|
65
|
+
MUTATORS = { array: %i[ << push append []= store ], hash: %i[ []= store merge! ] }
|
|
66
|
+
|
|
67
|
+
def initialize(loop_block)
|
|
68
|
+
@loop_block = loop_block
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def offense
|
|
72
|
+
RuboCop::Callbacksystems::Offense.new(loop_block.send_node, message) if accumulator && keeps_flow?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
attr_reader :loop_block
|
|
77
|
+
delegate :body, to: :loop_block, private: true
|
|
78
|
+
|
|
79
|
+
def accumulator
|
|
80
|
+
@accumulator ||= empty_collection_assignments_before.find { fills?(it) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def empty_collection_assignments_before
|
|
84
|
+
preceding_statements.select { empty_collection_assignment?(it) && !reassigned_after?(it) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def preceding_statements
|
|
88
|
+
@preceding_statements ||= statements_before(loop_block)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def empty_collection_assignment?(statement)
|
|
92
|
+
statement.type?(:lvasgn, :ivasgn) && empty_collection_literal?(statement.expression)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def reassigned_after?(assignment)
|
|
96
|
+
preceding_statements.drop_while { !it.equal?(assignment) }.drop(1).any? do |statement|
|
|
97
|
+
reassigned_within?(statement, assignment)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def reassigned_within?(statement, assignment)
|
|
102
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(statement).nodes_of_type(assignment.type).any? do |node|
|
|
103
|
+
node.name == assignment.name && !rebound_local?(node, assignment)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def rebound_local?(candidate, assignment)
|
|
108
|
+
assignment.lvasgn_type? &&
|
|
109
|
+
name_rebound_between?(assignment.name, node: candidate, boundary: loop_block.parent)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A statement beyond the mutation is a side effect `map` or `select` would drop, so such a loop is left alone.
|
|
113
|
+
def fills?(assignment)
|
|
114
|
+
leaves.any? && leaves.all? { mutation_of?(assignment, it) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def leaves
|
|
118
|
+
@leaves ||= Leaves.new(body).to_a
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mutation_of?(assignment, leaf)
|
|
122
|
+
leaf.call_type? && reads_variable?(leaf.receiver, assignment.name) && mutator_of?(assignment, leaf)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def mutator_of?(assignment, call)
|
|
126
|
+
MUTATORS[kind_of(assignment)].include?(call.method_name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def kind_of(assignment)
|
|
130
|
+
assignment.expression.type
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def keeps_flow?
|
|
134
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(body).none? do |node|
|
|
135
|
+
node.type?(:next, :break, :return) || raise?(node)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def raise?(node)
|
|
140
|
+
bare_send?(node) && node.method?(:raise)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def message
|
|
144
|
+
format(MESSAGES[kind_of(accumulator)], loop: loop_block.method_name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Terminal statements reached through conditional branches, in the order the source presents them.
|
|
148
|
+
class Leaves
|
|
149
|
+
include Enumerable
|
|
150
|
+
include RuboCop::Callbacksystems::Helpers
|
|
151
|
+
|
|
152
|
+
def initialize(body)
|
|
153
|
+
@body = body
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def each
|
|
157
|
+
if block_given?
|
|
158
|
+
pending = statements_in(body).reverse
|
|
159
|
+
until pending.empty?
|
|
160
|
+
statement = pending.pop
|
|
161
|
+
if branching?(statement)
|
|
162
|
+
branches_of(statement).reverse_each { pending.concat(statements_in(it).reverse) }
|
|
163
|
+
else
|
|
164
|
+
yield statement
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
to_enum(__method__)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
attr_reader :body
|
|
174
|
+
|
|
175
|
+
def branching?(statement)
|
|
176
|
+
statement.type?(:if, :case)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def branches_of(statement)
|
|
180
|
+
if statement.if_type?
|
|
181
|
+
[ statement.if_branch, statement.else_branch ]
|
|
182
|
+
else
|
|
183
|
+
[ *statement.when_branches.map(&:body), statement.else_branch ]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Detects memoization mixed with other statements in the same method. A
|
|
2
|
+
# memoized method reads as a value, so a statement beside the `||=` runs on the
|
|
3
|
+
# first read and never again, an effect hidden behind a getter that the caller
|
|
4
|
+
# has no way to see. Conditional memoization (with if/unless) and rescue
|
|
5
|
+
# blocks are allowed.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - memoization with other statements before
|
|
9
|
+
# def user
|
|
10
|
+
# validate_params
|
|
11
|
+
# @user ||= User.find(params[:id])
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# # bad - memoization with other statements after
|
|
15
|
+
# def user
|
|
16
|
+
# @user ||= User.find(params[:id])
|
|
17
|
+
# log_access
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good - memoization is the entire method body
|
|
21
|
+
# def user
|
|
22
|
+
# @user ||= User.find(params[:id])
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - conditional memoization
|
|
26
|
+
# def period
|
|
27
|
+
# @period ||= compute_period if params[:starts_on].present?
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # good - memoization with rescue
|
|
31
|
+
# def processor
|
|
32
|
+
# @processor ||= find_processor
|
|
33
|
+
# rescue ActiveRecord::RecordNotFound
|
|
34
|
+
# nil
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# # good - complex computation inside the memoization
|
|
38
|
+
# def user
|
|
39
|
+
# @user ||= begin
|
|
40
|
+
# data = fetch_data
|
|
41
|
+
# process(data)
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
class RuboCop::Cop::Callbacksystems::NoMixedMemoization < RuboCop::Cop::Callbacksystems::Base
|
|
46
|
+
def on_def(node)
|
|
47
|
+
report MemoizationBody.new(node)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
alias on_defs on_def
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
class MemoizationBody
|
|
54
|
+
include RuboCop::Callbacksystems::Helpers
|
|
55
|
+
|
|
56
|
+
MESSAGE = "Memoization should be the entire method body. Extract other statements to separate methods."
|
|
57
|
+
|
|
58
|
+
def initialize(method_node)
|
|
59
|
+
@method_node = method_node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
RuboCop::Callbacksystems::Offense.new(memoization_node, MESSAGE) if mixed?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :method_node
|
|
68
|
+
delegate :body, to: :method_node, private: true
|
|
69
|
+
|
|
70
|
+
def mixed?
|
|
71
|
+
memoization_node && !entire_body_is_memoization?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def memoization_node
|
|
75
|
+
@memoization_node ||= RuboCop::Callbacksystems::Execution::Immediate.new(body).nodes_of_type(:or_asgn)
|
|
76
|
+
.find { instance_variable_memoization?(it) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def instance_variable_memoization?(node)
|
|
80
|
+
node&.or_asgn_type? && node.children.first.ivasgn_type?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def entire_body_is_memoization?
|
|
84
|
+
simple_memoization? || multiple_memoizations? || conditional_memoization? ||
|
|
85
|
+
memoization_with_rescue? || memoization_then_field_return?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def simple_memoization?
|
|
89
|
+
instance_variable_memoization?(effective_body)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def effective_body
|
|
93
|
+
@effective_body ||= TransparentExpression.new(body).value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def multiple_memoizations?
|
|
97
|
+
effective_body.type?(:begin, :kwbegin) &&
|
|
98
|
+
effective_body.children.all? { instance_variable_memoization?(it) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def conditional_memoization?
|
|
102
|
+
effective_body.if_type? && effective_body.else_branch.nil? &&
|
|
103
|
+
instance_variable_memoization?(effective_body.if_branch)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def memoization_with_rescue?
|
|
107
|
+
instance_variable_memoization?(rescue_node&.children&.first)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def rescue_node
|
|
111
|
+
effective_body if effective_body.rescue_type?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# `@user ||= load; @user` reads back the field it just memoized, which is still a single concern.
|
|
115
|
+
def memoization_then_field_return?
|
|
116
|
+
effective_body.type?(:begin, :kwbegin) && effective_body.children.size == 2 &&
|
|
117
|
+
instance_variable_memoization?(effective_body.children.first) &&
|
|
118
|
+
returns_memoized_variable?(effective_body.children.last)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def returns_memoized_variable?(node)
|
|
122
|
+
reads_variable?(returned_expression_of(node), memoization_node.lhs.name)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class TransparentExpression
|
|
126
|
+
def initialize(node)
|
|
127
|
+
@node = node
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def value
|
|
131
|
+
node.then do |current|
|
|
132
|
+
current = current.children.first while transparent?(current)
|
|
133
|
+
current
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
attr_reader :node
|
|
139
|
+
|
|
140
|
+
def transparent?(current)
|
|
141
|
+
current.type?(:begin, :kwbegin) && current.children.one?
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Forbids nested modules inside classes.
|
|
2
|
+
# Modules should be defined at the top level or inside other modules,
|
|
3
|
+
# not inside classes.
|
|
4
|
+
#
|
|
5
|
+
# Nested modules in classes are often used to avoid creating proper
|
|
6
|
+
# objects, leading to imperative rather than declarative code.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad - nested module in class
|
|
10
|
+
# class Order
|
|
11
|
+
# module Calculations
|
|
12
|
+
# def total
|
|
13
|
+
# # ...
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # bad - nested module in private section
|
|
19
|
+
# class Order
|
|
20
|
+
# private
|
|
21
|
+
# module Calculations
|
|
22
|
+
# def total
|
|
23
|
+
# # ...
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# # good - use a nested class instead
|
|
29
|
+
# class Order
|
|
30
|
+
# private
|
|
31
|
+
# class Calculator
|
|
32
|
+
# def total
|
|
33
|
+
# # ...
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# # good - define module at top level
|
|
39
|
+
# module Order::Calculations
|
|
40
|
+
# def total
|
|
41
|
+
# # ...
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
class RuboCop::Cop::Callbacksystems::NoNestedModulesInClasses < RuboCop::Cop::Callbacksystems::Base
|
|
46
|
+
MESSAGE = "Do not nest modules inside classes. Use a nested class or define the module at the top level."
|
|
47
|
+
|
|
48
|
+
def on_module(node)
|
|
49
|
+
report NestedModule.new(node)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias on_casgn on_module
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
class NestedModule
|
|
56
|
+
include RuboCop::Callbacksystems::Helpers
|
|
57
|
+
|
|
58
|
+
def initialize(node)
|
|
59
|
+
@node = node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE) if declaration? && inside_class?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :node
|
|
68
|
+
|
|
69
|
+
def declaration?
|
|
70
|
+
node.module_type? ||
|
|
71
|
+
RuboCop::Callbacksystems::ClassStructure::BuilderAssignment.new(node).builds_module?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def inside_class?
|
|
75
|
+
enclosing_class_or_module_of(node)&.class_type?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Prohibits returning nil at the end of a method. A trailing `nil` is there to
|
|
2
|
+
# hide the value of the statement above it, which means a caller reads the
|
|
3
|
+
# method for a value it was never meant to give, so the method wants a shape
|
|
4
|
+
# whose last statement is its result. A `nil` that is the only statement is
|
|
5
|
+
# intentional, as in an interface method, and stays.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad
|
|
9
|
+
# def process
|
|
10
|
+
# do_something
|
|
11
|
+
# nil
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# # bad
|
|
15
|
+
# def process
|
|
16
|
+
# if condition
|
|
17
|
+
# do_something
|
|
18
|
+
# end
|
|
19
|
+
# nil
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # good - return value is implicit
|
|
23
|
+
# def process
|
|
24
|
+
# do_something
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # good - use early return if needed
|
|
28
|
+
# def process
|
|
29
|
+
# return unless condition
|
|
30
|
+
# do_something
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# # good - nil is clearly intentional (only statement)
|
|
34
|
+
# def some_interface_method
|
|
35
|
+
# nil
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
class RuboCop::Cop::Callbacksystems::NoNilAtEndOfMethod < RuboCop::Cop::Callbacksystems::Base
|
|
39
|
+
def on_def(node)
|
|
40
|
+
report TrailingNil.new(node)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
alias on_defs on_def
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
class TrailingNil
|
|
47
|
+
include RuboCop::Callbacksystems::Helpers
|
|
48
|
+
|
|
49
|
+
MESSAGE = "Avoid `nil` at the end of a method. Restructure the code to make it unnecessary."
|
|
50
|
+
|
|
51
|
+
def initialize(method_node)
|
|
52
|
+
@method_node = method_node
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def offense
|
|
56
|
+
RuboCop::Callbacksystems::Offense.new(last, MESSAGE) if trailing?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
attr_reader :method_node
|
|
61
|
+
|
|
62
|
+
def trailing?
|
|
63
|
+
last&.nil_type? && previous
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def last
|
|
67
|
+
@last ||= last_statement_in(method_node.body)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def previous
|
|
71
|
+
last.left_sibling if last.parent.type?(:begin, :kwbegin)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# A `validate` declaration already says what the callback does. Its name should
|
|
2
|
+
# describe the condition it checks, so the declaration reads as a sentence.
|
|
3
|
+
# Renaming needs an understanding of that condition and every caller, so this
|
|
4
|
+
# cop reports the registration without changing the callback or its definition.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad
|
|
8
|
+
# validate :validate_email
|
|
9
|
+
# validate :valid_membership
|
|
10
|
+
#
|
|
11
|
+
# # good
|
|
12
|
+
# validate :ensure_deliverable_email
|
|
13
|
+
# validate :no_expired_membership
|
|
14
|
+
# validate :expiration_date_cannot_be_in_the_past
|
|
15
|
+
#
|
|
16
|
+
class RuboCop::Cop::Callbacksystems::NoRedundantValidationName < RuboCop::Cop::Callbacksystems::Base
|
|
17
|
+
MESSAGE = "Validation callback `%<name>s` repeats `validate`. Name the condition instead, like `ensure_*` or `no_*`."
|
|
18
|
+
|
|
19
|
+
def on_send(node)
|
|
20
|
+
if validation_registration?(node)
|
|
21
|
+
node.arguments.each do |argument|
|
|
22
|
+
add_offense(argument, message: format(MESSAGE, name: argument.value)) if redundant_name?(argument)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias on_csend on_send
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def validation_registration?(node)
|
|
31
|
+
node.method?(:validate) && call_on_self?(node) && !enclosing_method_of(node)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def redundant_name?(argument)
|
|
35
|
+
argument.sym_type? && argument.value.match?(/\A(?:valid|validate)(?:_|[!?]?\z)/)
|
|
36
|
+
end
|
|
37
|
+
end
|